Documentation menu

Providers

locac talks to a model over one of four wire protocols. A "provider" is one of those protocol adapters plus a base URL and an API key, which is why pointing locac at a self-hosted vLLM is the same operation as pointing it at Anthropic, only with different values.

Two ways to name a model

# 1. A built-in provider, by name
locac run "" --provider anthropic --model claude-opus-4-8

# 2. Any endpoint, by protocol + URL
locac run "" --api openai-completions --base-url https://your-host/v1 --model your-model

--model is always required. Give neither form and locac refuses to start:

Provide either --provider <name> --model <id>, or --api <api> --base-url <url> --model <id>.

The same two shapes exist in ~/.locac/config.json as provider + model, or api + baseUrl + model. A third form, customProvider, names a saved endpoint from the config's providers[] array, which expands to the second form before resolution. See Configuration.

Wire protocols

--api valueRequest shapeNotes
anthropic-messagesAnthropic MessagesAuth style is selectable; see below
openai-completionsOpenAI Chat CompletionsThe adapter appends /chat/completions to your base URL
openai-responsesOpenAI ResponsesCarries a reasoning budget as reasoning.effort
ollama-chatOllama chatLocal, no API key

anthropic-messages is not Claude-only. Amazon Bedrock, Google Vertex, z.ai, DeepSeek, Moonshot and any local server exposing /v1/messages speak it too.

openai-completions base URLs must include the version segment the server expects (/v1, /openai/v1, /api/v1), because the adapter appends only /chat/completions.

Built-in providers

Anthropic-compatible

All auth with a bearer token except native Anthropic, which uses x-api-key.

--providerDefault base URLBase-URL overrideAPI keyContext
anthropichttps://api.anthropic.comANTHROPIC_BASE_URLANTHROPIC_API_KEY200,000
zaihttps://api.z.ai/api/anthropicZAI_BASE_URLZAI_API_KEY128,000
deepseekhttps://api.deepseek.com/anthropicDEEPSEEK_BASE_URLDEEPSEEK_API_KEY128,000
moonshothttps://api.moonshot.ai/anthropicMOONSHOT_BASE_URLMOONSHOT_API_KEY128,000

The three non-Anthropic vendors are pinned to 128,000 rather than inheriting the 200,000 default, and the direction of that error is deliberate. Claiming a window a model does not have is the one mistake that kills a run outright: compaction believes it has tens of thousands of tokens of headroom, never fires, and the provider rejects the request. 128k is the floor across each vendor's current lineup. Under-claiming costs only some early elision, and if your model is bigger you can raise it per-model with the config's models{} map.

OpenAI-compatible

All use Authorization: Bearer; there is no auth-scheme knob for this protocol.

--providerDefault base URLBase-URL overrideAPI key
openaihttps://api.openai.com/v1OPENAI_BASE_URLOPENAI_API_KEY
groqhttps://api.groq.com/openai/v1GROQ_BASE_URLGROQ_API_KEY
openrouterhttps://openrouter.ai/api/v1OPENROUTER_BASE_URLOPENROUTER_API_KEY
togetherhttps://api.together.xyz/v1TOGETHER_BASE_URLTOGETHER_API_KEY
xaihttps://api.x.ai/v1XAI_BASE_URLXAI_API_KEY
deepseek-openaihttps://api.deepseek.com/v1DEEPSEEK_OPENAI_BASE_URLDEEPSEEK_API_KEY
moonshot-openaihttps://api.moonshot.ai/v1MOONSHOT_OPENAI_BASE_URLMOONSHOT_API_KEY
moonshot-openai-cnhttps://api.moonshot.cn/v1MOONSHOT_OPENAI_CN_BASE_URLMOONSHOT_API_KEY
vllmhttp://localhost:8000/v1VLLM_BASE_URLVLLM_API_KEY
lmstudiohttp://localhost:1234/v1LMSTUDIO_BASE_URLLMSTUDIO_API_KEY
ollama-openaihttp://localhost:11434/v1OLLAMA_OPENAI_BASE_URLOLLAMA_API_KEY

Defaults for this family: context window 128,000, output cap 8,192, reasoning off.

The three local servers carry a placeholder key (local, lm-studio and ollama respectively) used when their *_API_KEY variable is unset. They accept any value, but some of them return 400 on a missing Authorization header, so locac always sends one.

Note that DeepSeek and Moonshot appear twice. Each vendor exposes both an Anthropic-shaped and an OpenAI-shaped endpoint; pick whichever protocol you prefer. Both read the same DEEPSEEK_API_KEY / MOONSHOT_API_KEY.

Ollama (native)

--providerDefault base URLBase-URL overrideAPI key
ollamahttp://localhost:11434OLLAMA_HOSTnone

Context window 8,192, output cap 2,048, cost zero. This is the native ollama-chat protocol; the ollama-openai row above is the same server through its OpenAI-compatible surface.

Authentication

ProtocolHeaderConfigurable
anthropic-messagesx-api-key (default) or Authorization: Bearer--auth-scheme x-api-key|bearer
openai-completionsAuthorization: BearerNo. Uniform across every server
openai-responsesAuthorization: BearerNo
ollama-chatnonen/a

Key resolution, highest precedence first:

  1. --api-key on the command line
  2. apiKey in the config file, either literal or a "$VAR" / "${VAR}" reference expanded at run time
  3. The provider's own environment variable from the tables above

An unset $VAR reference resolves to nothing rather than to an empty string, so the run fails loudly instead of sending a blank credential.

The key is never written to a finding, a transcript, a session record or a log, and never reaches the dashboard; the web UI is handed the placeholder •••• redacted •••• instead. The one place it does appear is locac config show, which prints the effective configuration including a literal key. Do not paste that output anywhere.

Output-token field

openai-completions servers disagree about which field caps output. OpenAI's o-series and GPT-5.x reject max_tokens outright, while most other servers implement only that one. locac resolves it like this:

ModelField sent
The built-in openai providermax_completion_tokens (pinned)
Every other built-in openai-completions providermax_tokens
A custom --api openai-completions endpointmax_completion_tokens, because a custom model defaults to reasoning: true
…with reasoning: false in its config entrymax_tokens

So the reasoning flag on a custom model is the switch: it says "this is a reasoning model", and its only effect is choosing between the two output-cap fields. If your self-hosted server rejects max_completion_tokens, set "reasoning": false on that model.

Reasoning effort

Only two adapters carry a reasoning budget:

ProtocolField sent
openai-completionsreasoning_effort
openai-responsesreasoning.effort

anthropic-messages, ollama-chat and anything added later carry no such field, so setting a level against them is refused rather than silently dropped:

locac: reasoning effort "high" ignored — anthropic-messages does not carry a reasoning budget.

Two vocabularies exist, on purpose:

  • --effort and the TUI's /effort menu offer exactly low, medium, high. A picker has to produce a value that lands on whatever endpoint happens to be configured, and those three are what every OpenAI-compatible server implements. Offering xhigh would promise a level that 400s on vLLM, LM Studio, Groq, OpenRouter and Together.
  • reasoningEffort in config.json accepts any lowercase token matching ^[a-z][a-z0-9-]{0,15}$, so minimal, xhigh and max all pass. There you are naming one endpoint you built and understand, so your own file is the authority. The charset is narrow because that string lands in the TUI footer (where a control byte garbles the frame), back into config.json, and on the wire.

A level that arrived from the config still shows up as a row in the /effort menu, so the picker can never contradict the badge drawn above it.

Custom endpoints

Anything that speaks one of the four protocols works. On the command line:

locac run "" \
  --api openai-completions \
  --base-url http://10.0.0.7:8000/v1 \
  --api-key '$MY_LAB_KEY' \
  --model qwen3-coder-480b \
  --context-window 262144

Saved in ~/.locac/config.json as a named provider, selectable with --custom-provider or from the TUI's /model menu:

{
  "providers": [
    {
      "name": "lab-vllm",
      "api": "openai-completions",
      "baseUrl": "http://10.0.0.7:8000/v1",
      "apiKey": "$MY_LAB_KEY",
      "models": [
        { "id": "qwen3-coder-480b", "contextWindow": 262144, "reasoningEffort": "xhigh" }
      ]
    }
  ]
}

Defaults applied to a fully custom --api + --base-url model: context window 200,000, output cap 8,192, reasoning on, cost zero. Set contextWindow per model, because it drives compaction pressure, and it is the one value locac cannot infer for an endpoint it has never seen.

finishReasonOptional

Some proxies never send finish_reason. locac normally reads its absence as a truncated stream and retries, which against such an endpoint means retrying every turn. Setting finishReasonOptional on a custom model turns that detection off for it.

Only set it if the endpoint genuinely never sends the field. It is the signal that catches a proxy silently cutting off a long reasoning turn, and turning it off costs you that.

Cost accounting

Every built-in provider ships with zero cost rates, so the footer's $ segment stays hidden until you supply real numbers. Rates are per million tokens, in a custom provider's model entry:

{ "id": "your-model", "cost": { "input": 3, "output": 15, "cacheRead": 0.3, "cacheWrite": 3.75 } }

Cost is reported, never acted on: no budget is denominated in dollars, and locac eval ab reports each arm's token cost but never scores it.

Next

Environment variables lists every variable named above, plus the ones that control the shell, the editor and colour output.