> ## Documentation Index
> Fetch the complete documentation index at: https://developer.revise.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools for your agent

> Hand the document to whatever model you already run.

The SDK exposes Revise's canonical document-local tool subset. The tools are
JSON-schema described, safe to call without React state or browser focus, and
operate on semantic document structure rather than UI geometry.

## The loop

```ts theme={null}
const definitions = editor.tools.getDefinitions();

const response = await yourModel.createMessage({
  messages,
  tools: definitions.map((tool) => ({
    name: tool.name,
    description: tool.description,
    input_schema: tool.inputSchema,
  })),
});

for (const call of response.toolCalls) {
  const result = await editor.tools.executeDynamic(call.name, call.input);
  messages.push({
    role: "tool",
    tool_call_id: call.id,
    content: result.ok
      ? (result.value.message ?? JSON.stringify(result.value.data))
      : `${result.error.code}: ${result.error.message}`,
  });
}
```

That is the whole integration: schemas out, results back in. The definitions
are generated from the same source as Revise's production agent, so they carry
the descriptions and constraints the tools were designed with.

## Results

Both surfaces — this one and `@reviseio/sdk/backend` — return the same
discriminated envelope, so result handling is shared verbatim between web
and Node:

```ts theme={null}
type ReviseToolResult<Name> =
  | { ok: true; value: ReviseToolResponse<Name> }
  | { ok: false; error: ReviseToolFailure<Name> }; // { callId, tool, code, message }

interface ReviseToolResponse<Name> {
  callId: string;
  tool: Name;
  message: string | null; // model-oriented explanation
  data: ReviseToolData<Name>; // typed structured metadata, per tool
  context: { format: "revise-html"; html: string } | null;
  suggestionIds: string[] | null; // tracked records this mutation created
}
```

<Warning>
  A rejected edit resolves — it does not throw. Always check `ok`, and feed
  `error.message` back to the model: it explains *why* a call failed in
  terms the model can act on, which is usually the difference between a retry
  that works and one that repeats the mistake. (`tools.call()` is the
  throwing variant for application code, raising a typed `ReviseToolError`.)
</Warning>

Read and search tools return `context.html`: HTML that preserves block IDs,
inline formatting, tables, and notes rather than flattening the document to
plain text. Those IDs are what mutation tools target, so the read/act cycle
composes. Suggesting-posture mutations report the tracked records they
created in `suggestionIds` — feed them straight to
`review.acceptSuggestions()`.

## Working with blocks

The model reads a window of the document, then edits by block ID:

```ts theme={null}
const read = await editor.tools.call("read_blocks_from_index", {
  index: 0,
  context_notes: "Looking for the liability clause",
});

await editor.tools.call("replace", {
  id: "b12",
  replacements: [
    {
      find: "capped at the fees paid",
      replace: "capped at two years of fees paid",
      occurrence: "unique",
    },
  ],
});
```

<Tip>
  `replace` operates inside **one** block. Two edits in different blocks are
  two calls — a single call with finds spanning blocks fails rather than
  partially applying.
</Tip>

## Editing every match at once

A search of the active document returns a `search_result_id` standing for the
whole unpaginated match set. Pass it to `replace`, `replace_block`,
`style_blocks`, or `remove_blocks` instead of an `id`, and the edit applies
independently to every matched block — no paging through IDs, no call per
occurrence:

```ts theme={null}
const search = await editor.tools.call("search_document", {
  queries: ["Acme Corp."],
  page: 0,
  context_notes: "Renaming the counterparty throughout",
});

await editor.tools.call("replace", {
  search_result_id: search.data.search_result_id,
  replacements: [
    { find: "Acme Corp.", replace: "Acme Holdings Ltd.", occurrence: "all" },
  ],
});
```

<Warning>
  The reference is scoped to the editor session that produced it, and is
  rejected once the matched blocks have changed underneath it — nothing is
  partially applied. Re-run `search_document` and use the new ID.
</Warning>

## Routing to a document

Every tool schema carries an optional `document_id`. Omit it for the active
document, or target any ready document in the same editor:

```ts theme={null}
await editor.tools.call("set_title", { title: "Exhibit A" }, {
  documentId: "exhibit-a",
});

// or bind a controller once
const exhibit = editor.tools.forDocument("exhibit-a");
await exhibit.execute("measure_document", {});
```

## Suggestions or direct edits

Mutations land as tracked changes by default, whatever mode the editor's own
typing surface is in. Pass `directMode` to apply them outright:

```ts theme={null}
await editor.tools.call("style_blocks", input, { directMode: true });
```

See [tracked changes](/editor-sdk/guides/tracked-changes) for when that is appropriate.

## What is not a tool

Review navigation, comment-panel state, focus, viewport, zoom, and direct
canvas rendering are host concerns, not tools in this external catalogue. An
agent should not be clicking "next suggestion" — it should be making semantic
edits and letting your UI present them. A delegated Revise agent can inspect
the mounted canvas with its internal `render_document_pages` tool. See
[architecture](/editor-sdk/concepts/architecture) and [delegation](/editor-sdk/guides/delegated-agent).

The full catalogue is in the [tool reference](/editor-sdk/api/agent-tools).

## Node: semantic editing without an editor

`@reviseio/sdk/backend` binds the same deterministic definitions and executor
to a host-owned `Y.Doc`; no model or React mount is involved. (Full surface —
conversion, room lifecycle, and the session API — in the
[backend reference](/editor-sdk/api/backend).) A session is
document-scoped, retains search references across calls, and must be disposed.
Disposal releases Revise observers but deliberately leaves your `Y.Doc` alive.

Because the session is bound to one document, its schemas and descriptions
carry no `document_id`. Literal tool names infer their exact schema input and
structured output. Use `call()` in ordinary application code; it returns the
successful response directly and throws `ReviseToolError` for an expected tool
rejection. Use `execute()` when you want a discriminated result, or
`executeDynamic()` for untrusted model-provided names and JSON. Every path is
serialized in arrival order on one session. Switch with
`document.setSuggestingMode()` and `document.setEditingMode()`; each call
captures the mode when submitted, so toggling is deterministic even with queued
work.

```ts theme={null}
import { readFile, writeFile } from "node:fs/promises";
import {
  createServerDocumentSession,
  decodeYDoc,
  encodeYDoc,
  fileToYDoc,
  ydocToDocx,
} from "@reviseio/sdk/backend";

const ydoc = await fileToYDoc(
  await readFile("agreement.docx"),
  "agreement.docx",
);
const document = await createServerDocumentSession(ydoc, {
  documentId: "agreement-1",
  mode: "suggesting",
});

const read = await document.tools.call("read_blocks_from_index", {
  index: 0,
  context_notes: "Locating the liability clause",
});
console.log(read.context?.html); // block-ID-preserving Revise semantic HTML

const search = await document.tools.call("search_document", {
  queries: ["fees paid"],
  page: 0,
  context_notes: "Liability cap",
});
const searchResultId = search.data.search_result_id;
if (!searchResultId) throw new Error("The search returned no editable matches");

// Exact-text changes require no authored HTML. Apply this one directly.
document.setEditingMode();
await document.tools.call(
  "replace",
  {
    search_result_id: searchResultId,
    replacements: [{ find: "fees paid", replace: "fees paid or payable" }],
  },
);

// Switch back whenever the workflow should produce tracked changes.
document.setSuggestingMode();
const governingLaw = await document.tools.call("search_document", {
  queries: ["Delaware"],
  page: 0,
  context_notes: "Suggesting a governing-law change",
});
const governingLawId = governingLaw.data.search_result_id;
if (!governingLawId) throw new Error("The governing-law text was not found");
const edit = await document.tools.call("replace", {
  search_result_id: governingLawId,
  replacements: [{ find: "Delaware", replace: "New York" }],
});

// Review by ID — the primary path. Mutation results report the tracked
// records they created; persist these with your review task.
const created = edit.suggestionIds ?? [];
const decision = document.acceptSuggestions(created);
// decision.resolved — settled now; decision.missing — stale or unknown IDs.

// Or inspect everything pending first. Records carry authorship, so a host
// can decide on its own agent's suggestions and leave collaborators' alone:
const mine = document
  .listSuggestions()
  .filter((record) => record.authorType === "ai");
// document.rejectSuggestions(mine.map((record) => record.id));

// Whole-document decisions exist, but they settle collaborators' pending
// suggestions too — reach for them only when the host owns the document:
// document.acceptAllSuggestions(); // or document.rejectAllSuggestions()

await writeFile("agreement.yjs", encodeYDoc(ydoc));
const restored = decodeYDoc(await readFile("agreement.yjs"));
await writeFile("agreement-amended.docx", await ydocToDocx(restored));
document.dispose();
```

For a model-driven loop, keep expected failures as data:

```ts theme={null}
const result = await document.tools.executeDynamic(call.name, call.input);
const toolMessage = result.ok
  ? (result.value.message ?? JSON.stringify(result.value.data))
  : `${result.error.code}: ${result.error.message}`;
```

Read/search `response.context.html` is constrained **Revise semantic HTML**:
semantic document markup with stable block IDs for paragraphs, formatted runs,
tables, comments, and notes where supported. Structured tool metadata is in
the inferred `response.data`; the concise model-oriented summary is
`response.message`. Structural insertion and whole-block replacement accept
this dialect because it is safer than constructing `DocRoot` or OOXML. It is
not arbitrary browser HTML/CSS and is not a lossless web representation of
unsupported Word geometry. The Yjs-backed Revise model remains authoritative,
DOCX remains the import/export format, and executing a tool does not reparse
untouched content through HTML.

Do not mutate `DocRoot` directly. Existing conversion calls may still return
that type for compatibility, but the supported server mutation contract is
`(await createServerDocumentSession(ydoc, ...)).tools`.
