makecindy / makecindy/cindy

feat(models): 对齐 Gateway 模型类型并补齐 OAuth 登录与模型目录

Open
#882 1 comment 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 的模型目录契约、类型分类、Provider 传输兼容和账号 OAuth 登录能力:

1. 模型类型以 Gateway 返回的原生 `mode` 为权威,不再依赖模型 ID 正则猜测。
2. 模型管理界面完整展示聊天、图片、视频、TTS、语音转写、实时音频、Embedding、压缩等类型。
3. 只有可用于 Agent 对话的模型进入新对话模型选择器;非聊天模型不再混入 Agent `availableModels`。
4. 补齐 Google 账号 OAuth 登录及账号可用模型发现,并补上现有订阅登录渠道缺失的模型。
5. 未识别的新 `mode` 必须无损保留和展示,不能静默丢弃。
6. 为 Codex CLI 与 Claude Code 建立能力级兼容契约;“目录中存在”不能再等同于“两个 runtime 都可执行”。
7. 修复 bundled/remote Provider 目录的发布与合并机制,保证已合入的新 Provider 能进入生产目录,并能看到目录版本和同步状态。

## 当前问题

- `ModelAccessGatewayModel` 没有 `mode`,当前 `/models` 契约只描述为 Gateway `/model-groups` 的 `mode=chat` 投影,但实际同步结果包含大量非聊天模型。
- Renderer 的 `ModelCategory` 只有笼统的 `audio`,TTS、STT/ASR、Realtime 全部混在一起。
- 分类主要靠模型 ID 正则:
- `gpt-realtime-2`、`gpt-realtime-mini`、`gpt-realtime-translate` 会落入 GPT 聊天分类;
- `x-ai/grok-4.5` 会因前缀不匹配落入错误厂商分类;
- `ai-gateway-doc` 只能落入 `other`,无法表达 Gateway 的压缩能力类型。
- `embedding` 目前只是界面分类,没有 Provider 级正式能力契约。
- 图片和视频使用本地专用字段,与 Gateway 其他能力没有统一的类型结构。
- 非聊天模型会进入 Agent 可用模型清单,造成“能看到但不能作为 Agent 执行”的假可用状态。
- 当前 Provider 预设只能粗粒度声明支持哪个 Agent,缺少按 runtime、模型和能力划分的兼容矩阵;文本能返回不代表工具调用、并行工具、图片、推理、取消和续接都可用。
- Provider 的协议差异和模型特例缺少统一归属,容易散落在 translator、Agent 或模型 ID 判断中。
- bundled Provider 目录与远端目录存在整表覆盖风险:远端目录发布滞后时,本地已经新增的 Provider 仍不会出现在生产列表,用户也看不到当前目录来源、版本或同步失败原因。

## 模型目录契约

### Gateway / model-access-server

- `/models` 为每个模型透传 Gateway 原生 `mode`;字段值保持原样,不在服务端或客户端改名。
- `mode` 按开放字符串处理,不使用会阻断未来新类型的封闭枚举。
- `group` 只负责展示分组/品牌,不再代替能力类型。
- 同步所有允许客户端展示的模型类型,而不是只声明或假设 `mode=chat`。
- 每条模型保留现有价格、上下文、能力、排序、图标和 per-agent 覆盖字段。

### Cindy 客户端

- `ModelAccessGatewayModel`、活动目录、Provider 投影和 Desktop/Mobile 投影完整保留 `mode`。
- UI 根据 `mode` 做本地化标签映射;模型 ID 正则只能作为旧缓存缺少 `mode` 时的兼容兜底,不能覆盖 Gateway 数据。
- 至少覆盖以下语义类型;具体字符串以 Gateway 实际返回值为准:
- Chat
- Image generation
- Video generation
- Text to speech
- Audio transcription / STT / ASR
- Realtime audio / multimodal
- Embedding
- Compression
- 未知 `mode` 显示为可识别的“其他能力”,同时保留原始 mode 名,便于排查和后续升级。
- 只有 Gateway 明确标记为聊天能力、对应 runtime 有路由且通过最低兼容验证的模型,才进入该 runtime 的 Agent `availableModels` 和新对话模型选择器。
- 设置 → 模型供应商可以查看全部模型,并按能力类型分组;非聊天类型不因 Agent 准入过滤而从管理界面消失。

## Provider、传输与双 Runtime 兼容契约

Provider 来源、模型类型、传输协议、runtime 准入和功能兼容必须拆成独立维度:

1. **Provider source**:Gateway、API Key、OAuth/订阅账号或本地服务。
2. **Model mode**:由 Gateway 或 Provider 原生目录给出的能力类型。
3. **Transport adapter**:例如 OpenAI Responses、OpenAI Chat Completions、Anthropic Messages、Google Code Assist 或由官方 CLI 接管整轮执行。
4. **Agent runtime**:Codex CLI、Claude Code,或后续新增 runtime。
5. **Feature compatibility**:文本流式输出、reasoning、单/并行工具调用、工具错误与结果配对、图片输入、取消、超时、会话续接、same-turn steer、子 Agent 等。

具体要求:

- Provider 预设必须显式选择 transport adapter,不能只靠 `baseUrl` 和模型名前缀猜协议。
- 每个 Provider/模型可分别声明 Codex CLI 与 Claude Code 的状态:`supported`、`degraded`、`unsupported`、`unverified`,并保留原因;UI 不把未验证显示成已支持。
- `availableModels` 按当前 runtime 过滤,同一 Provider 可以只支持其中一个 runtime,也可以在两个 runtime 下使用不同模型 ID、端点或能力降级。
- 模型/Provider 特例统一放在 adapter 或结构化 quirks 中,至少能表达:鉴权/header、端点形式、reasoning 字段、工具调用 ID、`tool_use`/`tool_result` 配对、图片输入、流事件映射和 token 统计差异。
- 未识别 transport 不能静默套用通用 OpenAI 兼容路径;应标为不可路由或未验证,并向用户显示原因。
- 跨 Provider 子 Agent 必须保留完整任务正文、工具结果和会话上下文;不允许因 runtime 的 opaque/encrypted state 丢失任务内容。
- OAuth/订阅额度、API Key 和 Gateway 计费来源必须保持可追踪。发生限额、冷却或鉴权失败时,不得未经明确策略静默切换到可能产生不同账单的来源。

## 当前已下发但缺少正确分类或准入的模型

以下模型当前能从 Gateway 目录看到,但 Cindy 尚未建立完整、准确的能力分类;模型清单仍应以运行时 Gateway 返回为权威,不应把本表变成永久硬编码目录。

### Image generation

- `gemini-3.1-flash-image`
- `gemini-3-pro-image`
- `gpt-image-1.5`
- `gpt-image-2`

### Embedding

- `text-embedding-3-large`
- `text-embedding-3-small`
- `gemini-embedding-2-preview`
- `voyage/voyage-code-3`
- `voyage/voyage-4-large`
- `voyage/voyage-4`
- `voyage/voyage-context-4`

### Text to speech

- `elevenlabs/eleven_v3`
- `elevenlabs/eleven_multilingual_v2`

### Audio transcription / STT / ASR

- `gpt-4o-transcribe`
- `gpt-4o-mini-transcribe`
- `elevenlabs/scribe_v1`
- `elevenlabs/scribe_v1_experimental`
- `elevenlabs/scribe_v2`
- `gpt-realtime-whisper`
- `qwen3-asr-flash-realtime`
- `fun-asr-realtime-2026-02-28`

### Realtime audio / multimodal

- `gpt-realtime-2`
- `gpt-realtime-mini`
- `gpt-realtime-translate`
- `gemini-omni-flash-preview`

### Video generation / editing

- `doubao-seedance-2-0-260128`
- `doubao-seedance-2-0-fast-260128`
- `happyhorse-1.1-t2v`
- `happyhorse-1.1-i2v`
- `happyhorse-1.1-r2v`
- `happyhorse-1.0-video-edit`

### Compression

- `ai-gateway-doc` 当前被本地硬编码为 `other`;最终类型必须按 Gateway 返回的 `mode` 归类。
- Gateway 后续返回的所有 compression mode 模型都应自动进入压缩分类,无需再修改客户端模型名正则。

## OAuth 登录支持

### 新增 Google 账号登录

同时支持两种账号能力,二者模型目录和传输不能混用:

1. Google 订阅 Agent 传输:通过用户 Google 登录复用订阅额度,由对应官方运行时负责整轮执行。
2. Gemini CLI Code Assist API:复用 Gemini CLI OAuth 凭证,Cindy 保留 Agent loop 并直接调用 Code Assist API。

完整行为:

- 登录、取消、超时、失败重试、刷新、登出、过期恢复、应用重启恢复。
- OAuth token 只存 main 进程安全存储,不进入 renderer、配置文件、日志或 Git 跟踪路径。
- 登录后动态拉取该账号实际可用模型;静态列表仅作为离线/接口失败兜底。
- 设置页明确展示登录渠道、账号状态、刷新模型、重新登录和退出登录。
- 登录前展示风险与使用说明,由用户确认后继续;提示风险不应直接禁用功能。
- Desktop 与 Mobile/device-link 看到一致的 provider 连接态和模型投影,但凭证不下发到移动端。
- 账号路由保留来源、配额/冷却状态和最近失败原因;登出或凭证失效后立即清除活动路由和账号动态模型,不残留幽灵可用项。

### 账号登录模型目录缺口

以下是当前 Cindy `availableModels` 中缺失或命名未对齐、需要纳入动态发现/兜底目录的模型:

#### Google

- `gemini-3.1-pro`
- `gemini-3.1-flash-lite`
- `gemini-2.5-pro`
- `gemini-2.5-flash`

已存在但也必须能从登录账号动态发现并路由:

- `gemini-3.6-flash`
- `gemini-3.5-flash`
- `gemini-3.1-pro-preview`
- `gemini-3-flash-preview`

#### ChatGPT

- `gpt-5.3-codex-spark`

#### xAI

- `grok-4-1-fast-reasoning`
- `grok-4-1-fast-non-reasoning`
- `grok-code-fast-1`(与当前 `grok-code-fast` 明确做规范 ID 或 alias 映射,不能出现两个不可解释的重复项)

Claude 当前订阅目录中的 `claude-opus-4-8`、`claude-sonnet-4-6`、`claude-haiku-4-5` 已覆盖,不属于本次缺失模型。

## Provider 目录发布、合并与可观测性

- bundled 目录作为随客户端发布的可用基线;远端目录作为按稳定 Provider ID 合并的增量/覆盖层,不能默认整表替换 bundled 目录。
- 远端同 ID 条目可以覆盖 bundled 元数据;删除必须使用显式 tombstone/disabled 语义,不能通过“远端没带这个条目”隐式删除。
- 目录包含 `schemaVersion`、`revision`、`generatedAt` 和来源信息;客户端记录最近一次成功同步版本、时间和失败原因。
- 远端目录解析、校验或同步失败时继续使用最近一次已验证目录或 bundled 基线,并清楚展示当前状态。
- Provider/模型列表使用稳定 ID 合并;显示名、别名或排序变化不能产生重复项或幽灵项。
- 新 Provider 合入 main 后,发布流程必须明确选择:随客户端 bundled 发布,或进入远端目录发布;不得停留在源码已存在、生产目录不可见的中间状态。
- 发布前自动比较源码预设、bundled 产物和远端产物,报告缺失 ID、意外删除、重复 ID、schema 不兼容和 runtime 支持退化。

## 兼容性验证框架

建立一套由所有 Agent Provider 共用的 conformance suite,避免每新增一个 Provider 只做“能返回一句文本”的人工验证:

- 离线契约/fixture 测试覆盖协议转换和异常事件,默认进入 CI。
- 有凭证时运行受控 live smoke;无凭证不阻塞普通贡献,但正式发布某 Provider 前必须有对应验证记录。
- 同一组场景分别跑 Codex CLI 和 Claude Code,并按 Provider/模型记录结果:
- 模型发现和规范 ID/alias;
- 最小文本流式请求;
- reasoning 内容与 usage;
- 单工具、并行工具、工具错误、结果配对和连续多轮工具调用;
- 图片输入(模型声明支持时);
- 取消、超时、限流、鉴权失败和可恢复重试;
- 会话续接、应用重启恢复和 same-turn steer;
- 同 Provider 与跨 Provider 子 Agent 任务传递。
- 兼容结果生成机器可读报告并反哺 runtime 准入;`unverified` 不能自动提升为 `supported`。

## 实现边界

- Gateway `mode` 是能力类型的单一事实来源,但不单独决定 Agent/runtime 准入。
- Provider/账号来源、模型能力类型、transport adapter、Agent runtime 准入是独立维度,不能继续混在同一个 `group` 或模型名前缀里。
- 同一模型可以来自 Gateway、API Key Provider 或 OAuth Provider;来源切换不能改变其能力类型,但可以有不同 transport、配额和 runtime 兼容状态。
- 动态模型发现结果优先于兜底清单;模型下线后应可从活动目录移除,不产生永久幽灵项。
- 旧缓存没有 `mode` 时允许正则兜底;下一次成功同步后必须被权威数据替换。
- 命令审批、Auto-review/Guardian 的决策逻辑不属于本 issue;本 issue 只保证模型/Provider 在进入对应 runtime 前经过明确兼容准入。

## 验收标准

### 自动测试

- [ ] model-access 契约测试覆盖 `mode` 原样透传及未知 mode。
- [ ] 活动目录测试覆盖同一模型多来源合并时保留能力类型。
- [ ] 分类测试覆盖 Chat、Image、Video、TTS、STT/ASR、Realtime、Embedding、Compression 和未知类型。
- [ ] 回归测试确认 `gpt-realtime-*` 不再进入 GPT 聊天组。
- [ ] 回归测试确认 `x-ai/grok-4.5` 不再落入错误厂商组。
- [ ] Agent 目录测试确认非聊天模型不会进入 `availableModels`,但仍会出现在模型管理界面。
- [ ] 双 runtime 准入测试覆盖 `supported`、`degraded`、`unsupported`、`unverified`,并确认两个 runtime 可得到不同模型清单。
- [ ] transport adapter 契约测试覆盖 Responses、Chat Completions、Anthropic Messages、Code Assist 和官方 runtime 接管路径。
- [ ] 工具回归测试覆盖单/并行调用、工具错误、调用 ID 与结果配对、多轮连续工具调用。
- [ ] 取消、超时、限流、鉴权失败、会话续接、same-turn steer 和跨 Provider 子 Agent 均有回归覆盖。
- [ ] OAuth 测试覆盖 state/PKCE、回调、取消、超时、刷新、登出、重启恢复和失效凭证。
- [ ] OAuth 模型发现测试覆盖动态列表、失败兜底、模型下线和 ID alias。
- [ ] Desktop/Mobile provider 投影测试确认不传递 OAuth 密钥。
- [ ] bundled + remote 合并测试覆盖远端新增、同 ID 覆盖、显式删除、远端滞后、坏数据回退和稳定 ID 去重。
- [ ] 发布检查能发现源码、bundled、远端三份目录之间的缺失 Provider 和 runtime 支持退化。
- [ ] Light/Dark 和中英日韩文案测试通过。

### 本地手工验证

- [ ] 使用独立 sandbox 数据库启动 Desktop dev 环境,不读取或修改正式用户数据库。
- [ ] 登录后模型管理页按 Gateway mode 展示所有类型及上方模型清单。
- [ ] 新对话选择器只出现当前 runtime 真正可执行的聊天模型,并正确展示未验证/降级原因。
- [ ] 选择同一 Provider,分别用 Codex CLI 和 Claude Code 完成文本、工具调用、取消和会话续接验证。
- [ ] 完成 Google 登录,刷新账号模型并分别发起一次可用模型请求。
- [ ] 重启应用后登录态和模型目录恢复;登出后凭证、来源和模型路由立即失效。
- [ ] 模拟 token 过期、回调端口占用、用户取消和模型目录请求失败,界面均可恢复且不丢已有 Provider 配置。
- [ ] 模拟远端目录滞后、不可达和 schema 错误,bundled 新 Provider 仍可见,界面能显示当前目录 revision 和同步状态。

## 完成定义

- Gateway 与 Cindy 使用同一份 `mode` 命名和结构。
- 上述分类、模型目录缺口、OAuth 登录、transport adapter 和双 runtime 准入全部落地,不再需要为单个新模型修改分类正则或把“能返回文本”当成完整兼容。
- bundled/remote 目录不会再因整表覆盖或发布遗漏导致新 Provider 在生产环境不可见。
- conformance suite 生成两个 runtime 的机器可读兼容报告,并作为 Provider 发布和 Agent 准入依据。
- 本地 sandbox dev 验证通过,并附模型分类截图、OAuth 登录状态截图、目录 revision、双 runtime 兼容报告和测试结果。

Contributor guide

Open the contributing guide

Research direction

Start with the model-access /models contract, Gateway mode flow, provider projections, OAuth entry points, bundled/remote directory merge, and the runtime compatibility paths described in the issue. Use the listed automated and sandbox verification checks as the completion criteria; this issue is broad enough that its work should likely be decomposed before implementation.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, react-native, typescript
Domain
api, authentication, backend, desktop, devtools, frontend, mobile, testing
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
15/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.