fix(catalog): preserved codex discovery token fallback
Keep the bearer resolved by the discovery preflight when it is not among the stored OAuth account resolutions. This preserves Codex discovery for env, runtime/config override, and stored non-OAuth credential sources while still unioning all configured OAuth account catalogs. Add a ModelRegistry regression test that drives runtime-key discovery and asserts the resolved bearer reaches the Codex models endpoint. Fixes #6265
This commit is contained in:
@@ -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<OpenAICodexAccount[]> {
|
||||
async function resolveCodexDiscoveryAccounts(
|
||||
authStorage: AuthStorage,
|
||||
resolvedAccessToken: string,
|
||||
): Promise<OpenAICodexAccount[]> {
|
||||
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,
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user