diff --git a/frontend/src/components/keys/UseKeyModal.vue b/frontend/src/components/keys/UseKeyModal.vue index e6bce556da..87983f94cc 100644 --- a/frontend/src/components/keys/UseKeyModal.vue +++ b/frontend/src/components/keys/UseKeyModal.vue @@ -562,29 +562,36 @@ windows_wsl_setup_acknowledged = true name = "OpenAI" base_url = "${baseUrl}" wire_api = "responses" -requires_openai_auth = true +requires_openai_auth = false +env_key = "SUB2API_API_KEY" +http_headers = { "x-openai-actor-authorization" = "local-image-extension" } [features] +image_generation = true goals = true` - // auth.json content - const authContent = `{ - "OPENAI_API_KEY": "${apiKey}" -}` - return [ { path: `${configDir}/config.toml`, content: configContent, hint: t('keys.useKeyModal.openai.configTomlHint') }, - { - path: `${configDir}/auth.json`, - content: authContent - } + generateCodexAPIKeyEnvironment(apiKey, isWindows) ] } +function generateCodexAPIKeyEnvironment(apiKey: string, isWindows: boolean): FileConfig { + return isWindows + ? { + path: 'PowerShell', + content: `$env:SUB2API_API_KEY="${apiKey}"` + } + : { + path: 'Terminal', + content: `export SUB2API_API_KEY="${apiKey}"` + } +} + function generateGrokFiles(baseUrl: string, apiKey: string): FileConfig[] { const isWindows = activeTab.value === 'windows' const configDir = isWindows ? '%userprofile%\\.grok' : '~/.grok' @@ -613,7 +620,7 @@ function generateOpenAIWsFiles(baseUrl: string, apiKey: string): FileConfig[] { const isWindows = activeTab.value === 'windows' const configDir = isWindows ? '%userprofile%\\.codex' : '~/.codex' - // config.toml content with WebSocket v2 + // config.toml content with the Responses WebSocket transport enabled const configContent = `model_provider = "OpenAI" model = "gpt-5.5" review_model = "gpt-5.5" @@ -627,27 +634,21 @@ name = "OpenAI" base_url = "${baseUrl}" wire_api = "responses" supports_websockets = true -requires_openai_auth = true +requires_openai_auth = false +env_key = "SUB2API_API_KEY" +http_headers = { "x-openai-actor-authorization" = "local-image-extension" } [features] -responses_websockets_v2 = true +image_generation = true goals = true` - // auth.json content - const authContent = `{ - "OPENAI_API_KEY": "${apiKey}" -}` - return [ { path: `${configDir}/config.toml`, content: configContent, hint: t('keys.useKeyModal.openai.configTomlHint') }, - { - path: `${configDir}/auth.json`, - content: authContent - } + generateCodexAPIKeyEnvironment(apiKey, isWindows) ] } diff --git a/frontend/src/components/keys/__tests__/UseKeyModal.spec.ts b/frontend/src/components/keys/__tests__/UseKeyModal.spec.ts index 24581d9f6b..4e216de8da 100644 --- a/frontend/src/components/keys/__tests__/UseKeyModal.spec.ts +++ b/frontend/src/components/keys/__tests__/UseKeyModal.spec.ts @@ -78,7 +78,7 @@ describe('UseKeyModal', () => { expect(parsed.provider.grok.models['gpt-5.6']).toBeUndefined() }) - it('renders GPT-5.5 and goals feature in OpenAI Codex config', () => { + it('renders API key mode and local image generation in OpenAI Codex config', () => { const wrapper = mount(UseKeyModal, { props: { show: true, @@ -107,10 +107,17 @@ describe('UseKeyModal', () => { expect(configToml).not.toContain('model = "gpt-5.4"') expect(configToml).not.toContain('model_context_window') expect(configToml).not.toContain('model_auto_compact_token_limit') - expect(configToml).toContain('[features]\ngoals = true') + expect(configToml).toContain('requires_openai_auth = false') + expect(configToml).toContain('env_key = "SUB2API_API_KEY"') + expect(configToml).toContain('http_headers = { "x-openai-actor-authorization" = "local-image-extension" }') + expect(configToml).not.toContain('supports_websockets') + expect(configToml).not.toContain('responses_websockets_v2') + expect(configToml).toContain('[features]\nimage_generation = true\ngoals = true') + expect(codeBlocks).toContain('export SUB2API_API_KEY="sk-test"') + expect(wrapper.text()).not.toContain('auth.json') }) - it('renders GPT-5.5 and goals feature in OpenAI Codex WebSocket config', async () => { + it('renders API key mode and current WebSocket settings in OpenAI Codex config', async () => { const wrapper = mount(UseKeyModal, { props: { show: true, @@ -147,7 +154,47 @@ describe('UseKeyModal', () => { expect(configToml).not.toContain('model = "gpt-5.4"') expect(configToml).not.toContain('model_context_window') expect(configToml).not.toContain('model_auto_compact_token_limit') - expect(configToml).toContain('[features]\nresponses_websockets_v2 = true\ngoals = true') + expect(configToml).toContain('requires_openai_auth = false') + expect(configToml).toContain('env_key = "SUB2API_API_KEY"') + expect(configToml).toContain('http_headers = { "x-openai-actor-authorization" = "local-image-extension" }') + expect(configToml).toContain('supports_websockets = true') + expect(configToml).not.toContain('responses_websockets_v2') + expect(configToml).toContain('[features]\nimage_generation = true\ngoals = true') + expect(codeBlocks).toContain('export SUB2API_API_KEY="sk-test"') + expect(wrapper.text()).not.toContain('auth.json') + }) + + it('renders a PowerShell API key command for Codex on Windows', async () => { + const wrapper = mount(UseKeyModal, { + props: { + show: true, + apiKey: 'sk-test', + baseUrl: 'https://example.com/v1', + platform: 'openai' + }, + global: { + stubs: { + BaseDialog: { + template: '
' + }, + Icon: { + template: '' + } + } + } + }) + + const windowsTab = wrapper.findAll('button').find( + (button) => button.text().trim() === 'Windows' + ) + + expect(windowsTab).toBeDefined() + await windowsTab!.trigger('click') + await nextTick() + + const codeBlocks = wrapper.findAll('pre code').map((code) => code.text()) + expect(codeBlocks).toContain('$env:SUB2API_API_KEY="sk-test"') + expect(wrapper.text()).not.toContain('auth.json') }) it('renders GPT-5.4 mini entry in OpenCode config', async () => { diff --git a/frontend/src/i18n/locales/en/admin/channels.ts b/frontend/src/i18n/locales/en/admin/channels.ts index 07a24d0fd5..eaeacc73b4 100644 --- a/frontend/src/i18n/locales/en/admin/channels.ts +++ b/frontend/src/i18n/locales/en/admin/channels.ts @@ -155,7 +155,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: 'Hosted image bridge for non-Responses Lite requests only. It may inject the image_generation tool into Codex /responses text requests; Responses Lite uses the client-side image executor instead.', 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', diff --git a/frontend/src/i18n/locales/en/dashboard.ts b/frontend/src/i18n/locales/en/dashboard.ts index 816f6bd440..3d70935a05 100644 --- a/frontend/src/i18n/locales/en/dashboard.ts +++ b/frontend/src/i18n/locales/en/dashboard.ts @@ -136,16 +136,16 @@ export default { noGroupTitle: 'Please assign a group first', noGroupDescription: 'This API key has not been assigned to a group. Please click the group column in the key list to assign one before viewing the configuration.', openai: { - description: 'Add the following configuration files to your Codex CLI config directory.', + description: 'Set SUB2API_API_KEY before starting Codex, then add the config.toml settings below. Responses Lite image generation uses Codex\'s local image executor; hosted force injection applies only to non-Responses Lite requests.', 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.', + note: 'Make sure ~/.codex exists, set the environment variable before Codex/Desktop starts, then fully restart the client and create a new thread so the image tool is registered.', + noteWindows: 'Make sure %userprofile%\\.codex exists, set the environment variable before Codex/Desktop starts, then fully restart the client and create a new thread so the image tool is registered.', }, cliTabs: { claudeCode: 'Claude Code', geminiCli: 'Gemini CLI', - codexCli: 'Codex CLI', - codexCliWs: 'Codex CLI (WebSocket)', + codexCli: 'Codex (API Key Mode)', + codexCliWs: 'Codex (API Key Mode, WebSocket)', grokCli: 'Grok CLI', opencode: 'OpenCode', }, diff --git a/frontend/src/i18n/locales/zh/admin/channels.ts b/frontend/src/i18n/locales/zh/admin/channels.ts index 93890e25b0..b0b3717f64 100644 --- a/frontend/src/i18n/locales/zh/admin/channels.ts +++ b/frontend/src/i18n/locales/zh/admin/channels.ts @@ -155,7 +155,7 @@ export default { webSearchEmulationHint: '⚠️ 开启后该渠道下所有 Anthropic 分组的账号将自动拦截 web_search 请求,请谨慎操作', webSearchEmulationGlobalDisabled: '请先在系统设置 → 网关 → Web Search 模拟中启用全局开关', codexImageGenerationBridge: 'Codex 图片生成桥接', - codexImageGenerationBridgeHint: '开启后,OpenAI 分组的 Codex /responses 文本请求可能会被自动注入 image_generation 工具。仅在路由账号支持图片生成时开启。', + codexImageGenerationBridgeHint: '仅用于非 Responses Lite 请求的服务端图片桥接,可向 Codex /responses 文本请求注入 image_generation 工具;Responses Lite 改用客户端图片执行器。', bedrockCCCompat: 'Bedrock CC 兼容', bedrockCCCompatHint: '⚠️ 开启后,该渠道下 Bedrock 账号的请求将进行 Claude Code 兼容处理(thinking 类型转换、tool_use ID 清理)', basicSettings: '基础设置', diff --git a/frontend/src/i18n/locales/zh/dashboard.ts b/frontend/src/i18n/locales/zh/dashboard.ts index a268919612..7472ec781c 100644 --- a/frontend/src/i18n/locales/zh/dashboard.ts +++ b/frontend/src/i18n/locales/zh/dashboard.ts @@ -136,17 +136,17 @@ export default { noGroupDescription: '此 API 密钥尚未分配分组,请先在密钥列表中点击分组列进行分配,然后才能查看使用配置。', openai: { - description: '将以下配置文件添加到 Codex CLI 配置目录中。', + description: '启动 Codex 前先设置 SUB2API_API_KEY,再添加以下 config.toml 配置。Responses Lite 生图使用 Codex 本地图片执行器;服务端强制注入仅用于非 Responses Lite 请求。', configTomlHint: '请确保以下内容位于 config.toml 文件的开头部分', - note: '请确保配置目录存在。macOS/Linux 用户可运行 mkdir -p ~/.codex 创建目录。', + note: '请确保 ~/.codex 目录存在,并在 Codex/Desktop 启动前设置环境变量;随后完全重启客户端并新建会话,以重新注册图片工具。', noteWindows: - '按 Win+R,输入 %userprofile%\\.codex 打开配置目录。如目录不存在,请先手动创建。' + '请确保 %userprofile%\\.codex 目录存在,并在 Codex/Desktop 启动前设置环境变量;随后完全重启客户端并新建会话,以重新注册图片工具。' }, cliTabs: { claudeCode: 'Claude Code', geminiCli: 'Gemini CLI', - codexCli: 'Codex CLI', - codexCliWs: 'Codex CLI (WebSocket)', + codexCli: 'Codex(API Key 模式)', + codexCliWs: 'Codex(API Key 模式,WebSocket)', grokCli: 'Grok CLI', opencode: 'OpenCode' },