You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Custom OpenAI Responses relay does not append /v1 when fetching models #3320
When a custom OpenAI Responses-compatible relay is configured with a root URL such as:
http://relay.example:3000
the connection test succeeds, but updating the model catalog fails.
Maka requests:
GET /models
However, the relay provides the OpenAI-compatible model list at:
GET /v1/models
Cherry Studio works with the same configuration because it automatically appends /v1 when the base URL does not contain an API version. OpenAI's standard endpoints are also versioned:
GET /v1/models
POST /v1/responses
Expected behavior
We should clarify whether the configured URL is a host root or a complete API base URL, and use the same URL normalization rule for model discovery and Responses requests.
Environment
Maka: 0.1.11
Surface: Desktop
OS: Windows
中文
问题描述
配置自定义 OpenAI Responses 兼容中转站时,如果服务地址填写为根地址:
http://relay.example:3000
连接测试可以成功,但更新模型目录失败。
Maka 当前请求:
GET /models
而该中转站的 OpenAI 兼容模型接口为:
GET /v1/models
相同配置在 Cherry Studio 中可以使用,因为当地址不包含 API 版本时,它会自动补充 /v1。OpenAI 的标准接口也包含版本前缀:
GET /v1/models
POST /v1/responses
期望行为
需要明确配置地址表示主机根地址还是完整 API Base URL,并让模型发现和 Responses 请求使用一致的 URL 处理规则。
providerSupportsModelDiscovery() currently means that a provider can attempt model discovery (modelDiscovery.kind !== 'fallback'). It does not mean that a specific connection has successfully fetched an authoritative model catalog.
For custom OpenAI Responses relays, a failed fetch leaves models: []. The catalog code then treats this defined empty array as an authoritative inventory, marks the manually configured fallback model as not_in_live_list, and removes it from the Chat model selector.
Expected behavior: only a successfully fetched catalog, such as modelSource === 'fetched', should be authoritative. A failed or not-yet-attempted discovery should preserve the manually configured picker fallback.
For the built-in OpenAI provider the default base is https://api.openai.com/v1, so both resolve to /v1/... correctly. With a bare-root relay (http://relay.example:3000) the two paths diverge only because the relay happens to accept /responses at the root (so the probe passes) while its model list lives at /v1/models (so discovery 404s). Maka's two call sites are actually consistent with each other; the relay is the inconsistent side.
Why it's not a one-liner. The anthropic/google helpers self-heal to their version (anthropicV1BaseUrl, googleV1BetaBaseUrl), but openResponsesUrl deliberately does not — its doc comment: "Normalize an Open Responses endpoint without assuming a /v1 prefix" (#2972). So:
Auto-append /v1 when the base has no version segment (the Cherry Studio behavior). Fixes this report, but contradicts the intentional no-/v1 Responses design and would break relays that legitimately serve /models / /responses at the root.
Treat the configured URL as the complete API base (status quo) and instead improve UX — e.g. detect a bare host root in the add/edit form and prompt for the versioned base, and/or make discovery reuse openResponsesUrl's exact normalization so the two are provably identical.
I lean toward (2): keep the base URL authoritative (no magic version rewriting, consistent with openResponsesUrl) and surface a clear hint when the user enters a bare root. But this is a product call — I'll implement whichever you prefer.
Related (from @yingning2021 above). Independent of the URL question, a failed discovery leaves models: [], which the catalog then treats as an authoritative empty inventory and drops the manually-configured fallback model from the picker (missingEntryUnavailableReason → not_in_live_list in packages/core/src/model-catalog.ts; the source === 'fetched' empty-is-authoritative branch in packages/storage/src/runtime-policy/connection-catalog-document.ts). Only a successfully fetched catalog should be authoritative; a failed/not-yet-attempted fetch should preserve the user's fallback. This is a safe, self-contained fix and arguably the more user-visible half of the bug — happy to take it in a separate PR if you'd like to split the two concerns.
中文
根因:探针与模型发现都是把端点直接拼到 base URL 后面——发现走 ${baseUrl}/models,连接测试走 openResponsesUrl 即 ${baseUrl}/responses。内置 OpenAI 的默认 base 是 https://api.openai.com/v1,两者都能正确落到 /v1/...;而根地址中转站因为 /responses 在根路径下也能应答(探针通过)、但模型列表在 /v1/models(发现 404),才显得不一致。其实 Maka 两处是一致的,不一致的是中转站本身。
两种修法方向相反:①无版本时自动补 /v1(Cherry Studio 行为),但与 openResponsesUrl 刻意"不假设 /v1"(#2972)的设计冲突,还会误伤真正在根路径提供 /models 的中转站;②把配置地址视为完整 API base(现状),改为在表单里识别"裸主机根地址"时提示补全版本,并让发现复用 openResponsesUrl 完全相同的归一化逻辑。我倾向 ②。方向定了我来提 PR。
This is still open, unassigned, and not resolved. I have not claimed or started an implementation; my comment above was root-cause analysis and an offer pending maintainer direction. Please coordinate with the maintainers before coding because the URL-normalization choice changes product behavior, while the failed-discovery fallback is a separate, self-contained bug that could be handled independently. Linking any proposed scope or PR here will help avoid duplicate work.
Opened #3797 for the remaining URL half of this issue. The other half — a failed discovery dropping the manually configured fallback model — merged as #3581.
Scope and the reason for it: only openai-responses-compatible normalizes its configured URL into one API base (host root self-heals to /v1, an authored path such as /v1 or /relay/v1 is preserved, the …/responses endpoint form reduces to its base). Discovery, the probe, and the send path all derive from that base, so they can no longer disagree. A blanket root → /v1 rule is not safe: DeepSeek ships https://api.deepseek.com and legitimately serves both /responses and /models at that unversioned root, and the PR carries a regression test for it. openResponsesUrl's deliberate no-assumed-/v1 contract (#2972) is untouched for every other provider.
@Xinbeok you are the assignee here — if you already have this in flight, say so and I will close #3797. Maintainers: if you prefer the other direction (keep the configured base authoritative and warn about a bare host root in the add/edit form instead), I will rework it that way.
Activity
yingning2021 commentedon Aug 20, 2026
Additional related issue
providerSupportsModelDiscovery()currently means that a provider can attempt model discovery (modelDiscovery.kind !== 'fallback'). It does not mean that a specific connection has successfully fetched an authoritative model catalog.For custom OpenAI Responses relays, a failed fetch leaves
models: []. The catalog code then treats this defined empty array as an authoritative inventory, marks the manually configured fallback model asnot_in_live_list, and removes it from the Chat model selector.Expected behavior: only a successfully fetched catalog, such as
modelSource === 'fetched', should be authoritative. A failed or not-yet-attempted discovery should preserve the manually configured picker fallback.相关补充
providerSupportsModelDiscovery()当前表示 Provider 可以尝试拉取模型(modelDiscovery.kind !== 'fallback'),并不表示某个具体连接已经成功获得了权威模型目录。对于自定义 OpenAI Responses 中转站,拉取失败后会保留
models: []。模型目录代码随后将这个空数组视为权威目录,把手工配置的兜底模型标记为not_in_live_list,并从聊天模型选择器中移除。期望行为:只有成功拉取的目录,例如
modelSource === 'fetched',才应被视为权威目录。拉取失败或尚未拉取时,应继续保留用户手工配置的模型选择器兜底。FenjuFu commentedon Aug 21, 2026
English
I traced this end to end — happy to send a PR once the direction is confirmed, since the two obvious fixes point in opposite directions.
Root cause. Both the probe and model discovery append their endpoint to the base URL as-is:
modelListUrl(baseUrl, …)→${baseUrl}/models(packages/runtime/src/model-fetcher.ts).openResponsesUrl(baseUrl)→${baseUrl}/responses(packages/runtime/src/test-connection.ts).For the built-in OpenAI provider the default base is
https://api.openai.com/v1, so both resolve to/v1/...correctly. With a bare-root relay (http://relay.example:3000) the two paths diverge only because the relay happens to accept/responsesat the root (so the probe passes) while its model list lives at/v1/models(so discovery 404s). Maka's two call sites are actually consistent with each other; the relay is the inconsistent side.Why it's not a one-liner. The anthropic/google helpers self-heal to their version (
anthropicV1BaseUrl,googleV1BetaBaseUrl), butopenResponsesUrldeliberately does not — its doc comment: "Normalize an Open Responses endpoint without assuming a/v1prefix" (#2972). So:/v1when the base has no version segment (the Cherry Studio behavior). Fixes this report, but contradicts the intentional no-/v1Responses design and would break relays that legitimately serve/models//responsesat the root.openResponsesUrl's exact normalization so the two are provably identical.I lean toward (2): keep the base URL authoritative (no magic version rewriting, consistent with
openResponsesUrl) and surface a clear hint when the user enters a bare root. But this is a product call — I'll implement whichever you prefer.Related (from @yingning2021 above). Independent of the URL question, a failed discovery leaves
models: [], which the catalog then treats as an authoritative empty inventory and drops the manually-configured fallback model from the picker (missingEntryUnavailableReason→not_in_live_listinpackages/core/src/model-catalog.ts; thesource === 'fetched'empty-is-authoritative branch inpackages/storage/src/runtime-policy/connection-catalog-document.ts). Only a successfully fetched catalog should be authoritative; a failed/not-yet-attempted fetch should preserve the user's fallback. This is a safe, self-contained fix and arguably the more user-visible half of the bug — happy to take it in a separate PR if you'd like to split the two concerns.中文
根因:探针与模型发现都是把端点直接拼到 base URL 后面——发现走
${baseUrl}/models,连接测试走openResponsesUrl即${baseUrl}/responses。内置 OpenAI 的默认 base 是https://api.openai.com/v1,两者都能正确落到/v1/...;而根地址中转站因为/responses在根路径下也能应答(探针通过)、但模型列表在/v1/models(发现 404),才显得不一致。其实 Maka 两处是一致的,不一致的是中转站本身。两种修法方向相反:①无版本时自动补
/v1(Cherry Studio 行为),但与openResponsesUrl刻意"不假设 /v1"(#2972)的设计冲突,还会误伤真正在根路径提供/models的中转站;②把配置地址视为完整 API base(现状),改为在表单里识别"裸主机根地址"时提示补全版本,并让发现复用openResponsesUrl完全相同的归一化逻辑。我倾向 ②。方向定了我来提 PR。另外 @yingning2021 提到的点是独立且安全的 bug:发现失败留下
models: []被当成权威空目录,把手工配置的兜底模型从选择器里删掉。只有"成功拉取"的目录才应权威,失败/未拉取应保留用户兜底。这块可以单独一个 PR。Xinbeok commentedon Aug 22, 2026
Has this issue been resolved? I want to claim this issue.
FenjuFu commentedon Aug 23, 2026
This is still open, unassigned, and not resolved. I have not claimed or started an implementation; my comment above was root-cause analysis and an offer pending maintainer direction. Please coordinate with the maintainers before coding because the URL-normalization choice changes product behavior, while the failed-discovery fallback is a separate, self-contained bug that could be handled independently. Linking any proposed scope or PR here will help avoid duplicate work.
Sandu1213 commentedon Aug 25, 2026
Opened #3797 for the remaining URL half of this issue. The other half — a failed discovery dropping the manually configured fallback model — merged as #3581.
Scope and the reason for it: only
openai-responses-compatiblenormalizes its configured URL into one API base (host root self-heals to/v1, an authored path such as/v1or/relay/v1is preserved, the…/responsesendpoint form reduces to its base). Discovery, the probe, and the send path all derive from that base, so they can no longer disagree. A blanket root →/v1rule is not safe: DeepSeek shipshttps://api.deepseek.comand legitimately serves both/responsesand/modelsat that unversioned root, and the PR carries a regression test for it.openResponsesUrl's deliberate no-assumed-/v1contract (#2972) is untouched for every other provider.@Xinbeok you are the assignee here — if you already have this in flight, say so and I will close #3797. Maintainers: if you prefer the other direction (keep the configured base authoritative and warn about a bare host root in the add/edit form instead), I will rework it that way.
nagarwal-godaddy commentedon Aug 25, 2026
Working on the URL-normalization half (PR #3581 covers only the fallback-model half).
fix(runtime): resolve a Responses relay from one API base
fix(runtime): resolve a Responses relay from one API base
fix(runtime): resolve a Responses relay from one API base
fix(runtime): fall back Responses relay discovery to /v1
fix(runtime): fall back Responses relay discovery to /v1
fix(runtime): fall back Responses relay discovery to /v1