Skip to content

Add custom-endpoint support to the Anthropic provider - #499

Open
sairohiththammana wants to merge 1 commit into
andrewyng:mainfrom
sairohiththammana:feature/anthropic-custom-endpoint
Open

Add custom-endpoint support to the Anthropic provider#499
sairohiththammana wants to merge 1 commit into
andrewyng:mainfrom
sairohiththammana:feature/anthropic-custom-endpoint

Conversation

@sairohiththammana

@sairohiththammana sairohiththammana commented Aug 13, 2026

Copy link
Copy Markdown

Mirrors the OpenAI slot: an optional base_url on the anthropic profile points the Anthropic SDK at any Messages-API-compatible gateway (LiteLLM, a corporate proxy). Blank keeps api.anthropic.com — and keeps the SDK's own ANTHROPIC_BASE_URL env support intact.

Anthropic needs less than OpenAI did: there is one wire either way (no Responses ⇄ Chat Completions switch), so the endpoint only swaps the host.

What it does need

  • The stored value is the ROOT. The SDK appends /v1/messages itself, so a /v1 carried over from the OpenAI box — where it belongs — is trimmed rather than doubled into /v1/v1/messages.
  • Fable/Mythos skip the beta server-side-fallback path on a custom endpoint. The beta header, the fallbacks param and the beta messages route are Anthropic-only; a gateway that doesn't know them 400s the whole turn. Losing the fallback there costs a refusal retry, not a working request.
  • Test falls back to a one-token /v1/messages call when a gateway proxies messages but not /v1/models, and treats an Anthropic-shaped 4xx as proof the endpoint speaks the API. "Anthropic-shaped" means the top-level "type": "error" discriminator — a bare nested error object is the OpenAI/LiteLLM shape, and accepting it would green-light an OpenAI URL pasted into this box: Test would pass and every turn would fail afterwards.
  • resolve_api_key grows a prefer_profile flag, set when an endpoint is configured, so a key typed alongside a gateway is never shadowed by an ambient ANTHROPIC_API_KEY meant for api.anthropic.com. Belt-and-braces on the router path (the registry already passes the profile key explicitly); it earns its keep for provider instances built from a SecretStore alone.

GUI

No new component. The endpoint row is descriptor-driven — ProviderForm renders it for any keyed provider that declares base_url — so the field appears in both Onboarding and Settings ▸ Models as soon as the descriptor has it. One behavior change: a stored endpoint now opens the disclosure instead of hiding behind the collapsed "Custom endpoint ⌄" link, since a configured gateway is the one thing a collapsed row would actively misrepresent. That applies to OpenAI too.

Testing

  • pytest tests — 1181 passed, 1 skipped.
  • New: endpoint normalization (/v1 trimming, blank → None), base_url reaching the SDK only when set, key precedence under a gateway, the beta-path skip, and four verify cases (custom endpoint URL, /v1/messages fallback, key rejection on the fallback, non-Anthropic host still failing — parametrized over a plain 404 page and an OpenAI-shaped error body).
  • GUI: two ProviderForm tests covering the descriptor-driven endpoint row. Not run locally (no Node on the authoring machine) — CI's gui-unit job covers them.

Known limitation

Gateways that require Authorization: Bearer instead of x-api-key are not supported; the SDK sends x-api-key. Adding an auth-style toggle would be a second field and a second verify branch — deliberately deferred until a real gateway needs it.

Mirrors the OpenAI slot: an optional `base_url` on the `anthropic` profile
points the Anthropic SDK at any Messages-API-compatible gateway (LiteLLM, a
corporate proxy). Blank keeps api.anthropic.com — and keeps the SDK's own
ANTHROPIC_BASE_URL env support intact.

Anthropic needs less than OpenAI did: there is one wire either way, so the
endpoint only swaps the host. What it does need:

- The stored value is the ROOT (the SDK appends /v1/messages itself), so a
  `/v1` carried over from the OpenAI box is trimmed rather than doubled.
- Fable/Mythos skip the beta server-side-fallback path on a custom endpoint:
  the beta header, `fallbacks` param and beta route are Anthropic-only and a
  gateway that doesn't know them 400s the whole turn.
- Test falls back to a one-token /v1/messages call when a gateway proxies
  messages but not /v1/models, and treats an Anthropic-shaped 4xx as proof the
  endpoint speaks the API. "Anthropic-shaped" means the top-level
  `"type": "error"` discriminator — a bare nested `error` object is the
  OpenAI/LiteLLM shape, and accepting it would green-light an OpenAI URL
  pasted into this box, passing Test and failing every turn afterwards.
- `resolve_api_key` grows a `prefer_profile` flag, set when an endpoint is
  configured, so a key typed alongside a gateway is never shadowed by an
  ambient ANTHROPIC_API_KEY meant for api.anthropic.com. Belt-and-braces on
  the router path (the registry already passes the profile key explicitly);
  it earns its keep for provider instances built from a SecretStore alone.

The GUI needed no new component: the endpoint row is descriptor-driven, so the
field appears in both Onboarding and Settings ▸ Models as soon as the provider
declares it. A stored endpoint now opens the disclosure instead of hiding
behind a collapsed link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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