Runner and PlugsHow-to guides
Write a local plug
Add a plug to your repository as a TypeScript file and use it from blaise.yml.
A plug is a TypeScript module that default-exports the result of definePlug. Any path in use:
that starts with ./ or ../ is loaded as a plug.
1. Create the module
import { z } from "zod";
import { definePlug } from "@blaiseagent/runner";
export default definePlug({
name: "acme/preamble",
inputs: z.object({ text: z.string() }),
// Add a line before every prompt the agent receives.
onMessage(ctx, message, next) {
if (message.method === "session/prompt") {
return next({
...message,
params: {
...message.params,
prompt: [{ type: "text", text: `${ctx.inputs.text}\n\n` }, ...message.params.prompt],
},
});
}
return next(message);
},
});The project needs @blaiseagent/runner and zod in its dependencies.
2. Use it
version: 1
setup:
- use: ./plugs/preamble.ts
with:
text: "Follow the house rules in docs/rules.md."The path is relative to the file that contains the entry. with: is checked against the plug's
inputs schema when the runner starts, so a mistake is reported before any session runs.
3. Test it
Run the plug in isolation with the testing harness:
import { describe, expect, test } from "vitest";
import type { Message } from "@blaiseagent/runner";
import { createPlugHarness } from "@blaiseagent/runner/testing";
import preamble from "./preamble";
const prompt: Message = {
direction: "to_agent",
method: "session/prompt",
params: { sessionId: "s", prompt: [{ type: "text", text: "Fix the bug." }] },
};
describe("preamble", () => {
test("puts the text before the prompt", async () => {
const harness = createPlugHarness(preamble, {
inputs: { text: "Follow the house rules." },
});
const { forwarded } = await harness.message(prompt);
expect(forwarded).toStrictEqual([
{
...prompt,
params: {
sessionId: "s",
prompt: [
{ type: "text", text: "Follow the house rules.\n\n" },
{ type: "text", text: "Fix the bug." },
],
},
},
]);
});
test("passes other messages on unchanged", async () => {
const harness = createPlugHarness(preamble, { inputs: { text: "x" } });
const cancel: Message = {
direction: "to_agent",
method: "session/cancel",
params: { sessionId: "s" },
};
const { forwarded } = await harness.message(cancel);
expect(forwarded).toStrictEqual([cancel]);
});
});Then start the runner to try it for real.
Things to know
- In
onMessage, pass what you do not care about on withreturn next(message). Change a message by passing a copy with differentparamstonext; the method cannot change. - The message the agent receives, and the one recorded in the transcript, include your change. A message that a plug answers itself is still recorded, tagged with the plug's name.
- Throw a
RequestErrorto answer an agent request with an error. Anything else you throw fails the run, which is what you want from a redactor that broke. - The full list of handlers and context is in the plug API reference.