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 });
}
toolBetaComputerToolset20260801
The tools[] entry to send.
BetaComputerToolset20260801owns(block) => boolean
Whether a content block belongs to this adapter.
(block) => booleanhandle(block) => Promise<BetaToolResultBlockParam>
Perform one computer action.
(block) => Promise<BetaToolResultBlockParam>handleAll(message) => Promise<BetaToolResultBlockParam[]>
Perform every computer action in one assistant message, in order.
(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.
zoomwould need server-side image cropping, which the machine API does not do. The model can screenshot at full resolution instead.cursor_positionhas 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.