fix: align gemini cli live backend runs

This commit is contained in:
Peter Steinberger
2026-04-07 09:05:43 +01:00
parent 0af808b457
commit d56831f81b
7 changed files with 110 additions and 29 deletions

View File

@@ -9,14 +9,23 @@ const GEMINI_MODEL_ALIASES: Record<string, string> = {
flash: "gemini-3.1-flash-preview",
"flash-lite": "gemini-3.1-flash-lite-preview",
};
const GEMINI_CLI_DEFAULT_MODEL_REF = "google-gemini-cli/gemini-3.1-pro-preview";
export function buildGoogleGeminiCliBackend(): CliBackendPlugin {
return {
id: "google-gemini-cli",
liveTest: {
defaultModelRef: GEMINI_CLI_DEFAULT_MODEL_REF,
defaultImageProbe: true,
docker: {
npmPackage: "@google/gemini-cli",
binaryName: "gemini",
},
},
config: {
command: "gemini",
args: ["--prompt", "--output-format", "json"],
resumeArgs: ["--resume", "{sessionId}", "--prompt", "--output-format", "json"],
args: ["--output-format", "json", "--prompt", "{prompt}"],
resumeArgs: ["--resume", "{sessionId}", "--output-format", "json", "--prompt", "{prompt}"],
output: "json",
input: "arg",
modelArg: "--model",

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
OPENCLAW_DOCKER_LIVE_AUTH_ALL=(.minimax)
OPENCLAW_DOCKER_LIVE_AUTH_ALL=(.gemini .minimax)
OPENCLAW_DOCKER_LIVE_AUTH_FILES_ALL=(
.codex/auth.json
.codex/config.toml
@@ -30,6 +30,9 @@ openclaw_live_should_include_auth_dir_for_provider() {
local provider
provider="$(openclaw_live_trim "${1:-}")"
case "$provider" in
gemini | gemini-cli | google-gemini-cli)
printf '%s\n' ".gemini"
;;
minimax | minimax-portal)
printf '%s\n' ".minimax"
;;
@@ -50,9 +53,6 @@ openclaw_live_should_include_auth_file_for_provider() {
printf '%s\n' ".claude/settings.json"
printf '%s\n' ".claude/settings.local.json"
;;
gemini | gemini-cli | google-gemini-cli)
printf '%s\n' ".gemini/settings.json"
;;
esac
}

View File

@@ -6,6 +6,7 @@ let createEmptyPluginRegistry: typeof import("../plugins/registry.js").createEmp
let setActivePluginRegistry: typeof import("../plugins/runtime.js").setActivePluginRegistry;
let normalizeClaudeBackendConfig: typeof import("./cli-backends.js").normalizeClaudeBackendConfig;
let resolveCliBackendConfig: typeof import("./cli-backends.js").resolveCliBackendConfig;
let resolveCliBackendLiveTest: typeof import("./cli-backends.js").resolveCliBackendLiveTest;
function createBackendEntry(params: {
pluginId: string;
@@ -22,6 +23,35 @@ function createBackendEntry(params: {
config: params.config,
...(params.bundleMcp ? { bundleMcp: params.bundleMcp } : {}),
...(params.normalizeConfig ? { normalizeConfig: params.normalizeConfig } : {}),
liveTest: {
defaultModelRef:
params.id === "claude-cli"
? "claude-cli/claude-sonnet-4-6"
: params.id === "codex-cli"
? "codex-cli/gpt-5.4"
: params.id === "google-gemini-cli"
? "google-gemini-cli/gemini-3.1-pro-preview"
: undefined,
defaultImageProbe: true,
docker: {
npmPackage:
params.id === "claude-cli"
? "@anthropic-ai/claude-code"
: params.id === "codex-cli"
? "@openai/codex"
: params.id === "google-gemini-cli"
? "@google/gemini-cli"
: undefined,
binaryName:
params.id === "claude-cli"
? "claude"
: params.id === "codex-cli"
? "codex"
: params.id === "google-gemini-cli"
? "gemini"
: undefined,
},
},
},
};
}
@@ -32,7 +62,8 @@ beforeEach(async () => {
vi.resetModules();
({ createEmptyPluginRegistry } = await import("../plugins/registry.js"));
({ setActivePluginRegistry } = await import("../plugins/runtime.js"));
({ normalizeClaudeBackendConfig, resolveCliBackendConfig } = await import("./cli-backends.js"));
({ normalizeClaudeBackendConfig, resolveCliBackendConfig, resolveCliBackendLiveTest } =
await import("./cli-backends.js"));
const registry = createEmptyPluginRegistry();
registry.cliBackends = [
createBackendEntry({
@@ -103,16 +134,7 @@ beforeEach(async () => {
"workspace-write",
"--skip-git-repo-check",
],
resumeArgs: [
"exec",
"resume",
"{sessionId}",
"--color",
"never",
"--sandbox",
"workspace-write",
"--skip-git-repo-check",
],
resumeArgs: ["exec", "resume", "{sessionId}", "--dangerously-bypass-approvals-and-sandbox"],
reliability: {
watchdog: {
fresh: {
@@ -135,8 +157,8 @@ beforeEach(async () => {
bundleMcp: false,
config: {
command: "gemini",
args: ["--prompt", "--output-format", "json"],
resumeArgs: ["--resume", "{sessionId}", "--prompt", "--output-format", "json"],
args: ["--output-format", "json", "--prompt", "{prompt}"],
resumeArgs: ["--resume", "{sessionId}", "--output-format", "json", "--prompt", "{prompt}"],
modelArg: "--model",
sessionMode: "existing",
sessionIdFields: ["session_id", "sessionId"],
@@ -165,11 +187,7 @@ describe("resolveCliBackendConfig reliability merge", () => {
"exec",
"resume",
"{sessionId}",
"--color",
"never",
"--sandbox",
"workspace-write",
"--skip-git-repo-check",
"--dangerously-bypass-approvals-and-sandbox",
]);
});
@@ -205,6 +223,26 @@ describe("resolveCliBackendConfig reliability merge", () => {
});
});
describe("resolveCliBackendLiveTest", () => {
it("returns plugin-owned live smoke metadata for codex", () => {
expect(resolveCliBackendLiveTest("codex-cli")).toEqual({
defaultModelRef: "codex-cli/gpt-5.4",
defaultImageProbe: true,
dockerNpmPackage: "@openai/codex",
dockerBinaryName: "codex",
});
});
it("returns plugin-owned live smoke metadata for gemini", () => {
expect(resolveCliBackendLiveTest("google-gemini-cli")).toEqual({
defaultModelRef: "google-gemini-cli/gemini-3.1-pro-preview",
defaultImageProbe: true,
dockerNpmPackage: "@google/gemini-cli",
dockerBinaryName: "gemini",
});
});
});
describe("resolveCliBackendConfig claude-cli defaults", () => {
it("uses non-interactive permission-mode defaults for fresh and resume args", () => {
const resolved = resolveCliBackendConfig("claude-cli");
@@ -585,13 +623,14 @@ describe("resolveCliBackendConfig google-gemini-cli defaults", () => {
expect(resolved).not.toBeNull();
expect(resolved?.bundleMcp).toBe(false);
expect(resolved?.config.args).toEqual(["--prompt", "--output-format", "json"]);
expect(resolved?.config.args).toEqual(["--output-format", "json", "--prompt", "{prompt}"]);
expect(resolved?.config.resumeArgs).toEqual([
"--resume",
"{sessionId}",
"--prompt",
"--output-format",
"json",
"--prompt",
"{prompt}",
]);
expect(resolved?.config.modelArg).toBe("--model");
expect(resolved?.config.sessionMode).toBe("existing");

View File

@@ -142,6 +142,28 @@ describe("buildCliArgs", () => {
}),
).toEqual(["-p", "--append-system-prompt", "Stable prefix\nDynamic suffix"]);
});
it("replaces prompt placeholders before falling back to a trailing positional prompt", () => {
expect(
buildCliArgs({
backend: {
command: "gemini",
modelArg: "--model",
},
baseArgs: ["--output-format", "json", "--prompt", "{prompt}"],
modelId: "gemini-3.1-pro-preview",
promptArg: "describe the image",
useResume: false,
}),
).toEqual([
"--output-format",
"json",
"--prompt",
"describe the image",
"--model",
"gemini-3.1-pro-preview",
]);
});
});
describe("writeCliImages", () => {

View File

@@ -250,8 +250,8 @@ function buildGoogleGeminiCliBackendFixture(): CliBackendPlugin {
id: "google-gemini-cli",
config: {
command: "gemini",
args: ["--prompt", "--output-format", "json"],
resumeArgs: ["--resume", "{sessionId}", "--prompt", "--output-format", "json"],
args: ["--output-format", "json", "--prompt", "{prompt}"],
resumeArgs: ["--resume", "{sessionId}", "--output-format", "json", "--prompt", "{prompt}"],
output: "json",
input: "arg",
modelArg: "--model",

View File

@@ -333,6 +333,16 @@ export function buildCliArgs(params: {
}
}
if (params.promptArg !== undefined) {
let replacedPromptPlaceholder = false;
for (let i = 0; i < args.length; i += 1) {
if (args[i] === "{prompt}") {
args[i] = params.promptArg;
replacedPromptPlaceholder = true;
}
}
if (replacedPromptPlaceholder) {
return args;
}
args.push(params.promptArg);
}
return args;

View File

@@ -7,7 +7,7 @@ import JSON5 from "json5";
type RestoreEntry = { key: string; value: string | undefined };
const LIVE_EXTERNAL_AUTH_DIRS = [".claude", ".codex", ".minimax"] as const;
const LIVE_EXTERNAL_AUTH_DIRS = [".claude", ".codex", ".gemini", ".minimax"] as const;
const LIVE_EXTERNAL_AUTH_FILES = [".claude.json"] as const;
const requireFromHere = createRequire(import.meta.url);
@@ -366,6 +366,7 @@ function stageLiveTestState(params: {
}
const tempStateDir = path.join(params.tempHome, ".openclaw");
fs.mkdirSync(tempStateDir, { recursive: true });
fs.mkdirSync(path.join(params.tempHome, ".gemini"), { recursive: true });
const realConfigPath = params.env.OPENCLAW_CONFIG_PATH?.trim()
? resolveHomeRelativePath(params.env.OPENCLAW_CONFIG_PATH, params.realHome)