From 9db04a27eb20609e6b2e2f06a564db8992b0ff9b Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Mon, 25 May 2026 08:42:30 +0530 Subject: [PATCH] fix(openai): scope external codex auth to realtime --- .../openai/realtime-voice-provider.test.ts | 4 +++ extensions/openai/realtime-voice-provider.ts | 3 ++ src/plugin-sdk/provider-auth.test.ts | 34 ++++++++++++++----- src/plugin-sdk/provider-auth.ts | 20 +++++++---- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/extensions/openai/realtime-voice-provider.test.ts b/extensions/openai/realtime-voice-provider.test.ts index 0abb6f118be..ac3bc5d1ba9 100644 --- a/extensions/openai/realtime-voice-provider.test.ts +++ b/extensions/openai/realtime-voice-provider.test.ts @@ -302,6 +302,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => { expect(resolveProviderAuthProfileApiKeyMock).toHaveBeenCalledWith({ provider: "openai-codex", cfg: {}, + includeExternalCliAuth: true, }); const request = requireFetchRequest(); expectRecordFields(request, "fetch request", { @@ -589,6 +590,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => { expect(isProviderAuthProfileConfiguredMock).toHaveBeenCalledWith({ provider: "openai-codex", cfg, + includeExternalCliAuth: true, }); }); @@ -632,6 +634,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => { expect(resolveProviderAuthProfileApiKeyMock).toHaveBeenCalledWith({ provider: "openai-codex", cfg, + includeExternalCliAuth: true, }); expectRecordFields(requireFetchHeaders(), "fetch headers", { Authorization: "Bearer oauth-realtime-token", // pragma: allowlist secret @@ -663,6 +666,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => { expect(resolveProviderAuthProfileApiKeyMock).toHaveBeenCalledWith({ provider: "openai-codex", cfg, + includeExternalCliAuth: true, }); expectRecordFields(requireFetchHeaders(), "fetch headers", { Authorization: "Bearer oauth-realtime-token", // pragma: allowlist secret diff --git a/extensions/openai/realtime-voice-provider.ts b/extensions/openai/realtime-voice-provider.ts index 90feec2f712..7adecb68605 100644 --- a/extensions/openai/realtime-voice-provider.ts +++ b/extensions/openai/realtime-voice-provider.ts @@ -349,6 +349,7 @@ async function resolveOpenAIRealtimeDefaultAuth(params: { const codexToken = await resolveProviderAuthProfileApiKey({ provider: "openai-codex", cfg: params.cfg, + includeExternalCliAuth: true, }); if (codexToken) { return { status: "available", kind: "codex-oauth", value: codexToken }; @@ -387,6 +388,7 @@ function hasOpenAIRealtimeBrowserAuthInput(params: { isProviderAuthProfileConfigured({ provider: "openai-codex", cfg: params.cfg, + includeExternalCliAuth: true, }) || hasOpenAIRealtimeApiKeyInput(undefined) ); } @@ -725,6 +727,7 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge { !isProviderAuthProfileConfigured({ provider: "openai-codex", cfg: cfg.cfg, + includeExternalCliAuth: true, }) ) { const directApiKey = resolveOpenAIRealtimeEnvApiKey(); diff --git a/src/plugin-sdk/provider-auth.test.ts b/src/plugin-sdk/provider-auth.test.ts index bb88b18e250..53ce2b6535f 100644 --- a/src/plugin-sdk/provider-auth.test.ts +++ b/src/plugin-sdk/provider-auth.test.ts @@ -86,10 +86,14 @@ describe("provider auth profile helpers", () => { ); }); - it("scopes external CLI auth discovery to provider profile resolution", async () => { + it("only discovers external CLI auth when provider resolution opts in", async () => { vi.resetModules(); - const store: AuthProfileStore = { + const primaryStore: AuthProfileStore = { + version: 1, + profiles: {}, + }; + const externalStore: AuthProfileStore = { version: 1, profiles: { "openai-codex:default": { @@ -102,7 +106,10 @@ describe("provider auth profile helpers", () => { }, }; const externalCli = { mode: "scoped", providerIds: ["openai-codex"] }; - const loadAuthProfileStoreForSecretsRuntime = vi.fn(() => store); + const loadAuthProfileStoreForSecretsRuntime = vi.fn( + (_agentDir?: string, options?: { externalCli?: unknown }) => + options?.externalCli ? externalStore : primaryStore, + ); vi.doMock("../agents/agent-scope-config.js", () => ({ resolveDefaultAgentDir: () => "/tmp/openclaw-agent", @@ -126,8 +133,8 @@ describe("provider auth profile helpers", () => { .map(([profileId]) => profileId), })); vi.doMock("../agents/auth-profiles/store.js", () => ({ - ensureAuthProfileStore: vi.fn(() => store), - ensureAuthProfileStoreForLocalUpdate: vi.fn(() => store), + ensureAuthProfileStore: vi.fn(() => primaryStore), + ensureAuthProfileStoreForLocalUpdate: vi.fn(() => primaryStore), loadAuthProfileStoreForSecretsRuntime, loadAuthProfileStoreWithoutExternalProfiles: vi.fn(() => ({ version: 1, profiles: {} })), updateAuthProfileStoreWithLock: vi.fn(), @@ -135,9 +142,18 @@ describe("provider auth profile helpers", () => { const { isProviderAuthProfileConfigured } = await import("./provider-auth.js"); - expect(isProviderAuthProfileConfigured({ provider: "openai-codex" })).toBe(true); - expect(loadAuthProfileStoreForSecretsRuntime).toHaveBeenCalledWith("/tmp/openclaw-agent", { - externalCli, - }); + expect(isProviderAuthProfileConfigured({ provider: "openai-codex" })).toBe(false); + expect( + isProviderAuthProfileConfigured({ + provider: "openai-codex", + includeExternalCliAuth: true, + }), + ).toBe(true); + expect(loadAuthProfileStoreForSecretsRuntime).toHaveBeenNthCalledWith(1, "/tmp/openclaw-agent"); + expect(loadAuthProfileStoreForSecretsRuntime).toHaveBeenNthCalledWith( + 2, + "/tmp/openclaw-agent", + { externalCli }, + ); }); }); diff --git a/src/plugin-sdk/provider-auth.ts b/src/plugin-sdk/provider-auth.ts index 386480cf476..c47a6e2ee79 100644 --- a/src/plugin-sdk/provider-auth.ts +++ b/src/plugin-sdk/provider-auth.ts @@ -289,6 +289,7 @@ export function listUsableProviderAuthProfileIds(params: { cfg?: OpenClawConfig; agentDir?: string; allowKeychainPrompt?: boolean; + includeExternalCliAuth?: boolean; }): { agentDir: string; profileIds: string[] } { try { const { agentDir, profileIds } = resolveUsableProviderAuthProfiles(params); @@ -303,6 +304,7 @@ export function isProviderAuthProfileConfigured(params: { cfg?: OpenClawConfig; agentDir?: string; allowKeychainPrompt?: boolean; + includeExternalCliAuth?: boolean; }): boolean { return listUsableProviderAuthProfileIds(params).profileIds.length > 0; } @@ -312,6 +314,7 @@ export async function resolveProviderAuthProfileApiKey(params: { cfg?: OpenClawConfig; agentDir?: string; allowKeychainPrompt?: boolean; + includeExternalCliAuth?: boolean; }): Promise { const { agentDir, profileIds, store } = resolveUsableProviderAuthProfiles(params); if (!agentDir || profileIds.length === 0) { @@ -336,14 +339,19 @@ function resolveUsableProviderAuthProfiles(params: { cfg?: OpenClawConfig; agentDir?: string; allowKeychainPrompt?: boolean; + includeExternalCliAuth?: boolean; }): { agentDir: string; profileIds: string[]; store: AuthProfileStore } { const agentDir = params.agentDir?.trim() || resolveDefaultAgentDir(params.cfg ?? {}); - const externalCli = externalCliDiscoveryForProviderAuth({ - cfg: params.cfg, - provider: params.provider, - allowKeychainPrompt: params.allowKeychainPrompt, - }); - const store = loadAuthProfileStoreForSecretsRuntime(agentDir, { externalCli }); + const externalCli = params.includeExternalCliAuth + ? externalCliDiscoveryForProviderAuth({ + cfg: params.cfg, + provider: params.provider, + allowKeychainPrompt: params.allowKeychainPrompt, + }) + : undefined; + const store = externalCli + ? loadAuthProfileStoreForSecretsRuntime(agentDir, { externalCli }) + : loadAuthProfileStoreForSecretsRuntime(agentDir); const profileIds = resolveAuthProfileOrder({ cfg: params.cfg, store,