Cook
DocsCustom Models

Custom Models

Cook connects to custom model endpoints for alternative providers, self-hosted models, and overriding built-in settings. This guide explains how to select models, configure endpoints, and integrate third-party providers.


Default Models

Cook is designed for bring-your-own-key use. Configure at least one model provider in ~/.cook/config.toml, then select its model in a new session.

List all available models:

cook models

Selecting a Model

CLI Flag

cook -p "Hello" -m <model-id>

Slash Command

In the TUI, switch models during a session:

/model grok-4.6

Or use the alias:

/m grok-4.6

Model Picker (Ctrl+M)

Press Ctrl+M from the scrollback pane to open the model picker. It lists all available models, both built-in and custom, and lets you switch with a single keystroke. With the prompt focused, Ctrl+M toggles multiline input instead -- use /model to switch without leaving the prompt.

Fleet allowlist (`requirements.toml`)

Enterprise hosts can pin the selectable set — not only the default — in signed requirements.toml. That list replaces any user allowed_models (it is not a union), so /model, Ctrl+M, and -m cannot offer models outside it.

[models]
default = "grok-4.5"
allowed_models = ["grok-4.5", "grok-4*"]

A fleet pin matches the model id (not a user-chosen catalog key), so a local [model.<name>] entry cannot widen the set. User-config allowed_models still matches catalog key or model id. Omit the key to leave user config standing. An empty array is unrestricted. A present-but-unreadable pin fail-closes (nothing selectable). A default or -m value outside the pinned set is rejected once the model catalog is fetched — contact your administrator; the list is not user-editable.

Config Default

Set a persistent default in ~/.cook/config.toml:

[models]
default = "grok-4.5"

Supported API Backends

Cook supports three API backends. Set api_backend in your [model.*] config to choose which protocol the model uses:

Value API Default
"chat_completions" OpenAI Chat Completions (/v1/chat/completions) Yes
"responses" OpenAI Responses (/v1/responses)
"messages" Anthropic Messages (/v1/messages)

When you omit api_backend, Cook uses chat_completions.

To send provider-specific authentication or version headers -- for example, Anthropic's x-api-key -- use the extra_headers field described below. Cook sends those headers verbatim with every request to the endpoint.


Configuring Custom Models

Add custom model endpoints in ~/.cook/config.toml under [model.<name>] sections:

[model.my-model]
model = "model-id"                        # Model identifier sent to the API
base_url = "https://api.example.com/v1"   # OpenAI-compatible endpoint
name = "Display Name"                     # Shown in the model picker
description = "Model description"          # Optional description
api_key = "sk-..."                        # API key for this provider (optional)
env_key = "XAI_API_KEY"                   # Env var holding the API key (optional; string or array)
api_backend = "chat_completions"          # "chat_completions", "responses", or "messages"
reasoning_summary = "concise"             # Responses API only: "none", "auto", "concise", or "detailed"
temperature = 0.7                         # Sampling temperature
top_p = 0.95                              # Nucleus sampling parameter
max_completion_tokens = 8192              # Maximum tokens per response
context_window = 128000                   # Total context window in tokens
extra_headers = { "x-api-key" = "sk-..." } # Extra request headers, sent verbatim (optional)
query_params = { api-version = "2026-07-22" } # Query params appended to every request URL (optional)
env_http_headers = { "X-Tenant" = "TENANT_TOKEN" }    # Headers from env vars, resolved at client build (optional)

Credential Resolution

Cook resolves the API key in this order:

  1. The api_key field in the model config
  2. The environment variable(s) named by env_key — a single string or an array of names. The first set, non-empty value wins (for example env_key = ["ANTHROPIC_AUTH_TOKEN", "LC_ANTHROPIC_AUTH_TOKEN"] for SSH LC_* forwarding)
  3. Your signed-in session token (from cook login), for a model with no api_key/env_key of its own
  4. The XAI_API_KEY environment variable (global fallback; Cook also accepts GROK_CODE_XAI_API_KEY for backward compatibility)

Context Window

The context_window value tells Cook when to trigger auto-compaction. When you override a known model, Cook inherits that model's context window. When you define a new model and omit context_window, Cook defaults to 200,000 tokens, so set it explicitly to match your provider.

Request Size Limit

max_request_bytes is the largest request body your endpoint accepts. Grok evicts older inline images from the conversation to stay under it, so a session with many screenshots keeps working instead of being rejected. When you omit it, Grok picks the default for the api_backend: 30 MB for messages, and 50 MiB for chat_completions and responses. Set it only when your host enforces a different cap.

The cap is a property of the endpoint, so it also works on a shared [model_providers.<id>] block, where every model pointing at that provider inherits it; a max_request_bytes on the model itself overrides the provider's value.

[model_providers.messages-gateway]
base_url = "https://gateway.example/v1"
api_backend = "messages"
max_request_bytes = 25000000   # every model on this provider inherits it

[model.claude-sonnet]
model = "claude-sonnet"
model_provider = "messages-gateway"   # inherits 25 MB

[model.claude-opus]
model = "claude-opus"
model_provider = "messages-gateway"
max_request_bytes = 20000000   # per-model override

Request Size Limit

max_request_bytes is the largest request body your endpoint accepts. Grok evicts older inline images from the conversation to stay under it, so a session with many screenshots keeps working instead of being rejected. When you omit it, Grok picks the default for the api_backend: 30 MB for messages, and 50 MiB for chat_completions and responses. Set it only when your host enforces a different cap.

The cap is a property of the endpoint, so it also works on a shared [model_providers.<id>] block, where every model pointing at that provider inherits it; a max_request_bytes on the model itself overrides the provider's value.

[model_providers.messages-gateway]
base_url = "https://gateway.example/v1"
api_backend = "messages"
max_request_bytes = 25000000   # every model on this provider inherits it

[model.claude-sonnet]
model = "claude-sonnet"
model_provider = "messages-gateway"   # inherits 25 MB

[model.claude-opus]
model = "claude-opus"
model_provider = "messages-gateway"
max_request_bytes = 20000000   # per-model override

Global Default Headers

To apply the same headers to every model in the catalog -- built-in, prefetched from /v1/models, or custom -- set them once under the global [models] section instead of repeating them per model:

[models]
extra_headers = { "X-Request-Tags" = "team=example,env=prod" }

These act as a base for each model's inference requests. A per-model [model.<id>].extra_headers entry overrides the global default per key (matched case-insensitively): a key set on the model wins, while any global-only keys are still inherited by that model. Like the per-model field, they ride on that model's inference calls -- not on separate services such as image generation or video generation -- which makes them handy for attribution tags (for example, cost tracking) without re-declaring them whenever a new model appears.

Global Default Values

A few common per-model settings can also be set once under [models] as a default for every model. A per-model [model.<id>] value always wins; the global only fills in where a model (or the server's model list) left the field unset:

[models]
temperature                 = 0.7
top_p                       = 0.95
max_completion_tokens       = 8192
max_retries                 = 8
rate_limit_retry_threshold  = 4
inference_idle_timeout_secs = 600
subagent_rate_limit_max_attempts = 8
stream_tool_calls           = true

This is a small, fixed set of environment-wide knobs. Settings that identify a specific model (model, base_url, api_key, context_window, ...) cannot be defaulted this way, and a few settings with their own dedicated configuration -- auto-compaction ([session]), the system-prompt label ([agent]), and reasoning effort ([models].default_reasoning_effort) -- keep their existing homes.

rate_limit_retry_threshold and subagent_rate_limit_max_attempts select different 429 retry paths for subagents. Configuring rate_limit_retry_threshold makes the sampler own those retries and disables the separate subagent wait loop, including its 150-second cumulative wait budget and wait telemetry. subagent_rate_limit_max_attempts applies only when the sampler threshold is unset.

Note on stream_tool_calls: this one affects request shape, not just sampling. A few endpoints (some BYOK providers) expect it left unset; if a global stream_tool_calls = true causes problems for such a model, opt that model out with stream_tool_calls = false in its [model.<id>] block.

Request Query Parameters

Some gateways route or version on the query string. query_params appends percent-encoded query parameters to every request Cook makes for a model. For example, a gateway that selects an API version this way:

[model.my-gateway]
model = "my-model"
base_url = "https://gateway.example/v1"
api_backend = "responses"
env_key = "GATEWAY_API_KEY"
query_params = { api-version = "2026-07-22" }

A key that also appears in the base_url query string is overridden (last value wins) rather than duplicated. Query parameters are saved in the session, so do not put secrets in them: use env_http_headers for a secret.

Environment-Variable Headers

env_http_headers maps a request header to the name of an environment variable that supplies its value, so a per-request secret never has to be written into config.toml:

[model.gateway]
model = "my-model"
base_url = "https://gateway.example/v1"
env_http_headers = { "X-Tenant-Token" = "GATEWAY_TENANT_TOKEN" }

Cook reads each variable when it builds the client for a session and places the value in the request headers only, never on disk. A header is skipped when its variable is unset or blank, and a resolved value overrides an extra_headers entry of the same name. Use extra_headers for a static value and env_http_headers for one that comes from the environment.

Both fields also work on a shared [model_providers.<id>] block. A model that points at a provider with model_provider = "<id>" inherits the provider's query_params and env_http_headers when it sets none of its own, matching how extra_headers is inherited.

Model Notice

notice puts a message above the prompt for as long as the model is selected. The banner cannot be dismissed. It goes away when you switch to a model without a notice.

[model.legacy]
model = "legacy-model"
base_url = "https://gateway.example/v1"
notice = { severity = "warning", text = "This model is deprecated on Oct 15. Switch to grok-4.6.", label = "deprecated" }

severity sets the banner color and is info, warning, or critical. It defaults to info. text is required, and long text wraps. label is an optional short tag shown before the text. A custom models endpoint can send the same object as notice or _meta.notice on a model entry. To remove a notice a built-in or remote model carries, set notice = { text = "" }.


Overriding Built-in Models

You can override specific fields of built-in models without redefining everything. Only specify the fields you want to change:

# Override only the API key for a default model
[model.grok-4.6]
api_key = "my-api-key"

# Override temperature and add a custom API key
[model.grok-4.6]
temperature = 0.5
api_key = "sk-custom"

When you override a built-in model, Cook starts with the default configuration (including the correct base_url), then applies only the fields you specify. Unspecified fields inherit from the default.

Priority Order

  1. Your config ([model.*]) -- highest priority
  2. Prefetched models from remote /v1/models
  3. Hardcoded defaults -- lowest priority

Provider Examples

Anthropic (Claude)

Use Claude models directly via the Anthropic Messages API:

[model.claude-opus]
model = "claude-opus-4-6"
base_url = "https://api.anthropic.com/v1"
name = "Claude Opus 4.6"
api_backend = "messages"
context_window = 200000
extra_headers = { "x-api-key" = "sk-ant-...", "anthropic-version" = "2023-06-01" }

The messages backend uses the Anthropic Messages protocol. Anthropic authenticates with an x-api-key header rather than Authorization: Bearer, so pass your key through extra_headers, which Cook sends verbatim.

OpenAI (Chat Completions)

[model.gpt-4o]
model = "gpt-4o"
base_url = "https://api.openai.com/v1"
name = "GPT-4o"
env_key = "OPENAI_API_KEY"

api_backend defaults to "chat_completions", so you don't need to set it explicitly for OpenAI.

OpenAI (Responses API)

If your provider supports the newer Responses API:

[model.gpt-4o-responses]
model = "gpt-4o"
base_url = "https://api.openai.com/v1"
name = "GPT-4o (Responses)"
api_backend = "responses"
env_key = "OPENAI_API_KEY"

On the Responses API, Cook asks for a concise reasoning summary by default; that is what the reasoning text shown in the UI comes from. reasoning_summary changes the request: detailed or auto for a fuller summary, or none to omit the field for gateways that reject it.

AWS Bedrock (Mantle)

Bedrock's OpenAI-compatible gateway rejects reasoning.summary, so set reasoning_summary = "none". It authenticates with a Bedrock API key as a bearer token; the example below mints a short-lived one through a named auth provider:

[auth_provider.bedrock]
command = "aws-bedrock-token"   # prints a Bedrock API key on stdout (e.g. via aws-bedrock-token-generator)
token_ttl_secs = 3600

[model."bedrock-grok-4.6"]
model = "xai.grok-4.6"
base_url = "https://bedrock-mantle.us-west-2.api.aws/openai/v1"
name = "Cook 4.6 (Bedrock)"
api_backend = "responses"
reasoning_summary = "none"
auth_provider = "bedrock"
context_window = 500000

Ollama (Local Models)

Run models locally with Ollama:

[model.ollama-codellama]
model = "codellama"
base_url = "http://localhost:11434/v1"
name = "CodeLlama (Ollama)"

Make sure Ollama is running (ollama serve) and the model is pulled (ollama pull codellama).

Together AI

[model.together-mixtral]
model = "mistralai/Mixtral-8x7B-Instruct-v0.1"
base_url = "https://api.together.xyz/v1"
name = "Mixtral 8x7B"
env_key = "TOGETHER_API_KEY"

Local OpenAI-Compatible Server

Any server that implements the OpenAI Chat Completions or Responses API:

[model.local-llama]
model = "llama-3.1-70b"
base_url = "http://localhost:8080/v1"
name = "Local Llama"
temperature = 0.8

Custom Models Endpoint

Point Cook at a custom OpenAI-compatible /v1/models endpoint instead of the default. Use this when your models sit behind a corporate gateway or a self-hosted inference service.

Environment Variables

Variable Required Description
GROK_MODELS_BASE_URL Yes Base URL for inference. Cook fetches the model list from {base_url}/models.
XAI_API_KEY Yes API key sent as Authorization: Bearer. Cook also accepts GROK_CODE_XAI_API_KEY.
GROK_MODELS_LIST_URL No Override the model-list URL when it differs from {base_url}/models.

Setup

export GROK_MODELS_BASE_URL="https://api.acme.com/v1"
export XAI_API_KEY="xai-..."
grok

Config File Alternative

[endpoints]
models_base_url = "https://api.acme.com/v1"

# Override only the API key for a specific model
[model.grok-4.6]
api_key = "my-api-key"

When you use [endpoints] with partial model overrides, Cook inherits the base_url from the endpoints config, so you do not need to specify it in each [model.*] section.

Auth Behavior

When you set models_base_url, Cook uses API key auth (Authorization: Bearer) instead of session auth. You do not need cook login -- the API key is enough.


Web Search Model

The web_search tool uses a separate model. Configure it with:

[models]
web_search = "grok-4.5"

Or via environment variable:

export GROK_WEB_SEARCH_MODEL="grok-4.5"

If you point web search at a custom model, you also need a [model.*] entry so Cook can reach it. Server-side ("backend") web search runs only when the model sets supports_backend_search = true (and the build enables backend search); it does not depend on api_backend:

[models]
web_search = "my-custom-model"

[model.my-custom-model]
model = "my-custom-model"
supports_backend_search = true

Using Custom Models

# List available models (including custom)
cook models

# Use in the TUI via slash command
/model my-model

# Use in headless mode
cook -p "Hello" -m my-model

# Set as default in config.toml:
[models]
default = "my-model"

Enterprise Deployment

A complete config for an enterprise deployment with custom models:

[cli]
auto_update = false

[auth]
auth_provider_command = "/usr/local/bin/my-company-auth-provider"
auth_provider_label = "Acme Corp"
auth_token_ttl = 3600

[models]
default = "company-grok"

[model.company-grok]
model = "grok-4.6"
base_url = "https://grok-proxy.acme.com/"
name = "Cook 4.6 (Proxy)"
context_window = 128000

[features]
telemetry = false

Troubleshooting

Model Not Found

# List available models
cook models

# Check config.toml for typos in [model.*] sections

Connection Errors

Verify the endpoint is reachable:

curl -s https://api.example.com/v1/models \
  -H "Authorization: Bearer $XAI_API_KEY"

Debug Logging

RUST_LOG=debug GROK_LOG_FILE=/tmp/cook.log cook
tail -f /tmp/grok.log

Look for log entries containing model or sampling to trace model selection and API calls.