Skip to content

fix(api-service): make image-generation tools account-capability aware - #1964

Open
MartyAlien wants to merge 1 commit into
jlcodes99:mainfrom
MartyAlien:fix/image-generation-403-for-text-requests
Open

fix(api-service): make image-generation tools account-capability aware#1964
MartyAlien wants to merge 1 commit into
jlcodes99:mainfrom
MartyAlien:fix/image-generation-403-for-text-requests

Conversation

@MartyAlien

Copy link
Copy Markdown

问题

账号池加入不支持图片生成的中转站账号(如 Sub2api)后,本地网关默认会把 image_generation 工具注入到普通文本请求里。上游因为账号组没有生图权限,直接拒绝了整个请求:

403 {"error":{"message":"Image generation is not enabled for this group","type":"permission_error"}}

结果是 /v1/responses/v1/chat/completions 和内置对话测试全部不可用——哪怕消息只是纯文本。v1.3.4 已经移除了 image_generation 的禁用开关,但错误提示还在引导用户去改一个界面里不存在的设置(#1176 也提过这一点,本次一并修正了文案)。

思路

与其像全局开关那样"一刀切"关掉生图(能生图的账号也会失去对话内生图的能力),不如让网关记住每个账号的生图能力,按账号下菜

  • 账号第一次被上游明确告知"没有生图权限"之后,网关会记下来。之后这个账号的文本请求自动不再带生图工具,纯文本照常可用;
  • 能生图的账号不受任何影响,依然可以在对话里让模型画图;
  • 明确要生图的请求,会优先去找能生图的账号;实在没有才体面降级。

变更

  • 精确认定:只认"生图权限缺失"这一种错误(状态码 + 报文内容双重匹配),配额、鉴权、模型等其他 403 不会被误伤;
  • 按账号学习:标记只保存在内存里,不落盘、不挂起账号(文本流量不受影响),重启后重新学习一次即可,成功生图后自动恢复;
  • 自动降级:在不能生图的账号上,网关会主动剥掉生图工具声明(包括客户端自己带上的)再按纯文本转发,并在响应头带 x-agtools-image-degraded: 1 告诉客户端;
  • 能力优先选号/v1/images/* 或携带生图工具声明的请求,优先路由到能生图的账号;
  • 换号重试:遇到生图权限 403 会自动换下一个账号重发一次;
  • 明确报错:显式图片接口命中无能力账号时,本地直接返回 403 image_generation_not_enabled,不再透传中转站晦涩错误;
  • 文案修正:错误提示改为描述自动降级行为,不再指向已删除的设置。

覆盖 Go sidecar(executor / auth / helps / main)与 Rust legacy 网关两条链路,双侧对称,均带单元测试。无新增配置项、无 UI 变更;手动请求头 x-agtools-disable-image-generation 的语义保持不变且始终优先。

验证

  • go test ./internal/runtime/executor/... ./sdk/cliproxy/auth/... .:全部通过;
  • cargo test --lib codex_local_access::tests(MSVC):237 通过 / 1 失败——失败项 builds_upstream_websocket_url_from_custom_base_url 读取本机全局代理配置,属环境性既有失败,与本改动无关;
  • 真机端到端(Sub2api 账号实测):纯文本首请求 403 一次(学习)→ 重试成功且响应头带降级标记;带生图工具声明的对话请求自动剥工具降级成功;/v1/images/generations 返回本地明确的 403。

Fixes #1940

Codex API 服务的账号池加入不支持图片生成的上游(如 Sub2api 等中转站)后,
网关默认会把 hosted image_generation 工具注入到普通文本请求中,导致上游对
整个请求返回 403 {"message":"Image generation is not enabled for this group"},
纯文本对话(/v1/responses、/v1/chat/completions 与内置对话测试)全部不可用
(fixes jlcodes99#1940,源自 jlcodes99#1176)。此前的错误提示还引导用户修改一个在 v1.3.4
已移除的设置项。

1. 精确认定:以 400/403/422 状态门 + 整包短语匹配(image_generation /
   image generation + not enabled / not available / not allowed /
   not supported / disabled)识别"上游无图片生成权限"错误,配额、鉴权、
   模型等无关 403 不会被误分类。
2. 乐观学习:账号初始未知,首次文本请求仍注入生图工具;命中上述错误即把
   该账号标记为不可生图(runtime-only、不落盘、不挂起模型,文本流量不受
   影响),成功生图后自动清除标记。
3. 按账号注入:对已标记账号,文本请求不再注入 hosted 工具,并把客户端
   声明 的生图工具与对应 tool_choice 一并剥除,纯文本正常转发(自动降级)。
4. 能力优先选号:显式生图请求(/v1/images/* 或 payload 声明生图工具)优先
   路由到未标记不可生图的账号;全池不可生图时保持原顺序、由降级路径兜底。
5. 换号重试:生图权限 403 允许恰好一次立即重发以更换账号(sidecar 请求
   无 previous_response_id 依赖;Rust legacy 在轮转前剥离
   previous_response_id 防止上下文不匹配 404)。
6. 明确错误:显式图片接口命中不可生图账号时,本地直接返回 403
   image_generation_not_enabled,不再透传中转站晦涩错误。
7. 降级可见:自动降级时响应头 x-agtools-image-degraded: 1
   (HTTP / SSE / WebSocket 全覆盖)。
8. 文案修正:相关错误提示改为描述自动降级行为,不再指向已删除的设置。

- 混合"能生图 / 不能生图"的账号池零配置可用:纯文本对话不再因生图工具
  声明被 403(重启后仅首次请求学习一次,之后全自动);
- 能生图账号完整保留"对话内模型自主生图"能力;
- 客户端显式生图请求自动流向有能力账号,全池不可用时体面降级;
- 显式图片接口对无能力账号返回本地明确错误。

改动范围:Go sidecar(executor / auth / helps / main)与 Rust legacy
(codex_local_access.rs),共 12 个文件,含双侧聚焦测试;无新增配置项、
无 UI 变更;手动请求头 x-agtools-disable-image-generation 语义保持不变
且始终优先。
@MartyAlien
MartyAlien force-pushed the fix/image-generation-403-for-text-requests branch from d352dc5 to be3ef6c Compare August 18, 2026 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API服务上游不支持图片请求的纯文本对话出现403报错

1 participant