diff --git a/frontend/src/api/__tests__/codex.spec.ts b/frontend/src/api/__tests__/codex.spec.ts
index 57b19d7557..3264d1abb3 100644
--- a/frontend/src/api/__tests__/codex.spec.ts
+++ b/frontend/src/api/__tests__/codex.spec.ts
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
+ buildCodexModelCatalogUrl,
buildCodexModelsManifestUrl,
fetchCodexModelsManifest
} from '../codex'
@@ -11,10 +12,18 @@ describe('Codex models API', () => {
it('builds the authenticated Codex manifest endpoint from the public API base', () => {
expect(buildCodexModelsManifestUrl('https://example.com/api/v1/')).toBe(
- 'https://example.com/api/v1/models?client_version=0.147.0'
+ 'https://example.com/api/v1/models?client_version=0.158.0'
)
})
+ it.each([
+ ['https://example.com', 'https://example.com/v1/models'],
+ ['https://example.com/api/v1/', 'https://example.com/api/v1/models'],
+ ['', `${window.location.origin}/v1/models`]
+ ])('builds a version-free provider catalog URL from %s', (baseUrl, expected) => {
+ expect(buildCodexModelCatalogUrl(baseUrl)).toBe(expected)
+ })
+
it('fetches a manifest with the current API key without adding it to the catalog', async () => {
const manifest = {
models: [
@@ -43,14 +52,14 @@ describe('Codex models API', () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
status: 200,
- json: async () => manifest
+ text: async () => JSON.stringify(manifest)
})
vi.stubGlobal('fetch', fetchMock)
const result = await fetchCodexModelsManifest('https://example.com/v1', 'sk-user-test')
expect(fetchMock).toHaveBeenCalledWith(
- 'https://example.com/v1/models?client_version=0.147.0',
+ 'https://example.com/v1/models?client_version=0.158.0',
expect.objectContaining({
headers: {
Accept: 'application/json',
@@ -59,6 +68,7 @@ describe('Codex models API', () => {
})
)
expect(result.modelCount).toBe(2)
+ expect(result.responseBytes).toBe(new TextEncoder().encode(JSON.stringify(manifest)).byteLength)
expect(JSON.parse(result.content)).toEqual(manifest)
expect(result.content).toContain('"effort": "xhigh"')
expect(result.content).toContain('"input_modalities"')
@@ -70,10 +80,21 @@ describe('Codex models API', () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
status: 200,
- json: async () => ({ object: 'list', data: [] })
+ text: async () => JSON.stringify({ object: 'list', data: [] })
}))
await expect(fetchCodexModelsManifest('https://example.com/v1', 'sk-user-test'))
.rejects.toThrow('valid manifest')
})
+
+ it('measures UTF-8 response bytes before formatting the downloadable catalog', async () => {
+ const text = '{"models":[{"slug":"test","description":"模型"}]}'
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, text: async () => text }))
+
+ const result = await fetchCodexModelsManifest('https://example.com', 'sk-test')
+
+ expect(result.responseBytes).toBe(new TextEncoder().encode(text).byteLength)
+ expect(result.responseBytes).toBeGreaterThan(text.length)
+ expect(result.content).toBe(JSON.stringify(JSON.parse(text), null, 2))
+ })
})
diff --git a/frontend/src/api/codex.ts b/frontend/src/api/codex.ts
index 948fa1eee9..8e2553ac81 100644
--- a/frontend/src/api/codex.ts
+++ b/frontend/src/api/codex.ts
@@ -1,9 +1,10 @@
export interface CodexModelsManifestResult {
content: string
modelCount: number
+ responseBytes: number
}
-const DEFAULT_CODEX_CLIENT_VERSION = '0.147.0'
+const DEFAULT_CODEX_CLIENT_VERSION = '0.158.0'
function normalizeCodexBaseUrl(baseUrl: string): string {
const fallback = typeof window !== 'undefined' ? window.location.origin : ''
@@ -16,9 +17,12 @@ export function buildCodexModelsManifestUrl(
baseUrl: string,
clientVersion = DEFAULT_CODEX_CLIENT_VERSION
): string {
- const url = normalizeCodexBaseUrl(baseUrl)
const params = new URLSearchParams({ client_version: clientVersion })
- return `${url}/models?${params.toString()}`
+ return `${buildCodexModelCatalogUrl(baseUrl)}?${params.toString()}`
+}
+
+export function buildCodexModelCatalogUrl(baseUrl: string): string {
+ return `${normalizeCodexBaseUrl(baseUrl)}/models`
}
function isCodexModelsManifest(value: unknown): value is { models: unknown[] } {
@@ -44,13 +48,15 @@ export async function fetchCodexModelsManifest(
throw new Error(`Codex models request failed with status ${response.status}`)
}
- const payload: unknown = await response.json()
+ const text = await response.text()
+ const payload: unknown = JSON.parse(text)
if (!isCodexModelsManifest(payload)) {
throw new Error('Codex models response is not a valid manifest')
}
return {
content: JSON.stringify(payload, null, 2),
- modelCount: payload.models.length
+ modelCount: payload.models.length,
+ responseBytes: new TextEncoder().encode(text).byteLength
}
}
diff --git a/frontend/src/components/keys/UseKeyModal.vue b/frontend/src/components/keys/UseKeyModal.vue
index 604af3048c..9abeef4640 100644
--- a/frontend/src/components/keys/UseKeyModal.vue
+++ b/frontend/src/components/keys/UseKeyModal.vue
@@ -186,7 +186,19 @@
{{ t('keys.useKeyModal.codexModelCatalog.description') }}
- {{ codexModelCatalogPath }}
+ {{ codexModelCatalogMode === 'remote' ? codexModelCatalogUrl : codexModelCatalogPath }}
+
+
+
+ {{ t('keys.useKeyModal.codexModelCatalog.oversized') }}