Skip to content

Commit 349ee4a

Browse files
committed
fix(agents): bootstrap OAuth credentials for Codex harness with openai/* model refs
When a plugin harness (e.g. Codex) owns its transport but the runtime plan resolved to openai-codex via agentRuntime.id: codex, the auth profile store was left empty because pluginHarnessOwnsTransport short- circuited initializeAuthProfile(). This caused 'No API key found for openai-codex' at runtime even though the OAuth profile existed in OpenClaw's store. - Add pluginHarnessNeedsOpenClawAuthBootstrap flag when harness owns transport but the provider is openai-codex and the API is openai-codex- responses - Populate authStore and attemptAuthProfileStore from OpenClaw's profile store in this case - Run initializeAuthProfile() to forward the OAuth token into the harness - Update overflow-compaction tests to expect 'openai-codex' provider and add dedicated test for OAuth bootstrap path
1 parent 2e3c316 commit 349ee4a

2 files changed

Lines changed: 126 additions & 17 deletions

File tree

src/agents/pi-embedded-runner/run.overflow-compaction.test.ts

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
mockedResolveAuthProfileOrder,
2828
mockedResolveContextWindowInfo,
2929
mockedResolveFailoverStatus,
30+
mockedResolveModelAsync,
3031
mockedRunContextEngineMaintenance,
3132
mockedRunEmbeddedAttempt,
3233
mockedSessionLikelyHasOversizedToolResults,
@@ -496,7 +497,7 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
496497
expect(mockedBuildAgentRuntimePlan).toHaveBeenCalledTimes(1);
497498
expect(pluginRunAttempt).toHaveBeenCalledTimes(1);
498499
const pluginParams = expectMockCallFields(pluginRunAttempt, {
499-
provider: "openai",
500+
provider: "openai-codex",
500501
authProfileId: "openai-codex:work",
501502
authProfileIdSource: "user",
502503
});
@@ -522,6 +523,107 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
522523
expect(successParams.profileId).toBe("openai-codex:work");
523524
});
524525

526+
it("bootstraps OAuth credentials for forced openai/* Codex response runs", async () => {
527+
const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js");
528+
const pluginRunAttempt = vi.fn<AgentHarness["runAttempt"]>(async () =>
529+
makeAttemptResult({ assistantTexts: ["ok"] }),
530+
);
531+
const codexAuthStorage = {
532+
setRuntimeApiKey: vi.fn(),
533+
getApiKey: vi.fn(async () => "stored-test-key"),
534+
};
535+
const runtimePlan = makeForwardedRuntimePlan({
536+
resolvedRef: {
537+
provider: "openai-codex",
538+
modelId: "gpt-5.5",
539+
harnessId: "codex",
540+
},
541+
auth: {
542+
providerForAuth: "openai-codex",
543+
authProfileProviderForAuth: "openai-codex",
544+
harnessAuthProvider: "openai-codex",
545+
forwardedAuthProfileId: "openai-codex:work",
546+
},
547+
});
548+
const codexAuthStore = {
549+
version: 1 as const,
550+
profiles: {
551+
"openai-codex:work": {
552+
type: "oauth" as const,
553+
provider: "openai-codex",
554+
access: "access-token",
555+
refresh: "refresh-token",
556+
expires: Date.now() + 60_000,
557+
},
558+
},
559+
};
560+
clearAgentHarnesses();
561+
registerAgentHarness({
562+
id: "codex",
563+
label: "Codex",
564+
supports: () => ({ supported: false }),
565+
runAttempt: pluginRunAttempt,
566+
});
567+
mockedEnsureAuthProfileStoreWithoutExternalProfiles.mockReturnValueOnce(codexAuthStore);
568+
mockedResolveModelAsync
569+
.mockResolvedValueOnce({
570+
model: {
571+
id: "gpt-5.5",
572+
provider: "openai",
573+
contextWindow: 200000,
574+
api: "openai-responses",
575+
},
576+
error: null,
577+
authStorage: { setRuntimeApiKey: vi.fn() },
578+
modelRegistry: {},
579+
})
580+
.mockResolvedValueOnce({
581+
model: {
582+
id: "gpt-5.5",
583+
provider: "openai-codex",
584+
contextWindow: 200000,
585+
api: "openai-codex-responses",
586+
},
587+
error: null,
588+
authStorage: codexAuthStorage,
589+
modelRegistry: {},
590+
});
591+
mockedBuildAgentRuntimePlan.mockReturnValueOnce(runtimePlan);
592+
593+
try {
594+
await runEmbeddedPiAgent({
595+
...overflowBaseRunParams,
596+
provider: "openai",
597+
model: "gpt-5.5",
598+
config: {
599+
agents: {
600+
defaults: {
601+
agentRuntime: { id: "codex" },
602+
},
603+
},
604+
},
605+
authProfileId: "openai-codex:work",
606+
authProfileIdSource: "user",
607+
runId: "forced-openai-codex-responses-bootstrap-oauth",
608+
});
609+
} finally {
610+
clearAgentHarnesses();
611+
}
612+
613+
expect(mockedGetApiKeyForModel).toHaveBeenCalledTimes(1);
614+
expectMockCallFields(mockedGetApiKeyForModel, {
615+
profileId: "openai-codex:work",
616+
});
617+
expect(codexAuthStorage.setRuntimeApiKey).toHaveBeenCalledWith("openai-codex", "test-key");
618+
expect(pluginRunAttempt).toHaveBeenCalledTimes(1);
619+
expectMockCallFields(pluginRunAttempt, {
620+
provider: "openai-codex",
621+
authProfileId: "openai-codex:work",
622+
authProfileIdSource: "user",
623+
resolvedApiKey: "test-key",
624+
});
625+
});
626+
525627
it("keeps auto-selected OpenAI Codex auth profiles for forced codex harness runs", async () => {
526628
const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js");
527629
const pluginRunAttempt = vi.fn<AgentHarness["runAttempt"]>(async () =>
@@ -573,7 +675,7 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
573675
expect(mockedBuildAgentRuntimePlan).toHaveBeenCalledTimes(1);
574676
expect(pluginRunAttempt).toHaveBeenCalledTimes(1);
575677
const pluginParams = expectMockCallFields(pluginRunAttempt, {
576-
provider: "openai",
678+
provider: "openai-codex",
577679
authProfileId: "openai-codex:default",
578680
authProfileIdSource: "auto",
579681
});
@@ -646,7 +748,7 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
646748
expect(mockedBuildAgentRuntimePlan).toHaveBeenCalledTimes(1);
647749
expect(pluginRunAttempt).toHaveBeenCalledTimes(1);
648750
const pluginParams = expectMockCallFields(pluginRunAttempt, {
649-
provider: "openai",
751+
provider: "openai-codex",
650752
authProfileId: "openai-codex:default",
651753
authProfileIdSource: "auto",
652754
});
@@ -731,7 +833,7 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
731833
expect(mockedBuildAgentRuntimePlan).toHaveBeenCalledTimes(1);
732834
expect(pluginRunAttempt).toHaveBeenCalledTimes(1);
733835
const pluginParams = expectMockCallFields(pluginRunAttempt, {
734-
provider: "openai",
836+
provider: "openai-codex",
735837
authProfileId: "openai:personal",
736838
authProfileIdSource: "auto",
737839
});
@@ -863,14 +965,14 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
863965
expect(mockedGetApiKeyForModel).not.toHaveBeenCalled();
864966
expect(pluginRunAttempt).toHaveBeenCalledTimes(2);
865967
const firstAttempt = expectMockCallFields(pluginRunAttempt, {
866-
provider: "openai",
968+
provider: "openai-codex",
867969
authProfileId: "openai-codex:sub",
868970
authProfileIdSource: "auto",
869971
});
870972
const secondAttempt = expectMockCallFields(
871973
pluginRunAttempt,
872974
{
873-
provider: "openai",
975+
provider: "openai-codex",
874976
authProfileId: "openai:backup",
875977
authProfileIdSource: "auto",
876978
},

src/agents/pi-embedded-runner/run.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
} from "../model-auth.js";
6363
import { ensureOpenClawModelsJson } from "../models-config.js";
6464
import {
65+
OPENAI_CODEX_PROVIDER_ID,
6566
listOpenAIAuthProfileProvidersForAgentRuntime,
6667
resolveContextConfigProviderForRuntime,
6768
resolveSelectedOpenAIPiRuntimeProvider,
@@ -672,16 +673,22 @@ export async function runEmbeddedPiAgent(
672673
startupStages.mark("model-resolution");
673674
notifyExecutionPhase("model_resolution", { provider, model: modelId });
674675

675-
const authStore = pluginHarnessOwnsTransport
676-
? createEmptyAuthProfileStore()
677-
: ensureAuthProfileStoreWithoutExternalProfiles(agentDir, {
678-
allowKeychainPrompt: false,
679-
});
680-
const attemptAuthProfileStore = pluginHarnessOwnsTransport
681-
? ensureAuthProfileStoreWithoutExternalProfiles(agentDir, {
682-
allowKeychainPrompt: false,
683-
})
684-
: authStore;
676+
const pluginHarnessNeedsOpenClawAuthBootstrap =
677+
pluginHarnessOwnsTransport &&
678+
provider === OPENAI_CODEX_PROVIDER_ID &&
679+
effectiveModel.api === "openai-codex-responses";
680+
const authStore =
681+
pluginHarnessOwnsTransport && !pluginHarnessNeedsOpenClawAuthBootstrap
682+
? createEmptyAuthProfileStore()
683+
: ensureAuthProfileStoreWithoutExternalProfiles(agentDir, {
684+
allowKeychainPrompt: false,
685+
});
686+
const attemptAuthProfileStore =
687+
pluginHarnessOwnsTransport && !pluginHarnessNeedsOpenClawAuthBootstrap
688+
? ensureAuthProfileStoreWithoutExternalProfiles(agentDir, {
689+
allowKeychainPrompt: false,
690+
})
691+
: authStore;
685692
const requestedProfileId = params.authProfileId?.trim();
686693
const requestedProfileIsUserLocked = params.authProfileIdSource === "user";
687694
const isForwardablePluginHarnessAuthProfile = (
@@ -934,7 +941,7 @@ export async function runEmbeddedPiAgent(
934941
// Plugin harnesses own their model transport/auth. Running PI's generic
935942
// auth bootstrap here can turn synthetic provider markers into real
936943
// vendor-token refresh attempts before the plugin gets control.
937-
if (!pluginHarnessOwnsTransport) {
944+
if (!pluginHarnessOwnsTransport || pluginHarnessNeedsOpenClawAuthBootstrap) {
938945
await initializeAuthProfile();
939946
} else if (lockedProfileId) {
940947
lastProfileId = lockedProfileId;

0 commit comments

Comments
 (0)