diff --git a/frontend/src/components/keys/UseKeyModal.vue b/frontend/src/components/keys/UseKeyModal.vue index 18440c60111..3077f2ed7e3 100644 --- a/frontend/src/components/keys/UseKeyModal.vue +++ b/frontend/src/components/keys/UseKeyModal.vue @@ -418,9 +418,9 @@ const currentFiles = computed((): FileConfig[] => { return generateAnthropicFiles(baseUrl, apiKey) } if (activeClientTab.value === 'codex-ws') { - return generateOpenAIWsFiles(baseUrl, apiKey) + return generateOpenAIWsFiles(apiBase, apiKey) } - return generateOpenAIFiles(baseUrl, apiKey) + return generateOpenAIFiles(apiBase, apiKey) case 'gemini': return [generateGeminiCliContent(baseUrl, apiKey)] case 'antigravity': @@ -528,39 +528,40 @@ ${keyword('$env:')}${variable('GEMINI_MODEL')}${operator('=')}${string(`"${model function generateOpenAIFiles(baseUrl: string, apiKey: string): FileConfig[] { const isWindows = activeTab.value === 'windows' const configDir = isWindows ? '%userprofile%\\.codex' : '~/.codex' + const configPath = isWindows ? `${configDir}\\config.toml` : `${configDir}/config.toml` + const envCommand = isWindows + ? `setx OPENAI_API_KEY "${apiKey}"` + : `export OPENAI_API_KEY="${apiKey}"` + const envPath = isWindows ? 'Command Prompt / PowerShell' : 'Terminal' // config.toml content - const configContent = `model_provider = "OpenAI" + const configContent = `preferred_auth_method = "apikey" model = "gpt-5.5" review_model = "gpt-5.5" +model_provider = "proxy" model_reasoning_effort = "xhigh" disable_response_storage = true network_access = "enabled" windows_wsl_setup_acknowledged = true -[model_providers.OpenAI] -name = "OpenAI" +[model_providers.proxy] +name = "OpenAi" base_url = "${baseUrl}" +env_key = "OPENAI_API_KEY" wire_api = "responses" -requires_openai_auth = true [features] goals = true` - // auth.json content - const authContent = `{ - "OPENAI_API_KEY": "${apiKey}" -}` - return [ { - path: `${configDir}/config.toml`, + path: configPath, content: configContent, hint: t('keys.useKeyModal.openai.configTomlHint') }, { - path: `${configDir}/auth.json`, - content: authContent + path: envPath, + content: envCommand } ] } @@ -568,41 +569,42 @@ goals = true` function generateOpenAIWsFiles(baseUrl: string, apiKey: string): FileConfig[] { const isWindows = activeTab.value === 'windows' const configDir = isWindows ? '%userprofile%\\.codex' : '~/.codex' + const configPath = isWindows ? `${configDir}\\config.toml` : `${configDir}/config.toml` + const envCommand = isWindows + ? `setx OPENAI_API_KEY "${apiKey}"` + : `export OPENAI_API_KEY="${apiKey}"` + const envPath = isWindows ? 'Command Prompt / PowerShell' : 'Terminal' // config.toml content with WebSocket v2 - const configContent = `model_provider = "OpenAI" + const configContent = `preferred_auth_method = "apikey" model = "gpt-5.5" review_model = "gpt-5.5" +model_provider = "proxy" model_reasoning_effort = "xhigh" disable_response_storage = true network_access = "enabled" windows_wsl_setup_acknowledged = true -[model_providers.OpenAI] -name = "OpenAI" +[model_providers.proxy] +name = "OpenAi" base_url = "${baseUrl}" +env_key = "OPENAI_API_KEY" wire_api = "responses" supports_websockets = true -requires_openai_auth = true [features] responses_websockets_v2 = true goals = true` - // auth.json content - const authContent = `{ - "OPENAI_API_KEY": "${apiKey}" -}` - return [ { - path: `${configDir}/config.toml`, + path: configPath, content: configContent, hint: t('keys.useKeyModal.openai.configTomlHint') }, { - path: `${configDir}/auth.json`, - content: authContent + path: envPath, + content: envCommand } ] } diff --git a/frontend/src/components/keys/__tests__/UseKeyModal.spec.ts b/frontend/src/components/keys/__tests__/UseKeyModal.spec.ts index b3fdeb937ab..cc5ca4dac16 100644 --- a/frontend/src/components/keys/__tests__/UseKeyModal.spec.ts +++ b/frontend/src/components/keys/__tests__/UseKeyModal.spec.ts @@ -38,23 +38,57 @@ describe('UseKeyModal', () => { }) const codeBlocks = wrapper.findAll('pre code').map((code) => code.text()) - const configToml = codeBlocks.find((content) => content.includes('model_provider = "OpenAI"')) + const configToml = codeBlocks.find((content) => content.includes('model_provider = "proxy"')) expect(configToml).toBeDefined() + expect(configToml).toContain('preferred_auth_method = "apikey"') expect(configToml).toContain('model = "gpt-5.5"') expect(configToml).toContain('review_model = "gpt-5.5"') + expect(configToml).toContain('[model_providers.proxy]') + expect(configToml).toContain('base_url = "https://example.com/v1"') + expect(configToml).toContain('env_key = "OPENAI_API_KEY"') 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).not.toContain('requires_openai_auth') expect(configToml).toContain('[features]\ngoals = true') + + const envCommand = codeBlocks.find((content) => content.startsWith('export OPENAI_API_KEY')) + expect(envCommand).toBe('export OPENAI_API_KEY="sk-test"') }) - it('renders GPT-5.5 and goals feature in OpenAI Codex WebSocket config', async () => { + it('normalizes root URLs in OpenAI Codex config', () => { const wrapper = mount(UseKeyModal, { props: { show: true, apiKey: 'sk-test', - baseUrl: 'https://example.com/v1', + baseUrl: 'https://example.com', + platform: 'openai' + }, + global: { + stubs: { + BaseDialog: { + template: '
' + }, + Icon: { + template: '' + } + } + } + }) + + const codeBlocks = wrapper.findAll('pre code').map((code) => code.text()) + const configToml = codeBlocks.find((content) => content.includes('model_provider = "proxy"')) + + expect(configToml).toContain('base_url = "https://example.com/v1"') + }) + + it('renders GPT-5.5 and goals feature in OpenAI Codex WebSocket config on Windows', async () => { + const wrapper = mount(UseKeyModal, { + props: { + show: true, + apiKey: 'sk-test', + baseUrl: 'https://example.com', platform: 'openai' }, global: { @@ -77,16 +111,34 @@ describe('UseKeyModal', () => { await wsTab!.trigger('click') await nextTick() + const windowsTab = wrapper.findAll('button').find((button) => + button.text().includes('Windows') + ) + + expect(windowsTab).toBeDefined() + await windowsTab!.trigger('click') + await nextTick() + const codeBlocks = wrapper.findAll('pre code').map((code) => code.text()) const configToml = codeBlocks.find((content) => content.includes('supports_websockets = true')) expect(configToml).toBeDefined() + expect(configToml).toContain('preferred_auth_method = "apikey"') expect(configToml).toContain('model = "gpt-5.5"') expect(configToml).toContain('review_model = "gpt-5.5"') + expect(configToml).toContain('model_provider = "proxy"') + expect(configToml).toContain('[model_providers.proxy]') + expect(configToml).toContain('base_url = "https://example.com/v1"') + expect(configToml).toContain('env_key = "OPENAI_API_KEY"') 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).not.toContain('requires_openai_auth') expect(configToml).toContain('[features]\nresponses_websockets_v2 = true\ngoals = true') + + expect(wrapper.text()).toContain('%userprofile%\\.codex\\config.toml') + const envCommand = codeBlocks.find((content) => content.startsWith('setx OPENAI_API_KEY')) + expect(envCommand).toBe('setx OPENAI_API_KEY "sk-test"') }) it('renders GPT-5.4 mini entry in OpenCode config', async () => { @@ -119,6 +171,7 @@ describe('UseKeyModal', () => { const codeBlock = wrapper.find('pre code') expect(codeBlock.exists()).toBe(true) + expect(codeBlock.text()).toContain('"baseURL": "https://example.com/v1"') expect(codeBlock.text()).toContain('"name": "GPT-5.4 Mini"') expect(codeBlock.text()).not.toContain('"name": "GPT-5.4 Nano"') }) diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index b2aeb2f83f5..ef75db12ffb 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -742,10 +742,10 @@ 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: 'Add config.toml to your Codex CLI config directory and set the OPENAI_API_KEY environment variable.', 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 the config directory exists. macOS/Linux users can run mkdir -p ~/.codex to create it. The environment variable command applies to the current terminal session; add it to your shell profile for a permanent setup.', + noteWindows: 'Press Win+R and enter %userprofile%\\.codex to open the config directory. Create it manually if it does not exist. Reopen your terminal after setting the environment variable.', }, cliTabs: { claudeCode: 'Claude Code', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 85d1feee456..9a8a6cb7a0c 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -741,11 +741,11 @@ export default { noGroupDescription: '此 API 密钥尚未分配分组,请先在密钥列表中点击分组列进行分配,然后才能查看使用配置。', openai: { - description: '将以下配置文件添加到 Codex CLI 配置目录中。', + description: '将 config.toml 添加到 Codex CLI 配置目录,并设置 OPENAI_API_KEY 环境变量。', configTomlHint: '请确保以下内容位于 config.toml 文件的开头部分', - note: '请确保配置目录存在。macOS/Linux 用户可运行 mkdir -p ~/.codex 创建目录。', + note: '请确保配置目录存在。macOS/Linux 用户可运行 mkdir -p ~/.codex 创建目录。环境变量命令会在当前终端会话中生效,如需永久配置请添加到 shell 配置文件。', noteWindows: - '按 Win+R,输入 %userprofile%\\.codex 打开配置目录。如目录不存在,请先手动创建。' + '按 Win+R,输入 %userprofile%\\.codex 打开配置目录。如目录不存在,请先手动创建。设置环境变量后请重新打开终端。' }, cliTabs: { claudeCode: 'Claude Code',