diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d319e3cee1..dad57a6a4f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Agents/Codex: keep public OpenAI API-key profiles from being treated as native Codex app-server auth while preserving persisted Codex OAuth sessions. - Control UI: keep collapsed tool cards labeled with the tool name and action instead of generic output text. Thanks @shakkernerd. - Agents/Codex: surface Skill Workshop guidance in Codex app-server prompts when `skill_workshop` is available. Thanks @shakkernerd. - CLI: keep `plugins list --json` on the snapshot-only path so plugin sweeps avoid loading the full runtime status graph. diff --git a/extensions/codex/src/app-server/session-binding.ts b/extensions/codex/src/app-server/session-binding.ts index fff6ea64dee..71a4d7a75fc 100644 --- a/extensions/codex/src/app-server/session-binding.ts +++ b/extensions/codex/src/app-server/session-binding.ts @@ -337,10 +337,10 @@ export function isCodexAppServerNativeAuthProfile( ...lookup, authProfileId, }); - return isCodexAppServerNativeAuthProvider({ - provider: credential?.provider, - config: lookup.config, - }); + if (!credential || credential.type === "api_key") { + return false; + } + return isOpenAiAuthProvider({ provider: credential.provider, config: lookup.config }); } catch (error) { embeddedAgentLog.debug("failed to resolve codex app-server auth profile provider", { authProfileId, @@ -403,7 +403,7 @@ function loadCodexAppServerAuthProfileStore(params: { ); } -function isCodexAppServerNativeAuthProvider(params: { +function isOpenAiAuthProvider(params: { provider?: string; config?: ProviderAuthAliasConfig; }): boolean { diff --git a/extensions/codex/src/app-server/thread-lifecycle.test.ts b/extensions/codex/src/app-server/thread-lifecycle.test.ts index 43ab1251228..6cc92b7bb7a 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.test.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.test.ts @@ -14,8 +14,10 @@ import { function createAttemptParams(params: { provider: string; authProfileId?: string; + authProfileType?: "oauth" | "api_key"; authProfileProvider?: string; authProfileProviders?: Record; + runtimeExternalProfileIds?: string[]; bootstrapContextMode?: "full" | "lightweight"; bootstrapContextRunKind?: "default" | "heartbeat" | "cron"; images?: EmbeddedRunAttemptParams["images"]; @@ -25,6 +27,7 @@ function createAttemptParams(params: { (params.authProfileId ? { [params.authProfileId]: params.authProfileProvider ?? "openai" } : {}); + const authProfileType = params.authProfileType ?? "oauth"; return { provider: params.provider, modelId: "gpt-5.4", @@ -40,15 +43,24 @@ function createAttemptParams(params: { 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, - }, + authProfileType === "api_key" + ? { + type: "api_key" as const, + provider, + key: "sk-test", + } + : { + type: "oauth" as const, + provider, + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, ]), ), + ...(params.runtimeExternalProfileIds + ? { runtimeExternalProfileIds: params.runtimeExternalProfileIds } + : {}), }, } as EmbeddedRunAttemptParams; } @@ -578,7 +590,11 @@ 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: "work" }), + createAttemptParams({ + provider, + authProfileId: "work", + runtimeExternalProfileIds: ["work"], + }), { cwd: "/repo", dynamicTools: [], @@ -596,6 +612,7 @@ describe("Codex app-server model provider selection", () => { createAttemptParams({ provider: "openai", authProfileProviders: { bound: "openai" }, + runtimeExternalProfileIds: ["bound"], }), { threadId: "thread-1", @@ -613,6 +630,7 @@ describe("Codex app-server model provider selection", () => { createAttemptParams({ provider: "openai", authProfileId: "openai:work", + authProfileType: "api_key", authProfileProvider: "openai", }), { @@ -626,6 +644,24 @@ describe("Codex app-server model provider selection", () => { expect(request.modelProvider).toBe("openai"); }); + it("omits public OpenAI modelProvider for persisted Codex OAuth profiles", () => { + const request = buildThreadStartParams( + createAttemptParams({ + provider: "openai", + authProfileId: "openai:work", + authProfileProvider: "openai", + }), + { + cwd: "/repo", + dynamicTools: [], + appServer: createAppServerOptions() as never, + developerInstructions: "test instructions", + }, + ); + + expect(request).not.toHaveProperty("modelProvider"); + }); + it("keeps public OpenAI modelProvider when no native Codex auth profile is selected", () => { const request = buildThreadStartParams(createAttemptParams({ provider: "openai" }), { cwd: "/repo",