Skip to content

feat(providers): opt in to provider-hosted web search per model - #624

Merged
vastsa merged 1 commit into
vastsa:mainfrom
zeroy1024:feat/native-web-search
Sep 19, 2026
Merged

vastsa merged 1 commit into
vastsa:mainfrom
zeroy1024:feat/native-web-search

Conversation

@zeroy1024

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in, per-model native web search capability: when enabled in the model's settings (attachment section), the provider's server-side search tool is attached on the wires that define one — web_search_20250305 for anthropic-messages, {type:"web_search"} for openai-responses / azure-openai-responses — and each search round renders as its own transcript activity row, live while streaming.

Unlike an MCP search server, a hosted tool executes inside the provider request and returns structured blocks that must round-trip for multi-turn grounding, so extraction and replay live in the adapters rather than in a plugin (rationale in ADR 0294).

How it works

  • Data: ModelBinding.nativeWebSearch (two-state opt-in; no catalog baseline since models.dev publishes no hosted-tool capability) flows through the existing model-config channel into pi-ai's Model.webSearch. One evaluation point, resolveNativeWebSearch in @pi-desktop/shared, keyed on the resolved wire API only — never vendor names or URL hostnames.
  • Wire (patches/@earendil-works__pi-ai@0.85.1.patch): attach the tool; ask Responses for web_search_call.action.sources via include; extract server_tool_use / web_search_tool_result / web_search_call / citation annotations into hostedSearch content blocks (raw wire payloads kept verbatim); accumulate streamed input_json_delta so the query is visible live; replay both block halves on later turns. The Azure Responses variant gets the same attachment.
  • Streaming (patches/@earendil-works__pi-agent-core@0.85.1.patch): the agent loop forwards the adapters' hosted_search_update as message_update; without it the events died in the loop's switch and search activity only appeared when the turn finished.
  • Runtime/UI: hostedSearchFromBlocks normalizes to UiMessage.hostedSearch.rounds[] (one round per provider call, paired by block id, per-round status/query/sources). Each round renders as a HostedSearchRow on the standard tool-row idiom, distinguishing search / open-page / find-in-page. Sources are plain text links — no favicon fetches, so transcripts never leak source hostnames to third parties (fix(desktop): keep hosted-search citations on the source origin #579 rule).
  • Persistence: additive hostedSearch transcript block via ui_to_record/record_to_ui; no SQL migration.

Affected docs

  • ADR 0294 (new): provider-hosted web search as an adapter capability
  • docs/spec/03-runtime/11-provider-model-system.md (rule 10a), 04-data-storage.md (block vocabulary) — both languages

Validation

  • pnpm build:js, workspace typecheck (0 errors), pnpm lint (biome + style tokens), architecture check
  • JS suites: shared 818 · agent-runtime 620 (incl. new stream/params contract tests and an agent-loop forwarding regression test) · desktop 2320 · i18n 25 · all other packages green
  • cargo test -p host-core --locked: 525 passed; cargo fmt --check; clippy clean apart from one pre-existing unrelated warning
  • Live wire-compatibility matrix (21 requests, all HTTP 200) across a third-party gateway and DeepSeek official endpoints, both wires: bare tool accepted everywhere; include safe everywhere; Kimi channels only return sources via include; DeepSeek's Anthropic wire executes real server-side search; DeepSeek's Responses wire accepts but never executes (no search rows, pseudo-call text logged by the existing pseudo-tool-call detector)
  • E2E harness: NOT RUN (no cross-process risk beyond the covered contracts; streaming path covered by the new agent-loop forwarding test)

Compatibility & risks

  • Default off; zero behavior change for existing providers/models.
  • Endpoints that accept but never execute the tool degrade to no search rows (no silent fake success); gateways that mangle replay blocks surface as provider errors on the next turn, remedied by unchecking the model's opt-in.
  • The two dependency patches are stopgaps meant for upstream submission to @earendil-works/pi-ai / pi-agent-core and removal once released.
  • pause_turn keeps pi-ai's existing mapping to stop; a long searching turn on the official Anthropic wire may end early. Known limitation, noted in the ADR.

Add a two-state per-model binding that attaches the vendor's hosted web
search tool on the anthropic-messages and openai-responses wires. The
pi-ai patch attaches the tool, extracts search blocks and citations from
the stream, and replays them verbatim on later turns; a pi-agent-core
patch forwards the new progress event so each search round renders live
as its own transcript activity row.

Search state normalizes to UiMessage.hostedSearch (one round per
provider call), persists as an additive transcript block without a SQL
migration, and degrades honestly on endpoints that accept but never
execute the tool. Recorded in ADR 0291.
@vastsa

vastsa commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Merged onto current main with a restart-replay fix in #635 (ADR renumbered to 0296 because 0294 was already taken). This PR can close once #635 lands.

@vastsa
vastsa merged commit 71420b7 into vastsa:main Sep 19, 2026
2 of 4 checks passed
@zeroy1024
zeroy1024 deleted the feat/native-web-search branch September 21, 2026 14:53
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.

2 participants