diff --git a/packages/coding-agent/src/config/model-registry.ts b/packages/coding-agent/src/config/model-registry.ts index b0a19d4ac..6d8268a52 100644 --- a/packages/coding-agent/src/config/model-registry.ts +++ b/packages/coding-agent/src/config/model-registry.ts @@ -434,13 +434,22 @@ function getOAuthCredentialsForProvider(authStorage: AuthStorage, provider: stri * must fetch per account and union the results; resolving a single access token * (as before) hid models available only through a sibling account (#6265). */ -async function resolveCodexDiscoveryAccounts(authStorage: AuthStorage): Promise { +async function resolveCodexDiscoveryAccounts( + authStorage: AuthStorage, + resolvedAccessToken: string, +): Promise { const accesses = await authStorage.getOAuthAccesses("openai-codex"); const accounts: OpenAICodexAccount[] = []; for (const access of accesses) { if (!access.ok) continue; accounts.push({ accessToken: access.accessToken, accountId: access.accountId }); } + if (!accounts.some(account => account.accessToken === resolvedAccessToken)) { + const matchingCredential = getOAuthCredentialsForProvider(authStorage, "openai-codex").find( + credential => credential.access === resolvedAccessToken, + ); + accounts.push({ accessToken: resolvedAccessToken, accountId: matchingCredential?.accountId }); + } return accounts; } @@ -1724,9 +1733,9 @@ export class ModelRegistry { providerId: "openai-codex", authoritative: true, resolveKey: value => value, - createOptions: () => + createOptions: accessToken => openaiCodexModelManagerOptions({ - resolveAccounts: () => resolveCodexDiscoveryAccounts(this.authStorage), + resolveAccounts: () => resolveCodexDiscoveryAccounts(this.authStorage, accessToken), fetch: this.#fetch, }), }, diff --git a/packages/coding-agent/test/model-discovery.test.ts b/packages/coding-agent/test/model-discovery.test.ts index 310c550d5..39a835c4f 100644 --- a/packages/coding-agent/test/model-discovery.test.ts +++ b/packages/coding-agent/test/model-discovery.test.ts @@ -327,6 +327,36 @@ describe("ModelRegistry runtime discovery", () => { expect(registry.find("openai-codex", "gpt-5.4-nano")).toBeUndefined(); }); + test("Codex discovery falls back to a resolved non-OAuth token when no OAuth accounts exist", async () => { + authStorage.setRuntimeApiKey("openai-codex", "runtime-openai-codex"); + let modelListCalls = 0; + const fetchMock: FetchImpl = async (input, init) => { + const url = String(input); + if (url.startsWith("https://chatgpt.com/backend-api") && url.includes("/models")) { + modelListCalls++; + expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer runtime-openai-codex"); + return Response.json({ + models: [ + { + slug: "runtime-codex-model", + display_name: "Runtime Codex Model", + context_window: 128_000, + supported_in_api: true, + input_modalities: ["text"], + }, + ], + }); + } + throw new Error(`Unexpected URL: ${url}`); + }; + const registry = new ModelRegistry(authStorage, modelsJsonPath, { fetch: fetchMock }); + + await registry.refreshProvider("openai-codex", "online"); + + expect(modelListCalls).toBe(1); + expect(registry.find("openai-codex", "runtime-codex-model")).toBeDefined(); + }); + test("configured discovery suppresses built-in special OAuth discovery", async () => { await authStorage.set("google-gemini-cli", { type: "oauth",