bug: Codex + DeepSeek 附图发送报 unsupported_feature,无图片能力的模型应在发送前提示
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 401
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
### 问题描述 / What happened
Codex + DeepSeek V4 Pro 组合下,用户在对话中附带图片发送,会直接在气泡里收到裸 JSON 报错:
```json
{"error":{"type":"invalid_request_error","code":"unsupported_feature","message":"Responses feature is not supported by the Chat Completions bridge: input content part 'input_image'"}}
```
期望:选择了不支持图片识别的模型时,**在发送前(最好是附图那一刻)就提示**「当前模型不支持图片识别」,而不是把请求发出去之后回显一段用户看不懂的 JSON。
### 环境 / Environment
- Cindy 版本或 commit: main `12c1667e`(已验证最新代码可复现)
- 平台与版本: macOS(与平台无关,链路在 maker-host / bridge)
- 安装方式: 源码
### 复现步骤 / Reproduction
1. Codex runtime 选择 DeepSeek preset(`deepseek-v4-pro`,任何 `wireProtocol: "openai-chat"` 的供应商同理,如 Kimi/GLM);
2. 在 composer 附加任意图片并发送;
3. 气泡返回上述 400 JSON。
也可以最小复现:直接调 `translateResponsesRequest`,input 里带 `{ type: 'input_image', ... }` content part 即抛错,错误串与 UI 显示逐字一致。
### 根因链路(已排查)
1. DeepSeek 的 codex preset 声明 `wireProtocol: "openai-chat"`(`packages/model-providers/catalog/providers.json`,DeepSeek 官方只有 Chat Completions API);
2. 该路由走本地 Responses→Chat 桥接:`apps/desktop/src/main/maker-host/codex-proxy-host.ts:224` 的 `createResponsesChatHandler`;
3. 翻译层 `packages/responses-chat-bridge/src/translate-request.ts:68` 只接受文本类 content part,遇到 `input_image` 按 fail-closed 原则抛 `UnsupportedResponsesFeatureError`(`types.ts` 注释写明:未知 input 必须 fail-closed,不能静默删上下文;`translate-request.test.ts:231` 有测试断言该行为);
4. `handler.ts:106` 把异常包成 400 JSON 返回,UI 原样展示。
**关键背景**:DeepSeek API 本身不支持图片输入(V4 Pro/Flash 均为纯文本模型),所以「让 bridge 把图片翻译成 Chat 的 `image_url` part」救不了这个场景——上游也会拒收。bridge 的 fail-closed 行为本身是对的,要修的是发送前的体验。
### 建议方案
**主修:发送前按模型能力位门控图片附件。**
现状地基(已确认):
- `packages/model-providers/src/types.ts:226` 已定义 `modalities.input` / `capabilities.attachment`,但目前是死字段——全仓无消费方,catalog 也没填值;
- Composer 已有同款先例:Fast Mode 按模型能力门控(`apps/desktop/src/renderer/lib/providerModels.ts:59` 的 `resolveFastSupported`),可照抄模式加 `resolveImageInputSupported`;
- 桌面端发送路径 `ChatInput.tsx:3466` 目前无条件带附件;移动端是独立的一套 composer(`apps/mobile/src/session/useMobileLocalAttachments.ts` 等),按仓库规则两端都要做。
实现要点建议(可讨论):
1. **未知模型默认放行(fail-open)**:只拦 catalog 里明确标了不支持图片的内置模型(DeepSeek V4 Pro/Flash、Kimi 等纯文本模型);自定义 provider 的模型没有元数据,不拦,避免误伤用户自己接的 vision 模型,上游报错仍是兜底。
2. **警示不禁用**:附件托盘上提示「当前模型不支持图片识别」,发送时阻断或确认;不要彻底禁用加图按钮,否则「先附图再切模型」的流程会别扭。注意反向路径:先附图、后切到不支持的模型,提示也要出现。
3. **第二道防线(顺带)**:把 bridge 这个 `unsupported_feature` 400 映射成友好的本地化提示,不再给用户看裸 JSON。handler 已有结构化错误码,改动很小。
4. UI 改动须同时交付 Light/Dark 两种模式(仓库门槛)。
Contributor guide
Research direction
Start with providerModels.ts and ChatInput.tsx to trace model capability checks and desktop attachment sending, then inspect the mobile attachment path and the bridge handler.ts. Review translate-request.test.ts before changing error handling. Done means unsupported models are warned about before sending on both platforms, the bridge error is user-friendly, and the UI works in Light and Dark modes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- backend-api-design, frontend, internationalization, mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100