fix(frontend): preserve legacy Codex config mode

This commit is contained in:
haruka
2026-07-14 22:41:38 +08:00
parent f09d63f54e
commit ad3522e34b
4 changed files with 207 additions and 9 deletions
+76 -4
View File
@@ -50,6 +50,57 @@
</nav>
</div>
<!-- Codex Authentication Mode -->
<div
v-if="showCodexAuthMode"
class="rounded-lg border border-gray-200 p-3 dark:border-dark-700"
>
<div class="mb-2">
<p class="text-sm font-medium text-gray-900 dark:text-white">
{{ t('keys.useKeyModal.openai.authModeTitle') }}
</p>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
{{ t('keys.useKeyModal.openai.authModeDescription') }}
</p>
</div>
<div
class="grid grid-cols-2 gap-1 rounded-lg bg-gray-100 p-1 dark:bg-dark-700"
role="radiogroup"
:aria-label="t('keys.useKeyModal.openai.authModeTitle')"
>
<button
type="button"
role="radio"
data-testid="codex-auth-mode-legacy"
:aria-checked="codexAuthMode === 'legacy'"
:class="[
'rounded-md px-3 py-2 text-sm font-medium transition-colors',
codexAuthMode === 'legacy'
? 'bg-white text-primary-700 shadow-sm dark:bg-dark-800 dark:text-primary-300'
: 'text-gray-600 hover:text-gray-900 dark:text-dark-300 dark:hover:text-white'
]"
@click="codexAuthMode = 'legacy'"
>
{{ t('keys.useKeyModal.openai.authModeLegacy') }}
</button>
<button
type="button"
role="radio"
data-testid="codex-auth-mode-api-key"
:aria-checked="codexAuthMode === 'api-key'"
:class="[
'rounded-md px-3 py-2 text-sm font-medium transition-colors',
codexAuthMode === 'api-key'
? 'bg-white text-primary-700 shadow-sm dark:bg-dark-800 dark:text-primary-300'
: 'text-gray-600 hover:text-gray-900 dark:text-dark-300 dark:hover:text-white'
]"
@click="codexAuthMode = 'api-key'"
>
{{ t('keys.useKeyModal.openai.authModeApiKey') }}
</button>
</div>
</div>
<!-- OS/Shell Tabs -->
<div v-if="showShellTabs" class="border-b border-gray-200 dark:border-dark-700">
<nav class="-mb-px flex space-x-4" aria-label="Tabs">
@@ -175,6 +226,8 @@ const { copyToClipboard: clipboardCopy } = useClipboard()
const copiedIndex = ref<number | null>(null)
const activeTab = ref<string>('unix')
const activeClientTab = ref<string>('claude')
type CodexAuthMode = 'legacy' | 'api-key'
const codexAuthMode = ref<CodexAuthMode>('legacy')
// Reset tabs when platform changes
const defaultClientTab = computed(() => {
@@ -195,8 +248,15 @@ const defaultClientTab = computed(() => {
watch(() => props.platform, () => {
activeTab.value = 'unix'
activeClientTab.value = defaultClientTab.value
codexAuthMode.value = 'legacy'
}, { immediate: true })
watch(() => props.show, (show) => {
if (show) {
codexAuthMode.value = 'legacy'
}
})
// Reset shell tab when client changes
watch(activeClientTab, () => {
activeTab.value = 'unix'
@@ -318,6 +378,11 @@ const openaiTabs: TabConfig[] = [
const showShellTabs = computed(() => activeClientTab.value !== 'opencode')
const showCodexAuthMode = computed(() =>
props.platform === 'openai' &&
(activeClientTab.value === 'codex' || activeClientTab.value === 'codex-ws')
)
const currentTabs = computed(() => {
if (!showShellTabs.value) return []
if (activeClientTab.value === 'codex' || activeClientTab.value === 'codex-ws' || activeClientTab.value === 'grok') {
@@ -562,8 +627,7 @@ windows_wsl_setup_acknowledged = true
name = "OpenAI"
base_url = "${baseUrl}"
wire_api = "responses"
requires_openai_auth = false
http_headers = { "x-openai-actor-authorization" = "local-image-extension" }
${generateCodexProviderAuthConfig()}
[features]
goals = true`
@@ -586,6 +650,15 @@ goals = true`
]
}
function generateCodexProviderAuthConfig(): string {
if (codexAuthMode.value === 'api-key') {
return `requires_openai_auth = false
http_headers = { "x-openai-actor-authorization" = "local-image-extension" }`
}
return 'requires_openai_auth = true'
}
function generateGrokFiles(baseUrl: string, apiKey: string): FileConfig[] {
const isWindows = activeTab.value === 'windows'
const configDir = isWindows ? '%userprofile%\\.grok' : '~/.grok'
@@ -628,8 +701,7 @@ name = "OpenAI"
base_url = "${baseUrl}"
wire_api = "responses"
supports_websockets = true
requires_openai_auth = false
http_headers = { "x-openai-actor-authorization" = "local-image-extension" }
${generateCodexProviderAuthConfig()}
[features]
responses_websockets_v2 = true
@@ -78,7 +78,7 @@ describe('UseKeyModal', () => {
expect(parsed.provider.grok.models['gpt-5.6']).toBeUndefined()
})
it('renders minimal image executor authorization in OpenAI Codex config', () => {
it('keeps legacy OpenAI Codex config as the default', () => {
const wrapper = mount(UseKeyModal, {
props: {
show: true,
@@ -107,8 +107,8 @@ 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('requires_openai_auth = false')
expect(configToml).toContain('http_headers = { "x-openai-actor-authorization" = "local-image-extension" }')
expect(configToml).toContain('requires_openai_auth = true')
expect(configToml).not.toContain('x-openai-actor-authorization')
expect(configToml).not.toContain('env_key')
expect(configToml).not.toContain('image_generation')
expect(configToml).not.toContain('supports_websockets')
@@ -118,7 +118,44 @@ describe('UseKeyModal', () => {
expect(wrapper.text()).toContain('auth.json')
})
it('renders minimal image executor authorization in OpenAI Codex WebSocket config', async () => {
it('renders API Key Mode authorization in OpenAI Codex config', async () => {
const wrapper = mount(UseKeyModal, {
props: {
show: true,
apiKey: 'sk-test',
baseUrl: 'https://example.com/v1',
platform: 'openai'
},
global: {
stubs: {
BaseDialog: {
template: '<div><slot /><slot name="footer" /></div>'
},
Icon: {
template: '<span />'
}
}
}
})
const apiKeyMode = wrapper.get('[data-testid="codex-auth-mode-api-key"]')
await apiKeyMode.trigger('click')
await nextTick()
const codeBlocks = wrapper.findAll('pre code').map((code) => code.text())
const configToml = codeBlocks.find((content) => content.includes('model_provider = "OpenAI"'))
expect(apiKeyMode.attributes('aria-checked')).toBe('true')
expect(configToml).toBeDefined()
expect(configToml).toContain('requires_openai_auth = false')
expect(configToml).toContain('http_headers = { "x-openai-actor-authorization" = "local-image-extension" }')
expect(configToml).not.toContain('env_key')
expect(configToml).not.toContain('image_generation')
expect(codeBlocks).toContain('{\n "OPENAI_API_KEY": "sk-test"\n}')
expect(wrapper.text()).toContain('auth.json')
})
it('keeps legacy OpenAI Codex WebSocket config as the default', async () => {
const wrapper = mount(UseKeyModal, {
props: {
show: true,
@@ -155,6 +192,51 @@ 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('requires_openai_auth = true')
expect(configToml).not.toContain('x-openai-actor-authorization')
expect(configToml).not.toContain('env_key')
expect(configToml).not.toContain('image_generation')
expect(configToml).toContain('supports_websockets = true')
expect(configToml).toContain('[features]\nresponses_websockets_v2 = true\ngoals = true')
expect(codeBlocks).toContain('{\n "OPENAI_API_KEY": "sk-test"\n}')
expect(wrapper.text()).toContain('auth.json')
})
it('preserves API Key Mode when switching to OpenAI Codex WebSocket config', async () => {
const wrapper = mount(UseKeyModal, {
props: {
show: true,
apiKey: 'sk-test',
baseUrl: 'https://example.com/v1',
platform: 'openai'
},
global: {
stubs: {
BaseDialog: {
template: '<div><slot /><slot name="footer" /></div>'
},
Icon: {
template: '<span />'
}
}
}
})
const apiKeyMode = wrapper.get('[data-testid="codex-auth-mode-api-key"]')
await apiKeyMode.trigger('click')
const wsTab = wrapper.findAll('button').find((button) =>
button.text().includes('keys.useKeyModal.cliTabs.codexCliWs')
)
expect(wsTab).toBeDefined()
await wsTab!.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(wrapper.get('[data-testid="codex-auth-mode-api-key"]').attributes('aria-checked')).toBe('true')
expect(configToml).toBeDefined()
expect(configToml).toContain('requires_openai_auth = false')
expect(configToml).toContain('http_headers = { "x-openai-actor-authorization" = "local-image-extension" }')
expect(configToml).not.toContain('env_key')
@@ -162,7 +244,43 @@ describe('UseKeyModal', () => {
expect(configToml).toContain('supports_websockets = true')
expect(configToml).toContain('[features]\nresponses_websockets_v2 = true\ngoals = true')
expect(codeBlocks).toContain('{\n "OPENAI_API_KEY": "sk-test"\n}')
expect(wrapper.text()).toContain('auth.json')
})
it('resets Codex authentication mode when the modal reopens or platform changes', async () => {
const wrapper = mount(UseKeyModal, {
props: {
show: true,
apiKey: 'sk-test',
baseUrl: 'https://example.com/v1',
platform: 'openai'
},
global: {
stubs: {
BaseDialog: {
template: '<div><slot /><slot name="footer" /></div>'
},
Icon: {
template: '<span />'
}
}
}
})
await wrapper.get('[data-testid="codex-auth-mode-api-key"]').trigger('click')
await wrapper.setProps({ show: false })
await wrapper.setProps({ show: true })
await nextTick()
expect(wrapper.get('[data-testid="codex-auth-mode-legacy"]').attributes('aria-checked')).toBe('true')
expect(wrapper.findAll('pre code').map((code) => code.text()).join('\n')).toContain('requires_openai_auth = true')
await wrapper.get('[data-testid="codex-auth-mode-api-key"]').trigger('click')
await wrapper.setProps({ platform: 'gemini' })
await wrapper.setProps({ platform: 'openai' })
await nextTick()
expect(wrapper.get('[data-testid="codex-auth-mode-legacy"]').attributes('aria-checked')).toBe('true')
expect(wrapper.findAll('pre code').map((code) => code.text()).join('\n')).not.toContain('x-openai-actor-authorization')
})
it('renders GPT-5.4 mini entry in OpenCode config', async () => {
@@ -137,6 +137,10 @@ export default {
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.',
authModeTitle: 'Codex authentication mode',
authModeDescription: 'Compatibility mode keeps the existing setup for older Codex clients. API Key Mode enables the client-side image executor.',
authModeLegacy: 'Compatibility mode',
authModeApiKey: 'API Key Mode',
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.',
@@ -137,6 +137,10 @@ export default {
'此 API 密钥尚未分配分组,请先在密钥列表中点击分组列进行分配,然后才能查看使用配置。',
openai: {
description: '将以下配置文件添加到 Codex CLI 配置目录中。',
authModeTitle: 'Codex 认证模式',
authModeDescription: '兼容模式保留旧版 Codex 配置;API Key Mode 用于启用客户端图片执行器。',
authModeLegacy: '兼容模式',
authModeApiKey: 'API Key Mode',
configTomlHint: '请确保以下内容位于 config.toml 文件的开头部分',
note: '请确保配置目录存在。macOS/Linux 用户可运行 mkdir -p ~/.codex 创建目录。',
noteWindows: