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
126 lines (125 loc) • 6.27 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 { ConfigError } from "../../core/errors.js";
import { withSchemaInstruction } from "../../core/structured.js";
import { MODEL_WORK_POLICY_ID } from "../../execution/source.js";
import { HARNESS_MODEL_WORK_IDS, HARNESS_NATIVE_AGENT_IDS } from "../harnesses/ids.js";
import { composeConversationFallbackPrompt } from "./conversation-fallback.js";
import { composePersonaFallbackPrompt } from "./persona-fallback.js";
/** A tool selection that actually names tools (not omitted, null, or empty). */
export function hasToolSelection(value) {
if (value === null || value === undefined)
return false;
if (typeof value === "string" || Array.isArray(value))
return value.length > 0;
return Object.keys(value).length > 0;
}
function translatesTools(mode, value) {
if (mode === "all")
return true;
if (typeof value === "string" || Array.isArray(value))
return mode === "flat" || mode === "sdk";
return mode === "sdk" && value !== null && Object.values(value).every((entry) => typeof entry === "boolean");
}
/** The notice for a request field a transport does not carry; dispatch continues. */
export function untranslated(adapter, field) {
return {
code: "untranslated-field",
severity: "warning",
adapter,
field,
message: `The ${adapter} lowerer does not translate resolved field ${field}; dispatch will continue optimistically.`,
};
}
/** Request fields carrying adapter-owned extensions, which no transport translates. */
export function extensionFields(request) {
return [
...(request.extensions ? ["extensions"] : []),
...(request.command.extensions ? ["command.extensions"] : []),
...(request.persona?.extensions ? ["persona.extensions"] : []),
...(request.engine.extensions ? ["engine.extensions"] : []),
...(request.runtime.extensions ? ["runtime.extensions"] : []),
];
}
/**
* Build one harness's request builder: map a resolved request onto the
* harness-neutral {@link AgentDispatchRequest} its argv builder consumes,
* composing into the prompt what the harness has no channel for.
*/
export function createAgentRequestLowerer(options) {
const supportedInference = new Set(options.inference ?? []);
return (_profile, request) => {
const modelWork = request.authorization.policy?.id === MODEL_WORK_POLICY_ID;
const notices = [];
const skip = (field) => {
notices.push(untranslated(options.adapter, field));
};
let prompt = request.command.content;
if (request.conversation && request.conversation.length > 0) {
prompt = composeConversationFallbackPrompt(request.conversation, prompt);
notices.push({
code: "conversation-prompt-composed",
severity: "warning",
adapter: options.adapter,
field: "conversation",
message: `The ${options.adapter} transport has no native multi-message channel; AKM composed the conversation prefix into one deterministic JSON prompt block.`,
});
}
const dispatch = { prompt };
if (request.persona) {
if (options.personaChannel === "native")
dispatch.systemPrompt = request.persona.content;
else {
const composed = composePersonaFallbackPrompt(request.persona.content, prompt, options.adapter);
prompt = composed.prompt;
notices.push(...composed.notices);
}
}
else if (typeof request.agent === "string") {
if (!HARNESS_NATIVE_AGENT_IDS.has(options.adapter)) {
throw new ConfigError(`The ${options.adapter} transport cannot consume native agent selector ${JSON.stringify(request.agent)}.`, "INVALID_CONFIG_FILE");
}
dispatch.agent = request.agent;
}
// Every agent transport gets the schema as the one instruction; a harness
// with a native channel (codex --output-schema) also reads dispatch.schema.
if (request.outputSchema) {
prompt = withSchemaInstruction(prompt, request.outputSchema);
dispatch.schema = request.outputSchema;
}
dispatch.prompt = prompt;
if (request.model)
dispatch.model = request.model.resolved;
if (Object.hasOwn(request, "inference")) {
dispatch.inference = request.inference ?? null;
for (const key of Object.keys(request.inference ?? {}).sort()) {
if (!(modelWork && supportedInference.has(key)))
skip(`inference.${key}`);
}
}
if (modelWork) {
if (!HARNESS_MODEL_WORK_IDS.has(options.adapter)) {
throw new ConfigError(`The ${options.adapter} transport cannot enforce the model-work tool policy.`, "INVALID_CONFIG_FILE");
}
// The builder selects its own confined agent or flags; another agent would replace them.
if (dispatch.agent) {
throw new ConfigError(`The ${options.adapter} transport cannot run native agent ${JSON.stringify(dispatch.agent)} under the model-work tool policy.`, "INVALID_CONFIG_FILE");
}
dispatch.modelWork = true;
}
else if (request.tools !== undefined) {
// An explicit empty selection still reaches the builder (e.g. an empty allowlist).
if (hasToolSelection(request.tools) && !translatesTools(options.tools, request.tools)) {
throw new ConfigError(`The ${options.adapter} transport cannot enforce the resolved tool policy.`, "INVALID_CONFIG_FILE");
}
dispatch.tools = request.tools;
}
const settings = request.runtime.settings;
if (settings && Object.keys(settings).length > 0)
skip("runtime.settings");
for (const field of extensionFields(request))
skip(field);
return { prompt, dispatch, notices };
};
}