Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ agent:
# Use claude-fable-5 for Anthropic's most capable (Mythos-class) model.
# Note: ~2x the cost of Opus 4.8 ($10/$50 per MTok in/out).
cron_model: claude-sonnet-4-6 # Cheaper model for cron jobs
# Extra Anthropic models offered in the web composer's model picker (alongside
# `model`). Lets you switch a message to a stronger model like Fable 5 on demand.
selectable_models:
- claude-fable-5
title_model: claude-haiku-4-5-20251001 # Session title generation
max_turns: 50 # Max agentic turns per request
max_concurrent: 4 # Max concurrent agent sessions
Expand Down
4 changes: 4 additions & 0 deletions nerve/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ def from_dict(cls, d: dict) -> PromptRewriteConfig:
class AgentConfig:
model: str = "claude-opus-4-8"
cron_model: str = "claude-sonnet-4-6"
# Extra Anthropic chat models to offer in the composer's model picker
# alongside `model`. Empty = only the default model is selectable.
selectable_models: list[str] = field(default_factory=list)
title_model: str = "claude-haiku-4-5-20251001" # Session title generation
max_turns: int = 100
max_concurrent: int = 4
Expand Down Expand Up @@ -175,6 +178,7 @@ def from_dict(cls, d: dict) -> AgentConfig:
return cls(
model=d.get("model", "claude-opus-4-8"),
cron_model=d.get("cron_model", "claude-sonnet-4-6"),
selectable_models=list(d.get("selectable_models") or []),
title_model=d.get("title_model", "claude-haiku-4-5-20251001"),
max_turns=d.get("max_turns", 100),
max_concurrent=d.get("max_concurrent", 4),
Expand Down
8 changes: 7 additions & 1 deletion nerve/gateway/routes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@ async def list_models(user: dict = Depends(require_auth)):
config = get_config()
default_model = config.agent.model

# Default model first, then any extra selectable Anthropic models
# (config.agent.selectable_models), de-duplicated, order preserved.
anthropic_ids: list[str] = [default_model]
for m in config.agent.selectable_models:
if m and m not in anthropic_ids:
anthropic_ids.append(m)
models: list[dict[str, str]] = [
{"id": default_model, "provider": "anthropic"},
{"id": m, "provider": "anthropic"} for m in anthropic_ids
]

ollama_available = False
Expand Down
10 changes: 10 additions & 0 deletions tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ def test_agent_config_cron_effort_default_and_override():
assert cfg.effort == "max"


def test_agent_config_selectable_models():
# Defaults to an empty list (only the default model is selectable).
assert AgentConfig.from_dict({}).selectable_models == []
# Explicit list preserved; None coerces to [].
assert AgentConfig.from_dict(
{"selectable_models": ["claude-fable-5"]}
).selectable_models == ["claude-fable-5"]
assert AgentConfig.from_dict({"selectable_models": None}).selectable_models == []


# ---------------------------------------------------------------------------
# _iter_response_with_timeout — hung-CLI detection
# ---------------------------------------------------------------------------
Expand Down
Loading