API reference
Every export, every option, every type — enough that you never need to open the source.
Everything is imported from the package root. There are no deep imports.
import { Agent, run, tool } from "@rohhitrz/jonaki";Exports at a glance
Classes — Agent, JonakiError, MemoryStore, FileStore, SQLiteStore
Functions — tool, run, stream, resume, replay, approve,
reject, redactPII, blockPatterns, requireApprovalFor,
userMessagesOnly, lastMessages, handoffToolName, formatTrace,
summarizeTrace, createSQLiteStore, createAnthropicProvider,
createOpenAIProvider, createGoogleProvider, resolveProvider
Constants — JONAKI_ERROR_CODES
new Agent(config)
Static configuration. Never mutated by a run.
class Agent<TOutput = string> {
constructor(config: AgentConfig<TOutput>);
readonly name: string;
readonly instructions?: string;
readonly models: string[];
readonly tools: AnyTool[];
readonly handoffs: HandoffConfig[];
readonly guardrails: Guardrail[];
readonly output?: z.ZodType<TOutput>;
readonly maxTurns?: number;
readonly maxCostUSD?: number;
readonly timeoutMs?: number;
readonly provider?: Provider;
readonly context?: ContextOptions;
toolByName(name: string): AnyTool | undefined;
handoffSpecs(): ToolSpec[];
handoffForToolName(toolName: string): HandoffConfig | undefined;
handoffConfigFor(agentName: string): HandoffConfig | undefined;
toolSpecs(): ToolSpec[];
registry(into?: Map<string, Agent<unknown>>): Map<string, Agent<unknown>>;
}AgentConfig<TOutput>
| Field | Type | Default |
|---|---|---|
name | string | required |
instructions | string | — |
model | string | string[] | env-resolved |
tools | AnyTool[] | [] |
handoffs | HandoffInput[] | [] |
guardrails | Guardrail[] | [] |
output | z.ZodType<TOutput> | — |
maxTurns | number | 10 |
maxCostUSD | number | — |
timeoutMs | number | — |
provider | Provider | env-resolved |
context | ContextOptions | no windowing |
Throws INVALID_CONFIG when name is missing.
tool(config)
function tool<TIn extends z.ZodType, TOut>(
config: ToolConfig<TIn, TOut>,
): Tool<TIn, TOut>;ToolConfig<TIn, TOut>
| Field | Type | Default |
|---|---|---|
name | string | required |
description | string | required |
input | TIn extends z.ZodType | required |
run | (args: z.infer<TIn>, ctx: ToolContext) => TOut | Promise<TOut> | required |
timeoutMs | number | 30000 |
needsApproval | boolean | (args: z.infer<TIn>) => boolean | false |
Tool<TIn, TOut>
interface Tool<TIn extends z.ZodType = z.ZodType, TOut = unknown> {
readonly name: string;
readonly description: string;
readonly input: TIn;
readonly parameters: object; // JSON Schema
readonly timeoutMs: number;
needsApproval(args: z.infer<TIn>): boolean;
execute(rawArgs: unknown, ctx: ToolContext): Promise<TOut>;
toSpec(): ToolSpec;
}AnyTool is Tool<z.ZodType, unknown>, for heterogeneous arrays.
ToolContext
interface ToolContext {
runId: string;
agentName: string;
signal: AbortSignal;
log: (message: string, data?: Record<string, unknown>) => void;
}run(agent, input, options?)
function run<TOutput = string>(
agent: Agent<TOutput>,
input: string | RunState,
options?: RunOptions,
): Promise<RunResult<TOutput>>;input is a string, or a RunState to continue.
RunOptions
| Option | Type | Default |
|---|---|---|
runId | string | generated |
maxTurns | number | agent's, else 10 |
maxCostUSD | number | agent's |
timeoutMs | number | agent's |
maxHandoffDepth | number | 3 |
signal | AbortSignal | — |
store | Store | — |
session | string | — (requires store) |
sessionLimit | number | all |
context | ContextOptions | agent's |
onEvent | (event: JonakiEvent) => void | — |
stream | boolean | false |
emitter | EventEmitter | created |
provider | Provider | env-resolved |
model | string | string[] | agent's |
now | () => number | Date.now |
record | boolean | JONAKI_RECORD env |
RunResult<T>
interface RunResult<T = unknown> {
runId: string;
status: RunStatus; // "completed" | "awaiting_approval" | "failed"
output: T; // undefined when awaiting approval
text: string; // raw final assistant text
state: RunState;
usage: Usage;
steps: Step[];
}stream(agent, input, options?)
function stream<TOutput = string>(
agent: Agent<TOutput>,
input: string | RunState,
options?: RunOptions,
): RunStream<TOutput>;
interface RunStream<TOutput> extends AsyncIterable<JonakiEvent> {
result(): Promise<RunResult<TOutput>>;
}Defaults stream to true, so text_delta events are emitted.
resume(runId, store, options)
function resume<TOutput = string>(
runId: string,
store: Store,
options: ResumeOptions,
): Promise<RunResult<TOutput>>;
interface ResumeOptions extends RunOptions {
agent: Agent<unknown>; // required — config is code, never persisted
approved?: boolean;
note?: string;
}Throws STORE_ERROR for an unknown runId or a mismatched agent, and
TOOL_FAILED if a paused run is resumed without approved.
replay(path, options)
function replay<TOutput = string>(
path: string,
options: ReplayOptions,
): Promise<ReplayResult<TOutput>>;
interface ReplayOptions extends Omit<RunOptions, "provider" | "store"> {
agent: Agent<unknown>; // required
input?: string; // defaults to the recorded first user message
}
interface ReplayResult<TOutput = string> extends RunResult<TOutput> {
recorded: RunState; // the run this was compared against
}Throws REPLAY_DIVERGED when the recording no longer fits the code.
Approval
function approve(state: RunState, note?: string): RunState;
function reject(state: RunState, reason?: string): RunState;Both return a new state with the decision appended to the trace. A no-op when nothing is pending.
Guardrails
interface Guardrail {
name: string;
stage: GuardrailStage; // "input" | "output" | "tool"
check(
payload: GuardrailPayload,
ctx: GuardrailContext,
): GuardrailResult | Promise<GuardrailResult>;
}
type GuardrailPayload =
| { stage: "input"; text: string }
| { stage: "output"; text: string; output: unknown }
| { stage: "tool"; toolName: string; args: unknown };
type GuardrailResult =
| { action: "allow" }
| { action: "block"; reason: string }
| { action: "modify"; payload: unknown }
| { action: "require_approval"; reason: string };
interface GuardrailContext {
runId: string;
agentName: string;
turn: number;
}Built-ins
function redactPII(options?: {
stage?: GuardrailStage; // default "input"
name?: string; // default "redact-pii"
kinds?: string[]; // subset of ["email", "creditCard", "phone"]
replacement?: (kind: string, match: string) => string;
}): Guardrail;
function blockPatterns(
patterns: (string | RegExp)[],
options?: {
stage?: GuardrailStage; // default "input"
name?: string; // default "block-patterns"
reason?: (match: string) => string;
},
): Guardrail;
function requireApprovalFor(
patterns: (string | RegExp)[],
options?: { stage?: GuardrailStage; name?: string }, // default stage "tool"
): Guardrail;Handoffs
type HandoffInput = Agent<unknown> | HandoffConfig;
interface HandoffConfig {
agent: Agent<unknown>;
toolName?: string; // default `transfer_to_<snake_name>`
description?: string;
contextFilter?: (messages: Message[], ctx: HandoffContext) => Message[];
}
interface HandoffContext {
runId: string;
from: string;
to: string;
reason?: string;
}
function handoffToolName(agentName: string): string;
function userMessagesOnly(messages: Message[]): Message[];
function lastMessages(count: number): (messages: Message[]) => Message[];Stores
interface Store {
saveRun(state: RunState): Promise<void>;
loadRun(runId: string): Promise<RunState | null>;
appendSession(sessionId: string, messages: Message[]): Promise<void>;
loadSession(sessionId: string, limit?: number): Promise<Message[]>;
listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
}
interface ListRunsFilter {
sessionId?: string;
limit?: number;
}
interface RunSummary {
runId: string;
agentName: string;
status: RunStatus;
sessionId?: string;
turn: number;
usage: Usage;
createdAt: number;
updatedAt: number;
}Implementations
import { FileStore, MemoryStore, createSQLiteStore } from "@rohhitrz/jonaki";
const memory = new MemoryStore();
const files = new FileStore({ dir: "runs" });
const sqlite = await createSQLiteStore({ path: "./app.db" });
sqlite.close();| Constructor | Options |
|---|---|
new MemoryStore(options?) | now?: () => number |
new FileStore(options) | dir: string (required), now?: () => number |
await createSQLiteStore(options?) | path?: string (default ":memory:"), db?: SqliteDatabase to inject an open handle, now?: () => number |
FileStore also exposes runPath(runId), sessionPath(sessionId),
saveRecording(runId, recording), and loadRecording(runId).
SQLiteStore exposes .db and .close().
Context
interface ContextOptions {
maxTokens?: number; // unset = no windowing
keepRecent?: number; // default 4
}Errors
class JonakiError extends Error {
constructor(code: JonakiErrorCode, options?: JonakiErrorOptions);
readonly code: JonakiErrorCode;
readonly details?: Record<string, unknown>;
readonly hint?: string;
readonly cause?: unknown;
static is(value: unknown, code?: JonakiErrorCode): value is JonakiError;
}
interface JonakiErrorOptions {
details?: Record<string, unknown>;
hint?: string;
cause?: unknown;
}
const JONAKI_ERROR_CODES: readonly JonakiErrorCode[];See Error handling for every code and its fix.
State & trace
interface RunState {
runId: string;
agentName: string;
status: RunStatus;
messages: Message[];
turn: number;
handoffDepth: number;
usage: Usage;
pendingApproval?: {
toolCall?: ToolCall;
guardrail?: { name: string; stage: GuardrailStage; reason: string };
};
steps: Step[];
output?: unknown;
error?: { code: string; message: string };
sessionId?: string;
sessionOffset?: number;
}
type RunStatus = "running" | "awaiting_approval" | "completed" | "failed";RunState is losslessly JSON round-trippable: no class instances, no
functions, no Dates.
Steps
Every step has at: number (epoch ms) and turn: number.
type Step =
| ModelStep // provider, model, text, toolCalls, finishReason, usage
| ToolStep // toolCallId, name, args, status, result?, error?, durationMs
| HandoffStep // toolCallId, from, to, depth, reason?, messageIndex
| GuardrailStep // stage, name, action, reason?, toolName?
| OutputRepairStep // issues, received
| ApprovalStep // toolCallId, approved, note?
| FinalizeStep; // text
type ToolStepStatus = "ok" | "error" | "repair" | "rejected";Formatting
function formatTrace(
state: RunState,
options?: { color?: boolean; maxValue?: number },
): string;
function summarizeTrace(state: RunState): TraceSummary;
interface TraceSummary {
runId: string;
agentName: string;
status: string;
turns: number;
usage: Usage;
durationMs: number;
modelCalls: number;
toolCalls: number;
handoffs: number;
failures: number;
}Events
type JonakiEvent =
| { type: "run_started"; runId; at; agentName; input }
| { type: "model_call_started"; runId; at; agentName; provider; model; turn }
| { type: "text_delta"; runId; at; delta }
| { type: "model_call_completed"; runId; at; step: ModelStep }
| { type: "tool_started"; runId; at; toolCallId; name; args }
| { type: "tool_completed"; runId; at; step: ToolStep }
| { type: "tool_failed"; runId; at; step: ToolStep }
| { type: "handoff_started"; runId; at; from; to; reason? }
| { type: "handoff_completed"; runId; at; step: HandoffStep }
| { type: "guardrail_triggered"; runId; at; step: GuardrailStep }
| { type: "approval_required"; runId; at; toolCall?; guardrail?; reason? }
| { type: "run_completed"; runId; at; output; usage }
| { type: "run_failed"; runId; at; code; message };
type JonakiEventType = JonakiEvent["type"];Providers
interface Provider {
id: ProviderId; // "anthropic" | "openai" | "google"
generate(model: string, req: ModelRequest): Promise<ModelResponse>;
stream(model: string, req: ModelRequest): AsyncIterable<ProviderChunk>;
}
interface ModelRequest {
messages: Message[];
system?: string;
tools?: ToolSpec[];
temperature?: number;
maxTokens?: number;
signal?: AbortSignal;
}
interface ModelResponse {
text: string;
toolCalls: ToolCall[];
usage: Usage;
finishReason: FinishReason; // "stop" | "tool_calls" | "length" | "error"
raw: unknown;
}
type ProviderChunk =
| { type: "text"; delta: string }
| { type: "tool_call"; call: ToolCall }
| { type: "done"; response: ModelResponse };Factories
function createAnthropicProvider(options?: {
apiKey?: string;
baseURL?: string;
client?: Anthropic;
}): Provider;
function createOpenAIProvider(options?: {
apiKey?: string;
baseURL?: string;
client?: OpenAI;
}): Provider;
function createGoogleProvider(options?: {
apiKey?: string;
client?: GoogleGenAI;
}): Provider;
function resolveProvider(options?: {
provider?: ProviderId | Provider;
model?: string;
env?: NodeJS.ProcessEnv;
}): { provider: Provider; model: string };Shared types
type Role = "system" | "user" | "assistant" | "tool";
interface Message {
role: Role;
content: string;
toolCalls?: ToolCall[]; // on assistant turns
toolCallId?: string; // on tool results
}
interface ToolCall {
id: string;
name: string;
args: unknown;
}
interface ToolSpec {
name: string;
description: string;
parameters: object; // JSON Schema
}
interface Usage {
inputTokens: number;
outputTokens: number;
costUSD: number;
}CLI
npx @rohhitrz/jonaki init [--dir .] [--name <name>] [--provider <id>]
[--tests|--no-tests] [--yes] [--force]
npx jonaki list [--dir runs] [--db <path>] [--session <id>] [--limit 20]
npx jonaki trace <runId> [--dir runs] [--db <path>] [--json] [--no-color]
npx jonaki test [dir] --agent <file>jonaki test exits 1 when any replay fails.
Environment variables
| Variable | Effect |
|---|---|
ANTHROPIC_API_KEY | Enables the Anthropic provider |
OPENAI_API_KEY | Enables the OpenAI provider |
GOOGLE_API_KEY | Enables the Google provider |
JONAKI_RECORD | Directory to record runs into; enables recording |