Skip to main content

Module model_catalog

Module model_catalog 

Source
Expand description

Model catalog — authoritative Claude model list from the Anthropic Models API (GET /v1/models), fetched with the agent’s OAuth Bearer token.

Why this exists: the agent-pane model dropdown was a hand-curated list whose version labels (“Sonnet 4.6”, …) drift every time Anthropic ships a model. The CLI has no list-models surface, and prompting a model for the list is hallucination-prone — so we read the authoritative catalog from GET /v1/models. Verified 2026-07-02: a Claude Max subscription OAuth token returns HTTP 200 (no API key, no oauth-2025-04-20 beta header) with the current models incl. claude-sonnet-5 | “Claude Sonnet 5”.

This is a metadata read only — never call /v1/messages with this token (that would cross the subscription-vs-API billing line). Access is treated as best-effort: any failure (missing token on macOS Keychain, 401/expiry, network) returns None, and the caller falls back to the bundled curated catalog. See docs/specs/SPEC_MODEL_CATALOG_REFRESH_2026_07_02.md.

STATUS: fetch + parse layer (this file). Remaining wiring per the spec — not yet implemented here:

  • cache the result per (provider, cli_version) under the data dir;
  • expose via a providers.models RPC (+ add models to Rust ProviderConfig, backend/providers.rs);
  • trigger a refresh on CLI install/upgrade (install_handlers.rs / cli_handlers.rs), plus an optional lazy “>30d & token valid” refetch modeled on backend/cron/mod.rs / memory_heartbeat.rs;
  • frontend: overlay providers/index.ts models with the cached catalog and converge the strip / /model dropdowns to read it.

Structs§

CatalogModel
One entry in the model dropdown. id is the concrete --model value the CLI accepts (e.g. claude-sonnet-5); display_name is the label to show (e.g. “Claude Sonnet 5”), taken verbatim from the API.

Constants§

CLAUDE_OAUTH_TOKEN_ACCOUNT 🔒
Fixed, sentinel account id for the user-supplied long-lived CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token), persisted via identity::secret_store::put/get. Those wrap it in account_key() ("acct:{account_id}"), so the actual keychain entry is "acct:system:claude-code-oauth-token" — this constant only needs to be distinct as an account id from the per-identity account ids used elsewhere (see identity::resolver, identity::oauth_client), which are account UUIDs and would never collide with this sentinel string. This is a single, system-wide fallback token, not tied to any AgentMux identity.

Functions§

fetch_model_catalog
Fetch the authoritative model catalog. Returns None (→ caller uses the bundled fallback) on network error, non-2xx (incl. 401 token expiry), or an empty/garbled body. Never logs the token.
parse_models 🔒
Map a GET /v1/models response body ({ "data": [ { id, display_name }, … ] }) to our catalog. display_name falls back to id when absent. Extracted from the network call so it is unit-testable without an outbound request.
read_oauth_access_token
Read the Claude OAuth access token from an isolated auth dir’s .credentials.json (claudeAiOauth.accessToken). Returns None when the file/token is absent — notably on macOS, where the CLI stores creds in the Keychain rather than this file (see the COMMAND_CHECK_CLI_AUTH handler in cli_handlers.rs); resolve_access_token then tries its other sources before falling back to the bundled catalog. Windows/Linux keep the token here.
resolve_access_token
Resolve the Claude OAuth access token to use for the model-catalog fetch, trying multiple sources in order before giving up (→ caller falls back to the bundled curated catalog, unchanged from before this existed). Order:
resolve_fallback_access_token 🔒
Pure resolution-order logic backing steps 2/3 of resolve_access_token, factored out so it is unit-testable without touching a real OS keychain (identity::secret_store wraps the keyring crate directly with no mockable seam, so tests inject plain closures here instead).