UNPKG

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
// 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] } : {}), }; }