Luxe

Custom providers

Connect any endpoint, and set a model's context window, thinking levels, request format and samplers.

View as Markdown

Luxe knows how to reach every provider in Models and providers, and the models.dev catalog tells it each model’s context window, output limit, thinking levels and price. This page is for what neither covers: your own endpoint, a model the catalog does not know, or a local server that needs its requests shaped a particular way.

Everything here goes in ~/.config/luxe/models.json. luxe models show <provider>/<id> shows what Luxe resolved for a model, and where each value came from.

Add an endpoint

Connect a local or custom model in Configuration asks for an endpoint and writes it here. By hand:

~/.config/luxe/models.json
{
  "providers": {
    "gateway": {
      "base_url": "https://llm.internal.example.com/v1",
      "api_key": "$GATEWAY_KEY",
      "api": "openai-completions"
    }
  },
  "models": {
    "gateway/qwen3-coder-480b": { "context_window": 262144, "max_tokens": 65536 }
  }
}

Then luxe --model gateway/qwen3-coder-480b, or choose it in /model.

Provider keyDoes
base_urlThe endpoint, including its version, such as /v1
api_keyThe key: the value, $VAR, ${VAR} or $env:VAR, or !command, which runs each time a request needs the key. An unset ${VAR} is empty, as in mcp.json, and Luxe logs which one.
apiThe API it speaks: openai-completions (the default), openai-responses, anthropic-messages
authHow the key is sent: bearer, x-api-key, api-key, or foundry for both of the last two
models_devThe models.dev provider to take model details from, when the names differ
compatHow requests are shaped; see Request format
sampling_paramsSamplers for every model it serves; see Samplers

A provider in models.json replaces the same provider’s block in auth.json whole, so keep a provider’s key and endpoint in one file. A provider Luxe already knows keeps everything you do not write. A key in a provider Luxe knows is used when its own variable, such as OPENAI_API_KEY, is unset; for one it does not know, the key you write comes first and OPENAI_API_KEY is the fallback. A key you signed in with comes before either.

A file Luxe cannot parse is skipped with a warning in the log, and a misspelled key is ignored, so check the result with luxe models show <provider>/<id> after editing.

Model settings

Settings for one model go under models, by provider/id. Each value overrides the catalog’s, and everything you leave out still comes from it. thinking_level_map, modalities and cost each replace the catalog’s whole: a cost you write drops the catalog’s long-prompt prices, and an output price you leave out is free.

KeyDoes
context_windowThe model’s context window, in tokens
max_tokensThe most it may write in one reply. Defaults to the model’s own limit from the catalog. Luxe lowers it per request to what the window has left, so setting the real limit is safe; setting a lower one is how replies get cut off.
nameThe name shown in the picker and statusline
reasoningWhether it can think
thinkingThe level a session starts at for this model
thinking_level_mapWhat each thinking level sends; see below
modalitiesWhat it accepts: ["text"] or ["text", "image"]
costPrices per million tokens: input, output, cache_read, cache_write, and cache_write_1h for a write kept for an hour (twice input if unset)
compat, sampling_paramsOverride the provider’s, key by key

A model the catalog does not know gets a 128k context window and no output limit until you set them. Anthropic endpoints require a limit, so there Luxe asks for 128k. If the model allows less, Anthropic’s refusal names its real limit, and Luxe retries with that and uses it for the rest of the session.

Thinking levels

thinking_level_map says what each of Luxe’s levels sends to the model. null marks a level the model cannot do, removing it from /thinking; a level Luxe needs falls back to the nearest one that is left. ultra is the name for a provider’s max.

~/.config/luxe/models.json
{
  "models": {
    "lmstudio/qwen3.8-27b": {
      "reasoning": true,
      "thinking": "medium",
      "thinking_level_map": {
        "off": "none",
        "minimal": null,
        "low": "low",
        "medium": "medium",
        "high": "high",
        "xhigh": "high",
        "ultra": null
      }
    }
  }
}

A level you do not map sends its own name, and off sends none; xhigh and ultra are offered only once mapped. Setting a map also marks the model as one that reasons.

An endpoint Luxe does not know sends no thinking setting at all until its compat says how: for most OpenAI-compatible servers, "thinking_format": "openai", which sends reasoning_effort on every request, off included. See Request format.

For Anthropic models, Luxe uses adaptive thinking with an effort level where the model supports it, and a token budget for older ones. If a model refuses how thinking was requested, Luxe tries the other way once and remembers it for the session. compat.force_adaptive_thinking forces adaptive thinking for an endpoint the catalog does not know.

Request format

compat says how a request has to be shaped for an endpoint to accept it. On a provider it applies to every model the provider serves; on a model, only the keys you write override the provider’s.

~/.config/luxe/models.json
{
  "providers": {
    "lmstudio": {
      "base_url": "http://127.0.0.1:1234/v1",
      "compat": { "supports_developer_role": true, "supports_reasoning_effort": true, "thinking_format": "openai" }
    }
  },
  "models": {
    "lmstudio/qwen3.6-27b-fable-fusion": {
      "compat": { "thinking_format": "chat_template", "supports_developer_role": false }
    }
  }
}
FlagDoes
supports_developer_roleSend the system prompt as developer rather than system
supports_reasoning_effortAllow reasoning_effort to be sent
thinking_formatHow thinking is requested: openai, qwen, deepseek, openrouter, together or chat_template
chat_template_kwargsWhat to send for chat_template; see below
max_tokens_fieldmax_tokens or max_completion_tokens
supports_usage_in_streamingAsk for token usage while streaming. On by default.
requires_tool_result_nameName the tool on each tool result. gpt-oss needs this.
cache_control_formatHow prompt caching is requested: anthropic or openrouter
force_adaptive_thinkingUse Anthropic’s adaptive thinking
supports_long_cache_retentionAsk Anthropic to keep cached prompts for an hour rather than five minutes. Each cache write costs 2x input instead of 1.25x. Off by default, since not every Anthropic-compatible endpoint accepts it.
stream_tool_argumentsStream a write call’s file from Anthropic as it is written, so its card fills in live. On by default; turn off for an endpoint that rejects a tool’s eager_input_streaming field.
defer_tool_loadingLoad a tool the agent asks for with activate_tools where it asked, so the cached prompt stays cached. On by default. Luxe stops on its own for an endpoint that refuses it; turn it off for one that accepts it and then misbehaves. Anthropic and Responses endpoints only.
copilot_headers, openrouter_headersSend those services’ identifying headers. With copilot_headers, each model also goes to the Copilot endpoint it takes.
use_responses_apiUse OpenAI’s Responses API. Provider only; prefer "api": "openai-responses".

anthropic-messages endpoints read only force_adaptive_thinking, cache_control_format, supports_long_cache_retention, stream_tool_arguments, defer_tool_loading and copilot_headers. The rest are for OpenAI-style endpoints. luxe models show lists any flag that differs from the provider’s.

Samplers

A local model’s card often gives a sampling recipe with settings the OpenAI format has no field for, such as top_k, min_p and repeat_penalty. sampling_params is added to the request as written, after Luxe’s own fields, so its values win:

~/.config/luxe/models.json
{
  "models": {
    "lmstudio/qwen3.8-27b": {
      "sampling_params": { "temperature": 1.0, "top_p": 0.95, "top_k": 20, "min_p": 0.0 }
    }
  },
  "providers": {
    "lmstudio": { "sampling_params": { "min_p": 0.0 } }
  }
}

A provider’s samplers apply to every model it serves, and a model’s override them one key at a time. Nothing is checked, so an unknown key goes to your server as is; model, messages, input, stream and tools are refused. OpenAI-style endpoints only.

Chat templates

A server that renders the model’s chat template, such as llama-server, takes thinking settings as chat_template_kwargs. Each template names them differently, so you describe them:

~/.config/luxe/models.json
{
  "providers": {
    "llama-server": {
      "base_url": "http://127.0.0.1:8080/v1",
      "compat": {
        "thinking_format": "chat_template",
        "chat_template_kwargs": {
          "enable_thinking": { "$var": "thinking.enabled" },
          "reasoning_effort": { "$var": "thinking.level", "omit_when_off": true },
          "preserve_thinking": true
        }
      }
    }
  }
}

thinking.enabled becomes true or false, and thinking.level the value thinking_level_map gives the current level. omit_when_off leaves the key out when thinking is off. Anything else is sent as written. LM Studio ignores chat_template_kwargs; use "thinking_format": "openai" there.

Model details

Luxe takes a model’s details from three places, each overriding the one before, value by value:

  1. The models.dev catalog built into Luxe, which works offline
  2. A fresher copy, refreshed in the background into ~/.cache/luxe/models-dev.json
  3. Your models.json
$ 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), never off  [models.dev]
  modalities        text, image  [models.dev]
  cost /Mtok        $5.00/$25.00  [models.dev]

Each value names where it came from: snapshot for the catalog built into Luxe, models.dev for the refreshed copy, config for your models.json, and default where none of them said.

VariableDoes
LUXE_MODELS_DEV_REFRESH=0Never refresh; the built-in catalog still answers
LUXE_MODELS_DEV_URLRefresh from a mirror
LUXE_MODELS_DEV_PATHKeep the catalog in this file instead. The refresh writes there too, so pair it with LUXE_MODELS_DEV_REFRESH=0 to pin a file of your own.
LUXE_MODELS_DEV_TTLSeconds between refreshes. 21600 by default.

Microsoft Foundry

Keyless sign-in with Microsoft Entra ID

If your Foundry resource has key sign-in turned off, give Luxe a command that prints a token:

~/.config/luxe/models.json
{
  "providers": {
    "foundry-anthropic": {
      "auth": "bearer",
      "api_key": "!az account get-access-token --scope https://ai.azure.com/.default --query accessToken -o tsv"
    },
    "foundry-openai": {
      "api_key": "!az account get-access-token --scope https://ai.azure.com/.default --query accessToken -o tsv"
    }
  }
}

Run az login first. Only the Claude provider needs auth; the others already send a bearer token. The command runs for each request and nothing is kept, so a token from az, which lasts about an hour, is replaced as it expires and a long session carries on.

Choose which deployments Luxe offers

Luxe records what a resource has deployed in each provider’s served_models, and the model behind any deployment named something else in deployments, which is where it looks up the context window and price. If your key cannot list deployments, or you want to choose the list yourself, turn discovery off and write both:

~/.config/luxe/models.json
{
  "providers": {
    "foundry-anthropic": {
      "discover_deployments": false,
      "served_models": ["sonnet-prod", "claude-opus-5"],
      "deployments": { "sonnet-prod": "claude-sonnet-5" }
    }
  }
}

discover_deployments: false on any of the three providers stops Luxe asking the resource at all.

Thinking on partner models

foundry-completions sends no thinking setting by default, because its models disagree on how to take one. Turn it on per model:

~/.config/luxe/models.json
{
  "models": {
    "foundry-completions/deepseek-v4-flash": {
      "compat": { "supports_reasoning_effort": true },
      "thinking_level_map": { "high": "high", "ultra": "max" }
    }
  }
}

Use reasoning_effort only: Foundry refuses DeepSeek’s own thinking format. Foundry does not report these models’ thinking tokens, so /cost counts them as output.

A Foundry deployment can have a different context window and price from the vendor’s own API. Luxe uses Azure’s figures for Foundry models.

OpenRouter

  • Luxe asks OpenRouter to cache prompts for Claude models, which it otherwise does not, and which makes later turns of a session far cheaper. "cache_control_format": null in the provider’s compat stops it.
  • Some models on OpenRouter cannot turn thinking off. For those, Luxe leaves the setting out and the model thinks at its own default. luxe models show openrouter/<id> says never off.
  • Luxe identifies itself to OpenRouter with its HTTP-Referer and X-Title headers, which carry nothing about you. "openrouter_headers": false in compat drops them.