@prismshadow/amsp
v0.2.16
Published
AMSP (Agent Message Stream Protocol): the wire types of a PenguinHarness Agent's API and a zero-dependency client for Node 24 and browsers.
Readme
@prismshadow/amsp
The client of a PenguinHarness agent's API, and the types of AMSP (Agent Message Stream Protocol), the event stream that API serves. No runtime dependencies; runs in Node 24 and in current browsers on fetch, ReadableStream, TextDecoder and AbortController.
A Project owner turns an agent's API on in the PenguinHarness Web App (the agent's API tab) and mints a key there. A program then runs the agent, reads the run as it happens, and continues the conversation by its Session id:
import { AgentClient } from "@prismshadow/amsp";
const client = new AgentClient({
baseUrl: "http://localhost:7364/api/amsp/v1",
agent: "demo/coder", // <projectId>/<agentId>
apiKey: process.env.PENGUIN_AGENT_KEY,
});
const run = client.stream({ input: "Summarize README.md" });
for await (const event of run) {
if (event.type === "text.delta") process.stdout.write(event.text);
}
const first = await run.result();
const second = await client.run({
sessionId: first.sessionId,
input: "Now list its headings",
onApproval: () => "allow", // without a callback, approval requests are denied
});
console.log(second.text, second.usage.total);stream()starts the run and returns it: iterate it once for the events, awaitsessionfor the Session id,result()for the outcome, and callabort()to have the server abort it.run()returns the result alone.result()resolves for every run that reachedrun.done, whatever itsstatus. A refusal before the stream rejects withAmspHttpError(status,code,message); a stream that ends or breaks beforerun.donerejects withAmspStreamError; aborting thesignalrejects with its reason.onApprovalanswers eachapproval.requested, and the client posts the answer;parseArgumentsreads a tool call's JSON arguments.agent(),session(id),abort(id)andapprove(id, toolCallId, decision)call the API's other routes;readSseis the SSE reader, for a program that reads a run's body itself.- Every AMSP event type is exported (
AmspEvent,RunDone,ToolCallDone, …). Events of a type the client does not know are passed through.
The reader without the client
A program that sends the request itself — its own fetch, its own credentials or URL — reads the body with readSse and folds the events with RunCollector, which AgentClient uses for result():
import { readSse, RunCollector } from "@prismshadow/amsp";
import type { AmspEvent } from "@prismshadow/amsp";
const collector = new RunCollector();
for await (const data of readSse(response.body!)) {
if (data === "[DONE]") break;
collector.add(JSON.parse(data) as AmspEvent);
}
const result = collector.result(); // throws AmspStreamError when the stream ended before run.doneDocumentation
- Agent API: turning an agent's API on, keys, approval modes, routes, errors, the client in Node and browsers
- AMSP: the protocol, event by event
Development
pnpm --filter @prismshadow/amsp build # tsup → dist/ (exports point at dist)
pnpm --filter @prismshadow/amsp typecheck
pnpm --filter @prismshadow/amsp testPart of PenguinHarness · Apache-2.0
