Skip to main content

Providers, Models, and Routing

go-code can talk to ten different LLM backends — OpenAI, Anthropic, DeepSeek, Groq, xAI, Kimi, Qwen, Together AI, OpenRouter, and Google Gemini — through a single unified interface. Every provider, every model, and every pricing rate is described in a JSON catalog (catalog/models.json) that the server loads at startup. This page explains how that catalog works, how go-code decides which provider to use for a given run, and how it tracks and caps spending.


The model catalog​

catalog/models.json is the source of truth for everything provider-related. It has two top-level fields:

  • catalog_version — a string tag for the catalog itself.
  • providers — a map from provider key (e.g. "openai") to a ProviderEntry.

What a ProviderEntry contains​

Each provider entry describes how to reach the API and what models are available:

type ProviderEntry struct {
DisplayName string `json:"display_name"`
BaseURL string `json:"base_url"`
APIKeyEnv string `json:"api_key_env"`
Protocol string `json:"protocol"`
Quirks []string `json:"quirks,omitempty"`
Models map[string]Model `json:"models"`
Aliases map[string]string `json:"aliases,omitempty"`
}

Both base_url and api_key_env are required — the catalog loader will reject an entry missing either field. The protocol field is descriptive metadata in the catalog; it is not consumed by routing. The actual wire client is selected by provider name — only the anthropic provider uses the native Anthropic client; all others use the OpenAI-compatible client. Observed values in the catalog are openai_compat, anthropic, and openai (used by the gemini provider).

What a Model entry contains​

Each model entry describes capabilities, pricing, and quirks:

type Model struct {
DisplayName string `json:"display_name"`
ContextWindow int `json:"context_window"` // required, must be > 0
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
Modalities []string `json:"modalities"`
ToolCalling bool `json:"tool_calling"`
ParallelToolCalls bool `json:"parallel_tool_calls,omitempty"`
Streaming bool `json:"streaming"`
ReasoningMode bool `json:"reasoning_mode,omitempty"`
Quirks []string `json:"quirks,omitempty"`
SpeedTier string `json:"speed_tier,omitempty"`
CostTier string `json:"cost_tier,omitempty"`
Pricing *ModelPricing `json:"pricing,omitempty"`
API string `json:"api,omitempty"`
}

The api field controls which HTTP endpoint is used: "responses" routes the call to POST /v1/responses (the OpenAI Responses API); any other value (or empty) uses POST /v1/chat/completions. Several OpenAI Codex models use the Responses API endpoint.


Supported providers​

go-code ships with 15 providers pre-wired in the catalog. The table shows the provider key you use in API calls, the required API key environment variable, and the wire protocol.

The anthropic provider uses a native Anthropic client (internal/provider/anthropic). All other providers use an OpenAI-compatible client (internal/provider/openai), even when the underlying API is not from OpenAI.

Provider keyDisplay nameAPI key env varProtocol
openaiOpenAIOPENAI_API_KEYopenai_compat
anthropicAnthropicANTHROPIC_API_KEYanthropic
deepseekDeepSeekDEEPSEEK_API_KEYopenai_compat
groqGroqGROQ_API_KEYopenai_compat
cerebrasCerebrasCEREBRAS_API_KEYopenai_compat
xaixAI (Grok)XAI_API_KEYopenai_compat
kimiKimi (Moonshot)MOONSHOT_API_KEYopenai_compat
kimi-subscriptionKimi Code Subscription(vendor-CLI session import)openai_compat
qwenQwen (DashScope)DASHSCOPE_API_KEYopenai_compat
togetherTogether AITOGETHER_API_KEYopenai_compat
codex-subscriptionCodex (ChatGPT subscription)(vendor-CLI session import)openai_compat
openrouterOpenRouterOPENROUTER_API_KEYopenai_compat
geminiGoogle GeminiGOOGLE_API_KEYopenai
ollamaOllama(none — local server)openai_compat
lmstudioLM Studio(none — local server)openai_compat

A provider is considered "configured" when its API key environment variable is set. You can check which providers are configured at runtime:

curl http://localhost:8080/v1/providers

Response shape:

{
"providers": [
{
"name": "openai",
"configured": true,
"api_key_env": "OPENAI_API_KEY",
"base_url": "https://api.openai.com/v1",
"model_count": 8
}
]
}

You can also set a provider's API key at runtime without restarting the server:

curl -X PUT http://localhost:8080/v1/providers/anthropic/key \
-H "Content-Type: application/json" \
-d '{"key": "sk-ant-..."}'

This endpoint requires the admin scope when authentication is enabled.


Model catalog by provider​

Several OpenAI Codex model IDs in the catalog (e.g. gpt-5.1-codex, gpt-5.2-codex, gpt-5.3-codex) may not be available on all accounts. Verify availability against your OpenAI account before selecting these models in production.

Model IDContextMax outputTool callingEndpoint
gpt-4.1-mini1,000,00016,384yes (parallel)chat/completions
gpt-4.11,000,00032,768yes (parallel)chat/completions
gpt-5.1-codex128,00032,768yesresponses
gpt-5.1-codex-mini128,00032,768yesresponses
gpt-5.1-codex-max128,00032,768yesresponses
gpt-5.2-codex128,00032,768yesresponses
gpt-5.3-codex128,00032,768yesresponses
computer-use-preview128,0008,192yesresponses

Aliases: gpt4-mini → gpt-4.1-mini, gpt4 → gpt-4.1, codex → gpt-5.1-codex-mini, codex-mini → gpt-5.1-codex-mini

You can browse all catalog models at runtime:

curl http://localhost:8080/v1/models

Routing: how go-code picks a provider​

Startup default​

When harnessd starts, resolveDefaultProvider works through four paths in order:

  1. Fake provider — if HARNESS_PROVIDER=fake, use the deterministic fake provider (no API key needed). Useful for smoke tests and CI.
  2. Catalog match — if the default model (set via HARNESS_MODEL, config file, or the built-in default "gpt-4.1-mini") resolves to a configured provider in the catalog, use that provider's client.
  3. Legacy OpenAI — if OPENAI_API_KEY is set, bootstrap an OpenAI client directly.
  4. Error — no provider configured; the server refuses to start.

The built-in default model is "gpt-4.1-mini". You can change the server-wide default via the HARNESS_MODEL environment variable or the model key in your TOML config file.

Per-run resolution​

Each POST /v1/runs call resolves a provider independently, letting different runs use different providers without restarting the server:

  1. Explicit provider — if provider_name is set in the RunRequest, that provider is used directly from the registry.
  2. Model lookup — ProviderRegistry.GetClientForModel(model) searches all providers in order: direct model ID match, then alias match, then (for OpenRouter) live discovery, then the / heuristic for OpenRouter's dynamic model namespace.
  3. Fallback — if lookup fails and allow_fallback is true, the run falls back to the server's default provider.

Alias resolution​

Aliases let you use short names like "codex" instead of "gpt-5.1-codex-mini". Each provider in the catalog can define an aliases map. The resolver follows chains up to 8 hops to prevent cycles.

Live model discovery​

Live discovery is provider-agnostic, not OpenRouter-only: OpenRouter, OpenAI, Anthropic, and DeepSeek entries all refresh from the provider's own models endpoint on a 5-minute TTL (internal/provider/openai/discovery.go:24, internal/provider/anthropic/discovery.go:24). When the matching API key is set, the harness fetches the live list (for OpenRouter: https://openrouter.ai/api/v1/models) and merges it additively with the static catalog — static metadata wins on ID conflicts. A failed refresh never removes static models; the last successful result is served stale.

As a convenience, any model ID containing / is automatically routed to the openrouter provider if the key is configured. This means you can pass "openai/gpt-4.1" directly in model without setting provider_name, and go-code will route it to OpenRouter.


Per-run model and provider selection​

Control routing at the run level with these RunRequest fields:

{
"prompt": "Refactor the auth module to use JWTs",
"model": "claude-sonnet",
"provider_name": "anthropic",
"allow_fallback": true,
"fallback_providers": ["openai", "openrouter"],
"reasoning_effort": "high",
"max_cost_usd": 0.50
}
FieldTypeWhat it does
modelstringModel ID or alias. The resolver maps this to a provider.
provider_namestringSkip auto-resolution and use this provider directly (e.g. "anthropic", "openrouter").
allow_fallbackboolIf the primary provider fails with a transient error (429, 5xx), try the next provider.
fallback_providers[]stringOrdered list of provider keys to try when allow_fallback is true.
reasoning_effortstringFor reasoning models: "low", "medium", or "high". Empty uses the provider's default.
max_cost_usdfloat64Per-run spending ceiling in USD. Zero means unlimited.

provider_name bypasses model lookup entirely — the specified model ID is sent to the chosen provider as-is. Use this when you want to override the catalog's routing decision or when a model is available through multiple providers and you prefer a specific one.


Pricing and cost accounting​

How pricing is resolved​

go-code uses a two-layer pricing system:

  1. External pricing file — if HARNESS_PRICING_CATALOG_PATH is set, a FileResolver reads rates from that JSON file (the repository ships catalog/pricing.json with version "2026-04-28").
  2. Inline catalog pricing — when HARNESS_PRICING_CATALOG_PATH is not set (the default), rates are taken from the pricing block embedded in each model entry in catalog/models.json.

Pricing is computed per turn: (tokens / 1,000,000) * rate_usd. Cache-read tokens are billed at the lower cache_read_per_1m_tokens_usd rate when the model supports prompt caching.

Selected rates (inline catalog, USD per 1M tokens)​

These rates come from the embedded pricing in catalog/models.json and are used by default. The separate catalog/pricing.json file shows different rates for some models (for example, deepseek-chat shows different input/output prices). Do not treat these numbers as authoritative billing figures — verify actual charges against your provider's invoices.

ProviderModelInputOutputCache read
openaigpt-4.1-mini$0.40$1.60—
openaigpt-4.1$2.00$8.00—
anthropicclaude-opus-4-6$15.00$75.00—
anthropicclaude-sonnet-4-6$3.00$15.00—
anthropicclaude-haiku-4-5-20251001$0.80$4.00—
deepseekdeepseek-chat$0.28$0.42$0.014
deepseekdeepseek-v4-flash$0.14$0.28—
deepseekdeepseek-v4-pro$0.435$0.87—
groqllama-3.3-70b-versatile$0.59$0.79—
groqqwen-qwq-32b$0.20$0.20—
xaigrok-3-mini$0.30$0.50—
xaigrok-4-1-fast-reasoning$5.00$25.00—
geminigemini-2.0-flash$0.10$0.40—
geminigemini-2.5-flash$0.15$0.60—
togethermeta-llama/Llama-4-Maverick-17B-128E$0.27$0.35—

Cost ceiling and the run.cost_limit_reached event​

Set max_cost_usd in a RunRequest to cap spending for that run. When cumulative cost crosses the ceiling, the harness emits a run.cost_limit_reached event after the current turn finishes, then stops the agent loop (no further turns run) and marks the run completed (not failed) — it is checked at the turn boundary, so it is never aborted mid-turn. You can also set a server-wide default ceiling with HARNESS_MAX_COST_PER_RUN_USD (0 = unlimited).

After a run completes, GET /v1/runs/{id}/summary returns total_cost_usd and cost_status so you can see exactly what was spent.


Provider quirks​

Some providers need special handling that the client applies automatically when the catalog entry carries the relevant quirk tag.

QuirkProvidersWhat it does
reasoning_content_passbackDeepSeek, xAI, OpenRouter/DeepSeekReplays prior assistant reasoning back to the API on multi-turn tool use calls.
speed_optimizedGroqInformational only; flags LPU-based inference.
no_parallel_tool_callsGeminiInformational tag indicating Gemini does not support parallel tool calls. The actual disabling is hardcoded by provider name in the client factory, not read from this quirk tag.

Key-free smoke testing​

To run the server without any provider API key — useful in CI or local development — use the fake provider:

HARNESS_PROVIDER=fake \
HARNESS_FAKE_TURNS=path/to/turns.json \
HARNESS_AUTH_DISABLED=true \
go run ./cmd/harnessd

With the server running, you can verify catalog routes work:

# List all providers
curl http://localhost:8080/v1/providers

# List all models
curl http://localhost:8080/v1/models

# Start a run against the fake provider
curl -X POST http://localhost:8080/v1/runs \
-H "Content-Type: application/json" \
-d '{"prompt": "hello"}'

The fake provider returns deterministic responses from the turns file and never calls a real LLM.


Configuration reference​

The server-wide default model and related paths flow through the standard configuration cascade. The most common environment variables for providers are:

VariableDefaultPurpose
HARNESS_MODEL"gpt-4.1-mini"Server-wide default model
HARNESS_MODEL_CATALOG_PATHauto-detectedPath to catalog/models.json
HARNESS_PRICING_CATALOG_PATH(none)Path to external pricing JSON; when unset, inline catalog pricing is used
HARNESS_PROVIDER(none)Set to "fake" for key-free deterministic mode
HARNESS_OPENROUTER_REFERER"https://github.com/dennisonbertram/go-agent-harness"HTTP Referer header sent to OpenRouter
HARNESS_OPENROUTER_TITLE"go-agent-harness"X-Title header sent to OpenRouter

Next steps​

  • See the HTTP routes reference for the full RunRequest schema and response shapes.
  • The configuration guide covers the full six-layer cascade for setting a default model in TOML config files.
  • The events reference lists every event emitted during a run, including provider.resolved, usage.delta, and run.cost_limit_reached.