Skip to main content
Tools are the primary way agents take actions - searching databases, calling APIs, reading files, or computing values. The model decides when to call a tool and with what arguments; Vibes validates the arguments against a Zod schema, executes the function, and appends the result to the message history for the next turn. Vibes provides four tool factories for different use cases: tool() (with dependency injection), plainTool() (no context), outputTool() (terminal - ends the run), and fromSchema() (raw JSON Schema instead of Zod).

Tool execution pipeline

tool() - tools with dependencies

tool<TDeps>() is the full-featured factory. The execute function receives a RunContext<TDeps> as its first argument, giving it access to injected deps, token usage, the current run ID, and more.
import { tool } from "@vibesjs/sdk";
import { z } from "zod";

type Deps = { db: Database };

const search = tool<Deps>({
  name: "search",
  description: "Search the database",
  parameters: z.object({ query: z.string() }),
  execute: async (ctx, { query }) => ctx.deps.db.search(query),
  argsValidator: ({ query }) => {
    if (query.length < 3) throw new Error("Query too short");
  },
  prepare: async (ctx) => ctx.deps.db.isConnected() ? undefined : null,
  requiresApproval: false,
  sequential: false,
  maxRetries: 1,
});

plainTool() - simple tools

plainTool() is a simpler factory for tools with no side effects and no dependency injection requirements. The execute function receives only the validated args - no RunContext.
import { plainTool } from "@vibesjs/sdk";
import { z } from "zod";

const add = plainTool({
  name: "add",
  description: "Add two numbers",
  parameters: z.object({ a: z.number(), b: z.number() }),
  execute: async ({ a, b }) => String(a + b),
});
Use plainTool when your tool is a pure function: math, formatting, text transformation, or any computation that doesn’t need external resources.

outputTool() - terminal tools

outputTool() creates a tool that ends the run. When the model calls it, the tool’s return value becomes the agent’s final output and no further turns occur. Use this with outputMode: 'tool' for structured output that the model fills in via a tool call.
import { outputTool } from "@vibesjs/sdk";
import { z } from "zod";

const done = outputTool({
  name: "done",
  description: "Return the final answer",
  parameters: z.object({ answer: z.string(), confidence: z.number() }),
  execute: async (_ctx, args) => args,
});

const agent = new Agent({
  model: anthropic("claude-sonnet-4-6"),
  tools: [done],
  outputMode: "tool",
});

fromSchema() - raw JSON schema tools

fromSchema() builds a tool from a plain JSON Schema object instead of Zod. Use this when integrating with external schema registries, OpenAPI specs, or when Zod is overkill for a simple schema. Note: no TypeScript type inference is available for the args.
import { fromSchema } from "@vibesjs/sdk";

const legacy = fromSchema({
  name: "legacy_api",
  description: "Call a legacy API",
  jsonSchema: {
    type: "object",
    properties: { endpoint: { type: "string" } },
    required: ["endpoint"],
  },
  execute: async (_ctx, args) => callApi(args.endpoint as string),
});

Tool options reference

All options accepted by tool():
OptionTypeRequiredDescription
namestringyesTool name - must be unique within an agent
descriptionstringyesDescribes the tool to the model
parametersZodTypeyesZod schema for the arguments
execute(ctx, args) => Promise<string | object>yesImplementation function
argsValidator(args) => void | Promise<void>noCross-field validation - throw to reject without consuming a retry
prepare(ctx) => ToolDefinition | null | undefinednoPer-turn availability check - return null to exclude this turn
requiresApprovalboolean | (ctx, args) => booleannoWhen true, throws ApprovalRequiredError before execution
sequentialboolean?noWhen true, acquires a run-level mutex so this tool never runs concurrently
maxRetriesnumber?noMax retries on execution failure (independent of result validation retries)

prepare - conditional tool availability

The prepare function is called once per model turn before the tool list is sent to the model. Return null or undefined to exclude the tool from that turn’s available set:
const search = tool<Deps>({
  name: "search",
  description: "Search the database",
  parameters: z.object({ query: z.string() }),
  execute: async (ctx, { query }) => ctx.deps.db.search(query),
  // Only include this tool when the database is connected
  prepare: async (ctx) => ctx.deps.db.isConnected() ? undefined : null,
});
You can also return a modified tool definition from prepare to dynamically change its description or parameters based on the current context.

Multi-modal returns

Tool execute functions can return strings, plain objects, or multi-modal content (images and uploaded files). Vibes serializes the result into the message history automatically:
import { tool } from "@vibesjs/sdk";
import type { BinaryContent } from "@vibesjs/sdk";

const screenshot = tool({
  name: "screenshot",
  description: "Take a screenshot",
  parameters: z.object({ url: z.string() }),
  execute: async (_ctx, { url }): Promise<BinaryContent> => {
    const buffer = await captureScreenshot(url);
    return { type: "image", data: buffer, mimeType: "image/png" };
  },
});
The result is passed back to the model as a multi-modal message part, enabling vision-capable models to reason about images.