Skip to content

Core API

The building blocks under createAgentTools: dispatch and listing against a config, the raw tool registry, the spill sweeper, and the error contract. Use these to build a custom transport — see The core API guide for the how-to.

dispatch

ts
function dispatch(
  name: string,
  args: Record<string, unknown>,
  config: ServerConfig,
): Promise<DispatchResult>;

Looks up name in the surface for config (respecting readOnly), validates args against the tool's JSON Schema with ajv, runs the handler, bounds the output to maxOutputBytes, and serializes any thrown error. It never throws — an unknown tool, invalid arguments, or a tool-level failure all come back as an isError result.

callTool / dispatch never throw for tool-level problems — they always resolve to a DispatchResult:

ts
interface DispatchResult {
  isError: boolean;
  text: string;
}
  • On success (isError: false), text is the tool's output, already bounded to maxOutputBytes. It is plain text for every tool except bash, whose success text is a JSON object { exit_code, stdout, stderr, signal, timed_out } — a non-zero exit is still a success.
  • On failure (isError: true), text is a JSON error envelope. An unknown tool name — or a mutating tool while readOnly is set — comes back as isError with code not_found.

listTools

ts
function listTools(config: ServerConfig): ToolInfo[];

The advertised surface for config{ name, description, inputSchema } per tool, honoring readOnly. See ToolInfo.

The registry

ts
const tools: ToolDef[]; // all nine
const readOnlyTools: ToolDef[]; // read_file, list_dir, glob, grep

function selectSurface(readOnly: boolean): ToolDef[];
function getTool(name: string, surface?: ToolDef[]): ToolDef | undefined;
  • tools — every ToolDef, in registration order.
  • readOnlyTools — the non-mutating subset.
  • selectSurface(readOnly)readOnlyTools when true, else tools. This is what dispatch/listTools use to honor config.readOnly.
  • getTool(name, surface = tools) — find one tool by name within a surface, or undefined.

ToolDef

ts
interface ToolDef {
  name: string;
  description: string;
  inputSchema: Record<string, unknown>; // JSON Schema, ajv-validated on dispatch
  bounded?: boolean; // when true, the handler bounds its own output (dispatch skips re-bounding)
  handler: (args: Record<string, unknown>, config: ServerConfig) => Promise<string>;
}

listTools projects a ToolDef down to ToolInfo (drops handler/bounded). If you call a handler yourself, you bypass validation and output bounding — prefer dispatch unless you have a reason not to.

sweepSpillDir

ts
function sweepSpillDir(workspaceRoot: string): Promise<void>;

Removes stale bash spill files from the .clarvis/ directory under workspaceRoot. Safe to call periodically in a long-lived process. See Limits & spill.

Errors

ts
type ErrorCode =
  | "invalid_input" | "not_found" | "not_a_file" | "is_binary"
  | "no_match" | "ambiguous_match" | "patch_failed" | "io_error"
  | "timeout" | "output_limit" | "too_large" | "path_escape" | "internal";

class ToolError extends Error {
  readonly code: ErrorCode;
  readonly fields: Record<string, unknown>;
  constructor(code: ErrorCode, message: string, fields?: Record<string, unknown>);
}

function serializeError(err: unknown): string;
function fsError(err: NodeJS.ErrnoException, path: string): ToolError;
  • ToolError — the structured error a tool handler throws. code is the stable error code; fields are merged into the serialized envelope.
  • serializeError(err) — turns a ToolError into the JSON envelope { error, message, ...fields }. Any non-ToolError is logged to stderr and returned as { "error": "internal", "message": "internal error" }, so raw internals never leak to the caller.
  • fsError(err, path) — maps a Node fs errno (ENOENTnot_found, EISDIR/ENOTDIRnot_a_file, else io_error) to a ToolError. Useful when writing your own tool handlers.

StartupError (thrown by resolveConfig / buildConfig on bad config) is a separate class, exported from the package root.

See also

Released under the MIT License.