Tools

Tools

The agent acts through tools. anyclaude-sdk ships the full Claude Code tool suite — files, shell, search, web, todos — and lets you add your own with defineTool or restrict the set with allow/deny lists.

Built-in tools

All built-ins are exported as ALL_CLAUDE_CODE_TOOLS and are the default when you don't pass tools. Each is a Tool = an OpenAI-shape function definition + a run(input, ctx) implementation.

ToolWhat it doesKey params
bashRun a shell command in the workspace; returns combined stdout+stderrcommand, timeout_ms?, description?
read_fileRead a file — text, images, PDF, notebooks, binary (see below)path, offset?, limit?, pages?
write_fileWrite a file, creating parent directories as neededpath, content
edit_fileExact string replacement in an existing filepath, old_string, new_string, replace_all?
multi_editSeveral exact replacements applied atomically to one filepath, edits[]
delete_fileDelete a file or directory recursivelypath
list_filesList directory contents (defaults to workspace root)path?
globFind files matching a glob pattern (e.g. src/**/*.ts)pattern, path?
grepSearch file contents with a regular expressionpattern, path?, glob?, output_mode?, -i, -n
notebook_editEdit cells in a Jupyter .ipynb notebookpath, cell_id?, new_source, cell_type?, edit_mode?
todo_writeCreate / replace the session's structured todo listtodos[] (content, status, activeForm?)
web_fetchFetch a URL and return clean, readable texturl, prompt?, headers?
web_searchSearch the web and return the top resultsquery, num_results?
tool_searchDiscover available tools by keyword (handy with large tool sets)query, limit?
configRead or write session configuration key/value pairsaction, key?, value?

The agent loop runs each tool behind the permission gate; image/PDF bytes returned by a tool are forwarded to the model as a follow-up multimodal user turn automatically.

Multimodal file reading

read_file is content-aware — it returns the right block type for what it finds:

Text

UTF-8 text with cat -n line numbers; slice with offset / limit.

Images

PNG/JPG/… decoded to a base64 image block (downsampled to fit maxImageBytes) and sent to the model.

PDF

Rendered per page; request a range with pages (e.g. "1-5"), capped at maxPdfPages.

Notebooks

.ipynb parsed into cells + outputs; edit with notebook_edit.

Binary

Detected and reported (size/type) instead of dumping bytes into the context.

Tuning limits

Pass limits to query() to adjust the read caps (FileReadLimits):

import { query } from 'anyclaude-sdk'

for await (const msg of query({
  prompt: 'Summarize report.pdf',
  workspace, llm, model,
  limits: {
    maxSizeBytes: 256 * 1024,   // reject text reads over 256 KB (default)
    maxTokens: 25_000,          // reject text whose estimate exceeds this
    maxImageBytes: 3.75 * 1024 * 1024, // max image bytes after downsampling
    maxPdfPages: 20,            // max PDF pages returned per read
  },
})) { /* … */ }

Custom tools

Define your own tool with defineTool — a name, description, JSON-Schema parameters, and a run method. run receives the parsed input and a ToolContext (fs, exec, cwd, signal, …) and returns a ToolResult ({ content, isError? }). It can do anything async — hit an API, query a DB, call the sandbox.

import { defineTool, query } from 'anyclaude-sdk'

const weather = defineTool({
  name: 'get_weather',
  description: 'Current weather for a city.',
  parameters: {
    properties: { city: { type: 'string', description: 'City name' } },
    required: ['city'],
  },
  run: async ({ city }, ctx) => {
    const r = await fetch(`https://api.example.com/wx?q=${encodeURIComponent(String(city))}`, { signal: ctx.signal })
    return { content: await r.text() }
  },
})

// ctx also exposes the workspace — a tool that reads a file directly:
const countLines = defineTool({
  name: 'count_lines',
  description: 'Count lines in a workspace file.',
  parameters: { properties: { path: { type: 'string' } }, required: ['path'] },
  run: async ({ path }, ctx) => {
    const text = await ctx.fs.readFile(String(path))
    return { content: `${text.split('\n').length} lines` }
  },
})

extraTools vs tools

extraTools adds your tools to the built-ins; tools replaces the whole set.

query({
  prompt: 'What\'s the weather in Paris?',
  workspace, llm, model,
  extraTools: [weather, countLines], // builtins (read/write/bash/grep…) STILL available
})
import { readFile, writeFile } from 'anyclaude-sdk'

query({
  prompt: '…',
  workspace, llm, model,
  tools: [readFile, writeFile, weather], // ONLY these three exist
})

A Tool is just { def, run, maxResultChars? }. defineTool builds the def for you; you can also hand-write the OpenAI function schema if you prefer.

Selecting & disabling tools

Narrow what the agent can use with allowedTools (whitelist — only these) and disallowedTools (denylist — remove these). They apply to built-ins, extraTools, and MCP tools alike. Set them per call or in .claude/settings.json.

// Whitelist: read-only research agent
query({ prompt, workspace, llm, model, allowedTools: ['read_file', 'glob', 'grep', 'web_search'] })

// Denylist: everything except shell + deletion
query({ prompt, workspace, llm, model, disallowedTools: ['bash', 'delete_file'] })
{
  "allowedTools": ["read_file", "write_file", "edit_file", "glob", "grep"],
  "disallowedTools": ["bash"]
}

Resolution order: start from tools (or built-ins) + extraTools → apply allowedTools → apply disallowedTools.

Interactive questions — ask_user_question

Ported from Claude Code's AskUserQuestion. The agent can pause and ask the human a multiple-choice question instead of guessing. The tool is registered only when you pass an onAskUser handler to query() — your handler renders the prompt (CLI, web, the <AskUser> React component) and resolves with the chosen label(s).

import { query } from 'anyclaude-sdk'

for await (const msg of query({
  prompt: 'Set up linting for this repo.',
  workspace, llm, model,
  onAskUser: async (q) => {
    // q = { question, header?, options: [{ label, description? }], multiSelect? }
    // return the chosen label, or string[] when q.multiSelect
    return await promptTheUser(q)
  },
})) { /* … */ }

If no onAskUser is provided the tool isn't offered, so the agent proceeds on its own. In the browser, wire onAskUser to the <AskUser> component.

Client-side tools — clientTools

When the agent runs on a server but a tool must execute on the client (e.g. a browser running bash in a WebContainer, or touching device-local state), list those tool names in clientTools. The server stays the brain; the client is the hands.

When the model calls a client tool, the run emits { type: 'system', subtype: 'client_tool_request' } with the tool name + input and pauses. The client executes it, then you resume the same session passing clientToolResults — the results are injected as the tool's output and the loop continues.

for await (const msg of query({
  prompt, workspace, llm, model, sessionId,
  clientTools: ['bash'],   // these run on the client
})) {
  if (msg.type === 'system' && msg.subtype === 'client_tool_request') {
    // msg.requests = [{ id, name, input }] — send to the client, then resume
  }
}
query({
  workspace, llm, model, sessionId,
  resume: true, continueRun: true,
  clientTools: ['bash'],
  clientToolResults: [{ id, content: stdoutFromBrowser }],
})

See the vercel-clienttools example — a serverless agent whose bash runs in the user's browser via WebContainer.

Large tool outputs

Tools can set maxResultChars. When a result exceeds it, the agent loop spills the full output to a file in the workspace and replaces it in-context with a short preview + the path — so a giant grep or log dump never blows the context window. Self-bounded tools (like read_file) opt out with maxResultChars: Infinity.

const bigReport = defineTool({
  name: 'gen_report',
  description: 'Generate a large report.',
  parameters: { properties: {}, required: [] },
  run: async () => ({ content: hugeString }),
  maxResultChars: 4000, // beyond this → spilled to a file + preview
})