Configuration
Host settings live in ~/.crystal/config.json. Provider and model definitions live in ~/.crystal/providers.json. The built-in DeepSeek and OpenAI catalog is available even when that file is absent. Your definitions overlay it. Edit the file you changed, then restart. Editing provider definitions in the TUI is not available. <workspace>/.crystal/config.json is not read.
providers.json |
What is read |
|---|---|
| Present | The operator catalog. The legacy config.json.providers field is ignored |
| Absent | The legacy field, including apiKey and apiKeyEnvironment |
Saving preferences does not rewrite an existing providers.json. If that file is absent and config.json still has a providers object, the save copies it to providers.json and keeps the original field. Later edits belong in providers.json.
Top-level fields:
| Field | Meaning |
|---|---|
provider |
Active provider name |
model |
Active model id (must exist under that provider) |
approval |
default, edit, review, audit, or full |
approvalModel |
Optional reviewer. enabled, provider, model, and optional thinkingEffort. Omitted means off. enabled: false keeps a stored provider, model, and gear unused |
compactionModel |
Optional summarizer. enabled, provider, model, and optional thinkingEffort (default, off, or a gear that model lists). Omitted means off, and summaries use the session model. enabled: false keeps a stored provider, model, and gear unused. The model does not need imageInput. Opening it requires a catalog model; a missing credential or unknown model is refused and the saved value stays |
imageModel |
Optional vision model for describe_image. provider, model, and optional thinkingEffort. Omitted means the tool is absent. The model must have imageInput. Thinking capability stays on the model entry |
thinkingEffort |
Host thinking gear: default, off (none is the same), or an effort token the model lists. max and maximum are different tokens. Tokens are stored in lowercase and sent unchanged |
contextSelections |
Saved local context budgets as a nested map, for example {"openai": {"my-model": 272000}}. /context writes this map; the older flat {"openai/my-model": 272000} form is still read and is rewritten nested on the next save. An absent or unsupported choice uses that model’s lowest declared budget |
tierSelections |
Saved service-tier tokens as a nested map, for example {"openai": {"my-model": "flex"}}. /tier writes this map. An absent choice, or a token the model no longer lists, omits the field |
speedSelections |
Saved speed tokens in the same shape as tierSelections. /speed writes this map |
skills |
Enable the skill tool and available-skill guidance (default true) |
ruleFiles |
OpenCode-compatible rule files. global and project each default to true and are omitted until off. Does not affect instructions.md or .crystal.md |
externalTools |
Enable operator tool set discovery (default true) |
plugins |
Enable operator plugin discovery (default true) |
workspaceTrust |
Ask before the first interactive session in a directory (default true; omitted when on). Does not apply to crystal run |
externalToolApproval |
Per-source trust for tool-set author declarations: home and project, each author or host (defaults Home author, Project host) |
estimatedTokens |
Show a live four-characters-per-token estimate on the progress row during Thinking and Writing (default false) |
verboseResults |
Parent switch for success result panels (default true; omitted when on). Off hides every success result already on screen, including read, search, skill, edit, write, bash, and background bash. Does not hide Tool cards; those follow verboseCalls. Failures stay. verboseTools and verboseCommands do not draw results while this is off. crystal run does not read it |
verboseCalls |
Show Tool cards (default true; omitted when on). Off hides every Tool card already on screen: read, search, edit, write, bash, background bash, and external tools such as Apply Patch. Success and failure results stay. Independent of verboseResults, verboseTools, and verboseCommands. crystal run does not read it |
verboseTools |
While verboseResults is on, show read, search, and skill result panels (default true; omitted when on). Edit and write results stay. No visible effect while verboseResults is off |
verboseCommands |
While verboseResults is on, show full output from bash, bash_start, bash_status, bash_logs, and bash_stop (default true; omitted when on). Off keeps a hidden-line hint and the last line. No visible effect while verboseResults is off |
verboseApprovals |
Show policy and grant auto-pass cards (default true; omitted when on). Off hides those cards already on screen. Review allow cards follow verboseReview. The ask overlay stays |
verboseReview |
Show review allow cards (default true; omitted when on). Off hides those cards already on screen, including Outcome and rationale. Independent of verboseApprovals. The ask overlay, reviewer Deny text, and crystal run stay |
verboseThinking |
Show Thinking panels in the interactive transcript (default true; omitted when on). Off hides live and restored cards. The gear, progress row, and export stay. crystal run --show-thinking is separate |
exportDirectory |
Export root: omitted or home for {home}/exports, workspace for <workspace>/.crystal/exports, or any absolute/~ path |
customStatusLine |
Enable the ordered custom status line (default false; the existing adaptive status line remains the default) |
statusLine |
Ordered custom fields used only when customStatusLine is enabled. Built-in names are checked on load. An extra name is kept when it matches a plugin field name, even if that plugin is not loaded yet |
compactionThreshold |
Fraction of the selected context budget that triggers compaction (greater than 0, at most 1; default 0.8) |
showCompactionSummary |
Print the summary after a manual /compact (default true; omitted when on). /compact summary toggles it; on or off sets it |
showAutomaticCompactionSummary |
Print the same summary after automatic compaction (default false; omitted when off). /compact auto toggles it (automatic is the same); on or off sets it. Does not change /compact |
microCompaction |
Optional removal of old successful tool results before each model call. Omitted means off. Object with enabled (default false), protectTurns (at least 1, default 2), protectTokens (non-negative, default 40000), and minimumTokens (non-negative, default 20000). Invalid values stop startup. /compact micro toggles it; on or off sets it. Does not summarize |
bashTimeoutSeconds |
Built-in bash per-command timeout. Omitted keeps 600 seconds. A positive integer up to 4,294,967 replaces it. null or "unlimited" disables that timer; cancelling the turn still stops the command |
backgroundBashTimeoutSeconds |
bash_start background task timeout. Omitted keeps 10800 seconds (3 hours). A positive integer up to 4,294,967 replaces it. null or "unlimited" disables that timer; bash_stop and session shutdown still stop the task |
retry |
Work-turn and compaction retry policy. Object with enabled (default true; false disables retries), maximumRetries (0-100, default 5), initialDelaySeconds (0-3600, default 2), backoffFactor (1-100, default 2), jitterFactor (0-1, default 0.25), and maximumDelaySeconds (0-86400, default 30; the backoff cap when Retry-After is absent). Invalid values stop startup. Work retries publish RetryScheduled |
approvalRetry |
The same object shape for the Review and Audit approval reviewer. Independent of retry. Reviewer retries are silent; exhausted retries fall back to your approval prompt |
Add named endpoints and model tables in providers.json, not in new
config.json configurations.
There is no global context window. Models that are not listed cannot be selected.
Built-in providers
Section titled “Built-in providers”Starter catalog (merged with providers.json):
| Provider | Protocol | Default base URI | Starter models |
|---|---|---|---|
deepseek |
deepseek |
https://api.deepseek.com/ |
deepseek-flash, deepseek-v4-flash (compatibility alias), deepseek-v4-pro |
openai |
openai |
https://api.openai.com/v1/ |
gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna |
gemini |
gemini |
https://generativelanguage.googleapis.com/v1beta/ |
gemini-3.8-flash, gemini-3.1-pro-preview |
ollama |
ollama |
http://localhost:11434/ |
qwen3:8b (pull this model before selecting it) |
Built-in DeepSeek V4 models enable thinking with efforts low,
high, and max. Each has a 1,000,000-token context window.
Starter OpenAI models use a 400,000-token window and do not enable
thinking unless you add it.
The Ollama starter assumes a 4,096-token local context. If the local model
uses a different context size, set that size in providers.json and configure
the Ollama model to match it. Other local or cloud model IDs may be added there.
Automatically fetching model-list information from a provider is planned
and is not available in this build.
Anthropic Messages is also a built-in protocol, but the starter catalog
ships no entry for it, so Claude models come from providers.json. See
Example: add an Anthropic (Claude) provider.
Provider fields
Section titled “Provider fields”| Field | Meaning |
|---|---|
protocol |
deepseek, openai, responses, anthropic, gemini, or ollama |
baseUri |
Absolute API base URI; the adapter appends the protocol’s chat path |
organization |
Optional OpenAI organization for the openai protocol |
project |
Optional OpenAI project for the openai protocol |
replayReasoningContent |
Replay provider reasoning content (DeepSeek always replays it); reasoning that cannot be replayed keeps its readable text (assistant text, or the protocol’s readable reasoning field) |
tokenLimit |
Chat Completions output field: max_tokens or max_completion_tokens (ignored by responses and anthropic) |
apiKeyEnvironment |
Environment variable name checked first for this provider |
apiKey |
Literal, {env:NAME}, or {file:path} |
requiresApiKey |
Whether a missing key is an error (default true, except ollama; set false for an unauthenticated endpoint) |
options |
Optional object of protocol-specific settings. Values are JSON strings, numbers, or booleans. Unknown names, and names that do not apply to protocol, fail loading. temperature, topP, and maxTokens stay on the model |
models |
Table of selectable model ids |
Provider names are letters, digits, hyphen, or underscore.
All protocol adapters send the constant User-Agent: Crystal Code, with no
version. responses authenticates with Authorization: Bearer; anthropic
uses x-api-key and anthropic-version: 2023-06-01. Both adapters use direct
HTTP and JSON/SSE handling; no provider SDK is required.
Gemini uses x-goog-api-key and native generateContent SSE. Ollama uses
native /api/chat JSON lines and omits authorization when no key is configured.
For a provider with requiresApiKey: false, an explicit provider key or its
specific environment variable is still used; CRYSTAL_API_KEY is skipped.
Amazon Bedrock models that support its OpenAI-compatible Chat Completions API
can be configured with protocol: "openai", a regional
https://bedrock-runtime.REGION.amazonaws.com/openai/v1/ base URI, and a
Bedrock API key. This path uses bearer authentication. IAM SigV4 credentials
are not supported by the current adapter.
Model fields
Section titled “Model fields”| Field | Meaning |
|---|---|
contextWindow |
Required positive maximum model context size in tokens. Upper bound for selectable local budgets; used as the sole budget when contextWindows is omitted or empty |
contextWindows |
Optional distinct positive token budgets no greater than contextWindow, for example [272000, 1000000]. /context cycles these values; the lowest is the initial budget. Compaction and the status bar use the selected budget |
temperature |
Optional, 0 to 2 |
topP |
Optional, 0 to 1 |
maxTokens |
Optional positive output-token cap |
thinking |
Whether the model accepts reasoning hints |
thinkingEfforts |
Effort tokens this endpoint accepts. A token starts with a letter and then uses only letters, digits, underscores, and hyphens. max and maximum are different tokens. The host stores each token in lowercase and sends it unchanged |
thinkingCanDisable |
Whether /thinking off is accepted (default true). Built-in Gemini 3 models set this to false |
serviceTiers |
Service-tier tokens this model accepts. A token starts with a letter and then uses only letters, digits, underscores, and hyphens. The host stores each token in lowercase and does not interpret it. default is sent when it is listed; it does not mean unset. An empty or omitted list means this model has no service tier |
speeds |
Speed tokens this model accepts, using the same token rules. fast and ultrafast belong here when the endpoint accepts them. An empty or omitted list means this model has no speed |
imageInput |
Whether this model may receive images (default false; supported by all built-in protocols) |
audioInput |
Declared audio input (default false). Stored and visible to plugins. The host does not accept or send audio |
videoInput |
Declared video input (default false). Stored and visible to plugins. The host does not accept or send video |
audioInput and videoInput only record the claim. Crystal Code stores
them and shows them to plugins. It does not attach, paste, persist, or send
audio or video, and those flags do not register multimodal tools.
imageInput is still the only flag that allows image input.
Service tier and speed are sent only when the active model lists the
selected token. The host does not choose the JSON field and does not
compare the response with the request. Chat Completions and OpenAI
Responses write one set slot to service_tier and reject both when the
client is created, before the choice is saved. Anthropic Messages writes
service_tier and speed separately. DeepSeek, Gemini, and Ollama reject
either slot when the client is created. A saved combination the protocol
cannot send fails at startup. The endpoint address is not used to decide.
Gemini and Ollama native adapters accept inline images in user messages.
Tool images are sent on Gemini functionResponse parts and on the Ollama
tool message images array. Gemini tool images require a model that accepts
multimodal function responses.
thinkingEffort is a host setting, not a model field. Changing
models never fails: if the model does not support thinking, requests
omit reasoning hints; if the stored gear is not in that model’s list,
the request uses the provider default and the stored choice is
unchanged. The host stores a listed token in lowercase and forwards
that text. Adapters send the token unchanged. max and maximum are
different tokens. openai and responses are also used for
third-party gateways such as OpenRouter, so a catalog value is not
renamed to xhigh. List xhigh, max, or maximum only when that
endpoint accepts that spelling. Gemini 3 uppercases the token as
thinkingLevel. Gemini 2.5 maps minimal, low, medium, and
high to thinking budgets and rejects any other token.
Anthropic requests summarized thinking text when thinking is on, unless
that endpoint sets options.thinkingDisplay to omitted.
An empty thinkingEfforts list is on/off only. When
thinkingCanDisable is false, Off is left out of the gear. A stored
Off choice uses the provider default and the status bar shows
Think Default.
Example: add an OpenAI-compatible provider
Section titled “Example: add an OpenAI-compatible provider”Add this entry to ~/.crystal/providers.json. Keep the secret out of this
file. Write it in ~/.crystal/credentials.json under openrouter.
{ "openrouter": { "protocol": "openai", "baseUri": "https://openrouter.ai/api/v1/", "replayReasoningContent": true, "tokenLimit": "max_tokens", "models": { "anthropic/claude-sonnet-4": { "contextWindow": 200000, "temperature": 0.2, "maxTokens": 8192, "thinking": true, "thinkingEfforts": ["low", "medium", "high"] } } }}OPENROUTER_API_KEY overrides that file when the process environment sets
it. Restart after editing providers.json. Select the model with
/model openrouter anthropic/claude-sonnet-4, or set provider and
model in config.json.
CLI --provider and --model override that selection for one run;
/approval (including /approval model and /approval thinking),
/thinking, /context, /tier, /speed, and /model write their values to config.json.
/context stores each model’s selected budget in contextSelections.
/tier and /speed store each model’s token in tierSelections and speedSelections.
Example: add an Anthropic (Claude) provider
Section titled “Example: add an Anthropic (Claude) provider”Anthropic Messages is a built-in protocol, but the starter catalog ships no
Anthropic entry, so Claude models are reached by adding one to
~/.crystal/providers.json. Write the key in
~/.crystal/credentials.json under anthropic.
{ "anthropic": { "protocol": "anthropic", "baseUri": "https://api.anthropic.com/v1/", "models": { "claude-sonnet-4": { "contextWindow": 200000, "maxTokens": 8192, "thinking": true, "thinkingEfforts": ["low", "medium", "high"], "imageInput": true } } }}ANTHROPIC_API_KEY overrides that file for one process. Restart after
editing providers.json, then select the model with
/model anthropic claude-sonnet-4, or set provider and model in
config.json. The same entry reaches any Anthropic Messages gateway:
change baseUri and the model id to match it. List the endpoint token
in thinkingEfforts, such as max or xhigh. The adapter sends that
token unchanged, and it asks for summarized thinking text whenever thinking is on. Set
options.thinkingDisplay to omitted on this endpoint to leave the
Anthropic display field off. summarized is the default and is not
written back. The name is valid only for the anthropic protocol. Omit
temperature and topP for models that reject sampling parameters.
Thinking tokens count toward maxTokens.
Example: add OpenCode Zen protocol endpoints
Section titled “Example: add OpenCode Zen protocol endpoints”One gateway can group models that use different wire protocols under one
provider name. Put this example in providers.json. The key for both
endpoints belongs in credentials.json under opencode-zen.
OPENCODE_ZEN_API_KEY overrides that file for one process.
{ "opencode-zen": [ { "protocol": "responses", "baseUri": "https://opencode.ai/zen/v1/", "models": { "gpt-5.6-sol": { "contextWindow": 1050000, "maxTokens": 128000, "thinking": true, "thinkingEfforts": ["low", "medium", "high", "maximum"], "imageInput": true } } }, { "protocol": "anthropic", "baseUri": "https://opencode.ai/zen/v1/", "models": { "claude-sonnet-5": { "contextWindow": 1000000, "maxTokens": 128000, "thinking": true, "thinkingEfforts": ["low", "medium", "high", "maximum"], "imageInput": true } } } ]}Add a protocol: "openai" object to the same array for Chat Completions
models. Each model id must be unique within a provider. Listed efforts
are sent as written. maximum is not rewritten to xhigh or max.