mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
chore(models): move the opus alias to claude-opus-5
Claude Opus 5 released today — same $5/$25 sticker, 1M context; Opus 4.8 moves to legacy. The pricing table gains a dedicated claude-opus-5 fragment (the claude-opus-4 substring doesn't cover it, so without the row the fleet's Opus usage would silently cost-track as $0 — exactly what test_opus_is_priced now guards).
This commit is contained in:
+1
-1
@@ -30,7 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
|
||||
### Changed
|
||||
|
||||
- **The `opus` alias upgrades from Claude Opus 4.6 to Claude Opus 4.8.** `MODEL_MAP["opus"]` now resolves to `claude-opus-4-8` — the newest Opus tier, at the same price as 4.6 — so every Opus-routed role (PR reviewer, Auditor, Board, CEO, Intake, Secretary) rides it at next spawn. Pricing already covered the id via the `claude-opus-4` fragment; a new `test_opus_is_priced` guard keeps the alias from ever pointing at an unpriced model.
|
||||
- **The `opus` alias upgrades from Claude Opus 4.6 to Claude Opus 5.** `MODEL_MAP["opus"]` now resolves to `claude-opus-5` — the newest Opus tier, released 2026-07-25 at the same $5/$25 price — so every Opus-routed role (PR reviewer, Auditor, Board, CEO, Intake, Secretary) rides it at next spawn. The pricing table gains a dedicated `claude-opus-5` fragment (the `claude-opus-4` substring doesn't cover it — without the row, Opus fleet usage would silently cost-track as $0), and a new `test_opus_is_priced` guard keeps the alias from ever pointing at an unpriced model.
|
||||
- **Panel dialogs converge on one shape: one disclosure primitive, DialogFooter everywhere, three dialog widths.** `collapsible-section` becomes the single sectioned-disclosure primitive — task dialogs' raw `Collapsible` and create-project's ad-hoc `showAdvanced` both move onto it; every hand-rolled dialog footer becomes `DialogFooter`; dialog widths collapse from ten ad-hoc classes to three named sizes, with deliberate outliers annotated. No behavioral change.
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -79,7 +79,7 @@ pnpm test
|
||||
| RAG Engine | in-house (asyncpg + pgvector, hybrid retrieval) |
|
||||
| Cache/Queue | Redis |
|
||||
| Container Runtime | Docker + Docker Compose |
|
||||
| Cloud LLM | Claude API (claude-opus-4-8) + xAI Grok (official `grok` CLI, SuperGrok subscription) + OpenAI (official `codex` CLI, ChatGPT subscription) + Google Gemini (official `gemini` CLI, OAuth login) |
|
||||
| Cloud LLM | Claude API (claude-opus-5) + xAI Grok (official `grok` CLI, SuperGrok subscription) + OpenAI (official `codex` CLI, ChatGPT subscription) + Google Gemini (official `gemini` CLI, OAuth login) |
|
||||
| Local LLM | Ollama (glm-5.2:cloud for RAG/hybrid retrieval) |
|
||||
| Embeddings | qwen3-embedding:0.6b (1024 dim) |
|
||||
| Frontend | Next.js 16 + TypeScript + Tailwind + Radix UI (in `panel/`) |
|
||||
|
||||
@@ -5134,7 +5134,7 @@ The Pydantic/dataclass domain surface of RoboCo — the typed contract the API,
|
||||
| `OrchestratorAgentState` | StrEnum | runtime.py:15 | offline/starting/active/waiting_short/waiting_long/idle/stopping |
|
||||
| `AgentInstance` | dataclass | runtime.py:70 | Running Claude Code container record + `usage_session_id` |
|
||||
| `SpawnGitContext` | dataclass | runtime.py:28 | Git context for spawn (project_slug, branch, `task_short_id` for worktree) |
|
||||
| `MODEL_MAP` | dict | runtime.py:106 | short-name→full Claude id (opus→claude-opus-4-8, sonnet→claude-sonnet-5, haiku→…) |
|
||||
| `MODEL_MAP` | dict | runtime.py:106 | short-name→full Claude id (opus→claude-opus-5, sonnet→claude-sonnet-5, haiku→…) |
|
||||
| `ROLE_MODEL_MAP` | dict | runtime.py:120 | per-role default tier: developer/qa/documenter/cell_pm/main_pm→sonnet, pr_reviewer/auditor/board/ceo/prompter/secretary→opus. **Haiku retired from every delivery-lifecycle role (#680, 2026-07-24)**: qa/documenter moved haiku→sonnet — haiku can't reliably emit the structured envelopes the lifecycle runs on (`pass_review`'s per-AC `criteria_verified`, `delegate`'s `covers_parent_criteria`, the findings ledger), so a haiku QA/PM claims, gets validation-rejected, idles, respawns, and loops without progress. A structured-verb capability floor (`_below_capability_floor`/`_floor_below_capability`, `roboco/services/llm.py`) additionally upgrades ANY below-floor Anthropic assignment to sonnet at resolution time — from a pin, a plain ROLE row, or a future map edit — so the floor can't be silently reintroduced. Non-Anthropic providers are untouched (an Anthropic-tier floor, not a provider policy). |
|
||||
| `ROLE_EFFORT_MAP` | dict | runtime.py | per-role `CLAUDE_CODE_EFFORT_LEVEL` override injected at spawn; **empty/inert by default** (opt-in per role after verifying the level moves usage) |
|
||||
| `MODEL_CATALOG` | tuple | llm_catalog.py:67 | Settings-dropdown source of truth; Anthropic entries derived from `MODEL_MAP` |
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ The Pydantic/dataclass domain surface of RoboCo — the typed contract the API,
|
||||
| `OrchestratorAgentState` | StrEnum | runtime.py:15 | offline/starting/active/waiting_short/waiting_long/idle/stopping |
|
||||
| `AgentInstance` | dataclass | runtime.py:70 | Running Claude Code container record + `usage_session_id` |
|
||||
| `SpawnGitContext` | dataclass | runtime.py:28 | Git context for spawn (project_slug, branch, `task_short_id` for worktree) |
|
||||
| `MODEL_MAP` | dict | runtime.py:106 | short-name→full Claude id (opus→claude-opus-4-8, sonnet→claude-sonnet-5, haiku→…) |
|
||||
| `MODEL_MAP` | dict | runtime.py:106 | short-name→full Claude id (opus→claude-opus-5, sonnet→claude-sonnet-5, haiku→…) |
|
||||
| `ROLE_MODEL_MAP` | dict | runtime.py:120 | per-role default tier: developer/qa/documenter/cell_pm/main_pm→sonnet, pr_reviewer/auditor/board/ceo/prompter/secretary→opus. **Haiku retired from every delivery-lifecycle role (#680, 2026-07-24)**: qa/documenter moved haiku→sonnet — haiku can't reliably emit the structured envelopes the lifecycle runs on (`pass_review`'s per-AC `criteria_verified`, `delegate`'s `covers_parent_criteria`, the findings ledger), so a haiku QA/PM claims, gets validation-rejected, idles, respawns, and loops without progress. A structured-verb capability floor (`_below_capability_floor`/`_floor_below_capability`, `roboco/services/llm.py`) additionally upgrades ANY below-floor Anthropic assignment to sonnet at resolution time — from a pin, a plain ROLE row, or a future map edit — so the floor can't be silently reintroduced. Non-Anthropic providers are untouched (an Anthropic-tier floor, not a provider policy). |
|
||||
| `ROLE_EFFORT_MAP` | dict | runtime.py | per-role `CLAUDE_CODE_EFFORT_LEVEL` override injected at spawn; **empty/inert by default** (opt-in per role after verifying the level moves usage) |
|
||||
| `MODEL_CATALOG` | tuple | llm_catalog.py:67 | Settings-dropdown source of truth; Anthropic entries derived from `MODEL_MAP` |
|
||||
|
||||
@@ -33,9 +33,9 @@ const {
|
||||
} = vi.hoisted(() => ({
|
||||
catalog: vi.fn(async () => [
|
||||
{
|
||||
model_name: "claude-opus-4-8",
|
||||
model_name: "claude-opus-5",
|
||||
provider_type: "anthropic",
|
||||
display_name: "Claude Opus 4.8",
|
||||
display_name: "Claude Opus 5",
|
||||
},
|
||||
{
|
||||
model_name: "grok-build-0.1",
|
||||
@@ -719,14 +719,14 @@ describe("AIRoutingCard", () => {
|
||||
|
||||
const devLow = screen.getByTestId("complexity-select-developer-low");
|
||||
fireEvent.click(
|
||||
await within(devLow).findByRole("option", { name: "Claude Opus 4.8" }),
|
||||
await within(devLow).findByRole("option", { name: "Claude Opus 5" }),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(setComplexityOverride).toHaveBeenCalledWith({
|
||||
role: "developer",
|
||||
complexity: "low",
|
||||
model_name: "claude-opus-4-8",
|
||||
model_name: "claude-opus-5",
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -764,7 +764,7 @@ describe("AIRoutingCard", () => {
|
||||
|
||||
const devLow = screen.getByTestId("complexity-select-developer-low");
|
||||
fireEvent.click(
|
||||
await within(devLow).findByRole("option", { name: "Claude Opus 4.8" }),
|
||||
await within(devLow).findByRole("option", { name: "Claude Opus 5" }),
|
||||
);
|
||||
|
||||
await waitFor(() => expect(setComplexityOverride).toHaveBeenCalled());
|
||||
|
||||
@@ -105,7 +105,7 @@ export const DEMO_TEAM_USAGE: TeamUsageRow[] = (
|
||||
|
||||
export const DEMO_MODEL_USAGE: ModelUsageSlice[] = (
|
||||
[
|
||||
["claude-opus-4-8", 42.1],
|
||||
["claude-opus-5", 42.1],
|
||||
["glm-5.2:cloud", 16.8],
|
||||
["grok-build", 7.64],
|
||||
] as Array<[string, number]>
|
||||
|
||||
@@ -57,11 +57,14 @@ _ANTHROPIC_FRAGMENTS = ("claude", "opus", "sonnet", "haiku")
|
||||
# Cache read is charged at ~10 % of the input price.
|
||||
# Cache write is charged at ~25 % of the input price.
|
||||
#
|
||||
# Match on *substring* of model name so "claude-opus-4-8" and "opus" both
|
||||
# Match on *substring* of model name so "claude-opus-5" and "opus" both
|
||||
# resolve to the same tier.
|
||||
# ---------------------------------------------------------------------------
|
||||
_PRICING: list[tuple[str, float, float, float, float]] = [
|
||||
# (fragment, input/1M, output/1M, cache_read/1M, cache_write/1M)
|
||||
# Opus 5 — same sticker as the Opus 4 family; needs its own fragment
|
||||
# because "claude-opus-4" doesn't substring-match it.
|
||||
("claude-opus-5", 5.00, 25.00, 0.50, 6.25),
|
||||
# Opus 4 family
|
||||
("claude-opus-4", 5.00, 25.00, 0.50, 6.25),
|
||||
# Sonnet 4 / 3.7 / 3.5 family
|
||||
|
||||
@@ -7,7 +7,7 @@ the router maps back to the correct pre-seeded provider row.
|
||||
|
||||
**Anthropic entries derive from `runtime.MODEL_MAP`** — that's the single
|
||||
source of truth for which Claude versions are supported. Bumping a model
|
||||
version there (e.g. `claude-opus-4-8` → a newer id) updates the
|
||||
version there (e.g. `claude-opus-5` → a newer id) updates the
|
||||
catalog automatically without a second edit here.
|
||||
|
||||
Ollama Cloud entries are hand-maintained because Ollama's cloud tags
|
||||
@@ -46,7 +46,7 @@ def _build_anthropic_entries() -> tuple[CatalogEntry, ...]:
|
||||
|
||||
Keeps the UI honest: a user picking "Claude Opus" sees exactly which
|
||||
underlying Claude Code model id will be used at spawn
|
||||
(e.g. "Claude Opus · claude-opus-4-8"), so version bumps in
|
||||
(e.g. "Claude Opus · claude-opus-5"), so version bumps in
|
||||
`runtime.MODEL_MAP` are immediately visible.
|
||||
"""
|
||||
entries: list[CatalogEntry] = []
|
||||
|
||||
@@ -110,7 +110,7 @@ class WaitingRecord:
|
||||
|
||||
# Model mapping for cost optimization
|
||||
MODEL_MAP: dict[str, str] = {
|
||||
"opus": "claude-opus-4-8",
|
||||
"opus": "claude-opus-5",
|
||||
"sonnet": "claude-sonnet-5",
|
||||
"haiku": "claude-haiku-4-5-20251001",
|
||||
}
|
||||
|
||||
@@ -565,6 +565,7 @@ class TestProviderAwareness:
|
||||
for name in (
|
||||
"claude-opus-4-6",
|
||||
"claude-opus-4-8",
|
||||
"claude-opus-5",
|
||||
"claude-fable-5",
|
||||
"opus",
|
||||
"sonnet",
|
||||
|
||||
@@ -106,7 +106,7 @@ def test_build_anthropic_entries_skips_missing_models() -> None:
|
||||
# Patch MODEL_MAP to be missing one entry → loop hits the `continue` branch.
|
||||
with patch(
|
||||
"roboco.models.llm_catalog.MODEL_MAP",
|
||||
{"opus": "claude-opus-4-8"}, # sonnet+haiku missing
|
||||
{"opus": "claude-opus-5"}, # sonnet+haiku missing
|
||||
):
|
||||
entries = _build_anthropic_entries()
|
||||
# Only one entry survives — opus.
|
||||
|
||||
@@ -12,10 +12,9 @@ def test_sonnet_alias_resolves_to_sonnet_5() -> None:
|
||||
assert MODEL_MAP["sonnet"] == "claude-sonnet-5"
|
||||
|
||||
|
||||
def test_opus_alias_resolves_to_opus_4_8() -> None:
|
||||
# CEO upgrade to the newest Opus tier ("Opus 5" does not exist — the
|
||||
# Claude 5 family is Fable/Mythos/Sonnet; Opus tops out at 4.8).
|
||||
assert MODEL_MAP["opus"] == "claude-opus-4-8"
|
||||
def test_opus_alias_resolves_to_opus_5() -> None:
|
||||
# CEO upgrade to the newest Opus tier (Opus 5, released 2026-07-25).
|
||||
assert MODEL_MAP["opus"] == "claude-opus-5"
|
||||
|
||||
|
||||
def test_sonnet_5_is_priced() -> None:
|
||||
|
||||
@@ -56,7 +56,7 @@ def _spec(**overrides: Any) -> _IntakeRunSpec:
|
||||
},
|
||||
"session_id": "sess-abc",
|
||||
"cwd": "/data/workspaces/roboco/board/intake-1",
|
||||
"cli_model": "claude-opus-4-8",
|
||||
"cli_model": "claude-opus-5",
|
||||
"api_url": "http://roboco-orchestrator:8000",
|
||||
"provider_base_url": None,
|
||||
"provider_auth_token": None,
|
||||
@@ -108,7 +108,7 @@ class TestBuildIntakeRunCmd:
|
||||
assert "ROBOCO_WORKSPACE=/data/workspaces/roboco/board/intake-1" in cmd
|
||||
assert "ROBOCO_API_URL=http://roboco-orchestrator:8000" in cmd
|
||||
assert "ROBOCO_AGENT_ID=intake-1" in cmd
|
||||
assert "CLAUDE_CODE_SUBAGENT_MODEL=claude-opus-4-8" in cmd
|
||||
assert "CLAUDE_CODE_SUBAGENT_MODEL=claude-opus-5" in cmd
|
||||
|
||||
def test_mounts_prompt_and_workspaces(self) -> None:
|
||||
cmd = AgentOrchestrator._build_intake_run_cmd(_spec())
|
||||
|
||||
@@ -99,7 +99,7 @@ class TestBuildManifestForAgent:
|
||||
mock_settings.manifest_host_dir = str(tmp_path)
|
||||
mock_settings.workspaces_root = str(tmp_path / "workspaces")
|
||||
|
||||
result = _build_manifest_for_agent("be-dev-1", "claude-opus-4-8")
|
||||
result = _build_manifest_for_agent("be-dev-1", "claude-opus-5")
|
||||
|
||||
assert result is not None
|
||||
data = json.loads(result.read_text())
|
||||
|
||||
@@ -87,7 +87,7 @@ class TestBuildForRole:
|
||||
role=role,
|
||||
team="board",
|
||||
workspace_path=Path("/tmp/x"),
|
||||
agent_model="claude-opus-4-8",
|
||||
agent_model="claude-opus-5",
|
||||
)
|
||||
)
|
||||
assert "read_messages" in m.do_tools
|
||||
|
||||
@@ -182,7 +182,7 @@ async def test_log_agent_event_resolves_slug_to_uuid(
|
||||
event_type="agent.spawned",
|
||||
agent_slug=slug,
|
||||
task_id=task_id,
|
||||
details={"container_id": "abc123def456", "model": "claude-opus-4-8"},
|
||||
details={"container_id": "abc123def456", "model": "claude-opus-5"},
|
||||
)
|
||||
|
||||
# Read back the row.
|
||||
|
||||
@@ -945,7 +945,7 @@ class TestGetRecentSessions:
|
||||
row = MagicMock()
|
||||
row.id = sid
|
||||
row.agent_slug = "product-owner"
|
||||
row.model = "claude-opus-4-8"
|
||||
row.model = "claude-opus-5"
|
||||
row.started_at = datetime.datetime(2026, 6, 11, 20, 41, tzinfo=datetime.UTC)
|
||||
row.ended_at = datetime.datetime(2026, 6, 11, 20, 42, tzinfo=datetime.UTC)
|
||||
row.tokens_input = exp_in
|
||||
@@ -966,7 +966,7 @@ class TestGetRecentSessions:
|
||||
s = out[0]
|
||||
assert s["id"] == str(sid)
|
||||
assert s["agent_slug"] == "product-owner"
|
||||
assert s["model"] == "claude-opus-4-8"
|
||||
assert s["model"] == "claude-opus-5"
|
||||
assert s["tokens_input"] == exp_in
|
||||
assert s["tokens_output"] == exp_out
|
||||
assert s["tokens_cache"] == exp_cr + exp_cw
|
||||
|
||||
Reference in New Issue
Block a user