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.modelsRPC (+ addmodelsto RustProviderConfig,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 onbackend/cron/mod.rs/memory_heartbeat.rs; - frontend: overlay
providers/index.tsmodelswith the cached catalog and converge the strip //modeldropdowns to read it.
Structs§
- Catalog
Model - One entry in the model dropdown.
idis the concrete--modelvalue the CLI accepts (e.g.claude-sonnet-5);display_nameis 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(fromclaude setup-token), persisted viaidentity::secret_store::put/get. Those wrap it inaccount_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 (seeidentity::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/modelsresponse body ({ "data": [ { id, display_name }, … ] }) to our catalog.display_namefalls back toidwhen 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). ReturnsNonewhen the file/token is absent — notably on macOS, where the CLI stores creds in the Keychain rather than this file (see theCOMMAND_CHECK_CLI_AUTHhandler incli_handlers.rs);resolve_access_tokenthen 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_storewraps thekeyringcrate directly with no mockable seam, so tests inject plain closures here instead).