fix(frontend): ensure Codex base URLs include /v1

This commit is contained in:
zhuzhihaocool-bit
2026-09-20 11:07:29 +08:00
parent 1a9d49e16f
commit fabdfb8a5b
4 changed files with 64 additions and 5 deletions
+5 -3
View File
@@ -736,12 +736,14 @@ const currentFiles = computed((): FileConfig[] => {
switch (props.platform) {
case 'openai':
if (activeClientTab.value === 'claude') {
return generateAnthropicFiles(baseUrl, apiKey)
// Anthropic clients append /v1/messages themselves.
return generateAnthropicFiles(baseRoot, apiKey)
}
if (activeClientTab.value === 'codex-ws') {
return generateOpenAIWsFiles(baseUrl, apiKey)
return generateOpenAIWsFiles(apiBase, apiKey)
}
return generateOpenAIFiles(baseUrl, apiKey)
// Codex appends /responses directly and does not add /v1.
return generateOpenAIFiles(apiBase, apiKey)
case 'gemini':
if (activeClientTab.value === 'codex') {
return generateRoutedCodexFiles(apiBase, apiKey, 'gemini')
@@ -384,6 +384,45 @@ describe('UseKeyModal', () => {
expect(wrapper.find('[data-testid="codex-api-key-restart-notice"]').exists()).toBe(false)
})
it('normalizes OpenAI Codex and Claude base URLs independently', async () => {
const wrapper = mount(UseKeyModal, {
props: {
show: true,
apiKey: 'sk-test',
baseUrl: 'https://example.com',
platform: 'openai',
allowMessagesDispatch: true
},
global: {
stubs: {
BaseDialog: {
template: '<div><slot /><slot name="footer" /></div>'
},
Icon: {
template: '<span />'
}
}
}
})
const codexConfig = wrapper.findAll('pre code')
.map((code) => code.text())
.find((content) => content.includes('model_provider = "OpenAI"'))
expect(codexConfig).toContain('base_url = "https://example.com/v1"')
const claudeTab = wrapper.findAll('button').find((button) =>
button.text().includes('keys.useKeyModal.cliTabs.claudeCode')
)
expect(claudeTab).toBeDefined()
await claudeTab!.trigger('click')
await nextTick()
const claudeConfig = wrapper.findAll('pre code')
.map((code) => code.text())
.find((content) => content.startsWith('export ANTHROPIC_BASE_URL'))
expect(claudeConfig).toContain('ANTHROPIC_BASE_URL="https://example.com"')
})
it('renders API Key Mode authorization in OpenAI Codex config', async () => {
const wrapper = mount(UseKeyModal, {
props: {
@@ -38,11 +38,29 @@ describe('ccswitchImport utils', () => {
expect(params.get('resource')).toBe('provider')
expect(params.get('app')).toBe('codex')
expect(params.get('endpoint')).toBe(baseInput.baseUrl)
expect(params.get('endpoint')).toBe(`${baseInput.baseUrl}/v1`)
expect(params.get('model')).toBe(OPENAI_CC_SWITCH_CODEX_MODEL)
expect(atob(params.get('usageScript') || '')).toBe(baseInput.usageScript)
})
it.each([
'https://api.example.com',
'https://api.example.com/',
'https://api.example.com/v1',
'https://api.example.com/v1/'
])('imports Codex with exactly one /v1 suffix for base URL %s', (baseUrl) => {
const params = paramsFromDeeplink(
buildCcSwitchImportDeeplink({
...baseInput,
baseUrl,
platform: 'openai',
clientType: 'claude'
})
)
expect(params.get('endpoint')).toBe('https://api.example.com/v1')
})
it.each([
'https://api.example.com',
'https://api.example.com/',
+1 -1
View File
@@ -39,7 +39,7 @@ export function resolveCcSwitchImportConfig(
case 'openai':
return {
app: 'codex',
endpoint: baseUrl,
endpoint: withV1Endpoint(baseUrl),
model: OPENAI_CC_SWITCH_CODEX_MODEL
}
case 'gemini':