澄清 Codex 图片桥接与本地执行器边界

配置界面现在区分非 Responses Lite 的 hosted 桥接与客户端本地 image_gen,并准确描述账号 strip 策略。API Key Mode 同时提示完全重启 Codex 后新建 task,以重建客户端工具注册表。

Constraint: 兼容模式继续保持默认,Responses Lite 的本地工具由客户端与账号策略决定
Rejected: 写入 features.image_generation | 当前 Codex 已默认启用且仓库既有配置刻意省略该项
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: 图片桥接文案必须区分 hosted 注入、客户端声明和 image-only 路由
Tested: 前端 lint、typecheck、关键套件 93 项、定向套件 43 项
Not-tested: Codex Desktop 真实工具注册表重建

Co-authored-by: OmX <omx@oh-my-codex.dev>
This commit is contained in:
viccy
2026-07-16 17:11:17 +08:00
co-authored by OmX
parent 2b5944fd6c
commit 9d1064badd
10 changed files with 60 additions and 29 deletions
@@ -1455,7 +1455,7 @@
</div>
</div>
<!-- OpenAI Codex 图片工具统一策略(自动注入 + 客户端显式携带) -->
<!-- OpenAI Codex hosted image_generation bridge policy -->
<div
v-if="account?.platform === 'openai' && (account?.type === 'oauth' || account?.type === 'setup-token' || account?.type === 'apikey')"
class="border-t border-gray-200 pt-4 dark:border-dark-600"
@@ -771,6 +771,10 @@ describe('EditAccountModal', () => {
const wrapper = mountModal(account)
expect(wrapper.text()).toContain('admin.accounts.openai.codexImageTool')
expect(wrapper.text()).toContain('admin.accounts.openai.codexImageToolDesc')
expect(wrapper.text()).toContain('admin.accounts.openai.codexImageToolEnabledDesc')
await wrapper.get('button[data-testid="codex-image-tool-enabled"]').trigger('click')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
@@ -809,6 +813,9 @@ describe('EditAccountModal', () => {
const wrapper = mountModal(account)
expect(wrapper.text()).toContain('admin.accounts.openai.codexImageToolBlock')
expect(wrapper.text()).toContain('admin.accounts.openai.codexImageToolBlockDesc')
await wrapper.get('button[data-testid="codex-image-tool-block"]').trigger('click')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
@@ -100,6 +100,14 @@
{{ t('keys.useKeyModal.openai.authModeApiKey') }}
</button>
</div>
<div
v-if="codexAuthMode === 'api-key'"
data-testid="codex-api-key-restart-notice"
class="mt-3 flex items-start gap-2 border-l-2 border-amber-400 bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-800 dark:border-amber-500 dark:bg-amber-950/30 dark:text-amber-200"
>
<Icon name="exclamationCircle" size="sm" class="mt-0.5 flex-shrink-0" />
<p>{{ t('keys.useKeyModal.openai.authModeApiKeyRestartNotice') }}</p>
</div>
</div>
<!-- OS/Shell Tabs -->
@@ -267,6 +267,7 @@ describe('UseKeyModal', () => {
expect(configToml).toContain('[features]\ngoals = true')
expect(codeBlocks).toContain('{\n "OPENAI_API_KEY": "sk-test"\n}')
expect(wrapper.text()).toContain('auth.json')
expect(wrapper.find('[data-testid="codex-api-key-restart-notice"]').exists()).toBe(false)
})
it('renders API Key Mode authorization in OpenAI Codex config', async () => {
@@ -304,6 +305,19 @@ describe('UseKeyModal', () => {
expect(configToml).not.toContain('image_generation')
expect(codeBlocks).toContain('{\n "OPENAI_API_KEY": "sk-test"\n}')
expect(wrapper.text()).toContain('auth.json')
const restartNotice = wrapper.get('[data-testid="codex-api-key-restart-notice"]')
expect(restartNotice.text()).toContain(
'keys.useKeyModal.openai.authModeApiKeyRestartNotice'
)
await wrapper.get('[data-testid="codex-auth-mode-legacy"]').trigger('click')
await nextTick()
expect(wrapper.find('[data-testid="codex-api-key-restart-notice"]').exists()).toBe(false)
expect(wrapper.findAll('pre code').map((code) => code.text()).join('\n')).not.toContain(
'x-openai-actor-authorization'
)
})
it('keeps legacy OpenAI Codex WebSocket config as the default', async () => {
+12 -12
View File
@@ -489,21 +489,21 @@ export default {
codexCLIOnlyAppServer: 'Allow Codex app-server clients',
codexCLIOnlyAppServerDesc:
"Effective only when the switch above is on. When enabled, this account also allows third-party clients that embed the Codex engine over the app-server protocol (e.g. Claude Code's codex plugin); they still pass the global engine-fingerprint gate. OR-combined with the global app-server toggle.",
codexImageTool: 'Codex image tool',
codexImageTool: 'Codex image bridge policy',
codexImageToolDesc:
'One policy for the image_generation tool on Codex /responses text requests: whether it is auto-injected, and whether client-provided tools pass through. Account policy takes precedence over channel and global settings; standalone image-generation endpoints are unaffected.',
'Controls the hosted image_generation bridge and client-declared image tools on Codex /responses text requests. Hosted auto-injection applies only to non-Responses Lite requests. Account policy takes precedence over channel and global settings; standalone image-generation endpoints are unaffected.',
codexImageToolInherit: 'Follow channel',
codexImageToolInheritDesc: 'No account override; injection follows the channel or global policy, and client-provided image tools pass through.',
codexImageToolEnabled: 'Force inject',
codexImageToolEnabledDesc: 'Always inject the image tool for Codex /responses requests.',
codexImageToolDisabled: 'No injection',
codexImageToolDisabledDesc: 'Never auto-inject; client-provided image tools still pass through.',
codexImageToolBlock: 'Block all',
codexImageToolBlockDesc: 'No injection, and client-provided image tools plus matching tool_choice are removed.',
codexImageToolInheritDesc: 'No account override; hosted injection for non-Lite requests follows the channel or global policy, while client-provided hosted tools and local image_gen declarations pass through.',
codexImageToolEnabled: 'Enable hosted bridge',
codexImageToolEnabledDesc: 'Inject the hosted image_generation tool only for non-Responses Lite requests; client-provided image tools still pass through.',
codexImageToolDisabled: 'No hosted injection',
codexImageToolDisabledDesc: 'Do not inject the hosted tool; client-provided hosted tools and local image_gen declarations still pass through.',
codexImageToolBlock: 'Strip client image tools',
codexImageToolBlockDesc: 'Do not auto-inject through the bridge, and remove client-provided hosted image_generation tools, local image_gen declarations, and matching tool_choice. Image-only model routing remains unaffected.',
codexImageToolBadgeInherit: 'Channel policy',
codexImageToolBadgeEnabled: 'Force inject',
codexImageToolBadgeDisabled: 'No injection',
codexImageToolBadgeBlock: 'Blocked',
codexImageToolBadgeEnabled: 'Hosted bridge on',
codexImageToolBadgeDisabled: 'No hosted injection',
codexImageToolBadgeBlock: 'Client image tools stripped',
compactMode: 'Compact mode',
compactModeDesc:
'Controls how this account participates in /responses/compact routing. Auto follows probe results, Force On always allows, Force Off always excludes.',
@@ -156,7 +156,7 @@ export default {
webSearchEmulationHint: '⚠️ When enabled, all accounts in this channel\'s Anthropic groups will intercept web_search requests. Use with caution.',
webSearchEmulationGlobalDisabled: 'Please enable the global switch first in Settings → Gateway → Web Search Emulation',
codexImageGenerationBridge: 'Codex Image Generation Bridge',
codexImageGenerationBridgeHint: 'When enabled, Codex /responses text requests in OpenAI groups may be automatically given the image_generation tool. Keep off unless the routed accounts support image generation.',
codexImageGenerationBridgeHint: 'When enabled, only non-Responses Lite Codex /responses text requests in OpenAI groups receive the hosted image_generation tool. The bridge does not inject tools for Responses Lite; local image_gen handling follows the client and account policy. Leave this off unless routed accounts support image generation.',
bedrockCCCompat: 'Bedrock CC Compatibility',
bedrockCCCompatHint: '⚠️ When enabled, requests to Bedrock accounts in this channel will be transformed for Claude Code compatibility (thinking type conversion, tool_use ID sanitization).',
basicSettings: 'Basic Settings',
+2 -1
View File
@@ -140,9 +140,10 @@ export default {
openai: {
description: 'Add the following configuration files to your Codex CLI config directory.',
authModeTitle: 'Codex authentication mode',
authModeDescription: 'Compatibility mode keeps the existing setup for older Codex clients. API Key Mode enables the client-side image executor.',
authModeDescription: 'Compatibility mode keeps the existing setup for older Codex clients. API Key Mode authorizes the client-side image executor.',
authModeLegacy: 'Compatibility mode',
authModeApiKey: 'API Key Mode',
authModeApiKeyRestartNotice: 'After saving this configuration, completely quit and restart Codex Desktop or CLI, then create a new task so the client can rebuild its tool registry.',
configTomlHint: 'Make sure the following content is at the beginning of the config.toml file',
note: 'Make sure the config directory exists. macOS/Linux users can run mkdir -p ~/.codex to create it.',
noteWindows: 'Press Win+R and enter %userprofile%\\.codex to open the config directory. Create it manually if it does not exist.',
+12 -12
View File
@@ -587,21 +587,21 @@ export default {
codexCLIOnlyDesc: '仅对 OpenAI OAuth 生效。开启后仅允许 Codex 官方客户端家族访问;关闭后完全绕过并保持原逻辑。',
codexCLIOnlyAppServer: '允许 Codex app-server 客户端',
codexCLIOnlyAppServerDesc: '仅在上方开关开启时生效。开启后本账号额外放行内嵌 Codex 引擎、经 app-server 协议接入的第三方客户端(如 Claude Code 的 codex 插件),仍需通过全局引擎指纹门;与全局 app-server 开关取 OR(任一开即放行)。',
codexImageTool: 'Codex 图片工具',
codexImageTool: 'Codex 图片桥接策略',
codexImageToolDesc:
'统一控制 Codex /responses 文本请求的 image_generation 图片工具:是否自动注入,以及客户端自带该工具时是否放行。账号级策略优先于渠道和全局配置,不影响独立图片生成接口。',
'统一控制 Codex /responses 文本请求的 hosted image_generation 桥接和客户端图片工具声明。hosted 工具自动注入仅适用于非 Responses Lite 请求;账号级策略优先于渠道和全局配置,不影响独立图片生成接口。',
codexImageToolInherit: '跟随渠道',
codexImageToolInheritDesc: '不写入账号覆盖,是否注入由渠道或全局策略决定;客户端自带的图片工具照常放行。',
codexImageToolEnabled: '强制注入',
codexImageToolEnabledDesc: '始终为 Codex /responses 请求注入图片工具。',
codexImageToolDisabled: '关闭注入',
codexImageToolDisabledDesc: '不自动注入;客户端自带的图片工具仍会放行。',
codexImageToolBlock: '完全阻断',
codexImageToolBlockDesc: '不注入,并移除客户端自带的图片工具及指向它的 tool_choice。',
codexImageToolInheritDesc: '不写入账号覆盖;非 Lite 请求是否注入 hosted 工具由渠道或全局策略决定,客户端显式携带的 hosted 工具和本地 image_gen 声明照常放行。',
codexImageToolEnabled: '启用 Hosted 桥接',
codexImageToolEnabledDesc: '仅为非 Responses Lite 请求注入 hosted image_generation 工具;客户端显式携带的图片工具仍会放行。',
codexImageToolDisabled: '不注入 Hosted 工具',
codexImageToolDisabledDesc: '不注入 hosted 工具;客户端显式携带的 hosted 工具和本地 image_gen 声明仍会放行。',
codexImageToolBlock: '移除客户端图片工具',
codexImageToolBlockDesc: '不通过桥接自动注入 hosted 工具,并移除客户端显式携带的 hosted image_generation 工具、本地 image_gen 声明及相关 tool_choice;image-only 模型路由不受影响。',
codexImageToolBadgeInherit: '渠道策略',
codexImageToolBadgeEnabled: '强制注入',
codexImageToolBadgeDisabled: '关闭注入',
codexImageToolBadgeBlock: '完全阻断',
codexImageToolBadgeEnabled: 'Hosted 桥接已开启',
codexImageToolBadgeDisabled: '不注入 Hosted 工具',
codexImageToolBadgeBlock: '客户端图片工具已移除',
compactMode: 'Compact 模式',
compactModeDesc:
'控制本账号在 /responses/compact 调度中的参与方式。Auto 跟随探测结果,Force On 强制允许,Force Off 强制排除。',
@@ -156,7 +156,7 @@ export default {
webSearchEmulationHint: '⚠️ 开启后该渠道下所有 Anthropic 分组的账号将自动拦截 web_search 请求,请谨慎操作',
webSearchEmulationGlobalDisabled: '请先在系统设置 → 网关 → Web Search 模拟中启用全局开关',
codexImageGenerationBridge: 'Codex 图片生成桥接',
codexImageGenerationBridgeHint: '开启后,OpenAI 分组的 Codex /responses 文本请求可能会被自动注入 image_generation 工具。仅在路由账号支持图片生成时开启。',
codexImageGenerationBridgeHint: '开启后,OpenAI 分组仅会为非 Responses Lite 的 Codex /responses 文本请求自动注入 hosted image_generation 工具。桥接不会为 Responses Lite 注入工具;本地 image_gen 的处理由客户端和账号策略决定。仅在路由账号支持图片生成时开启。',
bedrockCCCompat: 'Bedrock CC 兼容',
bedrockCCCompatHint: '⚠️ 开启后,该渠道下 Bedrock 账号的请求将进行 Claude Code 兼容处理(thinking 类型转换、tool_use ID 清理)',
basicSettings: '基础设置',
+2 -1
View File
@@ -140,9 +140,10 @@ export default {
openai: {
description: '将以下配置文件添加到 Codex CLI 配置目录中。',
authModeTitle: 'Codex 认证模式',
authModeDescription: '兼容模式保留旧版 Codex 配置;API Key Mode 用于启用客户端图片执行器。',
authModeDescription: '兼容模式保留旧版 Codex 配置;API Key Mode 用于授权客户端图片执行器。',
authModeLegacy: '兼容模式',
authModeApiKey: 'API Key Mode',
authModeApiKeyRestartNotice: '保存此配置后,必须完全退出并重启 Codex Desktop 或 CLI,然后新建 task,让客户端重新构建工具注册表。',
configTomlHint: '请确保以下内容位于 config.toml 文件的开头部分',
note: '请确保配置目录存在。macOS/Linux 用户可运行 mkdir -p ~/.codex 创建目录。',
noteWindows: