From 94814f35168f8b6024d0a41c389252eb21933cc9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 31 May 2026 01:25:38 +0100 Subject: [PATCH] fix(qa): restore OpenAI OAuth release contracts --- extensions/openai/openai-provider.ts | 9 ++++++ .../provider-catalog.contract-test-support.ts | 2 +- .../src/model-selection.runtime.test.ts | 30 ++++++++++++------- .../live-frontier/model-selection.runtime.ts | 6 ++-- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/extensions/openai/openai-provider.ts b/extensions/openai/openai-provider.ts index 5f01d4983307..077e64adea4b 100644 --- a/extensions/openai/openai-provider.ts +++ b/extensions/openai/openai-provider.ts @@ -389,6 +389,15 @@ export function buildOpenAIProvider(): ProviderPlugin { resolveUsageAuth: codexHooks.resolveUsageAuth, fetchUsageSnapshot: codexHooks.fetchUsageSnapshot, refreshOAuth: codexHooks.refreshOAuth, + buildMissingAuthMessage: (ctx) => { + if (normalizeProviderId(ctx.provider) !== PROVIDER_ID) { + return undefined; + } + if (ctx.listProfileIds(PROVIDER_ID).length === 0) { + return undefined; + } + return 'No API key found for provider "openai". You are authenticated with OpenAI ChatGPT/Codex OAuth. Use openai/gpt-5.5 with the ChatGPT/Codex OAuth profile, or set OPENAI_API_KEY for direct OpenAI API access.'; + }, matchesContextOverflowError: ({ errorMessage }) => /content_filter.*(?:prompt|input).*(?:too long|exceed)/i.test(errorMessage), resolveReasoningOutputMode: () => "native", diff --git a/extensions/openai/test-support/provider-catalog.contract-test-support.ts b/extensions/openai/test-support/provider-catalog.contract-test-support.ts index 88b9ac6ab60d..b3a417671e57 100644 --- a/extensions/openai/test-support/provider-catalog.contract-test-support.ts +++ b/extensions/openai/test-support/provider-catalog.contract-test-support.ts @@ -117,7 +117,7 @@ export function describeOpenAIProviderCatalogContract() { const { openaiProvider } = await contractDepsPromise; expectCodexMissingAuthHint( (params) => openaiProvider.buildMissingAuthMessage?.(params.context) ?? undefined, - "openai/gpt-*", + "openai/gpt-5.5", ); }); diff --git a/extensions/qa-lab/src/model-selection.runtime.test.ts b/extensions/qa-lab/src/model-selection.runtime.test.ts index 3185e36bc402..ee9132d31995 100644 --- a/extensions/qa-lab/src/model-selection.runtime.test.ts +++ b/extensions/qa-lab/src/model-selection.runtime.test.ts @@ -27,7 +27,9 @@ describe("qa model selection runtime", () => { vi.clearAllMocks(); resolveEnvApiKey.mockReturnValue(undefined); loadAuthProfileStoreForRuntime.mockReturnValue({ profiles: {} }); - listProfilesForProvider.mockReturnValue([]); + listProfilesForProvider.mockImplementation((store: { profiles?: Record }) => + Object.keys(store.profiles ?? {}), + ); }); it("keeps the OpenAI live default when an API key is configured", () => { @@ -39,9 +41,14 @@ describe("qa model selection runtime", () => { }); it("prefers the Codex OAuth live default when only Codex auth profiles are available", () => { - listProfilesForProvider.mockImplementation((_store: unknown, provider: string) => - provider === "openai" ? ["openai:user@example.com"] : [], - ); + loadAuthProfileStoreForRuntime.mockReturnValue({ + profiles: { + "openai:user@example.com": { + provider: "openai", + mode: "oauth", + }, + }, + }); expect(resolveQaPreferredLiveModel()).toBe("openai/gpt-5.5"); expect(defaultQaRuntimeModelForMode("live-frontier")).toBe("openai/gpt-5.5"); @@ -53,19 +60,20 @@ describe("qa model selection runtime", () => { }); it("keeps the OpenAI live default when stored OpenAI profiles are available", () => { - listProfilesForProvider.mockImplementation((_store: unknown, provider: string) => - provider === "openai" ? [`${provider}:user@example.com`] : [], - ); + loadAuthProfileStoreForRuntime.mockReturnValue({ + profiles: { + "openai:api-key": { + provider: "openai", + mode: "api_key", + }, + }, + }); expect(resolveQaPreferredLiveModel()).toBeUndefined(); expect(defaultQaRuntimeModelForMode("live-frontier")).toBe("openai/gpt-5.5"); }); it("leaves mock defaults unchanged", () => { - listProfilesForProvider.mockImplementation((_store: unknown, provider: string) => - provider === "openai" ? ["openai:user@example.com"] : [], - ); - expect(defaultQaRuntimeModelForMode("mock-openai")).toBe("mock-openai/gpt-5.5"); expect(defaultQaRuntimeModelForMode("mock-openai", { alternate: true })).toBe( "mock-openai/gpt-5.5-alt", diff --git a/extensions/qa-lab/src/providers/live-frontier/model-selection.runtime.ts b/extensions/qa-lab/src/providers/live-frontier/model-selection.runtime.ts index 8c7ca8fc0f73..3857447be8cf 100644 --- a/extensions/qa-lab/src/providers/live-frontier/model-selection.runtime.ts +++ b/extensions/qa-lab/src/providers/live-frontier/model-selection.runtime.ts @@ -16,10 +16,12 @@ export function resolveQaLiveFrontierPreferredModel() { allowKeychainPrompt: false, externalCliProviderIds: ["openai"], }); - if (listProfilesForProvider(store, "openai").length > 0) { + const openAiProfileIds = listProfilesForProvider(store, "openai"); + const openAiProfileModes = openAiProfileIds.map((profileId) => store.profiles[profileId]?.mode); + if (openAiProfileModes.some((mode) => mode === "api_key" || mode === "aws-sdk")) { return undefined; } - return listProfilesForProvider(store, "openai").length > 0 + return openAiProfileModes.some((mode) => mode === "oauth" || mode === "token") ? QA_CODEX_OAUTH_LIVE_MODEL : undefined; } catch {