TypeScript SDK
Install
npm install @codegraff/sdk
The package is JavaScript generated from graff --schema. Local Harness starts the graff executable; version 0.3.1 expects graff on PATH, while 0.4.0+ installs a matching optional platform package. It is not an in-process Node addon.
Quickstart
Run one turn and consume its typed event stream:
import { runAgent } from "@codegraff/sdk";
for await (const ev of runAgent({
prompt: "explain monads in 3 sentences",
model: "gpt-5.5",
yolo: true,
})) {
if (ev.type === "text") process.stdout.write(ev.text);
if (ev.type === "turn") console.log("
finished for $" + ev.cost_usd);
if (ev.type === "error") throw new Error(ev.message);
}Configure
The SDK uses the same provider logins and keys as the CLI. Authenticate once, then choose a startup model in HarnessOptions:
graff login # Codegraff gateway graff login codex # ChatGPT/Codex OAuth graff login xai # Grok/SuperGrok OAuth graff key set anthropic sk-ant-...
import { Harness } from "@codegraff/sdk";
const harness = Harness.init({
model: "claude-opus-4-8",
cwd: process.cwd(),
yolo: true,
});There is no provider or apiKeyconstructor option. Use the CLI login/key store or the provider's documented environment variable, then select the model with model.
Streaming events
The primary event names are text, tool_call, tool_result, ask_user, turn, and error. Unknown future events are safe to ignore.
const harness = Harness.init({ model: "gpt-5.5", yolo: true });
try {
for await (const ev of harness.chat("add a logout button")) {
switch (ev.type) {
case "text":
process.stdout.write(ev.text);
break;
case "tool_call":
console.log("->", ev.name, ev.input);
break;
case "tool_result":
console.log("<-", ev.name, ev.is_error ? "error" : "ok");
break;
case "ask_user":
harness.answer({ callId: ev.call_id, text: "continue" });
break;
case "turn":
console.log("done", ev.context_tokens, "context tokens");
break;
case "error":
throw new Error(ev.message);
}
}
} finally {
await harness.close();
}Multi-turn sessions
A Harness process keeps conversation history across calls. Its session view exposes the same agent with send and ask helpers:
import { Harness } from "@codegraff/sdk";
const session = Harness.init({ model: "deepseek-v4-pro", yolo: true }).session();
try {
console.log(await session.ask("add a logout button"));
console.log(await session.ask("now write a test for it")); // same history
} finally {
await session.close();
}Image inputs
Version requirement
@codegraff/sdk 0.4.0+ and graff 0.0.279+. Version 0.3.1 does not include the imagesoption.const answer = await harness.ask({
prompt: "read the six digits in this image",
images: [
{ type: "image_url", url: "https://example.com/code.png" },
{ type: "image_base64", mediaType: "image/png", data: pngBase64 },
],
});URL and base64 inputs remain native provider vision parts; encoded pixels are never flattened into prompt text. A turn accepts at most 16 images, with a 3.7 MB decoded limit per base64 image.
Cloudflare Workers, browsers, and edge runtimes
Edge runtimes cannot start a local executable. Import the fetch-only remote client and point it at a separately operated, authenticated graff serve bridge:
import { RemoteHarness } from "@codegraff/sdk/remote";
interface Env {
GRAFF_SERVE_URL: string;
GRAFF_SERVE_TOKEN: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const harness = RemoteHarness.init({
url: env.GRAFF_SERVE_URL,
token: env.GRAFF_SERVE_TOKEN,
model: "gpt-5.5",
yolo: true,
});
try {
const { prompt } = await request.json() as { prompt: string };
return new Response(await harness.ask(prompt));
} finally {
await harness.close();
}
},
};graff serve --host 127.0.0.1 --port 8787 --token "$GRAFF_SERVE_TOKEN"
Bridge security
Next.js Route Handler
Use local Harnessfrom the Node runtime. Start one harness per request when requests need isolated conversations; the package's remote entrypoint is the better fit for Edge deployments.
// next.config.js
module.exports = {
serverExternalPackages: ["@codegraff/sdk"],
};// app/api/chat/route.ts
import { Harness } from "@codegraff/sdk";
export const runtime = "nodejs";
export async function POST(request: Request) {
const { prompt } = await request.json() as { prompt: string };
const harness = Harness.init({ model: "gpt-5.5", yolo: true });
const encoder = new TextEncoder();
const body = new ReadableStream({
async start(controller) {
try {
for await (const ev of harness.chat(prompt)) {
if (ev.type === "text") controller.enqueue(encoder.encode(ev.text));
if (ev.type === "error") throw new Error(ev.message);
}
controller.close();
} catch (error) {
controller.error(error);
} finally {
await harness.close();
}
},
});
return new Response(body, {
headers: { "content-type": "text/plain; charset=utf-8" },
});
}The complete generated surface and runnable transport tests live in the SDK readme.