Agents: add pluggable CLIs

Co-authored-by: RealSid08 <RealSid08@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2025-12-02 10:42:27 +00:00
parent 52c311e47f
commit f31e89d5af
15 changed files with 624 additions and 150 deletions

65
src/agents/pi.ts Normal file
View File

@@ -0,0 +1,65 @@
import path from "node:path";
import type { AgentMeta, AgentParseResult, AgentSpec, BuildArgsContext } from "./types.js";
type PiAssistantMessage = {
role?: string;
content?: Array<{ type?: string; text?: string }>;
usage?: { input?: number; output?: number };
model?: string;
provider?: string;
stopReason?: string;
};
function parsePiJson(raw: string): AgentParseResult {
const lines = raw.split(/\n+/).filter((l) => l.trim().startsWith("{"));
let lastMessage: PiAssistantMessage | undefined;
for (const line of lines) {
try {
const ev = JSON.parse(line) as { type?: string; message?: PiAssistantMessage };
if (ev.type === "message_end" && ev.message?.role === "assistant") {
lastMessage = ev.message;
}
} catch {
// ignore
}
}
const text =
lastMessage?.content
?.filter((c) => c?.type === "text" && typeof c.text === "string")
.map((c) => c.text)
.join("\n")
?.trim() ?? undefined;
const meta: AgentMeta | undefined = lastMessage
? {
model: lastMessage.model,
provider: lastMessage.provider,
stopReason: lastMessage.stopReason,
usage: lastMessage.usage,
}
: undefined;
return { text, meta };
}
export const piSpec: AgentSpec = {
kind: "pi",
isInvocation: (argv) => argv.length > 0 && path.basename(argv[0]) === "pi",
buildArgs: (ctx) => {
const argv = [...ctx.argv];
// Non-interactive print + JSON
if (!argv.includes("-p") && !argv.includes("--print")) {
argv.splice(argv.length - 1, 0, "-p");
}
if (ctx.format === "json" && !argv.includes("--mode") && !argv.some((a) => a === "--mode")) {
argv.splice(argv.length - 1, 0, "--mode", "json");
}
// Session defaults
// Identity prefix optional; Pi usually doesn't need, but allow
if (!(ctx.sendSystemOnce && ctx.systemSent) && argv[ctx.bodyIndex]) {
const existingBody = argv[ctx.bodyIndex];
argv[ctx.bodyIndex] = [ctx.identityPrefix, existingBody].filter(Boolean).join("\n\n");
}
return argv;
},
parseOutput: parsePiJson,
};