Skip to content

Add tools for Pi and Tanstack harnesses - #149

Open
aron-cf wants to merge 3 commits into
mainfrom
tools-pi-tanstack
Open

aron-cf wants to merge 3 commits into
mainfrom
tools-pi-tanstack

Conversation

@aron-cf

@aron-cf aron-cf commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Add support for Pi and Tanstack AI tools alongside AI SDK.

This makes it easier to get started wiring up an agent and a computer workspace.

All three offer the same tools with the same names, descriptions and limits.

Usage for a Pi harness.

import { createPiTools } from "@cloudflare/computer/tools/pi";

const { tools, execute } = createPiTools({ workspace });

// `tools` goes in the context you send to the model.
const message = await models.complete(model, { messages, tools });

// Then run whatever the model asked for.
for (const block of message.content) {
  if (block.type !== "toolCall") continue;
  const { content, isError } = await execute(block);
  messages.push({ role: "toolResult", toolCallId: block.id, toolName: block.name, content, isError, timestamp: Date.now() });
}

Usage for Tanstack AI:

import { createTanStackTools } from "@cloudflare/computer/tools/tanstack";

const tools = createTanStackTools({
  workspace,
  approve: "mutating", // ask the user first before anything that changes files
});

const stream = chat({ adapter, messages, tools });

Devin Review

The tools that let an agent read and change workspace files were each
written against one specific agent library. The description the model
reads, the rules about what a valid request looks like, and the code
that does the work were all tangled together with that library's way
of declaring a tool.

That was fine while there was only one library to support. It meant
that supporting a second one would have required copying every tool
and keeping the copies in step by hand.

Describe each tool once instead, in a form that mentions no library at
all, and keep the list of which tools exist in a single place. The
existing support is now a thin translation layer on top of that
description, and it behaves exactly as it did before.

A tool can also say how it wants its result shown to the model:
ordinary text, a failure, structured data, or a picture. Each library
then renders that in whatever way it supports.
Agents built on pi or TanStack AI could not use the workspace tools
without writing their own wrappers around the file and command
surfaces first. Add support for both, so the same tools are available
whichever of the three libraries an agent is built on, with the same
names, the same descriptions, and the same limits.

The two libraries expect to be handed tools in different shapes, and
each is served in the shape it wants.

pi keeps the list of available tools separate from the code that runs
them, and expects the surrounding program to run them itself. So it
gets both pieces together: the list to show the model, and something
that takes the model's request, checks it, and runs it. A bad request
or a tool that fails comes back as an ordinary failed result, which
the model can learn from and try again, rather than as a crash that
would stop the program.

TanStack AI wants each tool to hand back a single answer, so a
long-running command reports the state of the run once it has
finished, with the option of also reporting progress along the way.

Neither addition pulls in the original library, so installing one of
the three does not drag in the other two.
Support for the two new libraries was added by reducing all three to
the small set of things the original one needed. That shared the code,
but it also meant throwing away features the new libraries have and
the original does not. Anyone using them through this package got less
than they would have by wiring the tools up themselves.

Let each tool state a few plain facts about itself instead: whether it
changes files, whether its request is the sort a model is likely to
get subtly wrong, and whether it reports progress while it runs. Every
tool states these once. Each library then makes what use of them it
can and quietly ignores the rest, so sharing the code no longer means
settling for the least capable option.

This buys something real in both. Some requests are easy to get wrong,
because they carry an exact copy of a piece of a file or a position
part way through one, and a wrong guess wastes a turn. pi can ask the
model provider to hold the model to the expected shape as it writes
the request, so those mistakes are prevented rather than reported.
Where a provider cannot do this, the request is simply sent the
ordinary way, because refusing to run at all would be worse.

TanStack AI is told the shape of a successful answer for the tools
that always answer the same way, which saves the caller describing it
again. It can also be asked to confirm with the user before any tool
that changes files, without having to list them, and to keep tools out
of sight until the model goes looking for them.
@changeset-bot

changeset-bot Bot commented Sep 18, 2026

Copy link
Copy Markdown

鈿狅笍 No Changeset found

Latest commit: a7520ad

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

Devin Review

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃攳 SDK contracts remain untested

Tests call locally declared shapes instead of either supported SDK. Contract drift can pass without exercising real Pi or TanStack integration.

(Refers to this code)

Devin Review


Was this helpful? React with 馃憤 or 馃憥 to provide feedback.

specs: ToolSpecSet,
options: Omit<CreateTanStackToolsOptions, keyof CreateToolsOptions> = {},
): TanStackToolSet {
const tools: TanStackToolSet = {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃敶 TanStack chat rejects tool set

Passing createTanStackTools() to chat fails because chat iterates an array, while tools is a record. No TanStack tool can run.

Learn more

Supported TanStack AI versions declare chat({ tools }) as a readonly array and call array methods on it during setup. The adapter instead builds an object keyed by tool name. The documented usage passes that object directly, so setup fails before the request begins.

Example: chat({ adapter, messages, tools: createTanStackTools({ workspace }) }) receives { read, ls, ... }. TanStack expects [read, ls, ...] and cannot iterate the record.

Recommended fix: Return a TanStack-compatible tool array from createTanStackTools and toTanStackTools. If a keyed registry is also useful, expose it through a separate API rather than using it as the chat input.

Devin Review


Was this helpful? React with 馃憤 or 馃憥 to provide feedback.

Comment on lines +126 to +129
// Only a successful result is described. The error branch is a
// normal outcome, so validating every return against the success
// shape would reject legitimate error results.
outputSchema: spec.outputSchema,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃煛 Tool errors fail output validation

When a mutation returns { error }, outputSchema rejects it because the schema only accepts success. TanStack reports a validation failure instead.

Learn more

TanStack validates every value returned by a server tool when outputSchema is present. The registered mutation schemas describe only successful results, but these executors return normal error objects for filesystem failures. The adapter preserves that error object, so TanStack rejects it against the success schema and changes the result into a generic execution failure.

Example: A read-only filesystem makes write return { error: "read-only filesystem" }. TanStack validates that against { path, bytesWritten } and reports Output validation failed for tool write instead of returning the original error object.

Recommended fix: Either include each executor's error branch in its outputSchema, or omit outputSchema for executors whose normal return union includes errors.

Devin Review


Was this helpful? React with 馃憤 or 馃憥 to provide feedback.

Comment on lines +168 to +171
// Strict schemas require every property, expressing "absent" as
// null, so drop those before validating against the Zod schema
// where the field is genuinely optional.
const parsed = spec.inputSchema.safeParse(dropNulls(call.arguments ?? {}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃煛 Null callable input gets dropped

A pi exec call with input: null reaches the callable backend as undefined. dropNulls removes this valid structured input.

Learn more

Pi's strict schemas encode omitted optional fields as null, so the adapter removes null-valued keys before Zod validation. It applies that conversion to every tool, including exec, even though exec.input accepts any JSON value and null is meaningful. The executor therefore cannot distinguish an explicitly supplied null input from an omitted input.

Example: A callable JavaScript module expects input === null. The model calls exec with { "command": "...", "input": null }, but the module receives undefined.

Recommended fix: Remove placeholder nulls only for fields made nullable by this adapter's strict-schema transformation. Do not remove null from fields whose original Zod schema accepts it, including exec.input.

Devin Review


Was this helpful? React with 馃憤 or 馃憥 to provide feedback.

Comment on lines +38 to +45
"./tools/pi": {
"types": "./dist/tools/pi.d.ts",
"import": "./dist/tools/pi.js"
},
"./tools/tanstack": {
"types": "./dist/tools/tanstack.d.ts",
"import": "./dist/tools/tanstack.js"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃攳 Public API change lacks changeset

Two public entrypoints and their APIs will ship without release notes or a version bump. Repository release rules require a changeset.

Devin Review


Was this helpful? React with 馃憤 or 馃憥 to provide feedback.

@pkg-pr-new

pkg-pr-new Bot commented Sep 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@cloudflare/computer@149

commit: a7520ad

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant