Skip to content
Closed
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
42 changes: 38 additions & 4 deletions nerve/agent/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,10 +739,9 @@ def _build_options(
self.config.agent.thinking,
model or self.config.agent.model,
)
effort = (
self.config.agent.effort
if self.config.agent.effort in ("low", "medium", "high", "xhigh", "max")
else None
effort = self._effective_effort(
self.config.agent.effort,
model or self.config.agent.model,
)
betas = (
["context-1m-2025-08-07"] if self.config.agent.context_1m else []
Expand Down Expand Up @@ -954,6 +953,41 @@ def _parse_thinking_config(value: str, model: str | None = None) -> dict | None:
logger.warning("Unknown thinking config '%s', using adaptive", value)
return {"type": "adaptive"}

# Effort levels accepted per Claude model — substring-matched against the
# full model name so dated aliases (e.g. "claude-opus-4-7-20260416") resolve.
# Ordered most-specific to least-specific; first match wins. Mirrors the
# pattern used by MODEL_PRICING in nerve/db/usage.py.
_MODEL_EFFORT_LEVELS: dict[str, tuple[str, ...]] = {
"opus-4-7": ("low", "medium", "high", "xhigh", "max"),
"opus-4-6": ("low", "medium", "high", "max"),
"sonnet-4-6": ("low", "medium", "high"),
}
_EFFORT_RANK: tuple[str, ...] = ("low", "medium", "high", "xhigh", "max")

@staticmethod
def _effective_effort(value: str, model: str | None = None) -> str | None:
"""Return ``value`` capped to the highest effort level ``model`` supports."""
if value not in AgentEngine._EFFORT_RANK:
return None
allowed: tuple[str, ...] | None = None
if model:
m = model.lower()
for key, levels in AgentEngine._MODEL_EFFORT_LEVELS.items():
if key in m:
allowed = levels
break
if not allowed or value in allowed:
return value
requested_rank = AgentEngine._EFFORT_RANK.index(value)
for level in reversed(AgentEngine._EFFORT_RANK[: requested_rank + 1]):
if level in allowed:
logger.debug(
"Capped effort %r to %r for model %r (model caps at %r)",
value, level, model, allowed[-1],
)
return level
return None

# ------------------------------------------------------------------ #
# SDK client lifecycle #
# ------------------------------------------------------------------ #
Expand Down
43 changes: 43 additions & 0 deletions tests/test_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Tests for nerve.agent.engine — pure helpers (no SDK state)."""

import pytest

from nerve.agent.engine import AgentEngine


@pytest.mark.parametrize(
"value, model, expected",
[
# Opus 4.7 supports every level
("max", "claude-opus-4-7", "max"),
("xhigh", "claude-opus-4-7", "xhigh"),
("high", "claude-opus-4-7", "high"),
# Dated alias resolves via substring match
("max", "claude-opus-4-7-20260416", "max"),
# Opus 4.6: max OK, xhigh caps to high (not registered)
("max", "claude-opus-4-6", "max"),
("xhigh", "claude-opus-4-6", "high"),
# Sonnet 4.6 tops out at high
("max", "claude-sonnet-4-6", "high"),
("xhigh", "claude-sonnet-4-6", "high"),
("high", "claude-sonnet-4-6", "high"),
("medium", "claude-sonnet-4-6", "medium"),
("low", "claude-sonnet-4-6", "low"),
# Unknown models (including Haiku which uses budget_tokens, not levels)
# pass through unchanged — capping is a no-op for non-level-based thinking
("max", "claude-haiku-4-5-20251001", "max"),
("max", "some-future-model", "max"),
("max", None, "max"),
("max", "", "max"),
# Invalid effort string → None (same as the pre-existing behaviour)
("invalid", "claude-opus-4-7", None),
("", "claude-sonnet-4-6", None),
],
)
def test_effective_effort(value, model, expected):
assert AgentEngine._effective_effort(value, model) == expected


def test_effective_effort_model_default_none():
# Signature symmetry with _parse_thinking_config
assert AgentEngine._effective_effort("max") == "max"