diff --git a/extensions/codex/src/app-server/session-binding.test.ts b/extensions/codex/src/app-server/session-binding.test.ts index 38ee27384c2..9a051729ff7 100644 --- a/extensions/codex/src/app-server/session-binding.test.ts +++ b/extensions/codex/src/app-server/session-binding.test.ts @@ -7,10 +7,26 @@ import { readCodexAppServerBinding, resolveCodexAppServerBindingPath, writeCodexAppServerBinding, + type CodexAppServerAuthProfileLookup, } from "./session-binding.js"; let tempDir: string; +const nativeAuthLookup: Pick = { + authProfileStore: { + version: 1, + profiles: { + work: { + type: "oauth", + provider: "openai-codex", + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, + }, + }, +}; + describe("codex app-server session binding", () => { beforeEach(async () => { tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-binding-")); @@ -46,21 +62,25 @@ describe("codex app-server session binding", () => { it("does not persist public OpenAI as the provider for Codex-native auth bindings", async () => { const sessionFile = path.join(tempDir, "session.json"); - await writeCodexAppServerBinding(sessionFile, { - threadId: "thread-123", - cwd: tempDir, - authProfileId: "openai-codex:work", - model: "gpt-5.4-mini", - modelProvider: "openai", - }); + await writeCodexAppServerBinding( + sessionFile, + { + threadId: "thread-123", + cwd: tempDir, + authProfileId: "work", + model: "gpt-5.4-mini", + modelProvider: "openai", + }, + nativeAuthLookup, + ); const raw = await fs.readFile(resolveCodexAppServerBindingPath(sessionFile), "utf8"); - const binding = await readCodexAppServerBinding(sessionFile); + const binding = await readCodexAppServerBinding(sessionFile, nativeAuthLookup); expect(raw).not.toContain('"modelProvider": "openai"'); expect(binding).toMatchObject({ threadId: "thread-123", - authProfileId: "openai-codex:work", + authProfileId: "work", model: "gpt-5.4-mini", }); expect(binding?.modelProvider).toBeUndefined(); @@ -75,7 +95,7 @@ describe("codex app-server session binding", () => { threadId: "thread-123", sessionFile, cwd: tempDir, - authProfileId: "openai-codex:work", + authProfileId: "work", model: "gpt-5.4-mini", modelProvider: "openai", createdAt: "2026-05-03T00:00:00.000Z", @@ -83,12 +103,53 @@ describe("codex app-server session binding", () => { })}\n`, ); - const binding = await readCodexAppServerBinding(sessionFile); + const binding = await readCodexAppServerBinding(sessionFile, nativeAuthLookup); - expect(binding?.authProfileId).toBe("openai-codex:work"); + expect(binding?.authProfileId).toBe("work"); expect(binding?.modelProvider).toBeUndefined(); }); + it("does not infer native Codex auth from the profile id prefix", async () => { + const sessionFile = path.join(tempDir, "session.json"); + await writeCodexAppServerBinding( + sessionFile, + { + threadId: "thread-123", + cwd: tempDir, + authProfileId: "openai-codex:work", + model: "gpt-5.4-mini", + modelProvider: "openai", + }, + { + authProfileStore: { + version: 1, + profiles: { + "openai-codex:work": { + type: "api_key", + provider: "openai", + key: "sk-test", + }, + }, + }, + }, + ); + + const binding = await readCodexAppServerBinding(sessionFile, { + authProfileStore: { + version: 1, + profiles: { + "openai-codex:work": { + type: "api_key", + provider: "openai", + key: "sk-test", + }, + }, + }, + }); + + expect(binding?.modelProvider).toBe("openai"); + }); + it("clears missing bindings without throwing", async () => { const sessionFile = path.join(tempDir, "missing.json"); await clearCodexAppServerBinding(sessionFile); diff --git a/extensions/codex/src/app-server/session-binding.ts b/extensions/codex/src/app-server/session-binding.ts index ce36a307908..7c3022d0d0b 100644 --- a/extensions/codex/src/app-server/session-binding.ts +++ b/extensions/codex/src/app-server/session-binding.ts @@ -1,11 +1,27 @@ import fs from "node:fs/promises"; import { embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime"; +import { + ensureAuthProfileStore, + resolveOpenClawAgentDir, + resolveProviderIdForAuth, + type AuthProfileStore, +} from "openclaw/plugin-sdk/agent-runtime"; import type { CodexAppServerApprovalPolicy, CodexAppServerSandboxMode } from "./config.js"; import type { CodexServiceTier } from "./protocol.js"; const CODEX_APP_SERVER_NATIVE_AUTH_PROVIDER = "openai-codex"; const PUBLIC_OPENAI_MODEL_PROVIDER = "openai"; +type ProviderAuthAliasLookupParams = Parameters[1]; +type ProviderAuthAliasConfig = NonNullable["config"]; + +export type CodexAppServerAuthProfileLookup = { + authProfileId?: string; + authProfileStore?: AuthProfileStore; + agentDir?: string; + config?: ProviderAuthAliasConfig; +}; + export type CodexAppServerThreadBinding = { schemaVersion: 1; threadId: string; @@ -28,6 +44,7 @@ export function resolveCodexAppServerBindingPath(sessionFile: string): string { export async function readCodexAppServerBinding( sessionFile: string, + lookup: Omit = {}, ): Promise { const path = resolveCodexAppServerBindingPath(sessionFile); let raw: string; @@ -45,15 +62,18 @@ export async function readCodexAppServerBinding( if (parsed.schemaVersion !== 1 || typeof parsed.threadId !== "string") { return undefined; } + const authProfileId = + typeof parsed.authProfileId === "string" ? parsed.authProfileId : undefined; return { schemaVersion: 1, threadId: parsed.threadId, sessionFile, cwd: typeof parsed.cwd === "string" ? parsed.cwd : "", - authProfileId: typeof parsed.authProfileId === "string" ? parsed.authProfileId : undefined, + authProfileId, model: typeof parsed.model === "string" ? parsed.model : undefined, modelProvider: normalizeCodexAppServerBindingModelProvider({ - authProfileId: typeof parsed.authProfileId === "string" ? parsed.authProfileId : undefined, + ...lookup, + authProfileId, modelProvider: typeof parsed.modelProvider === "string" ? parsed.modelProvider : undefined, }), approvalPolicy: readApprovalPolicy(parsed.approvalPolicy), @@ -80,6 +100,7 @@ export async function writeCodexAppServerBinding( > & { createdAt?: string; }, + lookup: Omit = {}, ): Promise { const now = new Date().toISOString(); const payload: CodexAppServerThreadBinding = { @@ -90,6 +111,7 @@ export async function writeCodexAppServerBinding( authProfileId: binding.authProfileId, model: binding.model, modelProvider: normalizeCodexAppServerBindingModelProvider({ + ...lookup, authProfileId: binding.authProfileId, modelProvider: binding.modelProvider, }), @@ -120,25 +142,44 @@ function isNotFound(error: unknown): boolean { return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT"); } -export function isCodexAppServerNativeAuthProfileId(authProfileId: string | undefined): boolean { - const normalized = authProfileId?.trim().toLowerCase(); - return Boolean( - normalized && - (normalized === CODEX_APP_SERVER_NATIVE_AUTH_PROVIDER || - normalized.startsWith(`${CODEX_APP_SERVER_NATIVE_AUTH_PROVIDER}:`)), - ); +export function isCodexAppServerNativeAuthProfile( + lookup: CodexAppServerAuthProfileLookup, +): boolean { + const authProfileId = lookup.authProfileId?.trim(); + if (!authProfileId) { + return false; + } + try { + const credential = resolveCodexAppServerAuthProfileCredential({ + ...lookup, + authProfileId, + }); + return isCodexAppServerNativeAuthProvider({ + provider: credential?.provider, + config: lookup.config, + }); + } catch (error) { + embeddedAgentLog.debug("failed to resolve codex app-server auth profile provider", { + authProfileId, + error, + }); + return false; + } } export function normalizeCodexAppServerBindingModelProvider(params: { authProfileId?: string; modelProvider?: string; + authProfileStore?: AuthProfileStore; + agentDir?: string; + config?: ProviderAuthAliasConfig; }): string | undefined { const modelProvider = params.modelProvider?.trim(); if (!modelProvider) { return undefined; } if ( - isCodexAppServerNativeAuthProfileId(params.authProfileId) && + isCodexAppServerNativeAuthProfile(params) && modelProvider.toLowerCase() === PUBLIC_OPENAI_MODEL_PROVIDER ) { return undefined; @@ -146,6 +187,35 @@ export function normalizeCodexAppServerBindingModelProvider(params: { return modelProvider; } +function resolveCodexAppServerAuthProfileCredential( + lookup: CodexAppServerAuthProfileLookup, +): AuthProfileStore["profiles"][string] | undefined { + const authProfileId = lookup.authProfileId?.trim(); + if (!authProfileId) { + return undefined; + } + const store = lookup.authProfileStore ?? loadCodexAppServerAuthProfileStore(lookup.agentDir); + return store.profiles[authProfileId]; +} + +function loadCodexAppServerAuthProfileStore(agentDir: string | undefined): AuthProfileStore { + return ensureAuthProfileStore(agentDir?.trim() || resolveOpenClawAgentDir(), { + allowKeychainPrompt: false, + }); +} + +function isCodexAppServerNativeAuthProvider(params: { + provider?: string; + config?: ProviderAuthAliasConfig; +}): boolean { + const provider = params.provider?.trim(); + return Boolean( + provider && + resolveProviderIdForAuth(provider, { config: params.config }) === + CODEX_APP_SERVER_NATIVE_AUTH_PROVIDER, + ); +} + function readApprovalPolicy(value: unknown): CodexAppServerApprovalPolicy | undefined { return value === "never" || value === "on-request" || diff --git a/extensions/codex/src/app-server/thread-lifecycle.test.ts b/extensions/codex/src/app-server/thread-lifecycle.test.ts index f8524cf8601..aa932cee3b5 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.test.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.test.ts @@ -9,11 +9,33 @@ import { function createAttemptParams(params: { provider: string; authProfileId?: string; + authProfileProvider?: string; + authProfileProviders?: Record; }): EmbeddedRunAttemptParams { + const authProfileProviders = + params.authProfileProviders ?? + (params.authProfileId + ? { [params.authProfileId]: params.authProfileProvider ?? "openai-codex" } + : {}); return { provider: params.provider, modelId: "gpt-5.4", authProfileId: params.authProfileId, + authProfileStore: { + version: 1, + profiles: Object.fromEntries( + Object.entries(authProfileProviders).map(([profileId, provider]) => [ + profileId, + { + type: "oauth" as const, + provider, + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, + ]), + ), + }, } as EmbeddedRunAttemptParams; } @@ -30,7 +52,7 @@ describe("Codex app-server model provider selection", () => { "omits public %s modelProvider when forwarding native Codex auth on thread/start", (provider) => { const request = buildThreadStartParams( - createAttemptParams({ provider, authProfileId: "openai-codex:work" }), + createAttemptParams({ provider, authProfileId: "work" }), { cwd: "/repo", dynamicTools: [], @@ -44,16 +66,40 @@ describe("Codex app-server model provider selection", () => { ); it("uses the bound native Codex auth profile when deciding thread/resume modelProvider", () => { - const request = buildThreadResumeParams(createAttemptParams({ provider: "openai" }), { - threadId: "thread-1", - authProfileId: "openai-codex:bound", - appServer: createAppServerOptions() as never, - developerInstructions: "test instructions", - }); + const request = buildThreadResumeParams( + createAttemptParams({ + provider: "openai", + authProfileProviders: { bound: "openai-codex" }, + }), + { + threadId: "thread-1", + authProfileId: "bound", + appServer: createAppServerOptions() as never, + developerInstructions: "test instructions", + }, + ); expect(request).not.toHaveProperty("modelProvider"); }); + it("does not infer native Codex auth from the profile id prefix", () => { + const request = buildThreadStartParams( + createAttemptParams({ + provider: "openai", + authProfileId: "openai-codex:work", + authProfileProvider: "openai", + }), + { + cwd: "/repo", + dynamicTools: [], + appServer: createAppServerOptions() as never, + developerInstructions: "test instructions", + }, + ); + + expect(request).toMatchObject({ modelProvider: "openai" }); + }); + it("keeps public OpenAI modelProvider when no native Codex auth profile is selected", () => { const request = buildThreadStartParams(createAttemptParams({ provider: "openai" }), { cwd: "/repo", diff --git a/extensions/codex/src/app-server/thread-lifecycle.ts b/extensions/codex/src/app-server/thread-lifecycle.ts index 61f151d9415..dbbcb98be95 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.ts @@ -25,9 +25,10 @@ import { } from "./protocol.js"; import { clearCodexAppServerBinding, - isCodexAppServerNativeAuthProfileId, + isCodexAppServerNativeAuthProfile, readCodexAppServerBinding, writeCodexAppServerBinding, + type CodexAppServerAuthProfileLookup, type CodexAppServerThreadBinding, } from "./session-binding.js"; @@ -41,7 +42,11 @@ export async function startOrResumeThread(params: { config?: JsonObject; }): Promise { const dynamicToolsFingerprint = fingerprintDynamicTools(params.dynamicTools); - const binding = await readCodexAppServerBinding(params.params.sessionFile); + const binding = await readCodexAppServerBinding(params.params.sessionFile, { + authProfileStore: params.params.authProfileStore, + agentDir: params.params.agentDir, + config: params.params.config, + }); if (binding?.threadId) { // `/codex resume ` writes a binding before the next turn can know // the dynamic tool catalog, so only invalidate fingerprints we actually have. @@ -75,16 +80,27 @@ export async function startOrResumeThread(params: { const fallbackModelProvider = resolveCodexAppServerModelProvider({ provider: params.params.provider, authProfileId: boundAuthProfileId, + authProfileStore: params.params.authProfileStore, + agentDir: params.params.agentDir, + config: params.params.config, }); - await writeCodexAppServerBinding(params.params.sessionFile, { - threadId: response.thread.id, - cwd: params.cwd, - authProfileId: boundAuthProfileId, - model: params.params.modelId, - modelProvider: response.modelProvider ?? fallbackModelProvider, - dynamicToolsFingerprint, - createdAt: binding.createdAt, - }); + await writeCodexAppServerBinding( + params.params.sessionFile, + { + threadId: response.thread.id, + cwd: params.cwd, + authProfileId: boundAuthProfileId, + model: params.params.modelId, + modelProvider: response.modelProvider ?? fallbackModelProvider, + dynamicToolsFingerprint, + createdAt: binding.createdAt, + }, + { + authProfileStore: params.params.authProfileStore, + agentDir: params.params.agentDir, + config: params.params.config, + }, + ); return { ...binding, threadId: response.thread.id, @@ -121,17 +137,28 @@ export async function startOrResumeThread(params: { const modelProvider = resolveCodexAppServerModelProvider({ provider: params.params.provider, authProfileId: params.params.authProfileId, + authProfileStore: params.params.authProfileStore, + agentDir: params.params.agentDir, + config: params.params.config, }); const createdAt = new Date().toISOString(); - await writeCodexAppServerBinding(params.params.sessionFile, { - threadId: response.thread.id, - cwd: params.cwd, - authProfileId: params.params.authProfileId, - model: response.model ?? params.params.modelId, - modelProvider: response.modelProvider ?? modelProvider, - dynamicToolsFingerprint, - createdAt, - }); + await writeCodexAppServerBinding( + params.params.sessionFile, + { + threadId: response.thread.id, + cwd: params.cwd, + authProfileId: params.params.authProfileId, + model: response.model ?? params.params.modelId, + modelProvider: response.modelProvider ?? modelProvider, + dynamicToolsFingerprint, + createdAt, + }, + { + authProfileStore: params.params.authProfileStore, + agentDir: params.params.agentDir, + config: params.params.config, + }, + ); return { schemaVersion: 1, threadId: response.thread.id, @@ -159,6 +186,9 @@ export function buildThreadStartParams( const modelProvider = resolveCodexAppServerModelProvider({ provider: params.provider, authProfileId: params.authProfileId, + authProfileStore: params.authProfileStore, + agentDir: params.agentDir, + config: params.config, }); return { model: params.modelId, @@ -190,6 +220,9 @@ export function buildThreadResumeParams( const modelProvider = resolveCodexAppServerModelProvider({ provider: params.provider, authProfileId: options.authProfileId ?? params.authProfileId, + authProfileStore: params.authProfileStore, + agentDir: params.agentDir, + config: params.config, }); return { threadId: options.threadId, @@ -345,6 +378,9 @@ function buildUserInput( function resolveCodexAppServerModelProvider(params: { provider: string; authProfileId?: string; + authProfileStore?: CodexAppServerAuthProfileLookup["authProfileStore"]; + agentDir?: string; + config?: CodexAppServerAuthProfileLookup["config"]; }): string | undefined { const normalized = params.provider.trim(); const normalizedLower = normalized.toLowerCase(); @@ -354,7 +390,7 @@ function resolveCodexAppServerModelProvider(params: { return undefined; } if ( - isCodexAppServerNativeAuthProfileId(params.authProfileId) && + isCodexAppServerNativeAuthProfile(params) && (normalizedLower === "openai" || normalizedLower === "openai-codex") ) { // When OpenClaw is forwarding ChatGPT/Codex OAuth, forcing the public diff --git a/extensions/codex/src/conversation-binding.test.ts b/extensions/codex/src/conversation-binding.test.ts index 9ecaeb7d09e..5339145b8f7 100644 --- a/extensions/codex/src/conversation-binding.test.ts +++ b/extensions/codex/src/conversation-binding.test.ts @@ -106,13 +106,25 @@ describe("codex conversation binding", () => { it("preserves Codex auth and omits the public OpenAI provider for native bind threads", async () => { const sessionFile = path.join(tempDir, "session.jsonl"); + agentRuntimeMocks.ensureAuthProfileStore.mockReturnValue({ + version: 1, + profiles: { + work: { + type: "oauth", + provider: "openai-codex", + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, + }, + }); await fs.writeFile( `${sessionFile}.codex-app-server.json`, JSON.stringify({ schemaVersion: 1, threadId: "thread-old", cwd: tempDir, - authProfileId: "openai-codex:work", + authProfileId: "work", modelProvider: "openai", }), ); @@ -136,7 +148,7 @@ describe("codex conversation binding", () => { }); expect(sharedClientMocks.getSharedCodexAppServerClient).toHaveBeenCalledWith( - expect.objectContaining({ authProfileId: "openai-codex:work" }), + expect.objectContaining({ authProfileId: "work" }), ); expect(requests).toHaveLength(1); expect(requests[0]).toMatchObject({ @@ -145,7 +157,7 @@ describe("codex conversation binding", () => { }); expect(requests[0]?.params).not.toHaveProperty("modelProvider"); await expect(fs.readFile(`${sessionFile}.codex-app-server.json`, "utf8")).resolves.toContain( - '"authProfileId": "openai-codex:work"', + '"authProfileId": "work"', ); await expect( fs.readFile(`${sessionFile}.codex-app-server.json`, "utf8"), diff --git a/extensions/codex/src/conversation-binding.ts b/extensions/codex/src/conversation-binding.ts index 1a2a92eb848..a0e0a17b197 100644 --- a/extensions/codex/src/conversation-binding.ts +++ b/extensions/codex/src/conversation-binding.ts @@ -19,10 +19,11 @@ import { } from "./app-server/protocol.js"; import { clearCodexAppServerBinding, - isCodexAppServerNativeAuthProfileId, + isCodexAppServerNativeAuthProfile, normalizeCodexAppServerBindingModelProvider, readCodexAppServerBinding, writeCodexAppServerBinding, + type CodexAppServerAuthProfileLookup, } from "./app-server/session-binding.js"; import { getSharedCodexAppServerClient } from "./app-server/shared-client.js"; import { @@ -82,7 +83,9 @@ export async function startCodexConversationThread( ): Promise { const workspaceDir = params.workspaceDir?.trim() || resolveCodexDefaultWorkspaceDir(params.pluginConfig); - const existingBinding = await readCodexAppServerBinding(params.sessionFile); + const existingBinding = await readCodexAppServerBinding(params.sessionFile, { + config: params.config, + }); const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({ authProfileId: params.authProfileId ?? existingBinding?.authProfileId, config: params.config, @@ -96,6 +99,7 @@ export async function startCodexConversationThread( model: params.model, modelProvider: params.modelProvider, authProfileId, + config: params.config, }); } else { await createThread({ @@ -105,6 +109,7 @@ export async function startCodexConversationThread( model: params.model, modelProvider: params.modelProvider, authProfileId, + config: params.config, }); } return createCodexConversationBindingData({ @@ -171,11 +176,13 @@ async function attachExistingThread(params: { model?: string; modelProvider?: string; authProfileId?: string; + config?: CodexAppServerAuthProfileLookup["config"]; }): Promise { const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig: params.pluginConfig }); const modelProvider = resolveThreadRequestModelProvider({ authProfileId: params.authProfileId, modelProvider: params.modelProvider, + config: params.config, }); const client = await getSharedCodexAppServerClient({ startOptions: runtime.start, @@ -197,19 +204,26 @@ async function attachExistingThread(params: { { timeoutMs: runtime.requestTimeoutMs }, ); const thread = response.thread; - await writeCodexAppServerBinding(params.sessionFile, { - threadId: thread.id, - cwd: thread.cwd ?? params.workspaceDir, - authProfileId: params.authProfileId, - model: response.model ?? params.model, - modelProvider: normalizeCodexAppServerBindingModelProvider({ + await writeCodexAppServerBinding( + params.sessionFile, + { + threadId: thread.id, + cwd: thread.cwd ?? params.workspaceDir, authProfileId: params.authProfileId, - modelProvider: response.modelProvider ?? params.modelProvider, - }), - approvalPolicy: runtime.approvalPolicy, - sandbox: runtime.sandbox, - serviceTier: runtime.serviceTier, - }); + model: response.model ?? params.model, + modelProvider: normalizeCodexAppServerBindingModelProvider({ + config: params.config, + authProfileId: params.authProfileId, + modelProvider: response.modelProvider ?? params.modelProvider, + }), + approvalPolicy: runtime.approvalPolicy, + sandbox: runtime.sandbox, + serviceTier: runtime.serviceTier, + }, + { + config: params.config, + }, + ); } async function createThread(params: { @@ -219,11 +233,13 @@ async function createThread(params: { model?: string; modelProvider?: string; authProfileId?: string; + config?: CodexAppServerAuthProfileLookup["config"]; }): Promise { const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig: params.pluginConfig }); const modelProvider = resolveThreadRequestModelProvider({ authProfileId: params.authProfileId, modelProvider: params.modelProvider, + config: params.config, }); const client = await getSharedCodexAppServerClient({ startOptions: runtime.start, @@ -247,19 +263,26 @@ async function createThread(params: { }, { timeoutMs: runtime.requestTimeoutMs }, ); - await writeCodexAppServerBinding(params.sessionFile, { - threadId: response.thread.id, - cwd: response.thread.cwd ?? params.workspaceDir, - authProfileId: params.authProfileId, - model: response.model ?? params.model, - modelProvider: normalizeCodexAppServerBindingModelProvider({ + await writeCodexAppServerBinding( + params.sessionFile, + { + threadId: response.thread.id, + cwd: response.thread.cwd ?? params.workspaceDir, authProfileId: params.authProfileId, - modelProvider: response.modelProvider ?? params.modelProvider, - }), - approvalPolicy: runtime.approvalPolicy, - sandbox: runtime.sandbox, - serviceTier: runtime.serviceTier, - }); + model: response.model ?? params.model, + modelProvider: normalizeCodexAppServerBindingModelProvider({ + config: params.config, + authProfileId: params.authProfileId, + modelProvider: response.modelProvider ?? params.modelProvider, + }), + approvalPolicy: runtime.approvalPolicy, + sandbox: runtime.sandbox, + serviceTier: runtime.serviceTier, + }, + { + config: params.config, + }, + ); } async function runBoundTurn(params: { @@ -387,13 +410,14 @@ function enqueueBoundTurn(key: string, run: () => Promise): Promise { function resolveThreadRequestModelProvider(params: { authProfileId?: string; modelProvider?: string; + config?: CodexAppServerAuthProfileLookup["config"]; }): string | undefined { const modelProvider = params.modelProvider?.trim(); if (!modelProvider || modelProvider.toLowerCase() === "codex") { return undefined; } if ( - isCodexAppServerNativeAuthProfileId(params.authProfileId) && + isCodexAppServerNativeAuthProfile(params) && (modelProvider.toLowerCase() === "openai" || modelProvider.toLowerCase() === "openai-codex") ) { return undefined;