增强自定义供应商模型发现:独立 modelsUrl 与 Anthropic 后缀回退
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 401
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
## 背景
Cindy 自定义供应商已经支持 per-runtime `baseUrl`、可选 `modelsUrl`、模型列表获取和 API Key 注入。近期在验证多协议供应商时发现:部分厂商的 **Anthropic 对话端点**与 **OpenAI 风格模型列表端点**是不同路径,例如阿里云百炼 Coding Plan:
```text
Claude Code 对话:https://coding.dashscope.aliyuncs.com/apps/anthropic
模型列表: https://coding.dashscope.aliyuncs.com/v1/models
```
如果 runtime 没有显式 `modelsUrl`,当前 `deriveModelsDiscoveryUrl()` 会从 `baseUrl` 机械推导:
```text
.../apps/anthropic → .../apps/anthropic/v1/models
```
从而得到 404。内置预设可以通过补 `modelsUrl` 修复,但用户自定义业务空间、私有网关和其它兼容厂商仍会遇到同类问题。
本 issue 独立跟踪模型发现增强,**不阻塞 makecindy/cindy#108 的 OpenAI Responses → Chat Completions 协议桥收尾**。
## 当前能力与缺口
已有能力:
- `ProviderPresetRuntime` / `CustomProviderRuntimeConfig` 已支持可选 `modelsUrl`;
- `provider-model-fetch.ts` 支持显式 `modelsUrl`,否则从 `baseUrl` 推导;
- 模型列表请求使用当前 runtime 的 API Key / headers;
- 响应支持 OpenAI / Anthropic 常见模型列表形状;
- 表单结果只在用户点击时获取,不后台轮询。
缺口:
1. `modelsUrl` 是隐藏字段,用户无法在自定义供应商 UI 中查看或编辑;
2. 默认推导不识别 `/apps/anthropic`、`/anthropic`、`/coding` 等兼容层后缀;
3. 只尝试一个 URL,没有 404/405 候选回退;
4. 显式 `modelsUrl` 当前仅在与 `baseUrl` 同源时采用,无法表达用户主动配置的跨 host 模型目录;
5. 404 主文案只提示“检查基础 URL”,无法区分“聊天端点可用,但不提供模型列表”;
6. 通用模型获取没有 TTL/stale fallback/失败冷却(目前用户点击触发,优先级低于 URL 发现正确性)。
## 参考实现
### cc-switch(主要参考)
cc-switch 有独立的 `modelsUrl`,并用候选链处理 Anthropic 对话地址与 OpenAI `/models` 地址分离:
- 显式 `models_url_override` 优先;
- base URL 已以 `/v1`、`/v4` 等版本段结尾时尝试 `{base}/models`;
- 否则尝试 `{base}/v1/models`;
- 识别并剥除 `/api/anthropic`、`/apps/anthropic`、`/anthropic`、`/coding`、`/claude` 等兼容后缀,再尝试根路径 `/v1/models` 与 `/models`;
- 仅 404/405 继续下一个候选;401/403 等立即停止;
- 通用获取每次点击实时请求,不做持久缓存。
参考:
- https://github.com/farion1231/cc-switch/blob/a377d79303bc1e592d2783d559ca5bd6b8ba1417/src-tauri/src/services/model_fetch.rs#L54-L203
- https://github.com/farion1231/cc-switch/blob/a377d79303bc1e592d2783d559ca5bd6b8ba1417/src/components/providers/forms/ClaudeFormFields.tsx#L287-L314
- https://github.com/farion1231/cc-switch/blob/a377d79303bc1e592d2783d559ca5bd6b8ba1417/src/config/claudeProviderPresets.ts#L822-L825
### OpenCodex(缓存/fallback 参考)
OpenCodex 有动态模型目录、5 分钟 TTL、last-known-good stale fallback 与 30 秒失败冷却,但其发现 URL 与 adapter 耦合,不能独立配置“Anthropic 对话 + OpenAI `/models`”,不适合作为本问题的 URL 模型直接照搬。
参考:
- https://github.com/lidge-jun/opencodex/blob/a44f21d8747fbbae15a4407a6ffd96ce7da3f671/src/codex/catalog.ts#L1484-L1649
- https://github.com/lidge-jun/opencodex/blob/a44f21d8747fbbae15a4407a6ffd96ce7da3f671/src/codex/model-cache.ts
- https://github.com/lidge-jun/opencodex/blob/a44f21d8747fbbae15a4407a6ffd96ce7da3f671/src/oauth/index.ts#L404-L459
## 建议方案
### P1.1:模型 URL 候选链
默认仅在同源范围内构造有序、去重的候选 URL:
1. 显式 `modelsUrl`;
2. base URL 版本段对应的 `/models`;
3. `{base}/v1/models`;
4. 剥除已知 Anthropic 兼容后缀后的 `{root}/v1/models`;
5. 剥除后的 `{root}/models`。
请求策略:
- 404/405:尝试下一个候选;
- 401/403、429、5xx、超时:立即返回对应结构化错误,不继续扫描;
- 候选 URL 与失败详情不得进入包含密钥的日志;
- 不按供应商名称硬编码。
### P1.2:显式模型列表 URL
在自定义供应商高级配置中暴露“模型列表 URL(可选)”:
- 缺省跟随自动发现;
- 用户显式填写时持久化现有 `modelsUrl` 字段;
- 必须为 HTTPS(localhost/dev 例外沿用现有 URL 规则需另行评估);
- 同源时直接允许;
- 跨 host 时必须让用户明确确认“API Key 将发送到该域名”,不能通过自动推导跨 host;
- 恢复默认 = 清除 `modelsUrl` override,重新跟随自动发现。
### P1.3:错误文案
区分:
- 聊天连接测试失败;
- 模型列表端点不存在;
- 模型列表响应无法解析。
模型列表 404/405 的建议文案:
> 该对话端点未提供模型列表接口。请手动添加模型,或在高级配置中填写模型列表 URL。
四语言同步(zh-CN/en/ja/ko)。
### P2:缓存与失败冷却(可后置)
仅在模型目录被自动/频繁刷新时再引入:
- 小 TTL 内存缓存;
- last-known-good stale fallback;
- 失败冷却避免重复超时;
- 用户主动点击“重新获取”应可绕过 TTL。
当前获取由用户点击触发,P2 不应阻塞 P1.1/P1.2。
## 安全边界
- 自动推导的候选 URL只能同源,禁止静默向另一个 host 发送 API Key;
- 跨 host 只接受用户显式配置并确认;
- API Key 不进 catalog、renderer provider list、日志或错误详情;
- 重定向后的跨源凭证行为需要 fail-closed 或剥离鉴权头,不能依赖 fetch 默认行为含糊处理;
- macOS / Windows 都使用 URL API,不做路径字符串平台假设。
## 验收标准
- `.../apps/anthropic` 可自动回退到同源根路径的 `/v1/models`;
- 404/405 才继续候选,鉴权/限流/服务错误不误扫;
- 用户可查看、填写、清除显式 `modelsUrl`;
- 跨 host 配置有明确凭证发送确认,不自动发生;
- 已有同源 `modelsUrl` 预设(DeepSeek/Kimi/百炼等)行为不回退;
- 手填静态模型仍始终可用,模型发现失败不破坏已填模型;
- main 侧带 URL 候选、错误分支和凭证不泄漏测试;
- UI 文案四语言完整。
## 已验证现状
2026-07-23 实测:
- 自定义添加阿里云百炼普通 API(非 Coding Plan):Claude Code / Codex 可用;
- 自定义添加 GLM:Claude Code / Codex 可用;
- 百炼业务空间:Codex OpenAI 兼容端点能获取模型;Claude Code `/apps/anthropic` 对话端点可用,但当前自动推导的模型列表路径返回 404;
- makecindy/cindy#108 的 OpenAI Chat 协议桥不依赖本 issue,可先独立收尾。
Contributor guide
Research direction
Start with provider-model-fetch.ts and the custom-provider advanced configuration entry point; trace current modelsUrl/baseUrl discovery and the runtime API-key/header flow. Then inspect the main-side URL, error-branch, and credential-leakage tests mentioned in the issue. Done means same-origin fallback and explicit cross-host confirmation work, existing models remain usable, and the four-language messages and tests cover the acceptance criteria.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, frontend, internationalization, security, testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100