Files
openclaw/src/agents/auth-profile-runtime-contract.test.ts
pashpashpash 02fe0d8978 Keep OpenAI Codex migrations on automatic runtime routing (#79238)
* fix: keep migrated openai codex routes automatic

* scope runtime policy to providers and models

* fix runtime policy surfaces

* fix ci runtime policy checks

* fix doctor stale session runtime pins
2026-05-08 16:05:35 +09:00

467 lines
17 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import {
AUTH_PROFILE_RUNTIME_CONTRACT,
createAuthAliasManifestRegistry,
expectedForwardedAuthProfile,
} from "openclaw/plugin-sdk/agent-runtime-test-contracts";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { SessionEntry } from "../config/sessions.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type * as ManifestRegistryModule from "../plugins/manifest-registry.js";
import { runAgentAttempt } from "./command/attempt-execution.js";
import type { EmbeddedPiRunResult } from "./pi-embedded.js";
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
type LoadPluginManifestRegistry = typeof ManifestRegistryModule.loadPluginManifestRegistry;
const loadPluginManifestRegistry = vi.hoisted(() =>
vi.fn<LoadPluginManifestRegistry>(() => ({
plugins: [],
diagnostics: [],
})),
);
const runCliAgentMock = vi.hoisted(() => vi.fn());
const runEmbeddedPiAgentMock = vi.hoisted(() => vi.fn());
vi.mock("../plugins/manifest-registry.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../plugins/manifest-registry.js")>();
return {
...actual,
loadPluginManifestRegistry,
};
});
vi.mock("../plugins/manifest-registry-installed.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../plugins/manifest-registry-installed.js")>();
return {
...actual,
loadPluginManifestRegistryForInstalledIndex: loadPluginManifestRegistry,
};
});
vi.mock("../plugins/plugin-registry.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../plugins/plugin-registry.js")>();
return {
...actual,
loadPluginRegistrySnapshot: () => ({ plugins: [] }),
};
});
vi.mock("./cli-runner.js", () => ({
runCliAgent: runCliAgentMock,
}));
vi.mock("./model-selection.js", () => ({
isCliProvider: (provider: string) => {
const normalized = provider.trim().toLowerCase();
return (
normalized === AUTH_PROFILE_RUNTIME_CONTRACT.claudeCliProvider ||
normalized === AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider
);
},
normalizeProviderId: (provider: string) => provider.trim().toLowerCase(),
}));
vi.mock("./pi-embedded.js", () => ({
runEmbeddedPiAgent: runEmbeddedPiAgentMock,
}));
function makeCliResult(text: string): EmbeddedPiRunResult {
return {
payloads: [{ text }],
meta: {
durationMs: 5,
finalAssistantVisibleText: text,
agentMeta: {
sessionId: AUTH_PROFILE_RUNTIME_CONTRACT.sessionId,
provider: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
model: "gpt-5.4",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
},
executionTrace: {
winnerProvider: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
winnerModel: "gpt-5.4",
fallbackUsed: false,
runner: "cli",
},
},
};
}
function makeEmbeddedResult(text: string): EmbeddedPiRunResult {
return {
payloads: [{ text }],
meta: {
durationMs: 5,
finalAssistantVisibleText: text,
agentMeta: {
sessionId: AUTH_PROFILE_RUNTIME_CONTRACT.sessionId,
provider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
model: "gpt-5.4",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
},
executionTrace: {
winnerProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
winnerModel: "gpt-5.4",
fallbackUsed: false,
runner: "embedded",
},
},
};
}
function providerRuntimeConfig(provider: string, runtime: string): OpenClawConfig {
return {
models: {
providers: {
[provider]: {
baseUrl: "https://api.openclaw.test/v1",
agentRuntime: { id: runtime },
models: [],
},
},
},
} as OpenClawConfig;
}
async function runAuthContractAttempt(params: {
tmpDir: string;
storePath: string;
providerOverride: string;
authProfileProvider: string;
authProfileOverride: string;
cfg?: OpenClawConfig;
sessionHasHistory?: boolean;
}) {
const cfg = params.cfg ?? ({} as OpenClawConfig);
const sessionEntry: SessionEntry = {
sessionId: AUTH_PROFILE_RUNTIME_CONTRACT.sessionId,
updatedAt: Date.now(),
authProfileOverride: params.authProfileOverride,
authProfileOverrideSource: "user",
};
const sessionStore: Record<string, SessionEntry> = {
[AUTH_PROFILE_RUNTIME_CONTRACT.sessionKey]: sessionEntry,
};
await fs.writeFile(params.storePath, JSON.stringify(sessionStore, null, 2), "utf-8");
await runAgentAttempt({
providerOverride: params.providerOverride,
originalProvider: params.providerOverride,
modelOverride: "gpt-5.4",
cfg,
sessionEntry,
sessionId: sessionEntry.sessionId,
sessionKey: AUTH_PROFILE_RUNTIME_CONTRACT.sessionKey,
sessionAgentId: "main",
sessionFile: path.join(params.tmpDir, "session.jsonl"),
workspaceDir: params.tmpDir,
body: AUTH_PROFILE_RUNTIME_CONTRACT.workspacePrompt,
isFallbackRetry: false,
resolvedThinkLevel: "medium",
timeoutMs: 1_000,
runId: AUTH_PROFILE_RUNTIME_CONTRACT.runId,
opts: { senderIsOwner: false } as Parameters<typeof runAgentAttempt>[0]["opts"],
runContext: {} as Parameters<typeof runAgentAttempt>[0]["runContext"],
spawnedBy: undefined,
messageChannel: undefined,
skillsSnapshot: undefined,
resolvedVerboseLevel: undefined,
agentDir: params.tmpDir,
onAgentEvent: vi.fn(),
authProfileProvider: params.authProfileProvider,
sessionStore,
storePath: params.storePath,
sessionHasHistory: params.sessionHasHistory ?? false,
});
return {
aliasLookupParams: {
config: cfg,
workspaceDir: params.tmpDir,
},
};
}
describe("Auth profile runtime contract - Pi and CLI adapter", () => {
let tmpDir: string;
let storePath: string;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-contract-"));
storePath = path.join(tmpDir, "sessions.json");
loadPluginManifestRegistry.mockReset().mockReturnValue(createAuthAliasManifestRegistry());
runCliAgentMock.mockReset();
runEmbeddedPiAgentMock.mockReset();
runCliAgentMock.mockResolvedValue(makeCliResult("ok"));
runEmbeddedPiAgentMock.mockResolvedValue(makeEmbeddedResult("ok"));
});
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
it.each([
[AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider, AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider],
[
AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
],
[
AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
],
[
AUTH_PROFILE_RUNTIME_CONTRACT.codexHarnessProvider,
AUTH_PROFILE_RUNTIME_CONTRACT.codexHarnessProvider,
],
] as const)(
"resolves %s through the provider auth alias resolver using a mocked manifest",
(provider, expectedAuthProvider) => {
expect(
resolveProviderIdForAuth(provider, {
config: {} as OpenClawConfig,
workspaceDir: tmpDir,
}),
).toBe(expectedAuthProvider);
},
);
it("forwards an OpenAI Codex auth profile when the selected provider is codex-cli", async () => {
const { aliasLookupParams } = await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
expect(runCliAgentMock).toHaveBeenCalledTimes(1);
expect(runCliAgentMock.mock.calls[0]?.[0]?.authProfileId).toBe(
expectedForwardedAuthProfile({
provider: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
aliasLookupParams,
sessionAuthProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
}),
);
});
it("forwards an OpenAI Codex auth profile when the auth provider is the legacy codex-cli alias", async () => {
const { aliasLookupParams } = await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
expect(runCliAgentMock).toHaveBeenCalledTimes(1);
expect(runCliAgentMock.mock.calls[0]?.[0]?.authProfileId).toBe(
expectedForwardedAuthProfile({
provider: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
aliasLookupParams,
sessionAuthProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
}),
);
});
it("does not leak an OpenAI API-key auth profile into the Codex CLI alias", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProfileId,
});
expect(runCliAgentMock).toHaveBeenCalledTimes(1);
expect(runCliAgentMock.mock.calls[0]?.[0]?.authProfileId).toBeUndefined();
});
it("does not leak an OpenAI Codex auth profile into an unrelated CLI provider", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.claudeCliProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
expect(runCliAgentMock).toHaveBeenCalledTimes(1);
expect(runCliAgentMock.mock.calls[0]?.[0]?.authProfileId).toBeUndefined();
});
it("does not let a configured Codex harness leak OpenAI Codex auth into unrelated CLI providers", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.claudeCliProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
cfg: {
models: {
providers: {
[AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider]: {
baseUrl: "https://api.openclaw.test/v1",
agentRuntime: { id: "codex" },
models: [],
},
},
},
} as OpenClawConfig,
});
expect(runCliAgentMock).toHaveBeenCalledTimes(1);
expect(runCliAgentMock.mock.calls[0]?.[0]?.authProfileId).toBeUndefined();
});
it("forwards an OpenAI Codex auth profile through the embedded Pi path", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.authProfileId).toBe(
AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
);
});
it("accepts the legacy codex-cli auth-provider alias on the embedded OpenAI Codex path", async () => {
const { aliasLookupParams } = await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.authProfileId).toBe(
expectedForwardedAuthProfile({
provider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider,
aliasLookupParams,
sessionAuthProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
}),
);
});
it("forwards an OpenAI auth profile through the explicit embedded OpenAI PI path", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProfileId,
cfg: providerRuntimeConfig(AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider, "pi"),
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toMatchObject({
provider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider,
authProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProfileId,
});
});
it("forwards an OpenAI Codex auth profile through the default OpenAI Codex harness path", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toMatchObject({
authProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
});
it("routes explicit OpenAI PI runs with Codex OAuth through OpenAI Codex transport", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
cfg: providerRuntimeConfig(AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider, "pi"),
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toMatchObject({
provider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
});
it("preserves OpenAI Codex auth profiles through the real codex/* harness startup path", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.codexHarnessProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
cfg: providerRuntimeConfig(AUTH_PROFILE_RUNTIME_CONTRACT.codexHarnessProvider, "codex"),
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toMatchObject({
authProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
});
it("validates openai/* forced through the Codex harness can use OpenAI Codex OAuth profiles", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
cfg: providerRuntimeConfig(AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider, "codex"),
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toMatchObject({
authProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
});
it("preserves configured Codex harness when a skeleton session entry is considered history", async () => {
await runAuthContractAttempt({
tmpDir,
storePath,
providerOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider,
authProfileProvider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider,
authProfileOverride: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
sessionHasHistory: true,
cfg: providerRuntimeConfig(AUTH_PROFILE_RUNTIME_CONTRACT.openAiProvider, "codex"),
});
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toMatchObject({
authProfileId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProfileId,
});
});
});