akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
115 lines (114 loc) • 5.03 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/**
* Gemini CLI agent command builder (P2, plan §"The adapter contract" step 2 /
* §"Capability matrix").
*
* Translates a platform-agnostic {@link AgentDispatchRequest} into the exact
* headless argv the `gemini` CLI expects. Per the capability matrix the
* headless invocation is:
*
* gemini -p "<prompt>"
*
* with `--output-format json` for structured output, `--model <m>` for model
* selection, and `--resume <id>` for resume; resume is not built here because
* `AgentDispatchRequest` carries no session id. The `GEMINI_CLI=1` identity
* env marker is likewise registry-side (`AkmHarness.identityEnv` /
* `agent-identity.ts`), not a builder concern.
*
* Platform-specific mapping decisions (all localized here, per the adapter
* contract):
*
* - **systemPrompt** — Gemini CLI has no system-prompt flag in headless mode
* (system text comes from `GEMINI.md` context files / `GEMINI_SYSTEM_MD`),
* so the system prompt is folded into the `-p` payload ahead of the task
* prompt, separated by a blank line.
* - **schema** — the matrix places Gemini in the "via prompt+validate" tier
* (no native `--output-schema` equivalent, unlike Codex — so no temp-file
* plumbing here), so the JSON Schema reaches it as the instruction the
* shared request lowering appends to the prompt, and
* `--output-format json` is emitted so stdout is the documented JSON
* envelope the gemini result extractor normalizes. The engine's shared
* retry-until-valid loop performs the actual validation.
* - **tools** — a string/array tool policy maps to repeated
* `--allowed-tools <t>` flags (Gemini's run-without-confirmation
* allowlist). A *structured* policy object is NOT expressible as Gemini
* flags; it is deliberately dropped without widening to an auto-approve
* flag (never silently widen a restriction). With no policy at all,
* NOTHING is emitted — the matrix's headless shape is the bare
* `gemini -p "<p>"`; autonomy flags (`--yolo`, `--approval-mode`) belong in
* `profile.args` where the operator opts in explicitly.
*
* Registered: `geminiBuilder` is `GeminiHarness.agentBuilder` (`./index.ts`),
* one of the ten harnesses `HARNESS_REGISTRY` constructs
* (`harnesses/index.ts`); `agent/builders.ts` derives `BUILTIN_BUILDERS` from
* that registry, so this builder is reachable under the `"gemini"` platform
* name without any further wiring.
*/
import { resolveDispatchModel } from "../../agent/builder-shared.js";
import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
/** Canonical harness/platform id used for model-alias resolution. */
export const GEMINI_PLATFORM = "gemini";
/**
* Split a tool policy into individual tool names for `--allowed-tools`.
* Strings are comma-separated lists; arrays are taken as-is. Structured
* policy objects return `undefined` (not expressible as Gemini flags — see
* module doc).
*/
function toolPolicyEntries(tools) {
if (typeof tools === "string") {
return tools
.split(",")
.map((t) => t.trim())
.filter(Boolean);
}
if (Array.isArray(tools)) {
return tools.map((t) => t.trim()).filter(Boolean);
}
return undefined;
}
/** Assemble the `-p` payload: optional system prompt, then the task prompt. */
function buildPromptPayload(req) {
const sections = [];
if (req.systemPrompt)
sections.push(req.systemPrompt);
sections.push(req.prompt);
return sections.join("\n\n");
}
/**
* Gemini CLI builder.
* Command shape:
* gemini [--model <m>] [--allowed-tools <t> ...]
* [--output-format json] -p "<systemPrompt?\n\nprompt>"
*/
export const geminiBuilder = {
platform: GEMINI_PLATFORM,
personaChannel: "prompt",
lower: createAgentRequestLowerer({
adapter: GEMINI_PLATFORM,
personaChannel: "prompt",
tools: "flat",
}),
build(profile, req) {
const args = [...profile.args];
if (req.model) {
const resolved = resolveDispatchModel(req, profile, GEMINI_PLATFORM);
args.push("--model", resolved);
}
if (req.tools) {
// Structured policy objects (entries === undefined) emit NO flags:
// dropping a restriction must never widen to auto-approval.
for (const tool of toolPolicyEntries(req.tools) ?? []) {
args.push("--allowed-tools", tool);
}
}
if (req.schema) {
// Structured unit: ask for the documented JSON envelope so the result
// extractor can pull the final message + session id deterministically.
args.push("--output-format", "json");
}
args.push("-p", buildPromptPayload(req));
return { argv: [profile.bin, ...args] };
},
};