Skip to content

models.json Configuration Guide ​

Overview ​

models.json is a configuration file used to customize the model list and control the model dropdown display. This configuration supports two levels:

  • User-level: ~/.codebuddy/models.json - Global configuration applicable to all projects
  • Project-level: <workspace>/.codebuddy/models.json - Project-specific configuration with higher priority than user-level

Configuration File Locations ​

User-level Configuration ​

~/.codebuddy/models.json

Project-level Configuration ​

<project-root>/.codebuddy/models.json

Configuration Priority ​

Configuration merge priority from highest to lowest:

  1. Project-level models.json
  2. User-level models.json
  3. Built-in default configuration

Project-level configuration will override user-level configuration for the same model definitions (based on id field matching). availableModels field: project-level completely overrides user-level, no merging.

Configuration Structure ​

json
{
  "models": [
    {
      "id": "model-id",
      "name": "Model Display Name",
      "vendor": "vendor-name",
      "apiKey": "sk-actual-api-key-value",
      "maxInputTokens": 200000,
      "maxOutputTokens": 8192,
      "url": "https://api.example.com/v1/chat/completions",
      "temperature": 0.7,
      "supportsToolCall": true,
      "supportsImages": true
    }
  ],
  "availableModels": ["model-id-1", "model-id-2"]
}

Configuration Field Description ​

TUI Model Description Language ​

Below the /model list, the full description of the currently highlighted model is shown; it updates as you move with the arrow keys and wraps automatically to the terminal width. The list shows at most 10 items, and fewer when the terminal is short or narrow, to leave room for the details and shortcut hints; use the arrow keys to keep browsing all models.

Description language detection priority:

  1. The explicit environment variable CODEBUDDY_TUI_LANGUAGE=zh or en (matching language tags such as zh-TW and en-US are also accepted). When unset or set to auto, the language is detected automatically.
  2. The macOS preferred system language list (AppleLanguages), or the Windows user interface language (Get-UICulture). The first supported Chinese or English language in preference order is selected, rather than the regional format used for dates and numbers.
  3. The terminal environment: LC_ALL → LC_MESSAGES → LANG; in non-C / POSIX environments, the colon-separated preference list in LANGUAGE takes precedence for matching Chinese or English. C / POSIX (including C.UTF-8) falls back to English.
  4. The Node.js Intl default locale; if still unrecognized, falls back to English.

In a Chinese environment, descriptionZh, descriptionEn, and description are read in that order; in English or other language environments, descriptionEn and then description are read. Blank descriptions are treated as missing, and the details box is hidden when no text is available. description is a legacy compatibility field and is not guaranteed to be in English; zh-Hans and zh-Hant currently share descriptionZh, with no automatic Simplified/Traditional conversion.

Terminal control characters are stripped from model display text; descriptions keep normal line breaks and are limited to 4096 characters, with an ellipsis shown when exceeded. Model selection still uses the original ID; action links that contain control characters, are too long, or are not HTTP(S) cannot be opened.

bash
CODEBUDDY_TUI_LANGUAGE=en cbc

This setting only affects TUI model descriptions. It does not change the model's reply language (/language), nor does it translate panel titles or shortcut hints. System detection runs only the first time the panel is opened and is cached in-process; restart the CLI after changing the system language. Over SSH or in containers, detection reflects the environment where the CLI runs, not the remote user's local UI language; override it with the environment variable above.

models ​

Type: Array<LanguageModel>

Define custom model list. You can add new models or override built-in model configurations.

LanguageModel Fields ​

FieldTypeRequiredDescription
idstring✓Model unique identifier
namestring-Model display name
vendorstring-Model vendor (e.g., OpenAI, Google)
apiKeystring-API key, supports environment variable references (see Security Configuration below)
maxInputTokensnumber-Maximum input tokens
maxOutputTokensnumber-Maximum output tokens
urlstring-API endpoint URL, supports environment variable references (must be complete interface path, typically ending with /chat/completions)
apistring-Request protocol, same values as pi's api. When set to openai-responses, uses the OpenAI Responses API instead, and url takes the base URL. See OpenAI Responses API Configuration Example
temperaturenumber-Sampling temperature, range 0-2, higher values produce more random output, lower values produce more deterministic output
supportsToolCallboolean-Whether tool calls are supported
supportsImagesboolean-Whether image input is supported
supportsReasoningboolean-Whether reasoning mode is supported
relatedModelsobject-Related model configuration, specifies which model id to use in different scenarios (lite/reasoning/vision/longContext/subagent). See Configuring Related Models for details

Important Notes:

  • Currently only supports OpenAI interface format API
  • url field must be the complete interface path, typically ending with /chat/completions
  • Examples: https://api.openai.com/v1/chat/completions or http://localhost:11434/v1/chat/completions

Security Configuration: Using Environment Variable References ​

To avoid storing API keys in plain text in configuration files, the apiKey and url fields support environment variable reference syntax ${VAR_NAME}.

Syntax Format:

${ENVIRONMENT_VARIABLE_NAME}

Configuration Example:

json
{
  "models": [
    {
      "id": "gpt-4o",
      "name": "GPT-4o",
      "vendor": "OpenAI",
      "apiKey": "${OPENAI_API_KEY}",
      "url": "https://api.openai.com/v1/chat/completions"
    }
  ]
}

Setting Environment Variables:

bash
# Add to ~/.zshrc or ~/.bashrc
export OPENAI_API_KEY="sk-your-actual-api-key"

# Or set temporarily at startup
OPENAI_API_KEY="sk-xxx" codebuddy

Using System Keychain (macOS):

bash
# Store key in Keychain
security add-generic-password -a "$USER" -s "openai-api-key" -w "sk-xxx"

# Configure auto-export in ~/.zshrc
export OPENAI_API_KEY=$(security find-generic-password -s "openai-api-key" -w 2>/dev/null)

Important Notes:

  • Environment variables are resolved at CLI startup
  • If an environment variable doesn't exist, the original placeholder is retained (which will cause API call failures)
  • It's recommended to set models.json file permissions to 600 (owner read/write only)
  • Do not commit configuration files containing actual keys to version control systems

availableModels ​

Type: Array<string>

Control which models are displayed in the model dropdown list. Only model IDs listed in this array will be shown in the UI.

  • If not configured or empty array, all models are displayed
  • When configured, only listed model IDs are displayed
  • Can include both built-in and custom model IDs

Use Cases ​

1. Add Custom Model ​

Add new model configuration at user or project level:

json
{
  "models": [
    {
      "id": "my-custom-model",
      "name": "My Custom Model",
      "vendor": "OpenAI",
      "apiKey": "sk-custom-key-here",
      "maxInputTokens": 128000,
      "maxOutputTokens": 4096,
      "url": "https://api.myservice.com/v1/chat/completions",
      "supportsToolCall": true
    }
  ]
}

2. Override Built-in Model Configuration ​

Modify default parameters of built-in models:

json
{
  "models": [
    {
      "id": "gpt-4-turbo",
      "name": "GPT-4 Turbo (Custom Endpoint)",
      "vendor": "OpenAI",
      "url": "https://my-proxy.example.com/v1/chat/completions",
      "apiKey": "sk-your-key-here"
    }
  ]
}

3. Limit Available Model List ​

Only display specific models in the dropdown list:

json
{
  "availableModels": [
    "gpt-4-turbo",
    "gpt-4o",
    "my-custom-model"
  ]
}

4. Project-Specific Configuration ​

Use different models or API endpoints for specific projects:

Project A (.codebuddy/models.json):

json
{
  "models": [
    {
      "id": "project-a-model",
      "name": "Project A Model",
      "vendor": "OpenAI",
      "url": "https://project-a-api.example.com/v1/chat/completions",
      "apiKey": "sk-project-a-key",
      "maxInputTokens": 100000,
      "maxOutputTokens": 4096
    }
  ],
  "availableModels": ["project-a-model", "gpt-4-turbo"]
}

Hot Reload ​

Configuration file supports hot reload:

  • File changes are automatically detected
  • Uses 1 second debounce delay to avoid frequent reloads
  • Configuration updates are automatically synced to the application

Monitored files:

  • ~/.codebuddy/models.json (user-level)
  • <workspace>/.codebuddy/models.json (project-level)

Tagging System ​

Models added through models.json are automatically tagged with the custom tag for easy identification and filtering in the UI.

Merge Strategy ​

Configuration uses SmartMerge strategy:

  • Model configurations with the same ID are overridden
  • Models with different IDs are appended
  • Project-level configuration takes priority over user-level configuration
  • availableModels filtering is executed after all merging is complete

Example Configurations ​

API Endpoint URL Format ​

Must use complete path: All custom model url fields should typically end with /chat/completions.

✅ Correct Examples:

https://api.openai.com/v1/chat/completions
https://api.myservice.com/v1/chat/completions
http://localhost:11434/v1/chat/completions
https://my-proxy.example.com/v1/chat/completions

❌ Incorrect Examples:

https://api.openai.com/v1
https://api.myservice.com
http://localhost:11434

OpenRouter Platform Configuration Example ​

Using OpenRouter to access various models:

json
{
  "models": [
    {
      "id": "openai/gpt-4o",
      "name": "open-router-model",
      "url": "https://openrouter.ai/api/v1/chat/completions",
      "apiKey": "sk-or-v1-your-openrouter-api-key",
      "maxInputTokens": 128000,
      "maxOutputTokens": 4096,
      "supportsToolCall": true,
      "supportsImages": false
    }
  ]
}

DeepSeek Platform Configuration Example ​

Using DeepSeek models (once url is configured, even entries with the same id as cloud defaults take effect with "full replacement" semantics and are not overridden by cloud default merges):

json
{
  "models": [
    {
      "id": "deepseek-v4-pro",
      "name": "DeepSeek V4 Pro",
      "vendor": "DeepSeek",
      "url": "https://api.deepseek.com/v1/chat/completions",
      "apiKey": "${DEEPSEEK_API_KEY}",
      "maxInputTokens": 128000,
      "maxOutputTokens": 8192,
      "supportsToolCall": true,
      "supportsImages": false
    },
    {
      "id": "deepseek-v4-flash",
      "name": "DeepSeek V4 Flash",
      "vendor": "DeepSeek",
      "url": "https://api.deepseek.com/v1/chat/completions",
      "apiKey": "${DEEPSEEK_API_KEY}",
      "maxInputTokens": 128000,
      "maxOutputTokens": 8192,
      "supportsToolCall": true,
      "supportsImages": false
    }
  ],
  "availableModels": [
    "deepseek-v4-pro",
    "deepseek-v4-flash"
  ]
}

Set the API key environment variable and start:

bash
export DEEPSEEK_API_KEY="<your-deepseek-api-key>"
codebuddy --model deepseek-v4-pro

Tip: If you prefer not to maintain a models.json, you can also connect to DeepSeek entirely through environment variables. See env-vars.md DeepSeek Integration Example.

OpenAI Responses API Configuration Example ​

With api set to openai-responses, the model uses the OpenAI Responses API: requests go to {url}/responses, and multi-turn conversations replay the encrypted reasoning (encrypted_content) returned by the previous turn as-is. Using gpt-6-sol on aihub as an example:

json
{
  "models": [
    {
      "id": "gpt-6-sol",
      "name": "GPT-6 Sol",
      "api": "openai-responses",
      "url": "http://api.aihub.woa.com/openai/v1",
      "apiKey": "${AIHUB_KEY}?provider=azure&cache_task_id=${CODEBUDDY_SESSION_ID}&timeout=3600",
      "supportsReasoning": true,
      "supportsToolCall": true,
      "maxOutputTokens": 128000,
      "reasoning": { "defaultEffort": "max", "supportedEfforts": ["low", "medium", "high", "xhigh", "max"] }
    }
  ]
}
  • url takes the base URL; a trailing /responses is optional.
  • Only an api explicitly written in the model configuration switches the protocol; built-in models marked as openai-responses in the model catalog still use the existing path.
  • ${CODEBUDDY_SESSION_ID} (or ${CLAUDE_SESSION_ID}) in apiKey is replaced with the main session id on each request. One-off auxiliary calls such as subagents, titles, and WebFetch summaries, /btw, and forked sessions (/fork, --fork-session, ACP fork) all reuse the source session's value, so replayed encrypted reasoning always lands on the upstream account that can decrypt it. Each Responses entry in the session JSONL records this value in providerData.upstream.rootSessionId, which fork and --resume use to carry it over. Load-time ${ENV} expansion skips these two names, so even if the process environment has a variable of the same name (e.g., when launching the CLI from another session's shell), it is not hard-coded prematurely. aihub uses it as cache_task_id to pin all requests of the same task to the same upstream account; use --session-id when you need a fixed session id.
  • When rate-limited, aihub returns HTTP 200 and emits error / response.failed in the stream. The CLI maps in-stream errors that occur before output starts back to the HTTP status the regular OpenAI API would return, so retry and model switching behave the same as chat completions: rate limiting is retried with backoff as 429; quota exhaustion (insufficient_quota) is also 429 but is not retried and is handed directly to the fallback model; context overflow and invalid request content or images are treated as 400 and not retried (context overflow triggers auto-compaction); server_error is classified as 500, but since the upstream already answered 200 and started streaming, it is not retried at the request level. Other in-stream errors are still handled as before as streaming errors. For unattended batch runs, setting CODEBUDDY_RETRY_WATCHDOG=1 (or increasing CODEBUDDY_MAX_RETRIES) is recommended.
  • Consistent with pi, include: ["reasoning.encrypted_content"] is sent only for models declaring supportsReasoning: true and only when the request carries reasoning parameters (thinking enabled), independent of store.
  • Images in tool results and user messages (including blob references after session resume) are restored as input_image, with detail defaulting to auto. Tool result shape matches pi: text is merged into one leading segment followed by images, and remains a string when there are no images. With supportsImages: false, images are replaced with an omission notice.
  • When replaying history, entries produced by other models, and calls whose reasoning entries cannot be replayed, omit upstream fc_ / msg_ ids to avoid API reasoning-pairing validation errors; call ids are normalized to [A-Za-z0-9_-] with a max length of 64 characters, and empty tool output is sent as (no tool output).
  • Messages inserted mid-execution (steer control requests, background task notifications, and other queued messages) also work under Responses. Regular user messages from stream-json input are queued as a separate turn and are not inserted mid-execution.
  • The max / xhigh tiers must be declared in reasoning.supportedEfforts; otherwise they are downgraded to high.
  • Requests always explicitly carry store: false by default, true when the ResponsesStore product feature is enabled. WorkBuddy enables it with --features '{"rollout":true}'; when running the CLI directly, set CODEBUDDY_RESPONSES_STORE_ENABLED=1.
  • For long reasoning, increase CODEBUDDY_FIRST_TOKEN_TIMEOUT_MS and CODEBUDDY_STREAM_TIMEOUT_MS (in milliseconds).
  • In the session JSONL, providerData.encrypted_content on reasoning entries is the ciphertext stored as-is, and providerData.upstream records model, region (x-ms-region), accountId (x-account-id), and rootSessionId (the cache_task_id for that call).
  • To capture the full request actually sent on each call (instructions, tools, input), set OTEL_LOG_RAW_API_BODIES=file:<dir>; see Monitoring.

During a single CodeBuddy Code session, the model is switched based on the scenario, to avoid using a large model for simple tasks or a general-purpose model for requests that need reasoning / vision / long-context. These scenarios are declared via the relatedModels field on a model entry.

Supported scenarios (variant type):

ScenarioPurposeCurrent Status
liteLightweight fast model, used for background extraction, summarization, and other low-value requests; also the model corresponding to the Agent tool's model: "lite" parameterActive
reasoningReasoning-enhanced model, used for complex reasoning requiring deep thinking; the model corresponding to the Agent tool's model: "reasoning" parameterActive
subagentDefault model used by sub-agents / team membersReserved, not yet active — sub-agents use a separate subagents resolution chain and do not read this field
visionVision understanding model, used for requests that need to process imagesReserved, not yet active — the type is defined but there is no call site consuming this variant
longContextLong-context model, used for requests with very long contextReserved, not yet active — the type is defined but there is no call site consuming this variant

Currently actually usable: only the two variants lite and reasoning are consumed by the agent-manager and mapped into the model switching logic. subagent / vision / longContext are only kept in the type definitions, reserved for future iterations; writing them into relatedModels today will not error but will also have no effect.

Key rule (must-read for custom models):

Custom models added via models.json do not inherit the product's built-in defaultRelatedModels. If a custom main model does not declare relatedModels, and there is no environment variable or variantModels override, lite and reasoning will fall back to the main model. Sub-agents do not read relatedModels.subagent, but sub-agents configured as lite or reasoning will still go through the variant resolution chain to determine the concrete model.

Configuration example (DeepSeek main model + flash as lite / reasoning):

json
{
  "models": [
    {
      "id": "deepseek-v4-pro",
      "name": "DeepSeek V4 Pro",
      "vendor": "DeepSeek",
      "url": "https://api.deepseek.com/v1/chat/completions",
      "apiKey": "${DEEPSEEK_API_KEY}",
      "maxInputTokens": 128000,
      "maxOutputTokens": 8192,
      "supportsToolCall": true,
      "relatedModels": {
        "lite": "deepseek-v4-flash",
        "reasoning": "deepseek-v4-pro"
      }
    },
    {
      "id": "deepseek-v4-flash",
      "name": "DeepSeek V4 Flash",
      "vendor": "DeepSeek",
      "url": "https://api.deepseek.com/v1/chat/completions",
      "apiKey": "${DEEPSEEK_API_KEY}",
      "maxInputTokens": 128000,
      "maxOutputTokens": 8192,
      "supportsToolCall": true
    }
  ],
  "availableModels": [
    "deepseek-v4-pro",
    "deepseek-v4-flash"
  ]
}

Variant resolution priority (from highest to lowest; currently only lite / reasoning go through this resolution chain):

  1. Corresponding environment variables (CODEBUDDY_SMALL_FAST_MODEL for lite, CODEBUDDY_BIG_SLOW_MODEL for reasoning)
  2. Project-level variantModels[variant]
  3. User global variantModels[variant]
  4. relatedModels[variant] on the current main model entry
  5. The product's built-in defaultRelatedModels[variant] (only takes effect for built-in models; custom models skip this step)
  6. Fall back to the main model itself

variantModels is stored in settings.json and can also be edited via the /model:lite / /model:reasoning commands. It is suitable for fixing lite / reasoning to specific models within the user or project scope; relatedModels is suitable for letting the mapping follow the current main model.

Built-in sub-agent resolution priority (from highest to lowest):

  1. CODEBUDDY_CODE_SUBAGENT_MODEL, universally covers all sub-agents
  2. The Agent tool call's model parameter (model ID, name, alias, or default / lite / reasoning)
  3. Project-level subagents.agents.<subagent-name>.model
  4. User global subagents.agents.<subagent-name>.model
  5. Product built-in declaration, e.g., Explore uses lite
  6. Inherit the main conversation model

Sub-agent models do not read relatedModels.subagent. When a sub-agent is configured as lite or reasoning, it will continue through the variant resolution chain above to determine the concrete model.

The following configuration should be written to user-level or project-level settings.json:

json
{
  "subagents": {
    "agents": {
      "Explore": { "model": "lite" },
      "Plan": { "model": "reasoning" }
    }
  },
  "variantModels": {
    "lite": "<fast-model-id>",
    "reasoning": "<reasoning-model-id>"
  }
}

When to use different configuration approaches:

  • Use relatedModels to let scenario mapping follow the main model.
  • Use variantModels or /model to fix specific lite / reasoning models within the user or project scope.
  • Use subagents.agents.<subagent-name>.model or /agents to select models or scenario variants for built-in sub-agents individually.
  • Use model environment variables for CI-level overrides. Environment variables take priority over persistent settings; after unsetting, the lower-priority settings will take effect again.

Complete Example ​

json
{
  "models": [
    {
      "id": "gpt-4o",
      "name": "GPT-4o",
      "vendor": "OpenAI",
      "apiKey": "sk-your-openai-key",
      "maxInputTokens": 128000,
      "maxOutputTokens": 16384,
      "supportsToolCall": true,
      "supportsImages": true
    },
    {
      "id": "my-local-llm",
      "name": "My Local LLM",
      "vendor": "Ollama",
      "url": "http://localhost:11434/v1/chat/completions",
      "apiKey": "ollama",
      "maxInputTokens": 8192,
      "maxOutputTokens": 2048,
      "supportsToolCall": true
    }
  ],
  "availableModels": [
    "gpt-4o",
    "my-local-llm"
  ]
}

Troubleshooting ​

Configuration Not Taking Effect ​

  1. Check if JSON format is correct
  2. Confirm file path is correct
  3. View log output to confirm configuration is loaded
  4. Confirm API keys in environment variables are set

Model Not Showing in List ​

  1. Check if model ID is listed in availableModels
  2. Confirm models configuration is correct
  3. Verify all required fields (id, name, provider) are provided

Hot Reload Not Triggered ​

  • Configuration file changes have 1 second debounce delay
  • Ensure file is actually saved to disk
  • Check if file watching started normally (view debug logs)