Skip to content
Raster
Esc
navigateopen⌘Jpreview
On this page

Anthropic

The computer toolset for Claude — one tool entry, one handler, and your own loop.

@raster/anthropic maps Claude’s computer-use toolset onto the machine API. It is not an agent framework and does not own a loop: you keep your own conversation, call handle on the tool_use blocks you get back, and send the results.

npm install @raster/anthropic @anthropic-ai/sdk

The shape

import Anthropic from "@anthropic-ai/sdk";
import { Client } from "@raster/sdk";
import { computerUse } from "@raster/anthropic";

const machine = await new Client().machines.create({ size: "standard" });
await machine.waitForDesktop();

const adapter = computerUse(machine);

adapter.tool is the tools[] entry to send. No beta header is needed for this version.

const model = new Anthropic();
const messages = [{ role: "user", content: task }];

for (let turn = 0; turn < 24; turn++) {
  const response = await model.beta.messages.create({
    model: "claude-opus-5",
    max_tokens: 16_000,
    tools: [adapter.tool],
    messages,
  });

  if (response.stop_reason === "refusal") break;
  messages.push({ role: "assistant", content: response.content });
  if (response.stop_reason !== "tool_use") break;

  // every computer action in this message, in order.
  const results = await adapter.handleAll(response);
  messages.push({ role: "user", content: results });
}
PropType
toolBetaComputerToolset20260801

The tools[] entry to send.

TypeBetaComputerToolset20260801
owns(block) => boolean

Whether a content block belongs to this adapter.

Type(block) => boolean
handle(block) => Promise<BetaToolResultBlockParam>

Perform one computer action.

Type(block) => Promise<BetaToolResultBlockParam>
handleAll(message) => Promise<BetaToolResultBlockParam[]>

Perform every computer action in one assistant message, in order.

Type(message) => Promise<BetaToolResultBlockParam[]>

Options: maxWaitSeconds (how long a wait action may block; the protocol’s own ceiling is 300) and sleep, overridable so a test does not spend real time sleeping.

Order matters, and so does stopping

handleAll performs the actions in the order the model emitted them and stops at the first failure. The actions queued behind it are answered as not executed rather than applied to the wrong window — a click that failed usually means the screen is not what the model thought.

Two members are withheld

The adapter declares zoom and cursor_position as disabled rather than accepting and failing them. Teaching a model that a tool exists and then wasting a turn on discovering it does not work is worse than saying so up front.

  • zoom would need server-side image cropping, which the machine API does not do. The model can screenshot at full resolution instead.
  • cursor_position has no read-only pointer query on the guest, and moving the pointer to find out where it is defeats the question.

Adding a shell

The toolset covers the GUI. One canonical tool from the SDK gives the model a shell without a terminal window, and the definition is passed through as-is rather than restated:

const generic = machine.tools.generic();
const execTool = generic.definitions.find((d) => d.name === "terminal_exec")!;

const tools = [
  adapter.tool,
  { name: execTool.name, description: execTool.description, input_schema: execTool.input_schema },
];

Your own tools are yours to handle. adapter.owns(block) tells the two apart, and the adapter does not touch anything it does not own — which is the point of it not being a framework:

const results = await adapter.handleAll(response);

for (const block of response.content) {
  if (block.type !== "tool_use" || block.toolset_name) continue; // not the computer toolset
  const result = await generic.execute(block.name, block.input);
  results.push({
    type: "tool_result",
    tool_use_id: block.id,
    is_error: result.is_error,
    content: result.content.map((part) =>
      part.type === "text"
        ? { type: "text", text: part.text }
        : { type: "image", source: { type: "base64", media_type: "image/png", data: part.data } },
    ),
  });
}

Failures reach the model

An action that fails comes back as a tool result with is_error and a sentence, not as a thrown exception. A model that is told the click landed outside the display can take another screenshot; one that gets nothing stares at an unchanged screen.

Cleaning up

await machine.close(); // releases the input lease and closes the session

A working example lives at packages/anthropic/examples/phase-one-proof.ts.

Was this page helpful?