Docs/SDKs/TypeScript SDK

TypeScript SDK

@codegraff/sdk drives the graff CLI over its typed JSON protocol. Use Harness in Node, or RemoteHarness with a separately operated graff serve bridge in edge runtimes.

Install

bash
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:

ts
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:

bash
graff login                 # Codegraff gateway
graff login codex           # ChatGPT/Codex OAuth
graff login xai             # Grok/SuperGrok OAuth
graff key set anthropic sk-ant-...
ts
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.

ts
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:

ts
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

Native image turns require @codegraff/sdk 0.4.0+ and graff 0.0.279+. Version 0.3.1 does not include the imagesoption.
ts
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:

ts
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();
    }
  },
};
bash
graff serve --host 127.0.0.1 --port 8787 --token "$GRAFF_SERVE_TOKEN"

Bridge security

Keep the bridge behind TLS and authentication when it is reachable beyond localhost. The Worker holds only its bridge URL/token; provider credentials stay on the graff host.

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.

js
// next.config.js
module.exports = {
  serverExternalPackages: ["@codegraff/sdk"],
};
ts
// 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.