makecindy / makecindy/cindy

增强自定义供应商模型发现:独立 modelsUrl 与 Anthropic 后缀回退

Open
#210 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.