How to Add a New Model

August 25, 2026 ยท View on GitHub

Adding a model from an existing family requires only config changes. A new Zen model family that uses a non-default endpoint (Responses, Gemini, or Messages) may require updating internal/models/classifier.go.

Step 1: Identify the Provider and Endpoint

Determine which upstream provider the model uses and which endpoint format it accepts:

ProviderEndpointFormat
opencode-go/v1/chat/completionsOpenAI Chat Completions (default)
opencode-go/v1/messagesAnthropic Messages (MiniMax, Qwen)
opencode-zen/v1/chat/completionsOpenAI Chat Completions
opencode-zen/v1/messagesAnthropic Messages (Claude, Qwen)
opencode-zen/v1/responsesOpenAI Responses (GPT, Grok, Muse Spark)
opencode-zen/v1/models/{id}Gemini
aws-bedrock/v1/chat/completionsOpenAI Chat Completions (Bedrock Mantle)
aws-bedrock/v1/messagesAnthropic Messages (Bedrock Mantle, requires wire_format: "anthropic")

Step 2: Check Endpoint Classification

Zen endpoint classification is prefix-based. Models in these existing families need no classifier change:

EndpointRecognized model prefixesClassifier
Anthropic Messagesclaude-*, qwen*IsZenAnthropicModel
OpenAI Responsesgpt-*, grok-*, muse-spark-*IsResponsesModel
Geminigemini-*IsGeminiModel

Update internal/models/classifier.go only when Zen introduces a new model family that uses a non-default endpoint. Add the family prefix to the appropriate classifier and add unit-test coverage. For example, a new Responses family would be added alongside the existing prefixes:

func IsResponsesModel(modelID string) bool {
    return strings.HasPrefix(modelID, "gpt-") ||
        strings.HasPrefix(modelID, "grok-") ||
        strings.HasPrefix(modelID, "muse-spark-") ||
        strings.HasPrefix(modelID, "my-responses-family-")
}

The Go provider is config-driven. If a Go model requires a non-default wire format, set wire_format on its model configuration instead of changing the Zen classifier:

{
  "provider": "opencode-go",
  "model_id": "my-new-model",
  "wire_format": "anthropic"
}

Supported Go-provider overrides are openai, anthropic, and responses. Zen classification functions are shared between internal/client and internal/provider so both paths route models consistently.

Step 3: Add to Config

Add the model to your config.json:

As a scenario model:

{
  "models": {
    "default": {
      "provider": "opencode-go",
      "model_id": "my-new-model",
      "temperature": 0.7,
      "max_tokens": 4096
    }
  }
}

As a model override (for direct requests):

{
  "model_overrides": {
    "my-new-model": {
      "provider": "opencode-go",
      "model_id": "my-new-model",
      "temperature": 0.7,
      "max_tokens": 8192
    }
  }
}

As a fallback:

{
  "fallbacks": {
    "default": [
      { "provider": "opencode-go", "model_id": "my-new-model" }
    ]
  }
}

Step 4: Test

# Validate config
routatic-proxy validate

# Test with a request
curl -X POST http://localhost:3456/v1/messages \
  -H "Content-Type: application/json" \
  -d '{
    "model": "my-new-model",
    "max_tokens": 100,
    "messages": [{"role": "user", "content": "Hello"}]
  }'

Model-Specific Considerations

Models requiring Anthropic endpoint

Some models (MiniMax, Qwen on Go provider) only accept Anthropic Messages format, not OpenAI Chat Completions. These need IsAnthropicModel to return true.

Models with thinking/reasoning

If the model supports thinking mode (DeepSeek, OpenAI o-series), configure:

{
  "thinking": { "type": "enabled" },
  "reasoning_effort": "high"
}

The proxy handles the Anthropic thinking โ†” OpenAI reasoning_content translation automatically.

Models with tool format issues

If the model doesn't support Anthropic's type: "custom" tool shorthands, set:

{
  "anthropic_tools_disabled": true
}

This forces the request through the Chat Completions transform path.

Models with vision support

Set "vision": true in the model config to enable image routing:

{
  "my-vision-model": {
    "provider": "opencode-go",
    "model_id": "my-vision-model",
    "vision": true
  }
}

Temperature constraints

Some models have hard temperature requirements (e.g., kimi-k2.7-code requires temperature=1). Add constraints in constrainTemperature in internal/transformer/request.go.

Cost-Based Routing

When cost_routing.enabled is true, the proxy uses a catalog of model pricing data to automatically select the cheapest eligible model for each scenario.

The catalog is downloaded from models.dev and cached locally in ~/.config/routatic-proxy/catalog/. The catalog schema uses provider-prefixed model keys:

{
  "providers": {
    "opencode-go": {
      "name": "opencode-go",
      "base_url": "https://opencode.ai/zen/go/v1/chat/completions",
      "enabled": true
    }
  },
  "models": {
    "opencode-go/my-new-model": {
      "id": "opencode-go/my-new-model",
      "name": "My New Model",
      "limit": { "context": 128000 },
      "rates": { "input": 1.0, "output": 2.0 },
      "tool_call": true,
      "modalities": { "input": ["text"], "output": ["text"] }
    }
  }
}

Key catalog fields:

FieldDescription
idFull model key (provider/model-name)
nameDisplay name
limit.contextContext window size (tokens)
rates.inputCost per million input tokens
rates.outputCost per million output tokens
tool_callWhether the model supports tools
modalities.inputInput types: ["text"] or ["text", "image"] for vision
modalities.outputOutput types: usually ["text"]
reasoningWhether the model supports reasoning mode

To add a model to the cost-based routing catalog, submit a PR to the models.dev repository or run:

routatic-proxy catalog sync --force