Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 28 additions & 26 deletions frontend/src/components/keys/UseKeyModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down Expand Up @@ -528,81 +528,83 @@ ${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
}
]
}

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
}
]
}
Expand Down
59 changes: 56 additions & 3 deletions frontend/src/components/keys/__tests__/UseKeyModal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<div><slot /><slot name="footer" /></div>'
},
Icon: {
template: '<span />'
}
}
}
})

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: {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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"')
})
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/i18n/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading