Skip to content

feat(discovery): fetch credential-scoped Anthropic models - #244

Merged
amondnet merged 11 commits into
mainfrom
amondnet/auto_include_builtin_models-2
Jul 28, 2026
Merged

feat(discovery): fetch credential-scoped Anthropic models#244
amondnet merged 11 commits into
mainfrom
amondnet/auto_include_builtin_models-2

Conversation

@amondnet

@amondnet amondnet commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fetch the caller's credential-scoped Anthropic model catalog on each GET /v1/models request instead of relying only on a quickly stale builtin snapshot.

A live re-probe of reference claude gateway 2.1.220 showed that its catalog had added claude-opus-5 within roughly ten days of the previous snapshot. More importantly, three real responses confirmed that the model list varies by credential:

  • x-api-key: 11 model IDs (the superset)
  • Claude subscription OAuth: 10 model IDs (excluding claude-opus-4-1-20250805)
  • Reference apps gateway 2.1.220: 10 model IDs (excluding claude-opus-4-5-20251101)

Because a shared cache could expose one caller's entitlement view to another, discovery remains stateless and uncached.

This PR:

  • Adds src/discovery/upstream.rs, which queries the first Anthropic-kind upstream per request, preferring server.default_provider and then declaration order.
  • Authenticates discovery with that request's effective credential: the forwarded credential for passthrough, the configured key for api_key, or the first resolvable account for claude_oauth.
  • Uses a 2-second timeout, follows has_more pagination, and relays capabilities verbatim.
  • Falls back silently to the builtin snapshot when no Anthropic upstream or credential is available, or when the upstream errors, times out, returns an invalid body, or returns an empty list.
  • Bypasses AccountPool so discovery does not affect inference selection, cooldown, or quota bookkeeping.
  • Lets a live result supersede the snapshot rather than merging it, avoiding re-advertising models the caller cannot use.
  • Replaces the 9-entry ID-only builtin catalog with an 11-entry public Anthropic API snapshot containing display names, timestamps, token limits, and real first_id / last_id values.

Milestone / spec

M3 — docs/m3-discovery.md §4 and §8

Also updates docs/gateway-protocol.md, README.md, and all four locales for:

  • guides/model-discovery
  • reference/configuration
  • reference/endpoints
  • guides/connect-claude-desktop

Checklist

  • cargo build passes (covered by the successful clippy and test builds)
  • cargo test passes (1,230 passed, 0 failed with cargo test --all-features --workspace)
  • cargo clippy --all-targets -- -D warnings clean (0 warnings with all features enabled)
  • cargo fmt --all --check clean
  • Source files stay under 500 lines
  • English only; matches surrounding style
  • Frozen spec in docs/ updated if this change deviates from it
  • User-facing docs updated for behavior/config/endpoint/CLI/provider/model changes — README.md / site/ as applicable (wiki/ is generated; don't hand-edit)
  • Any new GitHub Action is pinned to a full commit SHA (no GitHub Actions added)

Notes for reviewers

Behavior changes

  • /v1/models may now make one upstream request sequence. The previous "no upstream call" guarantee in docs/m3-discovery.md is updated. Each call is capped at 2 seconds and falls back, preserving the documented sub-3-second budget.
  • Responses gain optional created_at, max_input_tokens, max_tokens, and capabilities fields. Curated [[models]] entries retain the previous narrow shape.
  • auto_include_builtin_models = false remains strictly curated and now also suppresses the upstream call.
  • The fallback snapshot intentionally over-advertises because it has no caller credential to test. This preserves the existing policy that entitlement gaps surface as runtime errors rather than discovery-time probes.

Credential handling

The implementation removes a duplicate x-api-key when an sk-ant-oat… bearer credential is forwarded. Please review the passthrough, configured API-key, and Claude OAuth resolution paths closely.

Tests

The discovery suite adds 15 wiremock-backed tests covering:

  • Credential forwarding and complete field mapping
  • No upstream call when passthrough has no caller credential, asserted through received_requests
  • Removal of a duplicated x-api-key beside an sk-ant-oat… bearer
  • Configured api_key injection
  • has_more pagination
  • Fallback on upstream errors and empty lists
  • End-to-end confirmation that a live list supersedes the builtin snapshot

Open question

The upstream lookup currently runs whenever an Anthropic-kind upstream is configured, with no opt-out. A slow or unreachable Anthropic upstream can therefore add up to the 2-second timeout to every /v1/models request before fallback. If deployments need explicit control, an opt-out config key can be added as a small follow-up.


Summary by cubic

Serve a live, credential‑scoped Anthropic model list on GET /v1/models with a single 2 s overall timeout and a snapshot fallback. Live fetch runs only when the server.default_provider is Anthropic‑kind; otherwise we serve the 11‑model snapshot with upstream metadata and populated first/last ids.

  • New Features

    • Query only the Anthropic default provider using its auth mode: passthrough (forwards caller token; never replays gateway auth), api_key (configured key), or claude_oauth (first resolvable, non‑disabled account from the effective set). Stateless; no AccountPool. Relay capabilities, follow has_more pagination (max 5 pages), and drop a duplicated x-api-key beside an sk-ant-oat… bearer. auto_include_builtin_models = false returns only curated entries and suppresses the upstream call.
  • Bug Fixes

    • Bind discovery to the default provider so only routable Anthropic ids are advertised; use the snapshot when the default provider is not Anthropic‑kind. Enforce one 2 s timeout across credential resolution and all pages; fall back on upstream errors, timeouts, empty lists, unusable cursors, or incomplete pagination. Log upstream failures at warn. Minor refactor: clarified the consumed‑bearer check and removed a page‑limit allocation.

Written for commit 61edb47. Summary will update on new commits.

Demo

GET /v1/models serving the Anthropic-API-shaped list — 11 entries with human-readable display_name, created_at, token limits, and real first_id/last_id cursors

Discover models per request from the first eligible Anthropic upstream, preferring the default provider and using the caller's effective credential. Follow pagination with a two-second timeout and silently fall back on every failure without touching inference pool state.

Let a successful live response supersede the fallback so unavailable model IDs are not re-advertised. Expand the builtin snapshot to the 11-model public API superset with display names, timestamps, and token limits, and populate response boundary IDs.

Update the README, discovery and gateway protocol documentation, and all localized model-discovery, configuration, endpoint, and Claude Desktop guides to describe the observable behavior.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements live model discovery from the Anthropic upstream, enabling credential-scoped model lists with a fallback to an updated builtin snapshot. Feedback on the changes suggests optimizing URL formatting and query string allocations outside the pagination loop, and raises a concern regarding potential concurrent OAuth token refresh race conditions when bypassing the AccountPool.

Comment thread src/discovery/upstream.rs Outdated
Comment thread src/discovery/upstream.rs Outdated
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.26599% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/discovery/upstream.rs 89.13% 20 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 11 untouched benchmarks


Comparing amondnet/auto_include_builtin_models-2 (61edb47) with main (f4cd619)

Open in CodSpeed

amondnet added 3 commits July 28, 2026 18:43
…account set

Review-fix pass over the credential-scoped `/v1/models` fetch.

- Apply one overall `tokio::time::timeout` across credential resolution and
  every page. `FETCH_TIMEOUT` was attached per request inside a five-page loop,
  and the OAuth refresh ran before it with no timeout at all (the shared
  `reqwest::Client` sets no default and there is no timeout layer), so
  discovery could exceed its documented sub-3 s budget indefinitely.
- Resolve Claude OAuth accounts through `auth::shared::resolve_pool_accounts`
  instead of reading `provider.accounts` directly, and skip disabled accounts.
  The old path ignored store-scanned accounts and `account_scope`, so the live
  fetch was permanently inert for those deployments and silently fell back.
- Treat incomplete pagination as a failed refresh. Exhausting the page cap or
  receiving `has_more` without a usable cursor returned a partial list that
  superseded the builtin snapshot and was emitted with `has_more: false`,
  making a truncated catalog indistinguishable from a complete one.
- Stop relaying a credential shunt's own inbound auth gate consumed. Discovery
  requires gateway auth whenever configured, so a client token presented in
  `authorization`/`x-api-key` was forwarded upstream as if it were an Anthropic
  credential.
- Raise upstream failure logs from `debug` to `warn`. The default filter is
  `shunt=info`, so a persistently failing refresh was invisible while a
  plausible-looking snapshot was served.
- Reuse `strip_duplicate_oauth_api_key` rather than reimplementing the
  `sk-ant-oat` bearer check verbatim.
- Guard the env-mutating tests with `TEST_ENV_LOCK`; cargo runs tests
  concurrently, so the previous single-threaded SAFETY premise was false.
Match the documentation to the reviewed behavior of the live `/v1/models`
fetch, across README, docs/, and the site in all four locales.

- Scope the per-caller entitlement claim to `auth = "passthrough"`. Under
  `api_key` and `claude_oauth` the catalog comes from a gateway-owned
  credential shared by every caller, so "each caller sees its own entitled
  list" was only ever true for one of the three modes.
- Describe `claude_oauth` as the first resolvable, non-disabled account from
  the same effective set inference uses, including store-scanned accounts and
  `account_scope` ordering.
- State that a consumed `[server.auth]` or gateway-login credential is not
  replayed upstream, and that discovery logs why it fell back.
- Document the five-page pagination cap and that any incomplete or
  unfollowable paginated response falls back rather than returning partial
  data, and add the empty-response fallback condition.
- Note that upstream errors, timeouts, and malformed responses log at `warn`.
- Add the optional `capabilities` field to the documented list shape.
- Refresh the Anthropic provider page, which still described discovery as
  advertising the builtin catalog rather than live-first with builtin fallback.
Three findings verified as pre-existing or by-design rather than introduced by
the credential-scoped discovery change, so later review runs skip re-auditing
them: passthrough credential forwarding (the Messages path does the same), and
the stale `[[models]]` endpoint-table wording in the site reference and
docs/running.md (byte-identical on main since #208).
@amondnet
amondnet marked this pull request as ready for review July 28, 2026 09:44
Comment thread src/discovery/upstream.rs Outdated
Comment thread src/discovery/upstream.rs
@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds live, credential-scoped Anthropic model discovery with bounded pagination and snapshot fallback.

  • Fetches the catalog from the Anthropic-kind default provider using passthrough, configured API-key, or Claude OAuth credentials.
  • Relays upstream model metadata and replaces the builtin catalog with an expanded 11-model snapshot.
  • Aligns live discovery with default-provider routing and updates discovery documentation and localized guides.

Confidence Score: 3/5

The PR is not yet safe to merge because Claude OAuth discovery can advertise a catalog from a different account than inference will use.

Discovery still selects the first resolvable enabled OAuth account, while inference applies pool priority, quota, cooldown, rotation, and session selection; because catalogs are credential-scoped, a multi-account deployment can expose models that the serving account cannot use or omit models that it can.

Files Needing Attention: src/discovery/upstream.rs, src/adapters/anthropic/mod.rs

Important Files Changed

Filename Overview
src/discovery/upstream.rs Implements bounded live catalog fetching and default-provider alignment, while the previously reported mismatch between first-account discovery and pooled inference selection remains.
src/discovery.rs Integrates live results with curated models, snapshot fallback, expanded metadata, deduplication, and populated envelope cursors.
src/adapters/anthropic/mod.rs Exposes existing OAuth-header normalization helpers for reuse by discovery without changing their behavior.
src/auth/inbound.rs Exposes bearer-token parsing within the crate so discovery can avoid forwarding credentials consumed by gateway authentication.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Discovery as GET /v1/models
    participant Auth as Credential resolution
    participant Upstream as Default Anthropic provider
    Client->>Discovery: Request model catalog
    Discovery->>Auth: Resolve effective credential
    alt Credential and Anthropic default available
        Auth-->>Discovery: Upstream credential
        Discovery->>Upstream: GET /v1/models (2 s overall deadline)
        Upstream-->>Discovery: Credential-scoped pages
        Discovery-->>Client: Curated entries + live catalog
    else Resolution or upstream request fails
        Discovery-->>Client: Curated entries + builtin snapshot
    end
Loading

Reviews (7): Last reviewed commit: "refactor(discovery): name the consumed-b..." | Re-trigger Greptile

amondnet added 2 commits July 28, 2026 19:06
The new discovery coverage tripped SonarCloud's new-code duplication gate at
16.4% (limit 3%): the wiremock setup was copy-pasted across the suite, with 15
`AppState::new` blocks, 14 identical mock chains, and 10 byte-identical
single-model response bodies.

Extract `single_model_page`, `mount_models_ok`, `mount_models_ok_with_headers`,
`mount_models_ok_after_id`, and `state_for`, and use them wherever the pattern
repeated. Tests that genuinely need a bespoke mock — the paginating responder,
the delayed-response deadline case, the unusable-cursor raw body, the 401, and
the empty list — keep theirs.

No test was renamed, removed, or weakened, and the `TEST_ENV_LOCK` guards are
untouched; the suite is unchanged at 1238 passing.
…del table

SonarCloud's copy-paste detector read the eleven `BuiltinModel` struct literals
as a 70-line duplicated block (src/discovery.rs:36-105 against 43-112), which
alone kept new-code duplication above the 3% gate.

Expand the rows through a small `builtin_models!` macro so each model occupies
one line. The table stays the single place an upstream catalog change is
recorded, and reads more like the data it is.

No behavior change: `default_returns_builtin_models_in_api_order` asserts all
eleven entries with their exact ids, display names, `created_at`, and token
limits, so the rewritten rows are verified rather than assumed.
The models URL and the `limit` query value were rebuilt on every pagination
iteration. Format both once before the loop.

Applies gemini-code-assist's review suggestion on #244.
@amondnet

Copy link
Copy Markdown
Contributor Author

@greptileai review

@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@amondnet

Copy link
Copy Markdown
Contributor Author

@greptileai review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements live model discovery for the Anthropic upstream, querying the default provider using its configured authentication mode (passthrough, API key, or OAuth) and falling back to an updated offline snapshot of 11 models if the call fails or times out. It includes extensive documentation updates across multiple languages and robust test coverage. The review feedback recommends minor optimizations and readability enhancements, specifically using a static string literal for the page limit query parameter to avoid allocations, and extracting complex boolean logic into a descriptive variable to clarify when a bearer token is considered consumed.

Comment thread src/discovery/upstream.rs Outdated
Comment thread src/discovery/upstream.rs Outdated
@sonarqubecloud

Copy link
Copy Markdown

@amondnet
amondnet merged commit 014252d into main Jul 28, 2026
15 of 16 checks passed
@amondnet
amondnet deleted the amondnet/auto_include_builtin_models-2 branch July 28, 2026 11:37
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