Fast tier — detection, storage, and wires#
Where the fast feature's code lives, where its data comes from, and the
detection rules. Sibling doc: thinking-effort.md
(the same "model capability → UI toggle" declarative pattern; the structure
is deliberately aligned).
1. What Fast is#
The "Fast" toggle in the composer's + menu. When on, the request carries the vendor's high-speed knob:
| Family | Wire shape | Billing reality |
|---|---|---|
| GPT 5.4 / 5.5 / 5.6 | body service_tier: "priority" (OpenAI's priority processing) |
On the Codex subscription the endpoint advertises it as a per-model tier ("1.5x speed, increased usage"); which models expose it comes straight from service_tiers (§2.1), not a guess |
| Claude Opus 4.6 / 4.7 / 4.8 | body speed: "fast" + header anthropic-beta: fast-mode-2026-02-01 |
Also pay-as-you-go; a subscription account without usage credits gets Anthropic's 429 "Usage credits are required for fast mode", surfaced as-is — an account problem, not lack of support |
No other model family (Gemini / DeepSeek / Qwen / Llama / MiniMax…) has a fast tier at all.
2. Detection: supports_fast(provider, model) — three branches#
Entry point: openprogram/webui/_model_listing/listing.py. Strips the
wire-format "provider:" prefix first (the runtime records the current
model as openai-codex:gpt-5.5).
- openai-codex → reads the persisted
Model.fast. This field is not hand-written: it comes from the official codex models endpoint (§2.1), written into config alongside the spec at Fetch time, so detection is justget_model("openai-codex", id).fast. Tiers with no fast mode (such asgpt-5.4-mini) resolve False exactly, which an id-prefix guess cannot do. - claude-code → hand-written table
enabled_models.default_fast: id containsopus-4-6/4-7/4-8(hyphen or dot) → True. Its subscription endpoint is unverified, so this stays hand-written until an endpoint-backed source like codex's is confirmed to exist. - Everything else → models.dev, automatic: a mode with
service_tier == "priority"orid == "fast"→ True; none, or unknown provider → False.
Private gateways (such as frontier-intelligence) are not special-cased — unknown to the catalogue means no fast button. The config override (§3) covers them.
2.1 Codex's official source#
GET https://chatgpt.com/backend-api/codex/models?client_version=<ver> — the
same account-level endpoint the official codex CLI hits on startup —
authorized with the subscription OAuth bearer + chatgpt-account-id. Each
model carries service_tiers (an id:"priority" entry ⇔ has a fast tier),
supported_reasoning_levels (the thinking picker), and the real subscription
context_window (372k, not the API platform's 1050k). Requests and dispatch
use the originator: codex_cli_rs + version identity — the backend
greylists ids (e.g. gpt-5.6-luna) by client identity, so a different
originator gets the model in the list but 404s at dispatch.
models.dev is not the source here because it tracks the public API platform catalogue rather than the subscription front-door: it lists un-runnable ids, reports the wrong context window, and can only reconstruct a fast flag from an id-prefix guess.
3. Storage: read official → write config → read the file thereafter#
Codex principle: all info comes live from the vendor, no hand-kept model list.
| Layer | Location | Persistence |
|---|---|---|
| Codex official endpoint | webui/_model_listing/fetchers/codex.py::_fetch_codex_live |
remote; 10-min in-memory browse cache, no disk cache |
config spec row (with fast/thinking_levels/context) |
on enable, fetch_and_normalize's normalised row is written to ~/.openprogram/config.json; the Fetch button (fetch_models_remote) heals enabled rows with fresh endpoint data |
config file (this is the "write to a file" step) |
Model.fast field |
_build_model_from_row reads the config row's fast (row wins; codex rows always carry it); enters ENABLED_MODELS at registry build |
memory only (in-process dict, sourced from config) |
| claude-code hand table | providers/enabled_models.py::default_fast (only the Opus part is on the detection path now) |
source code |
| models.dev catalogue | webui/_model_listing/sources/models_dev.py |
remote; 1h in-memory cache, no disk cache |
Flow: official endpoint → normalise → config.json → registry → supports_fast / dispatch. Offline / not signed in → the endpoint returns an error and the saved config rows are kept, not blanked — you can't dispatch these models without a token anyway, so a token-less browse losing the list isn't a regression.
4. Event flow: adapts to any switch, no page reload#
connect / session switch / model switch / every turn ack+settle
→ frontend loadAgentSettings() (lib/runtime-bridge/providers.ts)
→ GET /api/agent_settings (webui/routes/runtime.py)
chat.fast = supports_fast(session's provider, model) ← recomputed
→ zustand agentSettings.chat.fast
→ composer re-renders: shows/hides the Fast menu item and chip
Send-side double gate: the composer only attaches
service_tier: "priority" when fastEnabled && fastSupported — a stale
per-session fast setting never leaks to an unsupported model.
5. Wire side (request builders)#
| Builder | Behavior |
|---|---|
providers/openai_responses / openai_completions |
opts.service_tier → body service_tier (pre-existing) |
providers/openai_codex (ChatGPT subscription) |
opts.service_tier → body passthrough; dispatch uses the originator: codex_cli_rs + version identity (the backend greylists ids by client identity — see §2.1) |
providers/anthropic |
opts.service_tier present and model.fast → body extra_body={"speed":"fast"} + _BETA_FAST header (appended via _build_client(fast=...), never clobbering other betas) |
| all other wires | no passthrough; the knob never leaves the process |
6. File map#
openprogram/providers/types.py Model.fast field
openprogram/providers/enabled_models.py default_fast (claude-code Opus only) + config-row backfill
openprogram/providers/openai_codex/{openai_codex,runtime}.py service_tier passthrough; codex_cli_rs identity + _CODEX_CLIENT_VERSION
openprogram/providers/anthropic/{anthropic,_claude_code_direct_runtime}.py Claude fast wire + registration backfill
openprogram/webui/_model_listing/fetchers/codex.py official endpoint fetch + normalise (fast/thinking/context source)
openprogram/webui/_model_listing/fetchers/__init__.py orchestration: passes through fetcher fast/thinking, enrich can't overwrite
openprogram/webui/_model_listing/listing.py supports_fast entry; list_models_for_provider prefers fetcher thinking
openprogram/webui/routes/runtime.py /api/agent_settings emits chat.fast
web/lib/session-store/types.ts AgentBadgeInfo.fast type
web/components/chat/composer/index.tsx toggle visibility + send gate
Change guide: codex fast/thinking is fully automatic — add/remove a model
needs nothing but a Fetch; change detection logic → touch only
listing.py::supports_fast; claude-code add/remove fast → the Opus part of
default_fast; another provider wanting fast → works once models.dev knows it.