Providers
Signing in, configuring endpoints, and model metadata.
Luxe speaks four wire APIs — OpenAI chat/completions, OpenAI responses, Anthropic messages, and the ChatGPT-subscription Codex backend — and ships a catalog of providers that know which one they use, where they live, and what environment variable they answer to. Anything not in the catalog still works through a custom OpenAI-compatible endpoint.
Signing in
luxe auth login [provider] # API key or subscription; omit the provider to pick from a list
luxe auth list # every provider and how each is configured
luxe auth status [provider] # credential kind, source, and OAuth expiry
luxe auth logout <provider> # remove a stored credential (environment variables are untouched)
In the TUI: /login (or /login openai), /logout <provider>, /auth. A first launch with
nothing configured opens the wizard by itself.
API keys work for every provider. Paste one at the prompt, or pipe it in with --stdin. The
stored value may be a literal key, $ENV_VAR, or !command — so a password manager works without
Luxe integrating with one:
{ "openai": { "type": "api_key", "key": "!op read op://vault/openai/credential" } }
Subscriptions are available for three providers:
| Provider | Sign-in | Needs |
|---|---|---|
openai-codex | Browser (PKCE) or device code | ChatGPT Plus or Pro |
github-copilot | GitHub device flow (GitHub or Enterprise) | Copilot subscription |
openrouter | OpenRouter’s PKCE flow — mints an API key you own | OpenRouter credits |
Usage on a plan is reported as usage, not as a bill. Tokens are counted in full; the published
rates are shown as an equivalent (≈$0.42, covered by your subscription) and never added to spend.
A session that mixes an API key and a subscription reports each separately.
Where a provider publishes it, Luxe also shows how much of the plan is left — the number a
subscriber actually steers by. A ChatGPT plan reports its allowance on every response, so the
statusline carries 12% weekly in the slot a dollar figure would occupy and /cost spells it out:
allowance plus · 12% of weekly (resets in 6d 23h)
The Codex backend serves a different model set than OpenAI’s API — gpt-5.6-sol and friends rather
than gpt-5.3-codex — so /model offers exactly what a ChatGPT account can run.
Anthropic and Google are API-key only, by decision. Anthropic’s terms forbid using a Claude Pro/Max subscription from a third-party harness (clarified in February 2026, enforced server-side, and the reason another OSS agent removed its Claude OAuth entirely), and Google’s terms forbid using Gemini CLI’s OAuth credentials from third-party software, with account suspensions attached. Luxe will not ship code that helps you violate terms you haven’t read. Use an API key — or use those vendors’ own clients for their subscriptions.
Credentials live in ~/.config/luxe/auth.json (0600, written atomically, never overwriting
anything else in the file). OAuth tokens refresh automatically, once, under a lock — two Luxe
processes can’t race a rotating refresh token. Nothing is written until the credential works.
Configuring providers by hand
Configure providers in ~/.config/luxe/auth.json or via environment variables.
Provider-specific reasoning controls live in ~/.config/luxe/models.json. A map
value of null marks a Luxe level as unsupported; Luxe safely clamps requests to
the nearest supported level. ultra is Luxe’s name for provider max (max is
also accepted as a map key):
{
"models": {
"openai_compat/gpt-5.6-sol": {
"reasoning": true,
"thinking_level_map": {
"minimal": "low",
"xhigh": "xhigh",
"ultra": "max"
}
},
"lmstudio/deepreinforce-ai/ornith-1.0-35b": {
"reasoning": true,
"thinking_level_map": {
"off": "off",
"minimal": null,
"low": null,
"medium": null,
"high": "on",
"xhigh": "on",
"ultra": null
}
}
},
"providers": {
"openai_compat": {
"compat": {
"supports_reasoning_effort": true,
"thinking_format": "openai"
}
},
"lmstudio": {
"base_url": "http://127.0.0.1:1234/v1",
"compat": {
"thinking_format": "qwen-chat-template"
}
}
}
}
Qwen/DashScope uses thinking_format: "qwen" and exposes a boolean thinking
toggle, not distinct effort tiers. Local Qwen and Ornith chat templates use
"qwen-chat-template"; mapping several Luxe levels to "on" keeps the UI
vocabulary while sending the truthful on/off control.
Anthropic thinking modes. Claude’s generations disagree on how thinking is requested, and asking the wrong way is a hard 400. Luxe picks the encoding from what the model’s catalog entry declares:
| The model offers | Luxe sends | Models |
|---|---|---|
| effort levels only | thinking: {type: "adaptive", display: "summarized"} + output_config.effort | Claude Sonnet 5, Opus 5, Opus 4.7/4.8, Fable 5 |
| a thinking token budget | thinking: {type: "enabled", budget_tokens: N} | Claude Sonnet 4.6/4.5, Opus 4.6/4.5, Haiku 4.5 |
A declared effort list is a closed set when effort is the only dial, so a level the model never
advertised clamps instead of going out as an invented effort value (minimal on Sonnet 5 becomes
low). thinking off sends {type: "disabled"} and no effort at all. If a model still rejects
the encoding — a release the catalog hasn’t caught up with — Luxe flips it, retries once, and
remembers the answer for the rest of the session; compat.force_adaptive_thinking pins adaptive by
hand for an endpoint no catalog knows. Check what Luxe resolved with
luxe models show anthropic/<id> (the thinking control row).
Model metadata
A model that shipped after your Luxe build still has to work properly — with its real context window, output cap, reasoning levels, and pricing, not silent fallbacks. Getting those wrong is quiet and expensive: an 8× undersized window compacts away context you paid for, and a 4096-token output cap truncates long edits.
Metadata resolves through four layers, each overriding the previous field by field:
| Layer | Source | Notes |
|---|---|---|
| 1 | Embedded snapshot | A models.dev catalog built into the binary — works offline, on first run, with no config |
| 2 | Refreshed cache | ~/.cache/luxe/models-dev.json, revalidated in the background |
| 3 | Your config | The models map in models.json/auth.json — always wins |
Because layers merge per field, pinning just context_window still inherits pricing and thinking
levels from the catalog. Anything nothing knows about falls back to safe defaults (128k context,
no forced output cap).
Inspect exactly what Luxe resolved, and which layer each field came from:
$ luxe models show anthropic/claude-opus-5
anthropic/claude-opus-5
name Claude Opus 5 [models.dev]
context window 1.0M [models.dev]
max output 128k [models.dev]
reasoning true [models.dev]
thinking levels high, low, medium, ultra, xhigh [models.dev]
thinking control effort (adaptive thinking) [models.dev]
modalities text, image [models.dev]
cost /Mtok $5.00/$25.00 [models.dev]
$ luxe models list --provider anthropic # everything routable, * active, + configured
$ luxe models refresh --force # revalidate the cache now
The refresh is an If-None-Match conditional GET, so an unchanged catalog costs a 304 and zero
bytes; it runs in the background from every entry point (TUI, headless, loop) and never blocks.
A provider whose name isn’t a models.dev id can point at one with "models_dev": "alibaba", and a
pinned -YYYYMMDD/-latest id inherits its base model’s metadata (the wire id is never rewritten).
| Variable | Effect |
|---|---|
LUXE_MODELS_DEV_REFRESH=0 | Never touch the network (the snapshot still answers everything) |
LUXE_MODELS_DEV_URL | Fetch from a mirror or self-hosted catalog |
LUXE_MODELS_DEV_PATH | Read a specific catalog file instead of the cache |
LUXE_MODELS_DEV_TTL | Seconds before the cache is revalidated (default 21600) |