roadmodel 0.2.61__tar.gz → 0.2.63__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {roadmodel-0.2.61 → roadmodel-0.2.63}/PKG-INFO +1 -1
  2. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/catalog.json +149 -7
  3. {roadmodel-0.2.61 → roadmodel-0.2.63}/pyproject.toml +1 -1
  4. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/catalog.json +149 -7
  5. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/__init__.py +1 -1
  6. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/recommend.py +87 -26
  7. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/scoring.py +212 -17
  8. {roadmodel-0.2.61 → roadmodel-0.2.63}/.gitignore +0 -0
  9. {roadmodel-0.2.61 → roadmodel-0.2.63}/LICENSE +0 -0
  10. {roadmodel-0.2.61 → roadmodel-0.2.63}/NOTICE +0 -0
  11. {roadmodel-0.2.61 → roadmodel-0.2.63}/README.md +0 -0
  12. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/benchmarks.json +0 -0
  13. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/model-selector.txt +0 -0
  14. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/model-tier-cost-scale.md +0 -0
  15. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/settings-display.md +0 -0
  16. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/templates/phase-roadmap-template.md +0 -0
  17. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/templates/planning-kit-how-to-use.md +0 -0
  18. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/templates/project-roadmap-template.md +0 -0
  19. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/templates/prompt-phase-roadmap.md +0 -0
  20. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/templates/prompt-project-roadmap.md +0 -0
  21. {roadmodel-0.2.61 → roadmodel-0.2.63}/docs/user-context.example.md +0 -0
  22. {roadmodel-0.2.61 → roadmodel-0.2.63}/hatch_build.py +0 -0
  23. {roadmodel-0.2.61 → roadmodel-0.2.63}/infra/README.md +0 -0
  24. {roadmodel-0.2.61 → roadmodel-0.2.63}/infra/gcp-killswitch/README.md +0 -0
  25. {roadmodel-0.2.61 → roadmodel-0.2.63}/infra/supabase/README.md +0 -0
  26. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/benchmarks.json +0 -0
  27. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/model-selector.txt +0 -0
  28. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/model-tier-cost-scale.md +0 -0
  29. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/phase-roadmap-template.md +0 -0
  30. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/planning-kit-how-to-use.md +0 -0
  31. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/project-roadmap-template.md +0 -0
  32. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/prompt-phase-roadmap.md +0 -0
  33. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/prompt-project-roadmap.md +0 -0
  34. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/settings-display.md +0 -0
  35. {roadmodel-0.2.61 → roadmodel-0.2.63}/roadmodel/data/user-context.example.md +0 -0
  36. {roadmodel-0.2.61 → roadmodel-0.2.63}/scripts/update_projects.py +0 -0
  37. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/__main__.py +0 -0
  38. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/cli.py +0 -0
  39. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/config.py +0 -0
  40. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/cost.py +0 -0
  41. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/errors.py +0 -0
  42. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/mcp_server.py +0 -0
  43. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/providers/__init__.py +0 -0
  44. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/providers/anthropic.py +0 -0
  45. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/providers/google.py +0 -0
  46. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/providers/openai.py +0 -0
  47. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/providers/openai_compatible.py +0 -0
  48. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/providers/registry.py +0 -0
  49. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/usage.py +0 -0
  50. {roadmodel-0.2.61 → roadmodel-0.2.63}/src/roadmodel/user_context.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: roadmodel
3
- Version: 0.2.61
3
+ Version: 0.2.63
4
4
  Summary: BYO-key CLI that recommends the right AI model, platform, and settings for a prompt.
5
5
  Project-URL: Homepage, https://roadmodel.ai
6
6
  Project-URL: Repository, https://github.com/nathanramoscfa/roadmodel
@@ -1,7 +1,7 @@
1
1
  {
2
- "schema_version": "3",
2
+ "schema_version": "4",
3
3
  "output_contract_version": 2,
4
- "generated_at_utc": "2026-10-02T17:12:09Z",
4
+ "generated_at_utc": "2026-10-02T18:08:21Z",
5
5
  "source_doc_sha256": {
6
6
  "model-selector.txt": "81c5530a28521df18ebe14810e56cd72e65f1e30ad93d0522d31dbf362516e93",
7
7
  "model-tier-cost-scale.md": "2b3b0eb82237eb5cb5bf468a3fe971bcaf0160ad7d0bc700698d2c16640bc41d"
@@ -1523,7 +1523,78 @@
1523
1523
  "exposes_max_mode": "no",
1524
1524
  "exposes_thinking": "yes",
1525
1525
  "exposes_orchestration": "yes",
1526
- "best_for": "Default for Claude coding or terminal tasks when a claude.ai Max subscription is active \u2014 $0 marginal cost until the Max budget is exhausted, full tool-use surface, runs as a CLI, IDE extension inside Cursor, native VS Code extension, and as of 2.1.170+ also ships Claude Fable 5 (Mythos-class) plus a first-class Claude in Chrome browser-control integration (GA in 2.1.198), with Claude Sonnet 5 now the default model (2.1.197+) at native 1M-token context; as of 2.1.219 Claude Opus 5 ships as the new default Opus (1M context, fast mode at $10/$50 per Mtok) and Opus 4.7 is removed from fast mode so `/fast` now applies to Opus 5 and Opus 4.8; 2.1.280 ships Claude Opus 5.5 as the new default Opus (1M context, $4/$20 per Mtok with $0.20/Mtok cache reads) and changes the Pro and Team Standard default model from Sonnet to Opus (matching Max / Team Premium / Enterprise); 2.1.220\u20132.1.280 are bug-fix / reliability / self-hosted-runner / UI-polish / plugin-safety / VS Code / Claude apps / AGENTS.md / auto-mode-server-classifier releases with no effort/thinking surface changes beyond the 2.1.267 `maxEffortLevel` cap and the 2.1.269 `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) knob for the Workflow tool's per-run concurrent-agent limit noted below (2.1.224 adds `claude self-hosted-runner` for turning your own machines into places where Claude Code web/mobile/desktop sessions can run, cross-session `SendMessage`, and archive-plugin sources; 2.1.225 adds gateway spend-limit reporting to the usage warning; 2.1.227 polishes the slash-command menu and fixes feature-flag evaluation for expired login tokens; 2.1.257 ships Claude Fable 5.1 as the new default Fable at 1M context and $10/$50 per Mtok; 2.1.259 adds `managedMcpServers` policy and `--permission-prompts none` for unattended headless hosts; 2.1.260 adds an inline `/diff` panel and a headless text form of `/advisor`; 2.1.261 adds `/skill-doctor` and `bashOutputMaxChars` / `taskOutputMaxChars` settings; 2.1.263 is a reliability release; 2.1.265 adds a `--plugin-dir` folder-of-plugins mode and tightens plugin path containment; 2.1.266 restores gateway/proxy behavior when `CLAUDE_CODE_USE_GATEWAY` is set alongside an API key or `apiKeyHelper`; 2.1.267 adds a `maxEffortLevel` setting \u2014 top-level or per model under `modelSettings` \u2014 that CAPS the effort level on every provider including Bedrock, Vertex, and Foundry while still letting users pick any lower level, plus `--system-prompt-snapshot off` for iterating on prompt text and a fix for `effort:` frontmatter on custom commands, skills, and subagents being ignored on models whose default effort is still pinned; 2.1.269 adds `claude plugin eval` for scored plugin eval suites, `/output-style [name]` to list and switch output styles (including over Remote Control and in headless sessions), a Bash-tool file-edit diff (setting `bashEditDiffEnabled`), `OTEL_METRICS_INCLUDE_REPOSITORY` for tagging OpenTelemetry metrics and events with `vcs.*` attributes, `CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY_TIMEOUT_MS` to extend the LLM gateway `/v1/models` discovery timeout, and `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) to raise the Workflow tool's per-run concurrent-agent limit for inference-bound fan-outs \u2014 none of which change the `/effort` vocabulary; 2.1.270 fixes read-only git commands in Bash unexpectedly asking for permission after a session had been running for a while, a 2.1.269 regression, with no surface changes; 2.1.271 adds fast mode in Claude Code Remote (cloud and self-hosted runner) sessions where organization policy allows it, mouse support in the `/config` panel, per-command Bash / PowerShell / Monitor `allowed_domains` in auto mode with sandboxing, agent-frontmatter and `--agents` JSON `omitClaudeMd` to let custom / plugin subagents run without CLAUDE.md files (managed policy still loads), and `--accept-command <sha256>` for `claude plugin install` / `update`; 2.1.272 is a bug-fix / reliability release; 2.1.273 adds `x-claude-code-*` LLM-gateway request-hint headers behind `CLAUDE_CODE_GATEWAY_HINT_HEADERS=1`, forking a `--remote-control` / `/remote-control` session from the Claude app as a local background session, and MCP-server-disconnected notifications, plus fixes for permission handling, credential/auth error messages, prompt-cache retention across `/login` / `/upgrade` / `/extra-usage`, and gateway 401 messaging \u2014 no effort/thinking surface changes; 2.1.274 adds a critical-memory-usage warning with recovery steps, `CLAUDE_CODE_MCP_STARTUP_WAIT_MS` to bound how long the first non-interactive turn waits for connecting MCP servers, an `effort` attribute on the `claude_code.llm_request` OpenTelemetry trace span, plus assorted VS Code, Claude Code on the web, Claude Tag, and Code Review fixes and improvements \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.275 adds signed-in account confirmation for Claude apps gateway sign-in, a send-now key (ctrl+enter, or ctrl+x ctrl+s) that flushes queued messages mid-turn, syncing of claude.ai-account skills and plugins into terminal sessions (opt out with `syncClaudeAiSkills: false` / `syncClaudeAiPlugins: false`), `/plugin install <plugin> --marketplace <source>`, and a startup warning when `otelHeadersHelper` fails, plus a broad set of reliability and rendering fixes \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.276 is a hotfix for a 2.1.275 regression where every request behind a proxy or gateway failed with `400 \u2026 Input tag 'advisor_20260301'`, with no surface changes; 2.1.277 adds AGENTS.md fallback support (in a project with no CLAUDE.md, Claude Code reads AGENTS.md instead \u2014 configurable under \"Project instructions\" in `/config`; extended by 2.1.281 to Bedrock, Vertex, Foundry, LLM gateways and telemetry-disabled sessions), `CLAUDE_GATEWAY_PROXY_IS_EGRESS_BOUNDARY=1` for Claude apps gateways whose only egress is a forward proxy, an optional `headers:` map on Claude apps gateway upstreams, plus assorted reliability, plugin, background-task, and VS Code / Claude Code on the web / Claude Tag fixes \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.278 changes auto mode for Claude API and Enterprise users, and on Bedrock, Vertex, Foundry and gateways, to default to the server-side classifier that does not charge for classifier overhead (opt out on Bedrock, Vertex, Foundry and gateways with `CLAUDE_CODE_AUTO_MODE_SERVER=0`; warns on billed fallback) and adds an `Auto mode server` row to `/status` showing whether this session's auto mode classifier runs on the server \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.280 ships Claude Opus 5.5 as the new default Opus model at 1M context and $4/$20 per Mtok, changes the Pro and Team Standard default model from Sonnet to Opus (matching Max / Team Premium / Enterprise), and stops Opus 4.7, Opus 4.8 and Fable 5 from holding their launch-default effort over an explicit `/effort`, `effortLevel`, project or managed setting \u2014 the `/effort` vocabulary itself is unchanged; 2.1.281\u20132.1.285 are reliability / plugin / gateway / VS Code / Claude Tag / Artifact releases, with 2.1.284 adding Claude Sonnet 5.5 as the default Sonnet on the Anthropic API (1M context, $2/$10 per Mtok) and making Ultracode its own `/effort` toggle, and 2.1.285 adding `claude --desktop` to open the Claude desktop app on the current directory and an `allowedProviders` managed setting). Exposes the full `/effort` dial (low/medium/high/xhigh/max \u2014 Opus 4.6 and Sonnet 4.6 top out at max with no xhigh step; Opus 5, Opus 5.5, Opus 4.7, Opus 4.8, Sonnet 5, Sonnet 5.5, Fable 5, and Fable 5.1 expose the full range; effort levels a model does not support fall back to the highest supported level at or below the requested one; deployments may set `maxEffortLevel` to cap the maximum reachable level on every provider), the Option+T/Alt+T, `alwaysThinkingEnabled`, and `MAX_THINKING_TOKENS=0` on/off controls, plus Ultracode (session-wide Dynamic Workflows tied to xhigh; since 2.1.284 its own `/effort` toggle \u2014 Tab or `/effort ultracode [on|off]` \u2014 that keeps the current effort level, with only `claude --effort ultracode` also setting xhigh; set via `/effort ultracode` or `\"ultracode\": true`; 2.1.202+ adds a `/config` \"Dynamic workflow size\" advisory guideline, 2.1.219 defaults it to medium (aim for fewer than 15 agents) with the `workflowSizeGuideline` settings key to set it from any settings file, and 2.1.269 adds `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) to raise the per-run concurrent-agent cap for the Workflow tool) and the per-turn `ultrathink` keyword (`think`/`think hard`/`think more` are not recognized)."
1526
+ "best_for": "Default for Claude coding or terminal tasks when a claude.ai Max subscription is active \u2014 $0 marginal cost until the Max budget is exhausted, full tool-use surface, runs as a CLI, IDE extension inside Cursor, native VS Code extension, and as of 2.1.170+ also ships Claude Fable 5 (Mythos-class) plus a first-class Claude in Chrome browser-control integration (GA in 2.1.198), with Claude Sonnet 5 now the default model (2.1.197+) at native 1M-token context; as of 2.1.219 Claude Opus 5 ships as the new default Opus (1M context, fast mode at $10/$50 per Mtok) and Opus 4.7 is removed from fast mode so `/fast` now applies to Opus 5 and Opus 4.8; 2.1.280 ships Claude Opus 5.5 as the new default Opus (1M context, $4/$20 per Mtok with $0.20/Mtok cache reads) and changes the Pro and Team Standard default model from Sonnet to Opus (matching Max / Team Premium / Enterprise); 2.1.220\u20132.1.280 are bug-fix / reliability / self-hosted-runner / UI-polish / plugin-safety / VS Code / Claude apps / AGENTS.md / auto-mode-server-classifier releases with no effort/thinking surface changes beyond the 2.1.267 `maxEffortLevel` cap and the 2.1.269 `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) knob for the Workflow tool's per-run concurrent-agent limit noted below (2.1.224 adds `claude self-hosted-runner` for turning your own machines into places where Claude Code web/mobile/desktop sessions can run, cross-session `SendMessage`, and archive-plugin sources; 2.1.225 adds gateway spend-limit reporting to the usage warning; 2.1.227 polishes the slash-command menu and fixes feature-flag evaluation for expired login tokens; 2.1.257 ships Claude Fable 5.1 as the new default Fable at 1M context and $10/$50 per Mtok; 2.1.259 adds `managedMcpServers` policy and `--permission-prompts none` for unattended headless hosts; 2.1.260 adds an inline `/diff` panel and a headless text form of `/advisor`; 2.1.261 adds `/skill-doctor` and `bashOutputMaxChars` / `taskOutputMaxChars` settings; 2.1.263 is a reliability release; 2.1.265 adds a `--plugin-dir` folder-of-plugins mode and tightens plugin path containment; 2.1.266 restores gateway/proxy behavior when `CLAUDE_CODE_USE_GATEWAY` is set alongside an API key or `apiKeyHelper`; 2.1.267 adds a `maxEffortLevel` setting \u2014 top-level or per model under `modelSettings` \u2014 that CAPS the effort level on every provider including Bedrock, Vertex, and Foundry while still letting users pick any lower level, plus `--system-prompt-snapshot off` for iterating on prompt text and a fix for `effort:` frontmatter on custom commands, skills, and subagents being ignored on models whose default effort is still pinned; 2.1.269 adds `claude plugin eval` for scored plugin eval suites, `/output-style [name]` to list and switch output styles (including over Remote Control and in headless sessions), a Bash-tool file-edit diff (setting `bashEditDiffEnabled`), `OTEL_METRICS_INCLUDE_REPOSITORY` for tagging OpenTelemetry metrics and events with `vcs.*` attributes, `CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY_TIMEOUT_MS` to extend the LLM gateway `/v1/models` discovery timeout, and `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) to raise the Workflow tool's per-run concurrent-agent limit for inference-bound fan-outs \u2014 none of which change the `/effort` vocabulary; 2.1.270 fixes read-only git commands in Bash unexpectedly asking for permission after a session had been running for a while, a 2.1.269 regression, with no surface changes; 2.1.271 adds fast mode in Claude Code Remote (cloud and self-hosted runner) sessions where organization policy allows it, mouse support in the `/config` panel, per-command Bash / PowerShell / Monitor `allowed_domains` in auto mode with sandboxing, agent-frontmatter and `--agents` JSON `omitClaudeMd` to let custom / plugin subagents run without CLAUDE.md files (managed policy still loads), and `--accept-command <sha256>` for `claude plugin install` / `update`; 2.1.272 is a bug-fix / reliability release; 2.1.273 adds `x-claude-code-*` LLM-gateway request-hint headers behind `CLAUDE_CODE_GATEWAY_HINT_HEADERS=1`, forking a `--remote-control` / `/remote-control` session from the Claude app as a local background session, and MCP-server-disconnected notifications, plus fixes for permission handling, credential/auth error messages, prompt-cache retention across `/login` / `/upgrade` / `/extra-usage`, and gateway 401 messaging \u2014 no effort/thinking surface changes; 2.1.274 adds a critical-memory-usage warning with recovery steps, `CLAUDE_CODE_MCP_STARTUP_WAIT_MS` to bound how long the first non-interactive turn waits for connecting MCP servers, an `effort` attribute on the `claude_code.llm_request` OpenTelemetry trace span, plus assorted VS Code, Claude Code on the web, Claude Tag, and Code Review fixes and improvements \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.275 adds signed-in account confirmation for Claude apps gateway sign-in, a send-now key (ctrl+enter, or ctrl+x ctrl+s) that flushes queued messages mid-turn, syncing of claude.ai-account skills and plugins into terminal sessions (opt out with `syncClaudeAiSkills: false` / `syncClaudeAiPlugins: false`), `/plugin install <plugin> --marketplace <source>`, and a startup warning when `otelHeadersHelper` fails, plus a broad set of reliability and rendering fixes \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.276 is a hotfix for a 2.1.275 regression where every request behind a proxy or gateway failed with `400 \u2026 Input tag 'advisor_20260301'`, with no surface changes; 2.1.277 adds AGENTS.md fallback support (in a project with no CLAUDE.md, Claude Code reads AGENTS.md instead \u2014 configurable under \"Project instructions\" in `/config`; extended by 2.1.281 to Bedrock, Vertex, Foundry, LLM gateways and telemetry-disabled sessions), `CLAUDE_GATEWAY_PROXY_IS_EGRESS_BOUNDARY=1` for Claude apps gateways whose only egress is a forward proxy, an optional `headers:` map on Claude apps gateway upstreams, plus assorted reliability, plugin, background-task, and VS Code / Claude Code on the web / Claude Tag fixes \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.278 changes auto mode for Claude API and Enterprise users, and on Bedrock, Vertex, Foundry and gateways, to default to the server-side classifier that does not charge for classifier overhead (opt out on Bedrock, Vertex, Foundry and gateways with `CLAUDE_CODE_AUTO_MODE_SERVER=0`; warns on billed fallback) and adds an `Auto mode server` row to `/status` showing whether this session's auto mode classifier runs on the server \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.280 ships Claude Opus 5.5 as the new default Opus model at 1M context and $4/$20 per Mtok, changes the Pro and Team Standard default model from Sonnet to Opus (matching Max / Team Premium / Enterprise), and stops Opus 4.7, Opus 4.8 and Fable 5 from holding their launch-default effort over an explicit `/effort`, `effortLevel`, project or managed setting \u2014 the `/effort` vocabulary itself is unchanged; 2.1.281\u20132.1.285 are reliability / plugin / gateway / VS Code / Claude Tag / Artifact releases, with 2.1.284 adding Claude Sonnet 5.5 as the default Sonnet on the Anthropic API (1M context, $2/$10 per Mtok) and making Ultracode its own `/effort` toggle, and 2.1.285 adding `claude --desktop` to open the Claude desktop app on the current directory and an `allowedProviders` managed setting). Exposes the full `/effort` dial (low/medium/high/xhigh/max \u2014 Opus 4.6 and Sonnet 4.6 top out at max with no xhigh step; Opus 5, Opus 5.5, Opus 4.7, Opus 4.8, Sonnet 5, Sonnet 5.5, Fable 5, and Fable 5.1 expose the full range; effort levels a model does not support fall back to the highest supported level at or below the requested one; deployments may set `maxEffortLevel` to cap the maximum reachable level on every provider), the Option+T/Alt+T, `alwaysThinkingEnabled`, and `MAX_THINKING_TOKENS=0` on/off controls, plus Ultracode (session-wide Dynamic Workflows tied to xhigh; since 2.1.284 its own `/effort` toggle \u2014 Tab or `/effort ultracode [on|off]` \u2014 that keeps the current effort level, with only `claude --effort ultracode` also setting xhigh; set via `/effort ultracode` or `\"ultracode\": true`; 2.1.202+ adds a `/config` \"Dynamic workflow size\" advisory guideline, 2.1.219 defaults it to medium (aim for fewer than 15 agents) with the `workflowSizeGuideline` settings key to set it from any settings file, and 2.1.269 adds `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) to raise the per-run concurrent-agent cap for the Workflow tool) and the per-turn `ultrathink` keyword (`think`/`think hard`/`think more` are not recognized).",
1527
+ "effort_levels": [
1528
+ "low",
1529
+ "medium",
1530
+ "high",
1531
+ "xhigh",
1532
+ "max"
1533
+ ],
1534
+ "effort_levels_by_model": {
1535
+ "claude-fable-5": [
1536
+ "low",
1537
+ "medium",
1538
+ "high",
1539
+ "xhigh",
1540
+ "max"
1541
+ ],
1542
+ "claude-fable-5.1": [
1543
+ "low",
1544
+ "medium",
1545
+ "high",
1546
+ "xhigh",
1547
+ "max"
1548
+ ],
1549
+ "claude-opus-5": [
1550
+ "low",
1551
+ "medium",
1552
+ "high",
1553
+ "xhigh",
1554
+ "max"
1555
+ ],
1556
+ "claude-opus-5-5": [
1557
+ "low",
1558
+ "medium",
1559
+ "high",
1560
+ "xhigh",
1561
+ "max"
1562
+ ],
1563
+ "claude-sonnet-5": [
1564
+ "low",
1565
+ "medium",
1566
+ "high",
1567
+ "xhigh",
1568
+ "max"
1569
+ ],
1570
+ "claude-sonnet-5-5": [
1571
+ "low",
1572
+ "medium",
1573
+ "high",
1574
+ "xhigh",
1575
+ "max"
1576
+ ],
1577
+ "opus-4.7": [
1578
+ "low",
1579
+ "medium",
1580
+ "high",
1581
+ "xhigh",
1582
+ "max"
1583
+ ],
1584
+ "opus-4.8": [
1585
+ "low",
1586
+ "medium",
1587
+ "high",
1588
+ "xhigh",
1589
+ "max"
1590
+ ],
1591
+ "sonnet-4.6": [
1592
+ "low",
1593
+ "medium",
1594
+ "high",
1595
+ "max"
1596
+ ]
1597
+ }
1527
1598
  },
1528
1599
  {
1529
1600
  "id": "claude-web",
@@ -1578,7 +1649,15 @@
1578
1649
  "exposes_max_mode": "no",
1579
1650
  "exposes_thinking": "yes",
1580
1651
  "exposes_orchestration": "no",
1581
- "best_for": "The same Codex surface driven by an OpenAI API key instead of a ChatGPT sign-in: the ONLY way to run the coding-specialised `-codex` variants (gpt-5.3-codex and the gpt-5.1-codex family), and the fallback for GPT work once the ChatGPT plan&apos;s limits bind. Billed per token at list price, so prefer `codex-cli` while the subscription still has headroom. Exposes the full Codex `reasoning_effort` dial (`low`, `medium`, `high`, `xhigh`, `max`, `ultra`)."
1652
+ "best_for": "The same Codex surface driven by an OpenAI API key instead of a ChatGPT sign-in: the ONLY way to run the coding-specialised `-codex` variants (gpt-5.3-codex and the gpt-5.1-codex family), and the fallback for GPT work once the ChatGPT plan&apos;s limits bind. Billed per token at list price, so prefer `codex-cli` while the subscription still has headroom. Exposes the full Codex `reasoning_effort` dial (`low`, `medium`, `high`, `xhigh`, `max`, `ultra`).",
1653
+ "effort_levels": [
1654
+ "low",
1655
+ "medium",
1656
+ "high",
1657
+ "xhigh",
1658
+ "max",
1659
+ "ultra"
1660
+ ]
1582
1661
  },
1583
1662
  {
1584
1663
  "id": "codex-cli",
@@ -1605,7 +1684,15 @@
1605
1684
  "exposes_max_mode": "no",
1606
1685
  "exposes_thinking": "yes",
1607
1686
  "exposes_orchestration": "no",
1608
- "best_for": "Default for GPT-driven autonomous coding sessions when a ChatGPT Plus/Pro subscription is active \u2014 pays from the ChatGPT budget instead of the per-token API rate, in the CLI and the IDE extension. A ChatGPT-account sign-in CANNOT run the `-codex` model variants: Codex answers 400 &quot;The &apos;gpt-5.3-codex&apos; model is not supported when using Codex with a ChatGPT account.&quot; Those variants are reachable only through `codex-api` (an OpenAI API key), so recommending one here would name a model the operator&apos;s subscription cannot run. Exposes the full Codex `reasoning_effort` dial (`low`, `medium`, `high`, `xhigh`, `max`, `ultra` \u2014 the top `ultra` step is session-wide multi-agent orchestration on par with Claude Code&apos;s `ultracode`) and no Max Mode."
1687
+ "best_for": "Default for GPT-driven autonomous coding sessions when a ChatGPT Plus/Pro subscription is active \u2014 pays from the ChatGPT budget instead of the per-token API rate, in the CLI and the IDE extension. A ChatGPT-account sign-in CANNOT run the `-codex` model variants: Codex answers 400 &quot;The &apos;gpt-5.3-codex&apos; model is not supported when using Codex with a ChatGPT account.&quot; Those variants are reachable only through `codex-api` (an OpenAI API key), so recommending one here would name a model the operator&apos;s subscription cannot run. Exposes the full Codex `reasoning_effort` dial (`low`, `medium`, `high`, `xhigh`, `max`, `ultra` \u2014 the top `ultra` step is session-wide multi-agent orchestration on par with Claude Code&apos;s `ultracode`) and no Max Mode.",
1688
+ "effort_levels": [
1689
+ "low",
1690
+ "medium",
1691
+ "high",
1692
+ "xhigh",
1693
+ "max",
1694
+ "ultra"
1695
+ ]
1609
1696
  },
1610
1697
  {
1611
1698
  "id": "cursor",
@@ -1677,7 +1764,12 @@
1677
1764
  "exposes_max_mode": "no",
1678
1765
  "exposes_thinking": "yes",
1679
1766
  "exposes_orchestration": "no",
1680
- "best_for": "Direct DeepSeek API access (provider-direct per-token; OpenAI-format at api.deepseek.com and Anthropic-format at api.deepseek.com/anthropic) for the DeepSeek V4-series models (deepseek-flash = DeepSeek-V4.1-Flash, deepseek-v4-pro) \u2014 cost-conscious coding / reasoning / long-context (1M) work when the cn jurisdiction is acceptable and a deepseek-api-key is configured. Exposes the full thinking dial (toggle + reasoning_effort `low`/`high`/`max`). Not routed via the Cursor pool. cn-jurisdiction: excluded by the default allowed-jurisdictions list unless the user opts into cn."
1767
+ "best_for": "Direct DeepSeek API access (provider-direct per-token; OpenAI-format at api.deepseek.com and Anthropic-format at api.deepseek.com/anthropic) for the DeepSeek V4-series models (deepseek-flash = DeepSeek-V4.1-Flash, deepseek-v4-pro) \u2014 cost-conscious coding / reasoning / long-context (1M) work when the cn jurisdiction is acceptable and a deepseek-api-key is configured. Exposes the full thinking dial (toggle + reasoning_effort `low`/`high`/`max`). Not routed via the Cursor pool. cn-jurisdiction: excluded by the default allowed-jurisdictions list unless the user opts into cn.",
1768
+ "effort_levels": [
1769
+ "low",
1770
+ "high",
1771
+ "max"
1772
+ ]
1681
1773
  },
1682
1774
  {
1683
1775
  "id": "gemini-app",
@@ -1743,7 +1835,57 @@
1743
1835
  "exposes_max_mode": "no",
1744
1836
  "exposes_thinking": "yes",
1745
1837
  "exposes_orchestration": "no",
1746
- "best_for": "Programmatic / scripted Gemini use with a Google API key. Pay-per-token at Google's published rates. Exposes the discrete Gemini thinking-level dial (`minimal`, `low`, `medium`, `high`) shared across the 3.x and 2.5 generations \u2014 per-model support varies (Gemini 3 Pro is low/high only; Gemini 3.1 Pro / 3.7 Flash / 3.8 Flash / 2.5 Pro / 2.5 Flash / 2.5 Flash-Lite are low/medium/high without `minimal`; `minimal` appears on Flash-line models such as 3.6 Flash, 3.5 Flash, 3.5 Flash-Lite, 3.1 Flash-Lite Image, and 3 Flash). Powers the roadmodel SaaS free-tier surfaces (/recommend on Gemini 2.5 Flash; /roadmap on Gemini 2.5 Flash with 3.1 Pro escalation)."
1838
+ "best_for": "Programmatic / scripted Gemini use with a Google API key. Pay-per-token at Google's published rates. Exposes the discrete Gemini thinking-level dial (`minimal`, `low`, `medium`, `high`) shared across the 3.x and 2.5 generations \u2014 per-model support varies (Gemini 3 Pro is low/high only; Gemini 3.1 Pro / 3.7 Flash / 3.8 Flash / 2.5 Pro / 2.5 Flash / 2.5 Flash-Lite are low/medium/high without `minimal`; `minimal` appears on Flash-line models such as 3.6 Flash, 3.5 Flash, 3.5 Flash-Lite, 3.1 Flash-Lite Image, and 3 Flash). Powers the roadmodel SaaS free-tier surfaces (/recommend on Gemini 2.5 Flash; /roadmap on Gemini 2.5 Flash with 3.1 Pro escalation).",
1839
+ "effort_levels": [
1840
+ "minimal",
1841
+ "low",
1842
+ "medium",
1843
+ "high"
1844
+ ],
1845
+ "effort_levels_by_model": {
1846
+ "gemini-2.5-flash": [
1847
+ "low",
1848
+ "medium",
1849
+ "high"
1850
+ ],
1851
+ "gemini-3-flash": [
1852
+ "minimal",
1853
+ "low",
1854
+ "medium",
1855
+ "high"
1856
+ ],
1857
+ "gemini-3-pro": [
1858
+ "low",
1859
+ "high"
1860
+ ],
1861
+ "gemini-3.1-pro": [
1862
+ "low",
1863
+ "medium",
1864
+ "high"
1865
+ ],
1866
+ "gemini-3.5-flash": [
1867
+ "minimal",
1868
+ "low",
1869
+ "medium",
1870
+ "high"
1871
+ ],
1872
+ "gemini-3.6-flash": [
1873
+ "minimal",
1874
+ "low",
1875
+ "medium",
1876
+ "high"
1877
+ ],
1878
+ "gemini-3.7-flash": [
1879
+ "low",
1880
+ "medium",
1881
+ "high"
1882
+ ],
1883
+ "gemini-3.8-flash": [
1884
+ "low",
1885
+ "medium",
1886
+ "high"
1887
+ ]
1888
+ }
1747
1889
  },
1748
1890
  {
1749
1891
  "id": "groq-api",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "roadmodel"
7
- version = "0.2.61"
7
+ version = "0.2.63"
8
8
  description = "BYO-key CLI that recommends the right AI model, platform, and settings for a prompt."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,7 +1,7 @@
1
1
  {
2
- "schema_version": "3",
2
+ "schema_version": "4",
3
3
  "output_contract_version": 2,
4
- "generated_at_utc": "2026-10-02T17:12:09Z",
4
+ "generated_at_utc": "2026-10-02T18:08:21Z",
5
5
  "source_doc_sha256": {
6
6
  "model-selector.txt": "81c5530a28521df18ebe14810e56cd72e65f1e30ad93d0522d31dbf362516e93",
7
7
  "model-tier-cost-scale.md": "2b3b0eb82237eb5cb5bf468a3fe971bcaf0160ad7d0bc700698d2c16640bc41d"
@@ -1523,7 +1523,78 @@
1523
1523
  "exposes_max_mode": "no",
1524
1524
  "exposes_thinking": "yes",
1525
1525
  "exposes_orchestration": "yes",
1526
- "best_for": "Default for Claude coding or terminal tasks when a claude.ai Max subscription is active \u2014 $0 marginal cost until the Max budget is exhausted, full tool-use surface, runs as a CLI, IDE extension inside Cursor, native VS Code extension, and as of 2.1.170+ also ships Claude Fable 5 (Mythos-class) plus a first-class Claude in Chrome browser-control integration (GA in 2.1.198), with Claude Sonnet 5 now the default model (2.1.197+) at native 1M-token context; as of 2.1.219 Claude Opus 5 ships as the new default Opus (1M context, fast mode at $10/$50 per Mtok) and Opus 4.7 is removed from fast mode so `/fast` now applies to Opus 5 and Opus 4.8; 2.1.280 ships Claude Opus 5.5 as the new default Opus (1M context, $4/$20 per Mtok with $0.20/Mtok cache reads) and changes the Pro and Team Standard default model from Sonnet to Opus (matching Max / Team Premium / Enterprise); 2.1.220\u20132.1.280 are bug-fix / reliability / self-hosted-runner / UI-polish / plugin-safety / VS Code / Claude apps / AGENTS.md / auto-mode-server-classifier releases with no effort/thinking surface changes beyond the 2.1.267 `maxEffortLevel` cap and the 2.1.269 `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) knob for the Workflow tool's per-run concurrent-agent limit noted below (2.1.224 adds `claude self-hosted-runner` for turning your own machines into places where Claude Code web/mobile/desktop sessions can run, cross-session `SendMessage`, and archive-plugin sources; 2.1.225 adds gateway spend-limit reporting to the usage warning; 2.1.227 polishes the slash-command menu and fixes feature-flag evaluation for expired login tokens; 2.1.257 ships Claude Fable 5.1 as the new default Fable at 1M context and $10/$50 per Mtok; 2.1.259 adds `managedMcpServers` policy and `--permission-prompts none` for unattended headless hosts; 2.1.260 adds an inline `/diff` panel and a headless text form of `/advisor`; 2.1.261 adds `/skill-doctor` and `bashOutputMaxChars` / `taskOutputMaxChars` settings; 2.1.263 is a reliability release; 2.1.265 adds a `--plugin-dir` folder-of-plugins mode and tightens plugin path containment; 2.1.266 restores gateway/proxy behavior when `CLAUDE_CODE_USE_GATEWAY` is set alongside an API key or `apiKeyHelper`; 2.1.267 adds a `maxEffortLevel` setting \u2014 top-level or per model under `modelSettings` \u2014 that CAPS the effort level on every provider including Bedrock, Vertex, and Foundry while still letting users pick any lower level, plus `--system-prompt-snapshot off` for iterating on prompt text and a fix for `effort:` frontmatter on custom commands, skills, and subagents being ignored on models whose default effort is still pinned; 2.1.269 adds `claude plugin eval` for scored plugin eval suites, `/output-style [name]` to list and switch output styles (including over Remote Control and in headless sessions), a Bash-tool file-edit diff (setting `bashEditDiffEnabled`), `OTEL_METRICS_INCLUDE_REPOSITORY` for tagging OpenTelemetry metrics and events with `vcs.*` attributes, `CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY_TIMEOUT_MS` to extend the LLM gateway `/v1/models` discovery timeout, and `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) to raise the Workflow tool's per-run concurrent-agent limit for inference-bound fan-outs \u2014 none of which change the `/effort` vocabulary; 2.1.270 fixes read-only git commands in Bash unexpectedly asking for permission after a session had been running for a while, a 2.1.269 regression, with no surface changes; 2.1.271 adds fast mode in Claude Code Remote (cloud and self-hosted runner) sessions where organization policy allows it, mouse support in the `/config` panel, per-command Bash / PowerShell / Monitor `allowed_domains` in auto mode with sandboxing, agent-frontmatter and `--agents` JSON `omitClaudeMd` to let custom / plugin subagents run without CLAUDE.md files (managed policy still loads), and `--accept-command <sha256>` for `claude plugin install` / `update`; 2.1.272 is a bug-fix / reliability release; 2.1.273 adds `x-claude-code-*` LLM-gateway request-hint headers behind `CLAUDE_CODE_GATEWAY_HINT_HEADERS=1`, forking a `--remote-control` / `/remote-control` session from the Claude app as a local background session, and MCP-server-disconnected notifications, plus fixes for permission handling, credential/auth error messages, prompt-cache retention across `/login` / `/upgrade` / `/extra-usage`, and gateway 401 messaging \u2014 no effort/thinking surface changes; 2.1.274 adds a critical-memory-usage warning with recovery steps, `CLAUDE_CODE_MCP_STARTUP_WAIT_MS` to bound how long the first non-interactive turn waits for connecting MCP servers, an `effort` attribute on the `claude_code.llm_request` OpenTelemetry trace span, plus assorted VS Code, Claude Code on the web, Claude Tag, and Code Review fixes and improvements \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.275 adds signed-in account confirmation for Claude apps gateway sign-in, a send-now key (ctrl+enter, or ctrl+x ctrl+s) that flushes queued messages mid-turn, syncing of claude.ai-account skills and plugins into terminal sessions (opt out with `syncClaudeAiSkills: false` / `syncClaudeAiPlugins: false`), `/plugin install <plugin> --marketplace <source>`, and a startup warning when `otelHeadersHelper` fails, plus a broad set of reliability and rendering fixes \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.276 is a hotfix for a 2.1.275 regression where every request behind a proxy or gateway failed with `400 \u2026 Input tag 'advisor_20260301'`, with no surface changes; 2.1.277 adds AGENTS.md fallback support (in a project with no CLAUDE.md, Claude Code reads AGENTS.md instead \u2014 configurable under \"Project instructions\" in `/config`; extended by 2.1.281 to Bedrock, Vertex, Foundry, LLM gateways and telemetry-disabled sessions), `CLAUDE_GATEWAY_PROXY_IS_EGRESS_BOUNDARY=1` for Claude apps gateways whose only egress is a forward proxy, an optional `headers:` map on Claude apps gateway upstreams, plus assorted reliability, plugin, background-task, and VS Code / Claude Code on the web / Claude Tag fixes \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.278 changes auto mode for Claude API and Enterprise users, and on Bedrock, Vertex, Foundry and gateways, to default to the server-side classifier that does not charge for classifier overhead (opt out on Bedrock, Vertex, Foundry and gateways with `CLAUDE_CODE_AUTO_MODE_SERVER=0`; warns on billed fallback) and adds an `Auto mode server` row to `/status` showing whether this session's auto mode classifier runs on the server \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.280 ships Claude Opus 5.5 as the new default Opus model at 1M context and $4/$20 per Mtok, changes the Pro and Team Standard default model from Sonnet to Opus (matching Max / Team Premium / Enterprise), and stops Opus 4.7, Opus 4.8 and Fable 5 from holding their launch-default effort over an explicit `/effort`, `effortLevel`, project or managed setting \u2014 the `/effort` vocabulary itself is unchanged; 2.1.281\u20132.1.285 are reliability / plugin / gateway / VS Code / Claude Tag / Artifact releases, with 2.1.284 adding Claude Sonnet 5.5 as the default Sonnet on the Anthropic API (1M context, $2/$10 per Mtok) and making Ultracode its own `/effort` toggle, and 2.1.285 adding `claude --desktop` to open the Claude desktop app on the current directory and an `allowedProviders` managed setting). Exposes the full `/effort` dial (low/medium/high/xhigh/max \u2014 Opus 4.6 and Sonnet 4.6 top out at max with no xhigh step; Opus 5, Opus 5.5, Opus 4.7, Opus 4.8, Sonnet 5, Sonnet 5.5, Fable 5, and Fable 5.1 expose the full range; effort levels a model does not support fall back to the highest supported level at or below the requested one; deployments may set `maxEffortLevel` to cap the maximum reachable level on every provider), the Option+T/Alt+T, `alwaysThinkingEnabled`, and `MAX_THINKING_TOKENS=0` on/off controls, plus Ultracode (session-wide Dynamic Workflows tied to xhigh; since 2.1.284 its own `/effort` toggle \u2014 Tab or `/effort ultracode [on|off]` \u2014 that keeps the current effort level, with only `claude --effort ultracode` also setting xhigh; set via `/effort ultracode` or `\"ultracode\": true`; 2.1.202+ adds a `/config` \"Dynamic workflow size\" advisory guideline, 2.1.219 defaults it to medium (aim for fewer than 15 agents) with the `workflowSizeGuideline` settings key to set it from any settings file, and 2.1.269 adds `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) to raise the per-run concurrent-agent cap for the Workflow tool) and the per-turn `ultrathink` keyword (`think`/`think hard`/`think more` are not recognized)."
1526
+ "best_for": "Default for Claude coding or terminal tasks when a claude.ai Max subscription is active \u2014 $0 marginal cost until the Max budget is exhausted, full tool-use surface, runs as a CLI, IDE extension inside Cursor, native VS Code extension, and as of 2.1.170+ also ships Claude Fable 5 (Mythos-class) plus a first-class Claude in Chrome browser-control integration (GA in 2.1.198), with Claude Sonnet 5 now the default model (2.1.197+) at native 1M-token context; as of 2.1.219 Claude Opus 5 ships as the new default Opus (1M context, fast mode at $10/$50 per Mtok) and Opus 4.7 is removed from fast mode so `/fast` now applies to Opus 5 and Opus 4.8; 2.1.280 ships Claude Opus 5.5 as the new default Opus (1M context, $4/$20 per Mtok with $0.20/Mtok cache reads) and changes the Pro and Team Standard default model from Sonnet to Opus (matching Max / Team Premium / Enterprise); 2.1.220\u20132.1.280 are bug-fix / reliability / self-hosted-runner / UI-polish / plugin-safety / VS Code / Claude apps / AGENTS.md / auto-mode-server-classifier releases with no effort/thinking surface changes beyond the 2.1.267 `maxEffortLevel` cap and the 2.1.269 `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) knob for the Workflow tool's per-run concurrent-agent limit noted below (2.1.224 adds `claude self-hosted-runner` for turning your own machines into places where Claude Code web/mobile/desktop sessions can run, cross-session `SendMessage`, and archive-plugin sources; 2.1.225 adds gateway spend-limit reporting to the usage warning; 2.1.227 polishes the slash-command menu and fixes feature-flag evaluation for expired login tokens; 2.1.257 ships Claude Fable 5.1 as the new default Fable at 1M context and $10/$50 per Mtok; 2.1.259 adds `managedMcpServers` policy and `--permission-prompts none` for unattended headless hosts; 2.1.260 adds an inline `/diff` panel and a headless text form of `/advisor`; 2.1.261 adds `/skill-doctor` and `bashOutputMaxChars` / `taskOutputMaxChars` settings; 2.1.263 is a reliability release; 2.1.265 adds a `--plugin-dir` folder-of-plugins mode and tightens plugin path containment; 2.1.266 restores gateway/proxy behavior when `CLAUDE_CODE_USE_GATEWAY` is set alongside an API key or `apiKeyHelper`; 2.1.267 adds a `maxEffortLevel` setting \u2014 top-level or per model under `modelSettings` \u2014 that CAPS the effort level on every provider including Bedrock, Vertex, and Foundry while still letting users pick any lower level, plus `--system-prompt-snapshot off` for iterating on prompt text and a fix for `effort:` frontmatter on custom commands, skills, and subagents being ignored on models whose default effort is still pinned; 2.1.269 adds `claude plugin eval` for scored plugin eval suites, `/output-style [name]` to list and switch output styles (including over Remote Control and in headless sessions), a Bash-tool file-edit diff (setting `bashEditDiffEnabled`), `OTEL_METRICS_INCLUDE_REPOSITORY` for tagging OpenTelemetry metrics and events with `vcs.*` attributes, `CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY_TIMEOUT_MS` to extend the LLM gateway `/v1/models` discovery timeout, and `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) to raise the Workflow tool's per-run concurrent-agent limit for inference-bound fan-outs \u2014 none of which change the `/effort` vocabulary; 2.1.270 fixes read-only git commands in Bash unexpectedly asking for permission after a session had been running for a while, a 2.1.269 regression, with no surface changes; 2.1.271 adds fast mode in Claude Code Remote (cloud and self-hosted runner) sessions where organization policy allows it, mouse support in the `/config` panel, per-command Bash / PowerShell / Monitor `allowed_domains` in auto mode with sandboxing, agent-frontmatter and `--agents` JSON `omitClaudeMd` to let custom / plugin subagents run without CLAUDE.md files (managed policy still loads), and `--accept-command <sha256>` for `claude plugin install` / `update`; 2.1.272 is a bug-fix / reliability release; 2.1.273 adds `x-claude-code-*` LLM-gateway request-hint headers behind `CLAUDE_CODE_GATEWAY_HINT_HEADERS=1`, forking a `--remote-control` / `/remote-control` session from the Claude app as a local background session, and MCP-server-disconnected notifications, plus fixes for permission handling, credential/auth error messages, prompt-cache retention across `/login` / `/upgrade` / `/extra-usage`, and gateway 401 messaging \u2014 no effort/thinking surface changes; 2.1.274 adds a critical-memory-usage warning with recovery steps, `CLAUDE_CODE_MCP_STARTUP_WAIT_MS` to bound how long the first non-interactive turn waits for connecting MCP servers, an `effort` attribute on the `claude_code.llm_request` OpenTelemetry trace span, plus assorted VS Code, Claude Code on the web, Claude Tag, and Code Review fixes and improvements \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.275 adds signed-in account confirmation for Claude apps gateway sign-in, a send-now key (ctrl+enter, or ctrl+x ctrl+s) that flushes queued messages mid-turn, syncing of claude.ai-account skills and plugins into terminal sessions (opt out with `syncClaudeAiSkills: false` / `syncClaudeAiPlugins: false`), `/plugin install <plugin> --marketplace <source>`, and a startup warning when `otelHeadersHelper` fails, plus a broad set of reliability and rendering fixes \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.276 is a hotfix for a 2.1.275 regression where every request behind a proxy or gateway failed with `400 \u2026 Input tag 'advisor_20260301'`, with no surface changes; 2.1.277 adds AGENTS.md fallback support (in a project with no CLAUDE.md, Claude Code reads AGENTS.md instead \u2014 configurable under \"Project instructions\" in `/config`; extended by 2.1.281 to Bedrock, Vertex, Foundry, LLM gateways and telemetry-disabled sessions), `CLAUDE_GATEWAY_PROXY_IS_EGRESS_BOUNDARY=1` for Claude apps gateways whose only egress is a forward proxy, an optional `headers:` map on Claude apps gateway upstreams, plus assorted reliability, plugin, background-task, and VS Code / Claude Code on the web / Claude Tag fixes \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.278 changes auto mode for Claude API and Enterprise users, and on Bedrock, Vertex, Foundry and gateways, to default to the server-side classifier that does not charge for classifier overhead (opt out on Bedrock, Vertex, Foundry and gateways with `CLAUDE_CODE_AUTO_MODE_SERVER=0`; warns on billed fallback) and adds an `Auto mode server` row to `/status` showing whether this session's auto mode classifier runs on the server \u2014 no changes to the `/effort` vocabulary or extended-thinking controls; 2.1.280 ships Claude Opus 5.5 as the new default Opus model at 1M context and $4/$20 per Mtok, changes the Pro and Team Standard default model from Sonnet to Opus (matching Max / Team Premium / Enterprise), and stops Opus 4.7, Opus 4.8 and Fable 5 from holding their launch-default effort over an explicit `/effort`, `effortLevel`, project or managed setting \u2014 the `/effort` vocabulary itself is unchanged; 2.1.281\u20132.1.285 are reliability / plugin / gateway / VS Code / Claude Tag / Artifact releases, with 2.1.284 adding Claude Sonnet 5.5 as the default Sonnet on the Anthropic API (1M context, $2/$10 per Mtok) and making Ultracode its own `/effort` toggle, and 2.1.285 adding `claude --desktop` to open the Claude desktop app on the current directory and an `allowedProviders` managed setting). Exposes the full `/effort` dial (low/medium/high/xhigh/max \u2014 Opus 4.6 and Sonnet 4.6 top out at max with no xhigh step; Opus 5, Opus 5.5, Opus 4.7, Opus 4.8, Sonnet 5, Sonnet 5.5, Fable 5, and Fable 5.1 expose the full range; effort levels a model does not support fall back to the highest supported level at or below the requested one; deployments may set `maxEffortLevel` to cap the maximum reachable level on every provider), the Option+T/Alt+T, `alwaysThinkingEnabled`, and `MAX_THINKING_TOKENS=0` on/off controls, plus Ultracode (session-wide Dynamic Workflows tied to xhigh; since 2.1.284 its own `/effort` toggle \u2014 Tab or `/effort ultracode [on|off]` \u2014 that keeps the current effort level, with only `claude --effort ultracode` also setting xhigh; set via `/effort ultracode` or `\"ultracode\": true`; 2.1.202+ adds a `/config` \"Dynamic workflow size\" advisory guideline, 2.1.219 defaults it to medium (aim for fewer than 15 agents) with the `workflowSizeGuideline` settings key to set it from any settings file, and 2.1.269 adds `CLAUDE_CODE_WORKFLOW_MAX_CONCURRENT_AGENTS` (1\u2013256) to raise the per-run concurrent-agent cap for the Workflow tool) and the per-turn `ultrathink` keyword (`think`/`think hard`/`think more` are not recognized).",
1527
+ "effort_levels": [
1528
+ "low",
1529
+ "medium",
1530
+ "high",
1531
+ "xhigh",
1532
+ "max"
1533
+ ],
1534
+ "effort_levels_by_model": {
1535
+ "claude-fable-5": [
1536
+ "low",
1537
+ "medium",
1538
+ "high",
1539
+ "xhigh",
1540
+ "max"
1541
+ ],
1542
+ "claude-fable-5.1": [
1543
+ "low",
1544
+ "medium",
1545
+ "high",
1546
+ "xhigh",
1547
+ "max"
1548
+ ],
1549
+ "claude-opus-5": [
1550
+ "low",
1551
+ "medium",
1552
+ "high",
1553
+ "xhigh",
1554
+ "max"
1555
+ ],
1556
+ "claude-opus-5-5": [
1557
+ "low",
1558
+ "medium",
1559
+ "high",
1560
+ "xhigh",
1561
+ "max"
1562
+ ],
1563
+ "claude-sonnet-5": [
1564
+ "low",
1565
+ "medium",
1566
+ "high",
1567
+ "xhigh",
1568
+ "max"
1569
+ ],
1570
+ "claude-sonnet-5-5": [
1571
+ "low",
1572
+ "medium",
1573
+ "high",
1574
+ "xhigh",
1575
+ "max"
1576
+ ],
1577
+ "opus-4.7": [
1578
+ "low",
1579
+ "medium",
1580
+ "high",
1581
+ "xhigh",
1582
+ "max"
1583
+ ],
1584
+ "opus-4.8": [
1585
+ "low",
1586
+ "medium",
1587
+ "high",
1588
+ "xhigh",
1589
+ "max"
1590
+ ],
1591
+ "sonnet-4.6": [
1592
+ "low",
1593
+ "medium",
1594
+ "high",
1595
+ "max"
1596
+ ]
1597
+ }
1527
1598
  },
1528
1599
  {
1529
1600
  "id": "claude-web",
@@ -1578,7 +1649,15 @@
1578
1649
  "exposes_max_mode": "no",
1579
1650
  "exposes_thinking": "yes",
1580
1651
  "exposes_orchestration": "no",
1581
- "best_for": "The same Codex surface driven by an OpenAI API key instead of a ChatGPT sign-in: the ONLY way to run the coding-specialised `-codex` variants (gpt-5.3-codex and the gpt-5.1-codex family), and the fallback for GPT work once the ChatGPT plan&apos;s limits bind. Billed per token at list price, so prefer `codex-cli` while the subscription still has headroom. Exposes the full Codex `reasoning_effort` dial (`low`, `medium`, `high`, `xhigh`, `max`, `ultra`)."
1652
+ "best_for": "The same Codex surface driven by an OpenAI API key instead of a ChatGPT sign-in: the ONLY way to run the coding-specialised `-codex` variants (gpt-5.3-codex and the gpt-5.1-codex family), and the fallback for GPT work once the ChatGPT plan&apos;s limits bind. Billed per token at list price, so prefer `codex-cli` while the subscription still has headroom. Exposes the full Codex `reasoning_effort` dial (`low`, `medium`, `high`, `xhigh`, `max`, `ultra`).",
1653
+ "effort_levels": [
1654
+ "low",
1655
+ "medium",
1656
+ "high",
1657
+ "xhigh",
1658
+ "max",
1659
+ "ultra"
1660
+ ]
1582
1661
  },
1583
1662
  {
1584
1663
  "id": "codex-cli",
@@ -1605,7 +1684,15 @@
1605
1684
  "exposes_max_mode": "no",
1606
1685
  "exposes_thinking": "yes",
1607
1686
  "exposes_orchestration": "no",
1608
- "best_for": "Default for GPT-driven autonomous coding sessions when a ChatGPT Plus/Pro subscription is active \u2014 pays from the ChatGPT budget instead of the per-token API rate, in the CLI and the IDE extension. A ChatGPT-account sign-in CANNOT run the `-codex` model variants: Codex answers 400 &quot;The &apos;gpt-5.3-codex&apos; model is not supported when using Codex with a ChatGPT account.&quot; Those variants are reachable only through `codex-api` (an OpenAI API key), so recommending one here would name a model the operator&apos;s subscription cannot run. Exposes the full Codex `reasoning_effort` dial (`low`, `medium`, `high`, `xhigh`, `max`, `ultra` \u2014 the top `ultra` step is session-wide multi-agent orchestration on par with Claude Code&apos;s `ultracode`) and no Max Mode."
1687
+ "best_for": "Default for GPT-driven autonomous coding sessions when a ChatGPT Plus/Pro subscription is active \u2014 pays from the ChatGPT budget instead of the per-token API rate, in the CLI and the IDE extension. A ChatGPT-account sign-in CANNOT run the `-codex` model variants: Codex answers 400 &quot;The &apos;gpt-5.3-codex&apos; model is not supported when using Codex with a ChatGPT account.&quot; Those variants are reachable only through `codex-api` (an OpenAI API key), so recommending one here would name a model the operator&apos;s subscription cannot run. Exposes the full Codex `reasoning_effort` dial (`low`, `medium`, `high`, `xhigh`, `max`, `ultra` \u2014 the top `ultra` step is session-wide multi-agent orchestration on par with Claude Code&apos;s `ultracode`) and no Max Mode.",
1688
+ "effort_levels": [
1689
+ "low",
1690
+ "medium",
1691
+ "high",
1692
+ "xhigh",
1693
+ "max",
1694
+ "ultra"
1695
+ ]
1609
1696
  },
1610
1697
  {
1611
1698
  "id": "cursor",
@@ -1677,7 +1764,12 @@
1677
1764
  "exposes_max_mode": "no",
1678
1765
  "exposes_thinking": "yes",
1679
1766
  "exposes_orchestration": "no",
1680
- "best_for": "Direct DeepSeek API access (provider-direct per-token; OpenAI-format at api.deepseek.com and Anthropic-format at api.deepseek.com/anthropic) for the DeepSeek V4-series models (deepseek-flash = DeepSeek-V4.1-Flash, deepseek-v4-pro) \u2014 cost-conscious coding / reasoning / long-context (1M) work when the cn jurisdiction is acceptable and a deepseek-api-key is configured. Exposes the full thinking dial (toggle + reasoning_effort `low`/`high`/`max`). Not routed via the Cursor pool. cn-jurisdiction: excluded by the default allowed-jurisdictions list unless the user opts into cn."
1767
+ "best_for": "Direct DeepSeek API access (provider-direct per-token; OpenAI-format at api.deepseek.com and Anthropic-format at api.deepseek.com/anthropic) for the DeepSeek V4-series models (deepseek-flash = DeepSeek-V4.1-Flash, deepseek-v4-pro) \u2014 cost-conscious coding / reasoning / long-context (1M) work when the cn jurisdiction is acceptable and a deepseek-api-key is configured. Exposes the full thinking dial (toggle + reasoning_effort `low`/`high`/`max`). Not routed via the Cursor pool. cn-jurisdiction: excluded by the default allowed-jurisdictions list unless the user opts into cn.",
1768
+ "effort_levels": [
1769
+ "low",
1770
+ "high",
1771
+ "max"
1772
+ ]
1681
1773
  },
1682
1774
  {
1683
1775
  "id": "gemini-app",
@@ -1743,7 +1835,57 @@
1743
1835
  "exposes_max_mode": "no",
1744
1836
  "exposes_thinking": "yes",
1745
1837
  "exposes_orchestration": "no",
1746
- "best_for": "Programmatic / scripted Gemini use with a Google API key. Pay-per-token at Google's published rates. Exposes the discrete Gemini thinking-level dial (`minimal`, `low`, `medium`, `high`) shared across the 3.x and 2.5 generations \u2014 per-model support varies (Gemini 3 Pro is low/high only; Gemini 3.1 Pro / 3.7 Flash / 3.8 Flash / 2.5 Pro / 2.5 Flash / 2.5 Flash-Lite are low/medium/high without `minimal`; `minimal` appears on Flash-line models such as 3.6 Flash, 3.5 Flash, 3.5 Flash-Lite, 3.1 Flash-Lite Image, and 3 Flash). Powers the roadmodel SaaS free-tier surfaces (/recommend on Gemini 2.5 Flash; /roadmap on Gemini 2.5 Flash with 3.1 Pro escalation)."
1838
+ "best_for": "Programmatic / scripted Gemini use with a Google API key. Pay-per-token at Google's published rates. Exposes the discrete Gemini thinking-level dial (`minimal`, `low`, `medium`, `high`) shared across the 3.x and 2.5 generations \u2014 per-model support varies (Gemini 3 Pro is low/high only; Gemini 3.1 Pro / 3.7 Flash / 3.8 Flash / 2.5 Pro / 2.5 Flash / 2.5 Flash-Lite are low/medium/high without `minimal`; `minimal` appears on Flash-line models such as 3.6 Flash, 3.5 Flash, 3.5 Flash-Lite, 3.1 Flash-Lite Image, and 3 Flash). Powers the roadmodel SaaS free-tier surfaces (/recommend on Gemini 2.5 Flash; /roadmap on Gemini 2.5 Flash with 3.1 Pro escalation).",
1839
+ "effort_levels": [
1840
+ "minimal",
1841
+ "low",
1842
+ "medium",
1843
+ "high"
1844
+ ],
1845
+ "effort_levels_by_model": {
1846
+ "gemini-2.5-flash": [
1847
+ "low",
1848
+ "medium",
1849
+ "high"
1850
+ ],
1851
+ "gemini-3-flash": [
1852
+ "minimal",
1853
+ "low",
1854
+ "medium",
1855
+ "high"
1856
+ ],
1857
+ "gemini-3-pro": [
1858
+ "low",
1859
+ "high"
1860
+ ],
1861
+ "gemini-3.1-pro": [
1862
+ "low",
1863
+ "medium",
1864
+ "high"
1865
+ ],
1866
+ "gemini-3.5-flash": [
1867
+ "minimal",
1868
+ "low",
1869
+ "medium",
1870
+ "high"
1871
+ ],
1872
+ "gemini-3.6-flash": [
1873
+ "minimal",
1874
+ "low",
1875
+ "medium",
1876
+ "high"
1877
+ ],
1878
+ "gemini-3.7-flash": [
1879
+ "low",
1880
+ "medium",
1881
+ "high"
1882
+ ],
1883
+ "gemini-3.8-flash": [
1884
+ "low",
1885
+ "medium",
1886
+ "high"
1887
+ ]
1888
+ }
1747
1889
  },
1748
1890
  {
1749
1891
  "id": "groq-api",
@@ -1,2 +1,2 @@
1
1
  # src/roadmodel/__init__.py
2
- __version__ = "0.2.61"
2
+ __version__ = "0.2.63"
@@ -501,8 +501,10 @@ _SAAS_LADDER_TABLE_HEADER: Final = (
501
501
  "gives both the same settings because a lower effort saves this user nothing or "
502
502
  "the dial has no lower level; say which in that block's RATIONALE, and never "
503
503
  "invent another difference.\n"
504
- "- BACKUP obeys the Step 7 HARD cross-provider rule and names a model this user "
505
- "can reach.\n"
504
+ "- Each block's BACKUP is the model the row names after `backup` in that tier's "
505
+ "cell, copied verbatim; code checks it against the row and replaces one that "
506
+ "differs. Where a cell names no backup, BACKUP obeys the Step 7 HARD "
507
+ "cross-provider rule and names a model this user can reach.\n"
506
508
  "- Each RATIONALE is three labelled segments (TASK: / PICK: / EFFORT:), one crisp "
507
509
  "sentence each. PICK says why the row's model fits its tier in the table's terms "
508
510
  "(where it sits on the user's frontier, its AA Index); EFFORT says why the setting "
@@ -1230,16 +1232,25 @@ def _ladder_tier_guard(picks: dict[str, dict[str, Any]]) -> dict[str, Any]:
1230
1232
  }
1231
1233
 
1232
1234
 
1233
- # The scorer's effort levels as the selector's output format writes them.
1235
+ # Effort levels as the selector's output format writes them: the scorer's
1236
+ # five and the native words some dials add (scoring.LEVEL_ORDER).
1234
1237
  _EFFORT_WORDS: Final = {
1238
+ "none": "None",
1239
+ "minimal": "Minimal",
1235
1240
  "low": "Low",
1236
1241
  "medium": "Medium",
1237
1242
  "high": "High",
1238
1243
  "xhigh": "XHigh",
1239
1244
  "max": "Max",
1245
+ "ultra": "Ultra",
1240
1246
  }
1241
1247
 
1242
1248
 
1249
+ def _effort_word(level: str) -> str:
1250
+ """A rung's level (native or scorer) as the EFFORT line writes it."""
1251
+ return _EFFORT_WORDS.get(scoring.normalize_level(level), level)
1252
+
1253
+
1243
1254
  def _model_key(ref: str) -> str:
1244
1255
  """A spelling-free key for a model reference: "Claude Opus 5.5",
1245
1256
  "Opus 5.5" and "claude-opus-5-5" all key the same."""
@@ -1304,7 +1315,7 @@ def _enforce_table_row(ladder: dict[str, dict[str, str]], row: scoring.Ladder) -
1304
1315
  continue
1305
1316
  rewritten.append(tier)
1306
1317
  c = rung.candidate
1307
- effort = _EFFORT_WORDS.get(rung.effort, rung.effort)
1318
+ effort = _effort_word(rung.level)
1308
1319
  sections = _split_rationale_sections(base.get("rationale", ""))
1309
1320
  task = (
1310
1321
  sections["task"]
@@ -1322,35 +1333,79 @@ def _enforce_table_row(ladder: dict[str, dict[str, str]], row: scoring.Ladder) -
1322
1333
  return rewritten
1323
1334
 
1324
1335
 
1325
- # Surfaces whose effort dial reads exactly the scorer's levels, so code can set
1326
- # the row's level verbatim. Claude Code's ladder is Low / Medium / High / XHigh /
1327
- # Max, with Ultracode above Max. Elsewhere the dial has its own words (Codex's
1328
- # Intelligence, Gemini's thinking levels) and the engine maps to the nearest.
1329
- _NATIVE_EFFORT_PLATFORMS: Final = frozenset({"claude-code"})
1336
+ def _enforce_table_backup(ladder: dict[str, dict[str, str]], row: scoring.Ladder) -> list[str]:
1337
+ """Make every rung's BACKUP the row's: the best model from another maker
1338
+ the user can run at no higher list price (scoring._backup_for). A rung
1339
+ whose row names no backup (no other maker funded) keeps the engine's,
1340
+ under the Step 7 guard. Returns the tiers whose backup it set."""
1341
+ adjusted: list[str] = []
1342
+ for tier in _LADDER_TIERS:
1343
+ backup = row.rungs[tier].backup
1344
+ if backup is None:
1345
+ continue
1346
+ b = backup.candidate
1347
+ written = ladder[tier].get("backup", "")
1348
+ if written and (
1349
+ cost.model_id_of(written) == b.model_id
1350
+ or _model_key(written) in {_model_key(b.model_id), _model_key(b.model_name)}
1351
+ ):
1352
+ continue
1353
+ ladder[tier]["backup"] = b.model_name
1354
+ adjusted.append(tier)
1355
+ return adjusted
1330
1356
 
1331
1357
 
1332
- def _effort_level(value: str) -> str:
1333
- level = re.sub(r"[\s_-]+", "", value.strip().lower())
1334
- return {"extrahigh": "xhigh"}.get(level, level)
1358
+ def _attach_backup_plans(picks: dict[str, dict[str, Any]], row: scoring.Ladder) -> None:
1359
+ """Give each pick whose BACKUP is the row's a ``backup_plan``: the
1360
+ platform and effort the row runs that backup at, so a caller can show the
1361
+ backup on the surface the scorer chose (the user's subscription surface
1362
+ where one reaches it) instead of guessing one. A backup the guard replaced
1363
+ gets none."""
1364
+ for tier in _LADDER_TIERS:
1365
+ backup = row.rungs[tier].backup
1366
+ if backup is None:
1367
+ continue
1368
+ b = backup.candidate
1369
+ if picks[tier].get("backup") != cost.canonical_model_name(b.model_name):
1370
+ continue
1371
+ picks[tier]["backup_plan"] = {
1372
+ "model": picks[tier]["backup"],
1373
+ "platform": b.platform_name,
1374
+ "effort": _effort_word(backup.level),
1375
+ }
1376
+
1377
+
1378
+ # Claude Code's Ultracode is a session setting above its Max level: it stands
1379
+ # where the row says max.
1380
+ _ULTRACODE_PLATFORM: Final = "claude-code"
1335
1381
 
1336
1382
 
1337
1383
  def _enforce_table_effort(ladder: dict[str, dict[str, str]], row: scoring.Ladder) -> list[str]:
1338
- """On a surface whose dial reads the scorer's levels, make every rung's
1339
- EFFORT the row's (Ultracode stands where the row says Max) and rewrite its
1340
- EFFORT segment to match. Two rungs on one model are told apart by effort
1341
- alone, so an engine that copies the model but not the effort makes them
1342
- identical. Returns the tiers whose effort it set."""
1384
+ """On every surface whose dial the catalog documents (the rung carries a
1385
+ native level), make the rung's EFFORT the row's level in that dial's own
1386
+ words and rewrite its EFFORT segment to match; on Claude Code, Ultracode
1387
+ stands where the row says max. Two rungs on one model are told apart by
1388
+ effort alone, so an engine that copies the model but not the effort makes
1389
+ them identical. A surface with no documented dial keeps the engine's
1390
+ mapping. Returns the tiers whose effort it set."""
1343
1391
  adjusted: list[str] = []
1344
1392
  for tier in _LADDER_TIERS:
1345
1393
  base = ladder[tier]
1346
1394
  rung = row.rungs[tier]
1347
- if rung.candidate.platform_id not in _NATIVE_EFFORT_PLATFORMS:
1395
+ if rung.native is None:
1396
+ continue
1397
+ written = scoring.normalize_level(base.get("effort", ""))
1398
+ want = scoring.normalize_level(rung.native)
1399
+ if written == want:
1348
1400
  continue
1349
- written = _effort_level(base.get("effort", ""))
1350
- if written == rung.effort or (rung.effort == "max" and written == "ultracode"):
1401
+ if (
1402
+ rung.candidate.platform_id == _ULTRACODE_PLATFORM
1403
+ and want == "max"
1404
+ and written == "ultracode"
1405
+ ):
1351
1406
  continue
1352
1407
  adjusted.append(tier)
1353
- effort = _EFFORT_WORDS.get(rung.effort, rung.effort)
1408
+ effort = _effort_word(rung.native)
1354
1409
  base["effort"] = effort
1355
1410
  sections = _split_rationale_sections(base.get("rationale", ""))
1356
1411
  if sections:
@@ -1398,11 +1453,14 @@ def recommend_structured_ladder(
1398
1453
  The picks come from this user's own cost/quality frontier: the scorer's
1399
1454
  ladder table (scoring.ladder_table) goes into the prompt, the engine
1400
1455
  classifies the task and copies its row, and every rung is then made the
1401
- row's (:func:`_enforce_table_row`), effort included where the surface's
1402
- dial reads the scorer's levels (:func:`_enforce_table_effort`). ``guard``
1403
- then reports ``mode: "frontier"``, the row, how it was found, the tiers
1404
- rewritten and the tiers whose effort was set, and is healthy: two rungs on
1405
- one model are the table's answer, not a collapse.
1456
+ row's (:func:`_enforce_table_row`), effort included on every surface
1457
+ whose dial the catalog documents (:func:`_enforce_table_effort`), and
1458
+ BACKUP included where the row names one (:func:`_enforce_table_backup`;
1459
+ such a pick also carries a ``backup_plan`` with the backup's platform and
1460
+ effort). ``guard`` then reports ``mode: "frontier"``, the row, how it was
1461
+ found, the tiers rewritten, the tiers whose effort was set and the tiers
1462
+ whose backup was set, and is healthy: two rungs on one model are the
1463
+ table's answer, not a collapse.
1406
1464
  Without a table, or a response the table cannot place, the engine's own
1407
1465
  picks stand under the tier-distinctness guard as before.
1408
1466
 
@@ -1449,6 +1507,7 @@ def recommend_structured_ladder(
1449
1507
  "found": found,
1450
1508
  "rewritten": _enforce_table_row(ladder, row),
1451
1509
  "effort_set": _enforce_table_effort(ladder, row),
1510
+ "backup_set": _enforce_table_backup(ladder, row),
1452
1511
  "frontier": [p.candidate.model_name for p in row.frontier],
1453
1512
  }
1454
1513
  else:
@@ -1463,6 +1522,8 @@ def recommend_structured_ladder(
1463
1522
  )
1464
1523
  for tier in _LADDER_TIERS
1465
1524
  }
1525
+ if frontier_guard is not None and frontier_guard["classification"] is not None:
1526
+ _attach_backup_plans(picks, table[frontier_guard["classification"]])
1466
1527
  guard = _ladder_tier_guard(picks)
1467
1528
  if frontier_guard is not None:
1468
1529
  guard.update(frontier_guard)
@@ -56,7 +56,7 @@ from __future__ import annotations
56
56
  import json
57
57
  import math
58
58
  import re
59
- from dataclasses import asdict, dataclass, field
59
+ from dataclasses import asdict, dataclass, field, replace
60
60
  from datetime import datetime, timedelta, timezone
61
61
  from importlib import resources
62
62
  from typing import Any, Final
@@ -79,6 +79,21 @@ CATEGORIES: Final[tuple[str, ...]] = (
79
79
  COMPLEXITIES: Final[tuple[str, ...]] = ("low", "medium", "high")
80
80
  BUDGETS: Final[tuple[str, ...]] = ("cheap", "balanced", "best")
81
81
  EFFORT_LADDER: Final[tuple[str, ...]] = ("low", "medium", "high", "xhigh", "max")
82
+ # Level names across every documented effort dial, lowest first: the scorer's
83
+ # five plus the words some surfaces add below or above them (Gemini's
84
+ # `minimal`, Codex's `ultra`). Orders and compares native levels; each
85
+ # surface's own list comes from the catalog's `effort_levels`, which
86
+ # update/build_catalog.py reads from the trackers' snapshots of its docs.
87
+ LEVEL_ORDER: Final[tuple[str, ...]] = (
88
+ "none",
89
+ "minimal",
90
+ "low",
91
+ "medium",
92
+ "high",
93
+ "xhigh",
94
+ "max",
95
+ "ultra",
96
+ )
82
97
 
83
98
  # The Artificial Analysis evidence figure per category. For the five derived
84
99
  # categories it is the evidence update/derive_ratings.py letters from, and the
@@ -621,6 +636,71 @@ def effort_for(task: Task, headroom: str, scarcity: float) -> str:
621
636
  return EFFORT_LADDER[rung]
622
637
 
623
638
 
639
+ def normalize_level(value: str) -> str:
640
+ """An effort level name compared case- and spacing-free: "XHigh",
641
+ "x-high" and "Extra high" are all ``xhigh``."""
642
+ level = re.sub(r"[\s_-]+", "", value.strip().lower())
643
+ return {"extrahigh": "xhigh"}.get(level, level)
644
+
645
+
646
+ def native_levels(
647
+ platform: str, model: str, *, catalog: dict[str, Any] | None = None
648
+ ) -> list[str] | None:
649
+ """The levels ``platform``'s effort dial offers for ``model``, lowest
650
+ first, as the catalog records them from that surface's own docs: the
651
+ method's ``effort_levels_by_model`` entry where its docs distinguish
652
+ models (a model they leave out has no documented dial), else its
653
+ ``effort_levels``. None when the catalog documents no dial; the engine
654
+ then maps the scorer's level to the surface itself."""
655
+ cat = catalog if catalog is not None else _cost._load_catalog()
656
+ for method in cat.get("access_methods", []):
657
+ if not isinstance(method, dict) or method.get("id") != platform:
658
+ continue
659
+ by_model = method.get("effort_levels_by_model")
660
+ levels = by_model.get(model) if isinstance(by_model, dict) else method.get("effort_levels")
661
+ if isinstance(levels, list) and levels and all(isinstance(x, str) for x in levels):
662
+ return list(levels)
663
+ return None
664
+ return None
665
+
666
+
667
+ def native_level(
668
+ platform: str, model: str, scorer_level: str, *, catalog: dict[str, Any] | None = None
669
+ ) -> str | None:
670
+ """The level to set on ``platform``'s dial for ``model`` when the scorer
671
+ says ``scorer_level``: the native level of that name, else the highest
672
+ native level below it, else the dial's lowest. None when the catalog
673
+ documents no dial for the pair."""
674
+ levels = native_levels(platform, model, catalog=catalog)
675
+ if not levels:
676
+ return None
677
+ want = normalize_level(scorer_level)
678
+ for level in levels:
679
+ if normalize_level(level) == want:
680
+ return level
681
+ order = {name: i for i, name in enumerate(LEVEL_ORDER)}
682
+ if want in order:
683
+ below = [lv for lv in levels if order.get(normalize_level(lv), len(order)) < order[want]]
684
+ if below:
685
+ return max(below, key=lambda lv: order[normalize_level(lv)])
686
+ return levels[0]
687
+
688
+
689
+ def _scorer_level(native: str) -> str:
690
+ """The scorer level a native level stands for: the highest of
691
+ EFFORT_LADDER at or below it, floored at ``low`` (Gemini's ``minimal``)
692
+ and capped at ``max`` (Codex's ``ultra``)."""
693
+ level = normalize_level(native)
694
+ if level in EFFORT_LADDER:
695
+ return level
696
+ order = {name: i for i, name in enumerate(LEVEL_ORDER)}
697
+ rank = order.get(level)
698
+ if rank is None:
699
+ return EFFORT_LADDER[0]
700
+ at_or_below = [e for e in EFFORT_LADDER if order[e] <= rank]
701
+ return at_or_below[-1] if at_or_below else EFFORT_LADDER[0]
702
+
703
+
624
704
  def _quality(
625
705
  model: dict[str, Any],
626
706
  task: Task,
@@ -944,16 +1024,46 @@ class FrontierPoint:
944
1024
  price_usd: float
945
1025
 
946
1026
 
1027
+ @dataclass(frozen=True)
1028
+ class Backup:
1029
+ """A rung's cross-maker substitute, read off the frontier of the other
1030
+ makers' models (see :func:`ladder`)."""
1031
+
1032
+ point: FrontierPoint
1033
+ effort: str
1034
+ native: str | None = None
1035
+
1036
+ @property
1037
+ def candidate(self) -> Candidate:
1038
+ return self.point.candidate
1039
+
1040
+ @property
1041
+ def level(self) -> str:
1042
+ return self.native or self.effort
1043
+
1044
+
947
1045
  @dataclass(frozen=True)
948
1046
  class Rung:
949
1047
  tier: str
950
1048
  point: FrontierPoint
1049
+ # The scorer's level (EFFORT_LADDER) this rung runs at.
951
1050
  effort: str
1051
+ # The same level as the platform's own dial names it (native_level), or
1052
+ # None when the catalog documents no dial for the platform and model.
1053
+ native: str | None = None
1054
+ # The best cross-maker substitute at no higher list price; None when the
1055
+ # pool holds no other maker's model.
1056
+ backup: Backup | None = None
952
1057
 
953
1058
  @property
954
1059
  def candidate(self) -> Candidate:
955
1060
  return self.point.candidate
956
1061
 
1062
+ @property
1063
+ def level(self) -> str:
1064
+ """The level the operator sets: the native word, else the scorer's."""
1065
+ return self.native or self.effort
1066
+
957
1067
 
958
1068
  @dataclass
959
1069
  class Ladder:
@@ -963,6 +1073,8 @@ class Ladder:
963
1073
  # complexity's requirement, cheapest first.
964
1074
  adequate: list[FrontierPoint]
965
1075
  rungs: dict[str, Rung]
1076
+ # Why a rung has no backup: the pool holds no other maker's model.
1077
+ backup_warning: str | None = None
966
1078
 
967
1079
  def to_dict(self) -> dict[str, Any]:
968
1080
  def point(p: FrontierPoint) -> dict[str, Any]:
@@ -982,7 +1094,24 @@ class Ladder:
982
1094
  "task": asdict(self.task),
983
1095
  "frontier": [point(p) for p in self.frontier],
984
1096
  "adequate": [p.candidate.model_id for p in self.adequate],
985
- "rungs": {t: {**point(r.point), "effort": r.effort} for t, r in self.rungs.items()},
1097
+ "rungs": {
1098
+ t: {
1099
+ **point(r.point),
1100
+ "effort": r.effort,
1101
+ "native_effort": r.native,
1102
+ "backup": (
1103
+ {
1104
+ **point(r.backup.point),
1105
+ "effort": r.backup.effort,
1106
+ "native_effort": r.backup.native,
1107
+ }
1108
+ if r.backup is not None
1109
+ else None
1110
+ ),
1111
+ }
1112
+ for t, r in self.rungs.items()
1113
+ },
1114
+ "backup_warning": self.backup_warning,
986
1115
  }
987
1116
 
988
1117
 
@@ -1030,12 +1159,16 @@ def ladder(
1030
1159
  at the balanced posture; with none between, the COST or QUALITY model at
1031
1160
  the balanced posture's effort, whichever differs from both other rungs.
1032
1161
  - Each rung runs at :func:`effort_for` its posture (cheap / balanced /
1033
- best) on its platform. A lower rung on the same model and platform as
1162
+ best), set on its platform's own dial (:func:`native_level`) where the
1163
+ catalog documents one. A lower rung on the same model and platform as
1034
1164
  the rung above runs at least one effort level below it
1035
1165
  (:func:`_below`), so the three picks stay distinct; at the dial's lowest
1036
1166
  level they may converge. On an `uncapped` pool every free rung runs at
1037
1167
  the top effort and two rungs on one model converge: a lower effort saves
1038
1168
  that operator nothing.
1169
+ - Each rung carries a BACKUP from another maker (:func:`_backup_for`):
1170
+ the best adequate point on the other makers' frontier at no higher list
1171
+ price. ``backup_warning`` says why a rung has none.
1039
1172
 
1040
1173
  None when no pool model carries an AA Index (no frontier to read)."""
1041
1174
  cat = catalog if catalog is not None else _cost._load_catalog()
@@ -1065,10 +1198,12 @@ def ladder(
1065
1198
 
1066
1199
  def rung(tier: str, p: FrontierPoint) -> Rung:
1067
1200
  t = Task(task.category, task.complexity, task.novel, TIER_BUDGET[tier])
1068
- return Rung(tier, p, effort_for(t, headroom, p.candidate.scarcity))
1201
+ effort = effort_for(t, headroom, p.candidate.scarcity)
1202
+ c = p.candidate
1203
+ return Rung(tier, p, effort, native_level(c.platform_id, c.model_id, effort, catalog=cat))
1069
1204
 
1070
1205
  def same(a: Rung, b: Rung) -> bool:
1071
- return a.candidate.model_id == b.candidate.model_id and a.effort == b.effort
1206
+ return a.candidate.model_id == b.candidate.model_id and a.level == b.level
1072
1207
 
1073
1208
  cost_rung = rung("cost", span[0])
1074
1209
  quality_rung = rung("quality", top)
@@ -1086,23 +1221,78 @@ def ladder(
1086
1221
  (r for r in options if not same(r, cost_rung) and not same(r, quality_rung)),
1087
1222
  options[0],
1088
1223
  )
1089
- balanced_rung = _below(balanced_rung, quality_rung)
1090
- cost_rung = _below(cost_rung, balanced_rung)
1091
- rungs = {"cost": cost_rung, "balanced": balanced_rung, "quality": quality_rung}
1092
- return Ladder(task=base, frontier=front, adequate=adequate, rungs=rungs)
1224
+ balanced_rung = _below(balanced_rung, quality_rung, cat)
1225
+ cost_rung = _below(cost_rung, balanced_rung, cat)
1226
+ pool = funded or ranking.candidates
1227
+ rungs = {
1228
+ r.tier: replace(r, backup=_backup_for(r, pool, task, headroom, bench, cat))
1229
+ for r in (cost_rung, balanced_rung, quality_rung)
1230
+ }
1231
+ warning: str | None = None
1232
+ if any(r.backup is None for r in rungs.values()):
1233
+ makers = sorted({c.provider for c in pool})
1234
+ warning = (
1235
+ f"No cross-provider backup: the models this user can run come from "
1236
+ f"{', '.join(makers) or 'no maker'} only. Add a second maker's subscription or "
1237
+ "API key so an outage or an exhausted pool has somewhere to go."
1238
+ )
1239
+ return Ladder(task=base, frontier=front, adequate=adequate, rungs=rungs, backup_warning=warning)
1240
+
1241
+
1242
+ def _backup_for(
1243
+ rung: Rung,
1244
+ pool: list[Candidate],
1245
+ task: Task,
1246
+ headroom: str,
1247
+ bench: dict[str, Any],
1248
+ catalog: dict[str, Any],
1249
+ ) -> Backup | None:
1250
+ """The rung's backup: the best substitute from another maker the operator
1251
+ can run, at no higher list price.
1252
+
1253
+ Over the pool's models from a maker other than the rung model's, draw the
1254
+ frontier and keep its adequate points. The backup is the adequate point
1255
+ with the highest list price at or below the rung's; else the cheapest
1256
+ adequate point above it; else, with no adequate point, the frontier's
1257
+ strongest for the category. It runs at the rung's posture's effort on its
1258
+ own platform. None when the pool holds no other maker's model."""
1259
+ front = frontier([c for c in pool if c.provider != rung.candidate.provider], bench)
1260
+ if not front:
1261
+ return None
1262
+ adequate = [p for p in front if p.candidate.requirement_penalty == 0]
1263
+ if adequate:
1264
+ within = [p for p in adequate if p.price_usd <= rung.point.price_usd]
1265
+ chosen = within[-1] if within else adequate[0]
1266
+ else:
1267
+ chosen = max(front, key=lambda p: (p.candidate.quality, p.aa_index, -p.price_usd))
1268
+ t = Task(task.category, task.complexity, task.novel, TIER_BUDGET[rung.tier])
1269
+ c = chosen.candidate
1270
+ effort = effort_for(t, headroom, c.scarcity)
1271
+ return Backup(chosen, effort, native_level(c.platform_id, c.model_id, effort, catalog=catalog))
1093
1272
 
1094
1273
 
1095
- def _below(lower: Rung, upper: Rung) -> Rung:
1274
+ def _below(lower: Rung, upper: Rung, catalog: dict[str, Any]) -> Rung:
1096
1275
  """``lower`` run at least one effort level below ``upper`` when both name
1097
1276
  the same model on the same platform and that path costs something
1098
1277
  (scarcity > 0); floored at the dial's lowest level, where the two may
1099
1278
  converge. Three columns that read the same offer no choice, and on a
1100
1279
  capped pool a lower effort is a real, cheaper option. On a free path
1101
1280
  (`uncapped`) a lower effort saves nothing, so the rungs keep their
1102
- posture's effort and converge."""
1281
+ posture's effort and converge.
1282
+
1283
+ Levels are compared on the platform's own dial where the catalog
1284
+ documents one, since two scorer levels can land on one native level
1285
+ (Gemini's dial tops out at high, where the scorer's xhigh and max land
1286
+ too); elsewhere on the scorer's ladder."""
1103
1287
  lc, uc = lower.candidate, upper.candidate
1104
1288
  if (lc.model_id, lc.platform_id) != (uc.model_id, uc.platform_id) or lc.scarcity == 0:
1105
1289
  return lower
1290
+ levels = native_levels(lc.platform_id, lc.model_id, catalog=catalog)
1291
+ if levels and lower.native in levels and upper.native in levels:
1292
+ cap = max(0, levels.index(upper.native) - 1)
1293
+ if levels.index(lower.native) <= cap:
1294
+ return lower
1295
+ return Rung(lower.tier, lower.point, _scorer_level(levels[cap]), levels[cap])
1106
1296
  cap = max(0, EFFORT_LADDER.index(upper.effort) - 1)
1107
1297
  if EFFORT_LADDER.index(lower.effort) <= cap:
1108
1298
  return lower
@@ -1165,14 +1355,19 @@ def render_ladder_table(table: dict[str, Ladder]) -> str:
1165
1355
  for p in any_ladder.frontier
1166
1356
  ),
1167
1357
  "Rows: <category>/<complexity>[/novel]: COST | BALANCED | QUALITY, each "
1168
- "<model> @ <platform> · <effort>.",
1358
+ "<model> @ <platform> · <effort>, the effort in the platform's own level words, "
1359
+ "then `backup <model> @ <platform>`: that pick's BACKUP.",
1169
1360
  ]
1361
+
1362
+ def cell(t: str, r: Rung) -> str:
1363
+ text = f"{t.upper()} = {r.candidate.model_name} @ {r.candidate.platform_name} · {r.level}"
1364
+ if r.backup is not None:
1365
+ b = r.backup.candidate
1366
+ text += f", backup {b.model_name} @ {b.platform_name}"
1367
+ return text
1368
+
1170
1369
  for key, lad in table.items():
1171
- cells = " | ".join(
1172
- f"{t.upper()} = {r.candidate.model_name} @ {r.candidate.platform_name} · {r.effort}"
1173
- for t in LADDER_TIERS
1174
- for r in [lad.rungs[t]]
1175
- )
1370
+ cells = " | ".join(cell(t, lad.rungs[t]) for t in LADDER_TIERS)
1176
1371
  lines.append(f"{key}: {cells}")
1177
1372
  lines.append("</ladder-table>")
1178
1373
  return "\n".join(lines)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes