The SDK uses a config-driven model registry so that new Claude models can be
added without recompiling application code. All model validation, listing, and
suggestion logic reads from Application config at runtime.
Default Configuration
The built-in defaults are set in config/config.exs:
config :claude_agent_sdk, :models, %{
short_forms: %{
"opus" => "opus",
"sonnet" => "sonnet",
"haiku" => "haiku",
"opus[1m]" => "opus[1m]",
"sonnet[1m]" => "sonnet[1m]"
},
full_ids: %{
"claude-opus-4-6" => "claude-opus-4-6",
"claude-sonnet-4-6" => "claude-sonnet-4-6",
"claude-haiku-4-5-20251001" => "claude-haiku-4-5-20251001",
"claude-opus-4-6[1m]" => "claude-opus-4-6[1m]",
"claude-sonnet-4-6[1m]" => "claude-sonnet-4-6[1m]"
},
default: "opus"
}| Key | Description |
|---|---|
short_forms | CLI-friendly aliases ("opus", "sonnet", etc.) |
full_ids | Versioned model identifiers as published by Anthropic |
default | The model used when none is specified |
Both maps use name => name identity mappings because the Claude CLI
accepts these strings directly and handles any internal resolution.
Using Models
In Options
# Short form
options = %ClaudeAgentSDK.Options{model: "opus"}
# Full ID
options = %ClaudeAgentSDK.Options{model: "claude-opus-4-6"}In Agents
agent = ClaudeAgentSDK.Agent.new(
description: "Research specialist",
prompt: "You excel at research",
model: "sonnet"
)Validation
{:ok, "opus"} = ClaudeAgentSDK.Model.validate("opus")
{:error, :invalid_model} = ClaudeAgentSDK.Model.validate("gpt-5")Listing
ClaudeAgentSDK.Model.list_models()
# => ["claude-haiku-4-5-20251001", "claude-opus-4-6", "haiku", "opus", ...]
ClaudeAgentSDK.Model.short_forms()
# => ["haiku", "opus", "sonnet", "sonnet[1m]"]
ClaudeAgentSDK.Model.full_ids()
# => ["claude-haiku-4-5-20251001", "claude-opus-4-6", ...]Suggestions
When a user provides an invalid model name the SDK can suggest corrections:
ClaudeAgentSDK.Model.suggest("opuss")
# => ["opus"]Adding Custom Models at Runtime
If Anthropic ships a new model before the SDK publishes an update, add it at boot time without waiting for a library release:
# In config/runtime.exs or Application.start callback:
config = Application.get_env(:claude_agent_sdk, :models)
updated =
config
|> Map.update!(:full_ids, &Map.put(&1, "claude-opus-5-20260301", "claude-opus-5-20260301"))
|> Map.update!(:short_forms, &Map.put(&1, "opus5", "opus5"))
Application.put_env(:claude_agent_sdk, :models, updated)After this, Model.validate("opus5") and Model.validate("claude-opus-5-20260301")
will both return {:ok, ...}.
Overriding the Default Model
config = Application.get_env(:claude_agent_sdk, :models)
Application.put_env(:claude_agent_sdk, :models, %{config | default: "opus"})Effort Level
Control how much reasoning effort Claude applies:
# Standard effort levels (all Opus and Sonnet models)
options = %ClaudeAgentSDK.Options{
model: "sonnet",
effort: :high # :low | :medium | :high | :max
}
# Maximum effort (Opus only)
options = %ClaudeAgentSDK.Options{
model: "opus",
effort: :max
}This emits --effort <level> to the CLI.
Effort Levels
| Level | Models | Description |
|---|---|---|
:low | Opus, Sonnet | Minimal thinking, fewer tool calls, terse output |
:medium | Opus, Sonnet | Balanced cost/performance (recommended default for Sonnet) |
:high | Opus, Sonnet | Deep thinking, detailed responses (API default) |
:max | Opus only | Absolute maximum capability, no token constraints |
Model Gating
- Haiku: Effort is not supported. The SDK logs a warning and silently omits the
--effortflag. - Sonnet: Supports
:low,:medium,:high. The SDK treats:maxas unsupported, logs a warning, and omits the--effortflag. - Opus: Supports all four levels including
:max. - nil model: The SDK resolves the configured default model first, then applies the same gating rules.
- Invalid effort values raise
ArgumentErrorwhen options are built or converted to CLI arguments.
See
examples/effort_gating_live.exsfor a runnable example covering supported and gated effort cases. Seeexamples/max_effort_opus_live.exsfor a standalone:maxeffort demo with Opus and Opus[1m] (not inrun_all.sh— expensive).
Thinking Configuration
The thinking option provides structured control over extended thinking:
# Adaptive (default budget 32000 tokens)
options = %ClaudeAgentSDK.Options{thinking: %{type: :adaptive}}
# Explicit budget
options = %ClaudeAgentSDK.Options{thinking: %{type: :enabled, budget_tokens: 16_000}}
# Disabled
options = %ClaudeAgentSDK.Options{thinking: %{type: :disabled}}Resolution rules:
thinkingtakes precedence overmax_thinking_tokens:adaptiveusesmax_thinking_tokensas fallback, or 32000 if nil:enabledusesbudget_tokensdirectly:disabledemits--max-thinking-tokens 0nilfalls back to rawmax_thinking_tokens
The max_thinking_tokens field is still supported for backward compatibility:
options = %ClaudeAgentSDK.Options{
model: "sonnet",
max_thinking_tokens: 8192
}Testing
Test code uses ClaudeAgentSDK.Test.ModelFixtures to avoid hardcoding model
strings in assertions:
import ClaudeAgentSDK.Test.ModelFixtures
test "handles message start" do
event = %{"type" => "message_start", "message" => %{"model" => test_model()}}
# ...
end| Helper | Value | Purpose |
|---|---|---|
test_model() | "test-model-alpha" | Primary fixture model |
test_model_alt() | "test-model-beta" | When two distinct models are needed |
real_default_model() | Reads live config | Tests that need an actual registry entry |
Architecture
graph LR
Config["config/config.exs<br/>:models map"] --> Model["Model module<br/>known_models/0"]
Runtime["runtime.exs /<br/>Application.put_env"] --> Model
Model --> Validate["validate/1"]
Model --> List["list_models/0"]
Model --> Suggest["suggest/1"]
Model --> Default["default_model/0"]
Mock["Mock module"] --> Default
Options["Options struct"] --> ValidateThe Model module never holds state of its own. Every call reads from
Application.get_env(:claude_agent_sdk, :models), making the registry
fully dynamic at runtime.