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
98 lines (97 loc) • 5.12 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/.
import { UsageError } from "../../core/errors.js";
import { warn } from "../../core/warn.js";
import { isPortableExecutionAgentSelector } from "../../execution/source.js";
import { fallbackAnnouncement, NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, withEngineFallback, } from "../../integrations/agent/engine-fallback.js";
import { executeInteractiveAgentInvocation } from "../../integrations/agent/runner-dispatch.js";
import { executeCommandInvocation } from "../command/command-execution.js";
function canonicalCurrent(options) {
const current = {};
if (options.agentRef !== undefined)
current.agent = options.agentRef;
if (options.engine !== undefined)
current.engine = options.engine;
if (options.selection?.model !== undefined)
current.model = options.selection.model;
if (Object.hasOwn(options.selection ?? {}, "inference"))
current.inference = options.selection?.inference;
if (options.selection?.outputSchema !== undefined)
current.outputSchema = options.selection.outputSchema;
if (options.selection?.tools !== undefined)
current.tools = options.selection.tools;
if (options.timeoutMs !== undefined)
current.timeout = options.timeoutMs;
if (options.cwd !== undefined)
current.workspace = options.cwd;
return current;
}
function rejectInvalidAgentRef(agentRef) {
if (agentRef === undefined || isPortableExecutionAgentSelector(agentRef))
return;
throw new UsageError(`agent expects an agent asset ref under agents/...; received ${JSON.stringify(agentRef)}.`, "INVALID_FLAG_VALUE");
}
/**
* Dispatch a `--prompt` / `--prompt-stdin` task through the canonical command
* path. The prompt is a person's free text, not a template: it is sent to the
* agent verbatim (`inlineContentMode: "literal"`), so prose containing `}}`
* from compact JSON, a `$VAR`, a shell snippet, or an `@path` reaches the
* agent instead of being rejected as an unsupported template construct.
*/
async function delegateCanonicalCommand(options, seams, action) {
const execute = seams.executeCommand ?? executeCommandInvocation;
const result = await execute({
action,
inlineContentMode: "literal",
config: options.agentConfig,
current: canonicalCurrent(options),
});
for (const message of result.warnings ?? [])
warn(message);
return result;
}
export async function akmAgentDispatch(options, seams = {}) {
if (!options.agentConfig)
throw new UsageError("agent requires a valid config with an agent engine.", "MISSING_REQUIRED_ARGUMENT");
rejectInvalidAgentRef(options.agentRef);
const hasResolvedSelection = options.agentRef !== undefined || options.selection !== undefined;
if (options.prompt !== undefined || hasResolvedSelection) {
if (options.prompt === undefined) {
throw new UsageError("Agent persona/model/tool/schema/inference selection requires an explicit task from --prompt or --prompt-stdin; it cannot fabricate an empty command. Omit those selections for a prompt-free interactive launch.", "MISSING_REQUIRED_ARGUMENT");
}
return delegateCanonicalCommand(options, seams, { content: options.prompt });
}
// Same implicit opencode-sdk fallback the workflow and task surfaces apply,
// so an engine-less install is usable everywhere or nowhere — not a mix.
const { config: agentConfig, fallbackEngineName } = withEngineFallback(options.agentConfig);
const engineName = options.engine ?? agentConfig.defaults?.engine;
// Announced, never silent: `options.engine` outranks the default, so the
// fallback is only reportable when it is the engine actually selected.
const engineAnnouncement = fallbackAnnouncement(fallbackEngineName, engineName);
if (engineAnnouncement)
warn(engineAnnouncement);
if (!engineName)
throw new UsageError(`agent ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "MISSING_REQUIRED_ARGUMENT");
const executeInteractive = seams.executeInteractive ?? executeInteractiveAgentInvocation;
const execution = await executeInteractive({
config: agentConfig,
engine: engineName,
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
});
const result = execution.result;
return {
schemaVersion: 2,
ok: result.ok,
shape: "agent-result",
engine: execution.engine,
exitCode: result.exitCode,
stdout: result.stdout ?? "",
stderr: result.stderr ?? "",
durationMs: result.durationMs,
...(result.error !== undefined ? { error: result.error } : {}),
...(result.reason !== undefined ? { reason: result.reason } : {}),
...(engineAnnouncement ? { warnings: [engineAnnouncement] } : {}),
};
}