Repository navigation
feat(agents): add web_search tool over Cloudflare's Web Search API - #2502
Conversation
🦋 Changeset detectedLatest commit: bf0f57b 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 import sizes: 4 entry points changed, no growth
Changed exports (17)
How this worksEach runtime export is bundled on its own, minified, and gzipped. Changes smaller than 100 B, or smaller than 1% and 1 KiB, are ignored. Growth over 10% or 5 KiB is marked 🔴. This report is informational and does not fail CI. The workflow artifact contains every measurement. Compared |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
aron-cf
left a comment
There was a problem hiding this comment.
This is great. I think the docs need a pass and I left some comments on naming and structure. Once in I think an example with a webfetch tool would be helpful.
973b0a1 to
fdd3b39
Compare
From Devin review on #2502: - The core races the whole source.search() against the deadline and the caller's signal, so a source that ignores its signal can no longer hang the tool call past timeoutMs. - The binding source checks for an already-aborted signal before it starts a billed search. - A 2xx body that is not a search response is a retryable web_search_unavailable, not a non-retryable failure. - Docs: the host output is normalised, not untouched.
Adds agents/websearch with a harness-neutral core and adapters for the pi harness (agents/websearch/pi), the AI SDK (agents/websearch/ai-sdk), and TanStack AI (agents/websearch/tanstack-ai). The host fixes gateway, provider, BYOK alias, and result cap; the model chooses query and count. The model sees trimmed text, the host gets the full API response. webSearchFromAI and webSearchFromRest search without a model. Ai.websearch is typed locally until @cloudflare/workers-types can be bumped past 5.20260812.1 (cloudflare/workerd#7026).
…ng binding support The model's input schema now caps limit at the host's limit instead of the API maximum of 10, so a model asking for 10 under a host cap of 5 is no longer silently clamped. webSearchFromAI fails with websearch_unsupported_runtime and the required workerd/wrangler versions when the AI binding has no websearch() method, instead of a TypeError.
…under tools/ webSearchFromAI and webSearchFromRest become createAIWebSearch and createHTTPWebSearch (option types AIWebSearchOptions and HTTPWebSearchOptions), matching the package's create* factories. The pi, AI SDK, and TanStack AI adapters move to src/websearch/tools/; import paths are unchanged.
WebSearchToolOptions is now an exclusive union: with `source`, passing `binding`, `gateway`, `provider`, or `byokAlias` is a type error rather than silently ignored. Adds type tests.
AiSdkHarness called convertToModelMessages without tools, so a tool's toModelOutput only shaped the turn that ran it; later turns got the raw output as JSON. For websearch that meant every later turn resent the full untrimmed response. Pass the tools, test it, and note the requirement in the websearch AI SDK docs for hosts converting messages themselves.
replay: "safe" means an interrupted search reruns on recovery (a second billed search), not that stored results are reused; say so in the pi JSDoc and docs. The model's error text has no code suffix. WebSearchRequest.limit is now optional and defaults to 5, matching the docs. Note the TanStack adapter's extra `name` option.
- Thread each framework's abort signal to the source (fetch signal for HTTP, a race for the binding) and add a host `timeoutMs` (default 30 s). A caller abort propagates instead of becoming a retryable result; a timeout is a retryable `web_search_timeout` failure. - Normalise 200 bodies: drop items without a URL, fall back to the URL for a missing title, keep only string optional fields, default metadata. - Every WebSearchError has a code (validation -> invalid_web_search_input, gateway config -> web_search_gateway_not_configured, else from status), keeps the numeric API code as `apiCode`, and is retryable for 429/5xx unless the API says otherwise. Codes are typed as WebSearchErrorCode. - The model gets guidance (fix the query / retry once / carry on without search) instead of the operator message. AI SDK and TanStack AI throw a WebSearchError with that text and the original as `cause`. - TanStack AI throws on failure so it reports an error result; pi failure details include the message. - Document the packages in packages/agents/AGENTS.md; update the docs. - Tests: TanStack execute path, HTTP error paths, BYOK alias and baseUrl, malformed items, timeouts and aborts, description override, trimming through pi and TanStack, and type tests for the adapters.
…rces
- C2: the zod input schema is shared by the AI SDK and TanStack AI
adapters (tools/input-schema.ts, kept out of the core so pi needs no
zod); the core exposes `render(output)`.
- C3: WebSearchSource is an object `{ search(request, { signal }), provider? }`
so wrappers keep `provider` by spreading.
- C4: the tool is named `web_search`, the name models know from
Anthropic's and OpenAI's built-in search.
- C5: the description says there is no next page and only mentions
fetching if a page-reading tool exists; no results suggests a broader
query; the schema states the host limit in the description instead of
rejecting larger values, which the core caps.
- C7: invalid limit, maxDescriptionChars, timeoutMs, or byokAlias throw a
RangeError when the tool or source is created.
- C8: each adapter re-exports the WebSearchError class.
- Rename constants to WEB_SEARCH_* to match WebSearchError and webSearchTool, and the runtime error code to web_search_unsupported_runtime to match the API's prefix. - Send the trimmed query that was validated. - Encode the account id in the HTTP URL and drop a trailing "/" from baseUrl. - The unsupported-runtime message names the workerd version and links the docs instead of listing wrangler and vite-plugin versions. - Truncate descriptions at a word boundary when one is within 20 characters.
From Devin review on #2502: - The core races the whole source.search() against the deadline and the caller's signal, so a source that ignores its signal can no longer hang the tool call past timeoutMs. - The binding source checks for an already-aborted signal before it starts a billed search. - A 2xx body that is not a search response is a retryable web_search_unavailable, not a non-retryable failure. - Docs: the host output is normalised, not untouched.
e8985cb to
bf0f57b
Compare
Summary
A
web_searchtool over Cloudflare's Web Search API (open beta), for the pi harness, the AI SDK, and TanStack AI on one core.Who decides what:
What each side sees:
Also fixes
AiSdkHarness: it calledconvertToModelMessages(messages)without{ tools }, sotoModelOutputnever applied to earlier tool results and later turns resent full tool outputs. Separate patch changeset.Docs:
docs/agents/search-the-web.md, covering only what the SDK adds; everything about the service links out.Ai.websearchis typed locally (AiWebSearchBindinginsource.ts) rather than via@cloudflare/workers-types. Every workers-types since5.20260812.1emitsdeclare const Buffer: any, which discards@types/node'sBufferand breakspackages/agents/src/vite.ts. See workerd#7026. The public option is stillbinding: Ai, and the shim goes when the repo bumps.Evidence
packages/agents/src/tests/websearch.test.ts, 41 testspackages/agents/src/tests-d/websearch-tool.test-d.ts:binding+sourceis rejected, and the adapters fitTool/ServerTool.src/tests/channels/ai-sdk-harness.test.ts: "shapes earlier tool results with toModelOutput on later turns". It fails without the harness fixceramic,exa, andlinkupall return 200. Rendered output, the missing-BYOK 400, the bad-gateway error, and the host cap were checked.env.AI.websearchthrough the binding, inwrangler devor deployed. The agents account has no gateway credits, so expectweb_search_payment_requireduntil it is funded or a BYOK key is added.Build, lint, format, export check, and agents typecheck are clean.
Merge Danger
Door: two-way
Four new
@betaentry points (minor changeset), plus a one-lineAiSdkHarnessfix (patch changeset).Blast Radius: small
The websearch code is opt-in, and nothing imports it yet. The harness fix changes what
AiSdkHarnesssends for earlier tool results whenever a tool definestoModelOutput. That is the intended behaviour, but it is a prompt change for existing users of such tools.