Skip to content

feat: add native Anthropic provider#36

Open
weeta-code wants to merge 1 commit into
mskayyali:mainfrom
weeta-code:anthropic-native
Open

feat: add native Anthropic provider#36
weeta-code wants to merge 1 commit into
mskayyali:mainfrom
weeta-code:anthropic-native

Conversation

@weeta-code
Copy link
Copy Markdown

Summary

Adds Anthropic as a first-class provider alongside OpenRouter, OpenAI, and Z.ai. Users can bring an sk-ant-... key directly and talk to the Messages API from the browser — no OpenRouter account, no local proxy.

How it works

  • New "anthropic" entry in AI_PROVIDER_PRESETS; the sidebar settings UI picks it up automatically (no UI code changes needed — the existing map over AI_PROVIDER_PRESETS handles it, and the grounding toggle is already scoped to openrouter/openai only).
  • getProviderHeaders branches to emit x-api-key + anthropic-version: 2023-06-01 + anthropic-dangerous-direct-browser-access: true instead of Authorization: Bearer.
  • ai-enrich.ts and ai-ghost.ts branch on provider === "anthropic" at the HTTP-call boundary:
    • POST to {baseUrl}/messages instead of /chat/completions
    • System prompt moved to top-level system field (Anthropic's Messages API shape)
    • Structured output via forced tool_use — a synthetic tool (enrichment_result / emergent_thesis) is defined with input_schema and forced via tool_choice: {type: "tool", name: "...", disable_parallel_tool_use: true}. Schema fidelity is equivalent to OpenAI's strict json_schema mode; no regex fallback needed.
    • Response parsing reads content[].type === "tool_use" and uses .input directly.
  • CSP connect-src in next.config.mjs gains https://api.anthropic.com (without this, Firefox silently shows NetworkError when attempting to fetch resource; Chrome says "Refused to connect... violates CSP").
  • README gets an "Anthropic (direct)" subsection mirroring the existing provider entries.

Design notes

Why no thinking field is sent, even on 4.7: forced tool_choice: {type: "tool"} is explicitly incompatible with extended/adaptive thinking — the API rejects the combination with a 400. Thinking defaults to off on all exposed models (Opus 4.5/4.6/4.7, Sonnet 4.5/4.6, Haiku 4.5), so omitting the field is correct, forward-compatible, and gives deterministic low-latency enrichment that matches the schema every time. Task-fit also supports this: classification, connection inference, and short annotations don't meaningfully benefit from deep reasoning, and enrichment is the user-facing latency path.

Ghost synthesis could plausibly benefit from adaptive thinking, but that would require dropping forced tool_choice to auto and handling two response shapes (tool_use + text fallback). That's a cleaner follow-up PR than bundling with provider addition.

Web grounding is intentionally not wired up on the Anthropic path. Anthropic has a server-side web-search tool but it's a separate feature; supportsGrounding: false on all Claude models cleanly hides the grounding UI (same pattern as the Z.ai provider).

Files changed

File Purpose
lib/ai-settings.ts Add "anthropic" to provider union, preset, ANTHROPIC_MODELS, header branch
lib/ai-enrich.ts Messages API + forced tool_use branch
lib/ai-ghost.ts Same pattern for synthesis
next.config.mjs CSP connect-src allowlist
README.md "Anthropic (direct)" subsection

Testing

  • npx tsc --noEmit — clean
  • npm run build — passes (2.3s compile, all pages generated)
  • Live test against claude-sonnet-4-6 and claude-opus-4-7: enrichment produces valid classifications, annotations, and influencedByIndices; ghost synthesis fires correctly after accumulating notes across categories
  • Existing OpenRouter, OpenAI, and Z.ai code paths unaffected (branches occur before divergent logic)

Adds Anthropic as a first-class provider alongside OpenRouter, OpenAI,
and Z.ai, letting users bring an sk-ant-... key directly to the native
Messages API without requiring an OpenRouter account or a local proxy.

Implementation:
- New 'anthropic' entry in AI_PROVIDER_PRESETS — sidebar UI picks it up
  automatically (no UI code changes needed)
- getProviderHeaders branches to emit x-api-key + anthropic-version +
  anthropic-dangerous-direct-browser-access for the Anthropic path
- ai-enrich.ts and ai-ghost.ts branch on provider === 'anthropic' at
  the HTTP-call boundary: POST to {baseUrl}/messages, system prompt
  moved to top-level 'system' field, structured output via forced
  tool_use (tool_choice: {type:'tool', name:'...', disable_parallel_tool_use})
- Response parsing uses content[].type === 'tool_use' and reads .input
  directly, giving schema fidelity equivalent to OpenAI's json_schema
  strict mode
- CSP connect-src in next.config.mjs gains https://api.anthropic.com
- README gets an 'Anthropic (direct)' subsection mirroring existing
  providers with the Opus 4.5/4.6/4.7, Sonnet 4.5/4.6, Haiku 4.5 lineup

Design note on thinking: no 'thinking' field is sent on any Claude
model, including 4.7. Forced tool_choice is incompatible with
extended/adaptive thinking per Anthropic's docs, and thinking defaults
to off on all exposed models, so omitting the field is correct,
forward-compatible, and gives deterministic low-latency enrichment.
Web grounding is intentionally not wired up (supportsGrounding: false
on all Claude models cleanly hides the UI toggle — same pattern as
the Z.ai provider).

Tested live against claude-sonnet-4-6 and claude-opus-4-7:
enrichment and ghost synthesis both produce valid, schema-conformant
output. Existing OpenRouter, OpenAI, and Z.ai code paths unchanged
(branches occur before divergent logic).
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