Add Kitesurf support to Browser Tools - #2098
Conversation
🦋 Changeset detectedLatest commit: be30ecd The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 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 |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
|
Pushed cc89773 for the two Devin Review findings (on top of c29c3c7):
|
|
Pushed 5596609 for the two new Devin Review findings:
|
| // This wrapper hands the result straight to the model, so apply the same | ||
| // model-facing summary the AI SDK path gets from `toModelOutput` — a | ||
| // screenshot is kept out of the model context. | ||
| const modelOutput = await executeTool.toModelOutput?.({ | ||
| toolCallId: crypto.randomUUID(), | ||
| input: { code }, | ||
| output: result | ||
| }); | ||
| if (modelOutput?.type === "text" || modelOutput?.type === "error-text") { | ||
| return modelOutput.value; | ||
| } | ||
| if (modelOutput?.type === "json") { | ||
| return modelOutput.value; | ||
| } | ||
| return result; |
There was a problem hiding this comment.
🟡 Screenshots taken through the TanStack browser tool are thrown away while the model is told they were attached
The screenshot bytes are discarded and replaced with a summary sentence (toModelOutput at packages/agents/src/browser/tanstack-ai.ts:72-79) before the tool result is handed back to the caller, so a TanStack host can no longer show the picture and the model is told the image is "attached to the chat" when nothing was attached.
Impact: Users of the TanStack integration lose every screenshot their agent takes, and the model reports an attachment that does not exist.
Why the AI SDK path keeps the image but the TanStack path does not
In the AI SDK path (packages/agents/src/browser/ai.ts:403-409) toModelOutput is only the model-facing projection: the full tool output — including the canonical { type: "browser_screenshot", mediaType, data } value preserved by transformBrowserResult (packages/agents/src/browser/ai.ts:262-269) — still reaches the UI through the tool result part, which is exactly what examples/ai-chat/src/client.tsx:105-121 renders.
The TanStack wrapper has a single return channel, so returning modelOutput.value collapses both channels: for a screenshot result it returns only "Screenshot captured successfully (…) and attached to the chat." (packages/agents/src/browser/ai.ts:271-281), dropping status, executionId, calls, and the image data that the wrapper previously returned. Also note type === "error-text" is returned as an ordinary success value, losing the error signal.
Prompt for agents
In packages/agents/src/browser/tanstack-ai.ts the wrapper now replaces the tool result with the AI-SDK model-facing projection produced by browser_execute's toModelOutput (defined in packages/agents/src/browser/ai.ts). For canonical screenshot results that projection is only the sentence "Screenshot captured successfully (…) and attached to the chat.", so the TanStack caller loses the image bytes and the rest of the ProxyToolOutput (status, executionId, calls), and the sentence is factually wrong because there is no chat attachment channel in this wrapper. Consider either returning a shape that keeps the screenshot payload available to the host (e.g. return the raw result and let the host decide), or making the model-facing summary wording channel-neutral and ensuring the image is still reachable. Also handle the error-text case distinctly instead of returning it as a successful result value.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Partly addressed in be30ecd; the rest is a genuine tradeoff, so flagging rather than flipping again.
TanStack ServerTool has one return channel — whatever the host gets is what goes into the model's context — so the image bytes cannot be both available to the host and kept out of the model. This thread asks for the opposite of #2098 (comment) predecessor finding (screenshots sent to the model unbounded), which is what the projection was added for.
What changed:
- the envelope is preserved for the screenshot case:
{ ...result, result: <summary> }keepsstatus,executionIdandcalls, only the base64 is replaced; error-textnow throws instead of being returned as a success value;- the summary wording is channel-neutral ("the image is kept for the UI and omitted here") since there is no chat attachment in this wrapper.
If TanStack hosts should be able to render screenshots, the right fix is a separate out-of-band channel (e.g. writing the image to R2/a workspace and returning a reference) rather than returning raw base64 — happy to do that as a follow-up if @cjol wants it.
This PR adds connection-scoped Kitesurf support to Agents Browser Tools and uses it in the
ai-chatexample. Fixes #2095.Why
/v1/devtools/browser?browser=kitesurf, where the WebSocket itself owns the browser lifetime.quickAction()RPC cannot currently carry an engine selector. Passingbrowserin its options is rejected as part of the action body.Public API Surface
ConnectBrowserOptions.browser"kitesurf"engine for direct CDP acquisition.BrowserConnectorSessionOptions.browsercreateBrowserTools()andcreateBrowserRuntime().Selecting Kitesurf defaults Quick Actions off because the binding cannot select that engine. Callers can explicitly request Quick Actions to create a mixed toolset where CDP uses Kitesurf and Quick Actions use Chromium.
Architectural Changes
Code Changes
browser-run.tsacquires Kitesurf directly over WebSocket and rejects incompatible Browser Run options before making a request.BrowserConnectorselects the Kitesurf acquisition path, narrows its model-facing tools and instructions, and fails explicitly when a connection can no longer resume.browser_executepreserves canonical screenshot results for AI SDK UIs whiletoModelOutputsends the model only a compact attachment summary.codemode.search().examples/ai-chatdemonstrates natural-language Kitesurf navigation and inline screenshot rendering with a remote Browser Run binding.