Skip to content

feat: add anthropic web search support - #12

Open
darknessest wants to merge 2 commits into
ghoulr:mainfrom
darknessest:feat/anthropic-provider
Open

feat: add anthropic web search support#12
darknessest wants to merge 2 commits into
ghoulr:mainfrom
darknessest:feat/anthropic-provider

Conversation

@darknessest

@darknessest darknessest commented Jul 29, 2026

Copy link
Copy Markdown

What

Adds Anthropic as a fourth websearch_cited provider, backed by Anthropic's server-side web_search_20260318 tool, pinned to allowed_callers: ["direct"].

{
  "provider": {
    "anthropic": {
      "options": {
        "websearch_cited": { "model": "claude-sonnet-4-6" }
      }
    }
  }
}

How

src/anthropic.ts follows the existing provider shape (createAnthropicWebsearchClientWebsearchClient), calls POST /v1/messages directly with fetch, and adds no dependencies.

Auth, both supported by opencode:

  • API keyx-api-key + anthropic-version.
  • Claude Pro/Max OAuthAuthorization: Bearer, anthropic-beta: oauth-2025-04-20, and the Claude Code system block that these credentials require as the first system entry.

The tool is pinned to allowed_callers: ["direct"] rather than the version default. On web_search_20260209 and later the default is ["code_execution_20260120"] (dynamic filtering), and a caller without code execution access gets HTTP 200 with code_execution_tool_result_error: too_many_requests, web_search_requests: 0, and an uncited answer from model knowledge — a silent failure for a tool whose contract is cited search. Measured on Claude Pro/Max OAuth credentials: two dynamic-filtering attempts failed that way, bracketing a successful direct call in between. Dynamic filtering is therefore not exposed as a config option.

Formatting mirrors the Google provider's output contract:

  • Citation markers are derived from web_search_result_location citations, deduplicated by URL, and rendered as [n] before any trailing whitespace so line breaks survive.
  • A Sources: list is appended only when at least one citation exists.
  • Text blocks emitted before the first web_search_tool_result are the model narrating its intent to search, so they are dropped from the answer.
  • A web_search_tool_result_error throws with its error_code, unless usable text was already produced — a later failed search should not discard an answer that is already grounded.
  • stop_reason of max_tokens / pause_turn appends a truncation notice rather than silently returning a cut-off answer.

Testing

  • bun check clean, bun test:agent 44 pass / 0 fail.
  • Formatting tests are driven by a payload captured live from POST https://api.anthropic.com/v1/messages with web_search_20260318 + allowed_callers: ["direct"] (trimmed, provenance recorded at the fixture), plus cases for narration, citations: null, non-web-search citation types, truncation, and search errors. A test asserts the tool definition keeps allowed_callers: ["direct"], so dropping it fails the suite.
  • Verified end-to-end against the live API with both claude-sonnet-4-6 and claude-haiku-4-5-20251001; "What is the latest stable version of Bun?" returns a cited answer with a Sources: list.

Note

Independent of, and does not depend on, the OpenAI streaming fix PR; both branch from main. They touch websearch.test.ts in different places, so whichever merges second may need a trivial rebase.

@darknessest
darknessest marked this pull request as ready for review July 29, 2026 19:36
@darknessest
darknessest marked this pull request as draft July 29, 2026 19:55
@darknessest
darknessest marked this pull request as ready for review July 30, 2026 15:11
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