Skip to content

API reference

The public API is exported from the runcell entrypoint.

createAgent(options): Agent

Creates a stateless agent bound to a model, credentials, tools, and event callbacks. Create one per process and reuse it across runs.

ts
const agent = createAgent({
  model: 'anthropic/claude-sonnet-4-5',
  systemPrompt: 'Be concise.',
  credentials: 'local',
  tools: { lookupCustomer },
  events: { onText: d => process.stdout.write(d) },
  maxRepairs: 1,
});
OptionTypeDescription
modelstringModel id, display name, or provider-qualified id (openai-codex/gpt-5.5). Required.
systemPromptstringPersistent system prompt: system role, re-applied every turn, survives thread resume.
credentialsCredentialsCredential source. Defaults to { type: 'env' }. See Credentials.
toolsRecord<string, ToolDefinition>Host functions the agent can call. See Files, tools, and events.
eventsAgentEventsLifecycle callbacks.
sandboxSandboxOptionAgent-level default sandbox mode. Defaults to 'virtual'.
maxRepairsnumberRepair-turn budget for structured runs. Defaults to 1.
piPiOptionsPi engine options. See Pi options.

A configured model that is not present in Pi's catalog fails at session startup with Unknown model "…" and up to five likely catalog matches. Runcell does not fall back to the agent directory's configured default model.

PiOptions

ts
type PiThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';

interface PiOptions {
  extensions?: readonly ExtensionFactory[];
  thinkingLevel?: PiThinkingLevel;
}

thinkingLevel sets the agent-level default reasoning effort. Pi maps it to such provider-native controls as Anthropic's thinking budget and OpenAI's reasoning_effort, then clamps it to what the selected model supports. When unset, Pi's default for that model applies. Invalid values throw InvalidOptionError when createAgent() is called.

PiThinkingLevel is exported from runcell. See Pi options for examples and extension semantics.

agent.run(options)

Two overloads:

ts
// with a schema: result.data is validated and typed
run<TSchema extends AgentSchema>(options: RunOptions<TSchema>): Promise<RunResult<InferSchemaOutput<TSchema>>>;

// without: a plain turn, result.data is undefined
run(options: RunOptionsBase): Promise<RunResult<undefined>>;

Run options

OptionTypeDescription
promptstringThe task prompt. Provide either prompt or messages.
messagesreadonly UIChatMessage[]A UI chat history (AI SDK UIMessage shape). Must end with a user message. Earlier turns replay as context. File parts on the last message become workspace files under attachments/.
maxAttachmentBytesnumberPer-attachment size limit for file parts in messages. Defaults to 20 MB.
schemaAgentSchemaStructured output contract (Standard Schema). Omit for a plain turn.
filesFileInput[]Files seeded into the workspace before the run. Relative paths only.
sandboxSandbox | SandboxOptionA caller-owned handle that Runcell does not destroy, or an ephemeral mode option.
threadThreadConversation to continue; mutated in place on success.
eventsAgentEventsPer-run lifecycle callbacks, invoked in addition to the agent-level ones.
pi{ thinkingLevel?: PiThinkingLevel }Per-run thinking-level override. Only thinkingLevel is accepted; it wins for this run only.
sessionIdstringResume a previous session by id.
signalAbortSignalCancels the run.

The per-run pi object accepts only thinkingLevel; extensions remain agent-level. Invalid values throw InvalidOptionError eagerly when run() is called.

With a schema, the first schema-valid submitResult call is terminal: runcell cancels the active model turn and returns that submission. A trailing stream timeout or transport error does not discard an already accepted result. Text and file changes observed before the submission are preserved.

RunResult<TData>

FieldTypeDescription
dataTDataValidated structured output, or undefined when no schema was given.
textstringThe model's prose and the output for plain turns.
filesChangedFile[]Files created/modified during this run ({ path, change, bytes }).
finishReasonstringWhy the final turn stopped, e.g. "stop".
sessionIdstringIdentifier of the underlying run session.
usageRunUsageToken usage and estimated cost for this run.

RunUsage

Token usage and estimated cost for one run, accumulated across every model turn in the run (including repair turns). Successful runs expose it as result.usage; use getRunUsage(error) to discover it safely on failures after a session starts.

FieldTypeDescription
inputTokensnumberNon-cached input tokens billed at the input rate.
outputTokensnumberOutput tokens, including reasoning tokens.
cacheReadTokensnumberInput tokens read from the provider's prompt cache.
cacheWriteTokensnumberInput tokens written to the provider's prompt cache.
totalTokensnumberSum of all token buckets.
costUsdnumberEstimated cost in US dollars at API list price.
costMeasuredbooleanWhether costUsd is a real measurement.

costUsd is computed from the models.dev-derived model catalog, including tiered pricing. It is always the as-if-API price: runs on subscription (OAuth) credentials report what the same tokens would have cost through the provider's API. Models the catalog does not price report 0 with costMeasured: false, so a zero can be told apart from genuinely free usage: when costMeasured is false, treat costUsd as “unpriced”, not “free”.

ts
const result = await agent.run({ prompt: 'Summarize feedback.txt.' });
console.log(result.usage);
// {
//   inputTokens: 1204, outputTokens: 380,
//   cacheReadTokens: 8600, cacheWriteTokens: 950,
//   totalTokens: 11134, costUsd: 0.0214,
// }

agent.stream(options): StreamRun

Same options and overloads as run, returned as a live stream plus a promise:

ts
const { textStream, result } = agent.stream({ prompt, thread });
for await (const delta of textStream) push(delta);
const final = await result; // always await this
FieldTypeDescription
textStreamAsyncIterable<string>The model's text deltas.
resultPromise<RunResult>Final result; rejects on failure. Always await it.
toUIMessageStream(options?)AsyncIterable<UIMessageChunk>The run as AI SDK UI Message Stream chunks.
toUIMessageStreamResponse(options?)ResponseThe run as a UI Message Stream SSE response for useChat / assistant-ui.

Both accept UIMessageStreamOptions, mirroring the AI SDK: sendReasoning, sendTools (blanket, 'names-only', or per-tool), and onError (masked by default so server error details never reach the client). toUIMessageStreamResponse also accepts ResponseInit fields. See wire-level controls.

The UI message stream carries text and reasoning deltas, tool calls and results (runcell-internal tools are hidden), one step per model turn (including repair turns), and a final finish chunk whose messageMetadata carries the run's usage and session id. A failed run ends the stream with an in-band error chunk (with usage metadata when measurable) while result still rejects. See Streaming for the route-handler pattern.

Sandboxes

createSandbox(option?: SandboxOption): Promise<Sandbox>

Creates a caller-owned sandbox handle on any backend (virtual, host, vercel, or custom). The option defaults to { type: 'virtual' }. Pass the same handle to multiple agent.run() calls to share one live provider session and workspace; runcell does not destroy caller-owned handles.

If provider session creation succeeds but workspace setup fails, runcell destroys the session before rejecting.

createVirtualSandbox(options?): Promise<Sandbox>

Creates a caller-owned in-memory sandbox. options.env sets environment variables for every command.

restoreSandbox(snapshot, options?): Promise<Sandbox>

Creates a fresh virtual sandbox and writes a snapshot's files back into it. The snapshot is validated before the sandbox is created. Escaping paths, duplicate paths, and malformed base64 throw InvalidOptionError.

Sandbox

MemberDescription
idStable identifier for the resource.
capabilities{ ports, nativeSnapshot, resume }: what this backend supports.
exec(command, opts?)Run a shell command → { exitCode, stdout, stderr }. opts: cwd, env, signal.
readFile(path)Uint8Array | null.
readTextFile(path)string | null.
writeFile(path, data)Writes text or bytes, creating parent directories.
remove(path)Removes a file or directory; no-op when missing.
snapshot()Portable, JSON-serializable capture of workspace files (SandboxSnapshot).
exposeUrl?(port)Public URL for a port. Present only when capabilities.ports is true.
lock(key, fn)Opt-in mutex, serialized per key on this handle.
destroy()Dispose the sandbox. Idempotent; later operations throw. Only the caller does this.

File paths passed to readFile, writeFile, remove, and similar methods must be relative POSIX paths. Absolute paths and .. throw InvalidOptionError.

SandboxOption (ephemeral modes)

ts
type SandboxOption =
  | 'virtual'
  | { type: 'virtual' }
  | {
      type: 'host';
      rootDir: string;
      isolation: 'external';
      env?: Record<string, string | undefined>;
      inheritHostEnv?: boolean;
    }
  | {
      type: 'vercel';
      runtime?: string;
      ports?: readonly number[];
      timeout?: number;
      [key: string]: unknown;
    }
  | { type: 'custom'; provider: SandboxProvider };

See Sandboxes for semantics; vercel requires the optional @ai-sdk/sandbox-vercel peer dependency and Node.js 22+.

SandboxProvider

The provider interface accepted by { type: 'custom' }: an object with specificationVersion: 'harness-sandbox-v1', a providerId, and a createSession() method. Existing providers from the @ai-sdk/sandbox-* family satisfy it directly.

Threads

createThread(options?): Thread

New empty conversation. options.id sets a stable id (defaults to a UUID).

threadFromJSON(state): Thread

Rebuilds a thread from a persisted ThreadState.

Thread

MemberDescription
idConversation id.
messagesreadonly ThreadMessage[]: the readable turn log.
clone()Deep, independent copy (fork the conversation).
toJSON()ThreadState: plain JSON-safe value; persist anywhere.

ThreadMessage

ts
{ role: 'user' | 'agent'; content: string; data?: unknown; createdAt: string }

ThreadState.continuation contains opaque engine state required to resume a thread. Store it without modifying it. See Threads.

Tools

ts
interface ToolDefinition<TSchema extends AgentSchema = AgentSchema> {
  description: string;
  schema: TSchema; // Standard Schema; input validated + typed
  execute(input: InferSchemaOutput<TSchema>): unknown; // sync or async
}

Reserved tool names: read, write, edit, bash, grep, glob, ls, submitResult, fileChange.

toolContent(parts): ToolContent

Builds an explicit multi-part result for a host tool. Return it from execute() to send text verbatim and images as real image blocks instead of JSON-stringified text.

ts
const result = toolContent([
  { type: 'text', text: 'Rendered page:' },
  { type: 'image', data: pngBytes, mediaType: 'image/png' },
]);

parts must be a non-empty array. Image data accepts Uint8Array or a base64 string; bytes are base64-encoded by the helper. A base64 string must be standard padded canonical base64 — no whitespace, no data: URL prefix, no base64url alphabet. Supported media types are image/png, image/jpeg, image/gif, and image/webp. Matching is case-insensitive and image/jpg normalizes to image/jpeg. Each image is limited to 5 MB decoded. Invalid inputs throw eagerly. Text-only content is valid.

The returned envelope has { type: 'runcell.tool-content', version: 1, content: [...] }. It is an explicit discriminator, not a duck-typed array: returning a bare content-like array from a tool keeps the ordinary JSON-stringified behavior. Tool-result events and result projections expose only the normalized content array, with base64 image data.

isToolContent(value): value is ToolContent

Returns whether value is a valid normalized ToolContent envelope. Beyond the structural shape (discriminator, version, part shapes, supported media types) it re-checks the data invariants: image data must be canonical padded base64 and at most 5 MB decoded. A hand-built or deserialized envelope that violates these is rejected.

Tool content types

ts
type ToolContentPartInput = ToolContentTextPart | ToolContentImageInput;
type ToolContentPart = ToolContentTextPart | ToolContentImagePart;
type ToolContentImageMediaType =
  | 'image/png'
  | 'image/jpeg'
  | 'image/gif'
  | 'image/webp';

interface ToolContentTextPart {
  readonly type: 'text';
  readonly text: string;
}

interface ToolContentImageInput {
  readonly type: 'image';
  readonly data: Uint8Array | string;
  readonly mediaType: string;
}

interface ToolContentImagePart {
  readonly type: 'image';
  readonly data: string;
  readonly mediaType: ToolContentImageMediaType;
}

interface ToolContent {
  readonly type: typeof TOOL_CONTENT_TYPE;
  readonly version: 1;
  readonly content: readonly ToolContentPart[];
}

const TOOL_CONTENT_TYPE = 'runcell.tool-content';

ToolContent, ToolContentPart, ToolContentPartInput, ToolContentTextPart, ToolContentImagePart, ToolContentImageInput, ToolContentImageMediaType, and TOOL_CONTENT_TYPE are exported from runcell.

Runcell does not pre-check model vision support. Provider failures surface through onError like other model errors.

Events (AgentEvents)

The optional callbacks are onText, onToolCall, onToolResult, onFileChange, onRepair, onFinish, and onError. Callbacks registered at the agent and run levels both fire. See Files, tools, and events.

Files

ts
type FileInput =
  | { path: string; text: string }
  | { path: string; bytes: Uint8Array };

Paths must be relative workspace paths (no absolute paths, no ..).

Errors

All runcell errors extend RuncellError:

ErrorThrown when
InvalidOptionErrorOptions are malformed (bad sandbox option, reserved tool name, foreign thread…).
IncompleteResultErrorA structured run exhausted its repair budget without a valid payload. Its usage includes every unsuccessful repair turn.
TurnErrorThe engine reported a terminal turn error, such as a provider failure or abort. The original error is available as cause; reconciled usage is available as usage.
CredentialErrorCredential configuration is unsafe or malformed (e.g. local in production).
ExtensionErrorA supplied Pi extension failed to load or registered a colliding tool. Raised before any model request; the original error is cause.
NotImplementedErrorA declared-but-unavailable capability was invoked.
ts
import { getRunUsage } from 'runcell';

try {
  await agent.run({ prompt, schema });
} catch (error) {
  const usage = getRunUsage(error);
  if (usage) console.log(usage.totalTokens, usage.costUsd);
}

getRunUsage(value) validates every token bucket, the total, cost, and measurement flag before returning RunUsage; malformed or absent usage returns undefined.

Failures after session startup reject with one of Runcell's own error classes. Runtime-created TurnError and IncompleteResultError carry the reconciled usage directly; every other rejection value — harness errors, tool errors, caller abort reasons — is wrapped in a TurnError that carries the usage, with the original value unmodified as cause. Runcell never mutates objects it does not own, so an externally supplied abort reason or a third-party error (including one with its own usage property) is always recovered exactly via error.cause. The same final error is delivered to onError and used to reject the run.

TurnError and IncompleteResultError have optional usage: runtime-created failures after session startup carry it, while manually constructed instances do not receive misleading zero defaults. Option, credential, extension initialization, and session initialization failures happen outside the measurable run lifecycle and do not carry usage.

Schema typing helpers

ts
type AgentSchema<TOutput = unknown> = StandardSchemaV1<unknown, TOutput>;
type InferSchemaOutput<TSchema extends AgentSchema> =
  StandardSchemaV1.InferOutput<TSchema>;

Utility exports

normalizeFiles, normalizeCredentials, assertSafeWorkspacePath, resolveSandboxConfig, and createSandboxProvider expose validation and configuration utilities used by createAgent.