test: route cli backend live refs through runtime

This commit is contained in:
Peter Steinberger
2026-05-02 20:19:24 +01:00
parent f969ae45a3
commit 40b8cb5837
4 changed files with 156 additions and 14 deletions

View File

@@ -562,6 +562,61 @@ describe("CLI attempt execution", () => {
);
});
it("routes canonical OpenAI models through the configured Codex CLI runtime", async () => {
const sessionKey = "agent:main:direct:canonical-codex-cli";
const sessionEntry: SessionEntry = {
sessionId: "openclaw-session-canonical-codex-cli",
updatedAt: Date.now(),
};
const sessionStore: Record<string, SessionEntry> = { [sessionKey]: sessionEntry };
await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2), "utf-8");
runCliAgentMock.mockResolvedValueOnce(makeCliResult("canonical codex cli"));
await runAgentAttempt({
providerOverride: "openai",
originalProvider: "openai",
modelOverride: "gpt-5.4",
cfg: {
agents: {
defaults: {
agentRuntime: { id: "codex-cli", fallback: "none" },
},
},
} as OpenClawConfig,
sessionEntry,
sessionId: sessionEntry.sessionId,
sessionKey,
sessionAgentId: "main",
sessionFile: path.join(tmpDir, "session.jsonl"),
workspaceDir: tmpDir,
body: "route this",
isFallbackRetry: false,
resolvedThinkLevel: "medium",
timeoutMs: 1_000,
runId: "run-canonical-codex-cli",
opts: { senderIsOwner: false } as Parameters<typeof runAgentAttempt>[0]["opts"],
runContext: {} as Parameters<typeof runAgentAttempt>[0]["runContext"],
spawnedBy: undefined,
messageChannel: "telegram",
skillsSnapshot: undefined,
resolvedVerboseLevel: undefined,
agentDir: tmpDir,
onAgentEvent: vi.fn(),
authProfileProvider: "openai",
sessionStore,
storePath,
sessionHasHistory: false,
});
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
expect(runCliAgentMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "codex-cli",
model: "gpt-5.4",
}),
);
});
it("keeps one-shot model runs on the raw embedded provider path", async () => {
const sessionKey = "agent:main:direct:model-run-raw";
const sessionEntry: SessionEntry = {

View File

@@ -115,6 +115,38 @@ describe("gateway cli backend live helpers", () => {
expect(shouldRunCliModelSwitchProbe("codex-cli", "codex-cli/gpt-5.5")).toBe(false);
});
it("configures legacy CLI model refs as canonical provider models plus CLI runtime", async () => {
const { resolveCliBackendLiveModelSelection } =
await import("./gateway-cli-backend.live-helpers.js");
expect(
resolveCliBackendLiveModelSelection({
rawModel: "codex-cli/gpt-5.4",
defaultProvider: "claude-cli",
}),
).toEqual({
providerId: "codex-cli",
cliModelKey: "codex-cli/gpt-5.4",
configModelKey: "openai/gpt-5.4",
configModelSwitchTarget: undefined,
agentRuntime: { id: "codex-cli", fallback: "none" },
});
expect(
resolveCliBackendLiveModelSelection({
rawModel: "claude-cli/claude-sonnet-4-6",
defaultProvider: "claude-cli",
modelSwitchTarget: "claude-cli/claude-opus-4-6",
}),
).toEqual({
providerId: "claude-cli",
cliModelKey: "claude-cli/claude-sonnet-4-6",
configModelKey: "anthropic/claude-sonnet-4-6",
configModelSwitchTarget: "anthropic/claude-opus-4-6",
agentRuntime: { id: "claude-cli", fallback: "none" },
});
});
it("lets env disable the model switch probe", async () => {
const { shouldRunCliModelSwitchProbe } = await import("./gateway-cli-backend.live-helpers.js");

View File

@@ -2,6 +2,8 @@ import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { resolveCliBackendLiveTest } from "../agents/cli-backends.js";
import { migrateLegacyRuntimeModelRef } from "../agents/model-runtime-aliases.js";
import { parseModelRef } from "../agents/model-selection.js";
import {
loadOrCreateDeviceIdentity,
publicKeyRawBase64UrlFromPem,
@@ -33,6 +35,14 @@ export type SystemPromptReport = {
injectedWorkspaceFiles?: Array<{ name?: string }>;
};
export type CliBackendLiveModelSelection = {
providerId: string;
cliModelKey: string;
configModelKey: string;
configModelSwitchTarget: string | undefined;
agentRuntime: { id: string; fallback: "pi" | "none" };
};
export type CliBackendLiveEnvSnapshot = {
configPath?: string;
stateDir?: string;
@@ -49,6 +59,41 @@ export type CliBackendLiveEnvSnapshot = {
anthropicApiKeyOld?: string;
};
export function resolveCliBackendLiveModelSelection(params: {
rawModel: string;
defaultProvider: string;
modelSwitchTarget?: string;
}): CliBackendLiveModelSelection {
const parsed = parseModelRef(params.rawModel, params.defaultProvider);
if (!parsed) {
throw new Error(
`OPENCLAW_LIVE_CLI_BACKEND_MODEL must resolve to a CLI backend model. Got: ${params.rawModel}`,
);
}
const migrated = migrateLegacyRuntimeModelRef(params.rawModel);
if (migrated?.cli) {
return {
providerId: migrated.runtime,
cliModelKey: `${migrated.runtime}/${migrated.model}`,
configModelKey: migrated.ref,
configModelSwitchTarget: params.modelSwitchTarget
? (migrateLegacyRuntimeModelRef(params.modelSwitchTarget)?.ref ?? params.modelSwitchTarget)
: undefined,
agentRuntime: { id: migrated.runtime, fallback: "none" },
};
}
const modelKey = `${parsed.provider}/${parsed.model}`;
return {
providerId: parsed.provider,
cliModelKey: modelKey,
configModelKey: modelKey,
configModelSwitchTarget: params.modelSwitchTarget,
agentRuntime: { id: "pi", fallback: "pi" },
};
}
export function parseJsonStringArray(name: string, raw?: string): string[] | undefined {
const trimmed = raw?.trim();
if (!trimmed) {

View File

@@ -17,6 +17,7 @@ import {
parseImageMode,
resolveCliModelSwitchProbeTarget,
resolveCliBackendLiveArgs,
resolveCliBackendLiveModelSelection,
parseJsonStringArray,
restoreCliBackendLiveEnv,
shouldRunCliImageProbe,
@@ -204,25 +205,34 @@ describeLive("gateway live (cli backend)", () => {
logCliBackendLiveStep("env-ready", { port });
const rawModel = process.env.OPENCLAW_LIVE_CLI_BACKEND_MODEL ?? DEFAULT_MODEL;
const parsed = parseModelRef(rawModel, "claude-cli");
if (!parsed) {
throw new Error(
`OPENCLAW_LIVE_CLI_BACKEND_MODEL must resolve to a CLI backend model. Got: ${rawModel}`,
);
}
const providerId = parsed.provider;
const modelKey = `${providerId}/${parsed.model}`;
const initialParsed = parseModelRef(rawModel, "claude-cli");
const initialProviderId = initialParsed?.provider ?? "";
const initialModelKey = initialParsed
? `${initialProviderId}/${initialParsed.model}`
: rawModel;
const initialModelSwitchTarget = resolveCliModelSwitchProbeTarget(
initialProviderId,
initialModelKey,
);
const modelSelection = resolveCliBackendLiveModelSelection({
rawModel,
defaultProvider: "claude-cli",
modelSwitchTarget: initialModelSwitchTarget,
});
const providerId = modelSelection.providerId;
const modelKey = modelSelection.cliModelKey;
const configModelKey = modelSelection.configModelKey;
const backendResolved = resolveCliBackendConfig(providerId);
const enableCliImageProbe = shouldRunCliImageProbe(providerId);
const enableCliMcpProbe = shouldRunCliMcpProbe(providerId);
const enableCliModelSwitchProbe = shouldRunCliModelSwitchProbe(providerId, modelKey);
const modelSwitchTarget = enableCliModelSwitchProbe
? resolveCliModelSwitchProbeTarget(providerId, modelKey)
? modelSelection.configModelSwitchTarget
: undefined;
logCliBackendLiveStep("model-selected", {
providerId,
modelKey,
configModelKey,
enableCliImageProbe,
enableCliMcpProbe,
enableCliModelSwitchProbe,
@@ -328,7 +338,7 @@ describeLive("gateway live (cli backend)", () => {
providers: {
...cfg.models?.providers,
openai: {
...openAiProviderConfigForCodexCli(modelKey),
...openAiProviderConfigForCodexCli(configModelKey),
...cfg.models?.providers?.openai,
},
},
@@ -347,12 +357,12 @@ describeLive("gateway live (cli backend)", () => {
defaults: {
...cfg.agents?.defaults,
...(bootstrapWorkspace ? { workspace: bootstrapWorkspace.workspaceRootDir } : {}),
model: { primary: modelKey },
model: { primary: configModelKey },
models: {
[modelKey]: {},
[configModelKey]: {},
...(modelSwitchTarget ? { [modelSwitchTarget]: {} } : {}),
},
agentRuntime: { id: "pi", fallback: "pi" },
agentRuntime: modelSelection.agentRuntime,
cliBackends: {
...existingBackends,
[providerId]: {