mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-22 06:40:21 +08:00
fix(frontend): preserve legacy Codex config mode
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user