TypeScript Library
The TypeScript library lives under ts/ and ships as an ESM-only npm package.
Installation
sh
npm install @driangle/agentrunnerRequires Node.js >= 18.
Creating a Runner
Claude Code
ts
import { createClaudeRunner } from "@driangle/agentrunner/claudecode";
const runner = createClaudeRunner();
// With config
const runner = createClaudeRunner({
binary: "/usr/local/bin/claude",
logger: console,
});Ollama
ts
import { createOllamaRunner } from "@driangle/agentrunner/ollama";
const runner = createOllamaRunner();
// With config
const runner = createOllamaRunner({
baseURL: "http://localhost:11434",
logger: console,
});Running a Prompt
ts
const result = await runner.run("What is 2+2?", {
model: "claude-sonnet-4-20250514",
timeout: 30_000,
});
console.log(result.text);
console.log(`Cost: $${result.costUSD.toFixed(4)}`);
console.log(`Tokens: ${result.usage.inputTokens} in, ${result.usage.outputTokens} out`);Streaming
Using start (full session control)
ts
import { messageText, messageToolName } from "@driangle/agentrunner/claudecode";
const session = runner.start("Explain this codebase", {
workingDir: "/path/to/project",
});
for await (const msg of session.messages) {
switch (msg.type) {
case "assistant":
process.stdout.write(messageText(msg) ?? "");
break;
case "tool_use":
console.log(`\n[tool: ${messageToolName(msg)}]`);
break;
}
}
const result = await session.result;Using runStream (convenience wrapper)
ts
for await (const msg of runner.runStream("Explain this codebase")) {
const text = messageText(msg);
if (text) process.stdout.write(text);
}Common Options
ts
await runner.run("prompt", {
model: "claude-sonnet-4-20250514",
systemPrompt: "You are a helpful assistant.",
workingDir: "/path/to/project",
env: { DEBUG: "1" },
maxTurns: 10,
timeout: 300_000,
signal: controller.signal,
});Claude Code Options
ts
await runner.run("prompt", {
skipPermissions: true,
allowedTools: ["Read", "Write", "Bash"],
disallowedTools: ["WebSearch"],
mcpConfig: "/path/to/mcp.json",
jsonSchema: '{"type": "object", ...}',
maxBudgetUSD: 1.0,
resume: "session-id",
continueSession: true,
includePartialMessages: true,
channelEnabled: true,
channelLogFile: "/tmp/channel.log",
channelLogLevel: "debug",
onMessage: (msg) => console.log(msg.type),
});See the Channels guide for two-way communication details.
Ollama Options
ts
await runner.run("prompt", {
temperature: 0.7,
numCtx: 4096,
numPredict: 256,
seed: 42,
stop: ["END"],
topK: 40,
topP: 0.9,
format: "json",
keepAlive: "5m",
think: true,
onMessage: (msg) => console.log(msg.type),
});Session Resume
ts
// First run — capture the session ID
const result = await runner.run("Start a new task");
const sessionId = result.sessionId;
// Later — resume the session
const resumed = await runner.run("Continue where you left off", {
resume: sessionId,
});Error Handling
Errors are thrown as typed error classes:
ts
import {
NotFoundError,
TimeoutError,
NonZeroExitError,
CancelledError,
} from "@driangle/agentrunner";
try {
await runner.run("prompt");
} catch (err) {
if (err instanceof NotFoundError) {
console.error("CLI binary not found");
} else if (err instanceof TimeoutError) {
console.error("Execution timed out");
} else if (err instanceof NonZeroExitError) {
console.error(`CLI exited with code ${err.exitCode}`);
} else if (err instanceof CancelledError) {
console.error("Cancelled");
}
}Message Accessors
Claude Code provides typed accessor functions:
ts
import {
messageText,
messageThinking,
messageToolName,
messageToolInput,
messageToolOutput,
messageIsError,
messageTextDelta,
messageThinkingDelta,
} from "@driangle/agentrunner/claudecode";
messageText(msg); // text content
messageThinking(msg); // reasoning content
messageToolName(msg); // tool being called
messageToolInput(msg); // tool arguments
messageToolOutput(msg); // tool result
messageIsError(msg); // is this an error?
messageTextDelta(msg); // incremental text (partial messages)
messageThinkingDelta(msg); // incremental thinking (partial messages)Exports
The package uses subpath exports:
ts
import { ... } from "@driangle/agentrunner"; // common types, errors
import { ... } from "@driangle/agentrunner/claudecode"; // Claude Code runner
import { ... } from "@driangle/agentrunner/ollama"; // Ollama runnerLogging
Pass any object with debug and error methods:
ts
const runner = createClaudeRunner({
logger: console,
});