Conversation
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 detectedLatest commit: 830fe85 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
🔍 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)
Was this helpful? React with 👍 or 👎 to provide feedback.
commit: |
Add one example per new library, each a one-shot agent: post a task, it works in a durable workspace, it replies when done. Both use the Workers AI binding, so neither needs an API key. The two examples exist to show the difference in shape. The pi one writes the loop out in full, because pi keeps the tool list apart from the code that runs the tools and expects the surrounding program to do the running. The TanStack AI one has no loop at all, because chat() owns it, so the agent is about ten lines. Seeing them side by side is the clearest statement of what each library takes on. The Workers AI provider for pi is lifted from the pi harness example in cloudflare/agents, cut down to the one model this example uses. Writing these turned up a mistake in the tool documentation, now fixed: chat() takes tools as a list, not keyed by name. The set is still returned keyed by name, which is what a server-side registry wants, so the examples pass Object.values(tools). A tool also needs a phantom marker to satisfy the union chat() accepts, and its execute takes the validated arguments loosely, matching the signature chat() calls it with.
Running both example agents against a scripted model, rather than only typechecking them, turned up two real faults. A script per example now drives its loop locally with no Cloudflare account, so the next person can reproduce either in one command. A tool that failed reported the wrong reason under TanStack AI. It validates every value a tool returns against the tool's output shape, and those shapes described only success, so an ordinary failure such as a missing file was replaced by a complaint about the shape. The model was told the output was malformed instead of what went wrong. The shapes now describe failure too, because for a filesystem tool a failure is an ordinary outcome rather than a fault. A deliberate null was lost under pi. Strict argument checking makes every field required and sends an omitted one as null, so those nulls were stripped before use. They were stripped from every field of every tool, including one that accepts any value at all, so a caller passing null to a command could not be told apart from one passing nothing. Only the fields the strict transformation widened are stripped now. Both faults have a test that fails without the fix, and a changeset covers the new entrypoints.
Every TanStack entry point takes an array: chat(), mergeAgentTools and createToolRegistry all call array methods on what they are given. The tools came back keyed by name instead, so each caller had to convert, and the docs and both examples carried that conversion. The record was also a trap rather than merely inconvenient. createToolRegistry rejected it outright, but mergeAgentTools accepted it without complaint and returned something still keyed by name, so the failure surfaced later inside chat() as a missing array method and pointed away from its cause. A list is what the library consumes, so a list is what these builders return. tanStackToolsByName covers the rarer case of reaching one tool directly, such as adjusting a single tool before the call. An earlier note claimed a server-side registry wanted the record. It does not; mergeAgentTools is typed ReadonlyArray on that argument, and nothing in the library asks for the keyed shape.
Replaces the separate tanStackToolsByName helper with a `format` option on the builders, so one call site covers both shapes. It defaults to "array", which every TanStack entry point takes, so the common path is unchanged and needs no argument. "object" keys the same tools by name for a caller that reaches one directly. The option is generic rather than a plain union, so the return type follows from the literal: passing "object" types the result as a record and "array" or nothing types it as a list. A caller that passes a computed format gets the union and has to narrow, which is honest about what it asked for.
The three libraries shared one ToolSpec description per tool, and each entrypoint adapted that shared set. Every tool therefore had to be expressible in terms every library could accept, and adding a fourth meant extending the shared vocabulary before writing any tools. Each library now has its own directory and declares its own tools, so the same tool is written out three times. That duplication is the point: a provider can reach for what its own library offers instead of what all three have in common. What they share is the workspace logic underneath, in common/: reading a file by line and byte offset, applying an edit and rendering its diff, buffering a command's output, along with each tool's schema and description text and the options deciding which tools exist. None of that mentions a library, and none of it is a tool. The ToolSpec and registry layers are gone, about 480 lines. Public entrypoints are unchanged: ./tools, ./tools/pi and ./tools/tanstack resolve as before, and the standalone AI SDK tool constructors keep working.
Most of the commentary on this branch restated its subject: a header naming the module, a doc line repeating a type, a note describing the statement below it. That kind of comment adds a second thing to keep true and gives nothing back, and two had already gone stale — a header still advertised a createPiToolExecutor that never shipped, and another explained an import cycle the restructure had removed. What is left is what reading the code cannot tell you: that pi's tool results carry images but not PDFs, that a null on a widened field means absent while a null anywhere else is a value, that TanStack validates error returns against the output schema too, that the AI adapter's Ai cast is a types-package version skew rather than a real mismatch, that a base URL is never dialed. No behavior changes.
The pi dispatcher promised that a failing tool comes back as an error result rather than an exception, but turning a tool's output into the model's view ran outside that guard. A failure there escaped to the caller and broke its agent loop instead of costing the model a turn.
Aborting a TanStack chat stopped the model but left a running command going in the backend, still changing the workspace after the user asked it to stop. The adapter assumed the tool context carried no abort signal and relied on a separate signal passed to the factory, which callers had to remember to wire up. TanStack does pass each tool call the run's abort signal, in every version this package supports. Use it, and drop the factory option that existed only to work around its supposed absence.
Progress events were held back by one snapshot so that the final one could be returned rather than emitted. A command that printed once and then went quiet showed nothing until it exited, which defeated the point of asking for a live view. Snapshots already say whether a run has finished, so emit each running one immediately and keep returning the final one.
Split each tool into a framework-neutral core under tools/common (schema, description, executor; zod only) and an adapter per agent library. tools/ai-sdk wraps the core with `tool()` from `ai`; tools/pi-ai and tools/tanstack-ai build their own shapes from the same core without importing their libraries, so each entry point pulls in only what it uses. The exec core is the one #181 and #182 settled on: `exec` takes a backend map, every Workspace backend is offered by default, `backend` appears only when there is a choice, and `input` only when a backend is callable. All three tool sets resolve options through the same resolveToolOptions, so they offer the same tools. The pi and TanStack adapters come from #149. Co-authored-by: aron <263346377+aron-cf@users.noreply.github.com>
Two one-shot agents on a Worker-shell Workspace: pi-ai, where `run` is the whole loop, and tanstack-ai, where chat() owns it. Both build their tools from the Workspace alone; with one backend the model never names it. `npm run local` drives each loop in Node with a scripted model, through a small shim for `cloudflare:workers`. Bumps the libraries to current releases (pi-ai 0.99, @tanstack/ai 0.63, @tanstack/ai-cloudflare 0.2) and adds both to the CI examples matrix. Docs, READMEs, and a changeset cover the two entry points. The examples come from #149. Co-authored-by: aron <263346377+aron-cf@users.noreply.github.com>
Two one-shot agents on a Worker-shell Workspace: pi-ai, where `run` is the whole loop, and tanstack-ai, where chat() owns it. Both build their tools from the Workspace alone; with one backend the model never names it. `npm run local` drives each loop in Node with a scripted model, through a small shim for `cloudflare:workers`. Bumps the libraries to current releases (pi-ai 0.99, @tanstack/ai 0.63, @tanstack/ai-cloudflare 0.2) and adds both to the CI examples matrix. Docs, READMEs, and a changeset cover the two entry points. The examples come from #149. Co-authored-by: aron <263346377+aron-cf@users.noreply.github.com>
|
@aron-cf #186 carries this onto the #172 stack, after #181/#182 changed |
|
Update: #186 now keeps your function names, |
Split each tool into a framework-neutral core under tools/common (schema, description, executor; zod only) and an adapter per agent library. tools/ai-sdk wraps the core with `tool()` from `ai`; tools/pi-ai and tools/tanstack-ai build their own shapes from the same core without importing their libraries, so each entry point pulls in only what it uses. createAITools stays exported from @cloudflare/computer/tools. The exec core keeps the current options: a `shell` with a backend map and a default backend. All three tool sets resolve options through the same resolveToolOptions, so they offer the same tools. The pi and TanStack adapters come from #149. Co-authored-by: aron <263346377+aron-cf@users.noreply.github.com>
Two one-shot agents on a Worker-shell Workspace: pi-ai, where `run` is the whole loop, and tanstack-ai, where chat() owns it. Both pass the one worker shell to `exec` through `shell`. `npm run local` drives each loop in Node with a scripted model, through a small shim for `cloudflare:workers`. Bumps the libraries to current releases (pi-ai 0.99, @tanstack/ai 0.63, @tanstack/ai-cloudflare 0.2) and adds both to the CI examples matrix. Docs, READMEs, and a changeset cover the two entry points. The examples come from #149. Co-authored-by: aron <263346377+aron-cf@users.noreply.github.com>
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.
Usage for Tanstack AI: