one-code-extension 0.4.0 → 0.5.0
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.
- package/README.md +219 -219
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +32 -23
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +27 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +66 -34
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +123 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +515 -0
- package/extensions/background/index.ts +514 -236
- package/extensions/background/loop-fire.ts +136 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +52 -0
- package/extensions/background/wakeup.ts +159 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +88 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +229 -71
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +446 -58
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +216 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/index.ts +24 -5
- package/extensions/context-management/index.ts +25 -0
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +72 -19
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +337 -26
- package/extensions/file-tracker/replay.ts +96 -10
- package/extensions/file-tracker/restore.ts +63 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +32 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +198 -31
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +13 -5
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/lib/agent-cron.ts +130 -0
- package/extensions/lib/agent-loader.ts +15 -1
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +42 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +78 -0
- package/extensions/lib/context-stack.ts +81 -0
- package/extensions/lib/deferred.ts +219 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/local-command.ts +47 -13
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +174 -0
- package/extensions/lib/model-catalog/models-dev.json +18297 -0
- package/extensions/lib/model-catalog/openrouter.json +5252 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +308 -64
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +21 -2
- package/extensions/lib/plan-mode-channels.ts +20 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/process-tree.ts +131 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +364 -58
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +439 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +13 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +113 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +149 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +143 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +70 -4
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +87 -5
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +209 -71
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +151 -67
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1300 -381
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +6 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +156 -69
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +61 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +12 -7
- package/extensions/recap/index.ts +65 -60
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +421 -101
- package/extensions/skill/invoke.ts +128 -18
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +116 -23
- package/extensions/subagents/announce.ts +69 -0
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +76 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1493 -824
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +2 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/runner.ts +108 -80
- package/extensions/subagents/runs.ts +39 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +131 -47
- package/extensions/system-prompt/template.ts +24 -50
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +175 -13
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/announce.ts +16 -0
- package/extensions/tool-search/index.ts +189 -41
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +105 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +145 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +29 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +121 -39
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +19 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
|
@@ -1,63 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Which system-prompt register + tool surface the active model gets. Claude Code
|
|
3
|
-
* ships a capability-tiered prompt — a
|
|
4
|
-
* explicit one for
|
|
5
|
-
*
|
|
3
|
+
* ships a capability-tiered prompt — a short one for Opus ≥ 4.8, Sonnet ≥ 5.5
|
|
4
|
+
* and Fable, a much longer, more explicit one for Haiku and the older Sonnets
|
|
5
|
+
* and Opuses (findings §56). One Code serves every provider, so
|
|
6
6
|
* its non-frontier audience extends *below* Haiku; a single lean prompt
|
|
7
7
|
* under-instructs it. This module maps a model to one of FOUR tiers:
|
|
8
8
|
*
|
|
9
|
-
* frontier
|
|
10
|
-
* workhorse
|
|
11
|
-
* cheap
|
|
12
|
-
* tiny
|
|
9
|
+
* frontier Claude Code's own short-prompt models, and OpenAI's GPT-6+ Astra and Sol
|
|
10
|
+
* workhorse a vendor's large current models — the long register
|
|
11
|
+
* cheap a vendor's smaller or older models — the long register
|
|
12
|
+
* tiny literally small models — verbose + weak-model scaffolding + grep/find/ls
|
|
13
13
|
*
|
|
14
14
|
* `extensions/system-prompt` picks the prompt text from the tier;
|
|
15
|
-
* `extensions/search-tools` activates grep/find/ls only for `tiny`.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* class sets the tier and the model's GENERATION (models.dev release dates,
|
|
22
|
-
* `model-facts.ts`) demotes it — price is not consulted once facts are known, so
|
|
23
|
-
* an unpriced but current flagship on a hosted catalog keeps the workhorse
|
|
24
|
-
* register. Rows without facts fall back to the name-class cap, the "pro"-class
|
|
25
|
-
* hint and the absolute price floor, where unpriced/opaque means maximum
|
|
26
|
-
* scaffolding. The Artificial Analysis index never sets a register; it feeds the
|
|
27
|
-
* optional measured SELECTION floor (`capability-index.ts`) — see
|
|
28
|
-
* `classifyModelTier` and `docs/decisions/model-tiers.md`.
|
|
15
|
+
* `extensions/search-tools` activates grep/find/ls only for `tiny`. Frontier is
|
|
16
|
+
* a version-gated first-party allowlist (Claude Code's own rule); below it the
|
|
17
|
+
* tier comes from the public catalogs (`model-catalog.ts`: release date, price
|
|
18
|
+
* and size against the same vendor's current models), with no model names,
|
|
19
|
+
* dollar floors or benchmark scores in the code. Rationale + rejected
|
|
20
|
+
* alternatives: `working-docs/decisions/model-tiers.md`.
|
|
29
21
|
*/
|
|
30
22
|
|
|
23
|
+
import { statSync } from "node:fs";
|
|
24
|
+
import { homedir } from "node:os";
|
|
31
25
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
26
|
+
import { readJsonFile } from "./atomic-write.ts";
|
|
27
|
+
import { autoSelectable, catalogModelFor, sizeFromName, smallByName, TINY_PARAMS } from "./model-catalog.ts";
|
|
32
28
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
import {
|
|
29
|
+
BUILTIN_PROVIDER_POLICIES,
|
|
30
|
+
baseModelId,
|
|
31
|
+
isDatedDuplicate,
|
|
32
|
+
isExperimentalBuild,
|
|
33
|
+
modelIdentity,
|
|
34
|
+
modelsContainedToSession,
|
|
35
|
+
modelSpec,
|
|
36
|
+
pricedInput,
|
|
37
|
+
providerPolicy,
|
|
38
|
+
supportsImageInput,
|
|
39
|
+
} from "./model-policy.ts";
|
|
40
|
+
import { oneCodeSettingsPath } from "./one-code-settings.ts";
|
|
45
41
|
|
|
46
42
|
export type PromptTier = "frontier" | "workhorse" | "cheap" | "tiny";
|
|
47
43
|
|
|
48
|
-
/** More-scaffolded = higher rank.
|
|
44
|
+
/** More-scaffolded = higher rank. */
|
|
49
45
|
const TIER_RANK: Record<PromptTier, number> = { frontier: 0, workhorse: 1, cheap: 2, tiny: 3 };
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
|
|
47
|
+
/** Whether `value` names one of the four tiers. */
|
|
48
|
+
export function isPromptTier(value: unknown): value is PromptTier {
|
|
49
|
+
return typeof value === "string" && Object.hasOwn(TIER_RANK, value);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* `economicalContainedCandidates`).
|
|
59
|
-
*/
|
|
60
|
-
const COST_PREFERENCE: Record<Exclude<PromptTier, "tiny">, number> = { cheap: 0, workhorse: 1, frontier: 2 };
|
|
52
|
+
/** How many tiers `tier` sits below `reference` (negative when above). */
|
|
53
|
+
export function tiersBelow(tier: PromptTier, reference: PromptTier): number {
|
|
54
|
+
return TIER_RANK[tier] - TIER_RANK[reference];
|
|
55
|
+
}
|
|
61
56
|
|
|
62
57
|
/**
|
|
63
58
|
* `CC_PROMPT_TIER=frontier|workhorse|cheap|tiny` forces a register; `auto`/unset
|
|
@@ -66,14 +61,15 @@ const COST_PREFERENCE: Record<Exclude<PromptTier, "tiny">, number> = { cheap: 0,
|
|
|
66
61
|
*/
|
|
67
62
|
export function tierOverride(env: NodeJS.ProcessEnv = process.env): PromptTier | undefined {
|
|
68
63
|
const raw = env.CC_PROMPT_TIER?.trim().toLowerCase();
|
|
69
|
-
return raw
|
|
64
|
+
return isPromptTier(raw) ? raw : undefined;
|
|
70
65
|
}
|
|
71
66
|
|
|
72
67
|
/**
|
|
73
|
-
* Anthropic first-party frontier gate: Opus
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
68
|
+
* Anthropic first-party frontier gate: Opus ≥ 4.8, Sonnet ≥ 5.5 and every
|
|
69
|
+
* Fable — the models Claude Code gives its short prompt and tool forms (Opus
|
|
70
|
+
* 4.7 and Sonnet 5 get the long ones; findings §56). Haiku 5 and later get the
|
|
71
|
+
* short forms too, but only for the prompt (`isHaikuWithFrontierForms`).
|
|
72
|
+
* Claude Code decides this by version, so One Code does too. Adapted from pi-ai's
|
|
77
73
|
* `defaultSupportsToolReferences` — never Haiku, and the `length < 8` guard stops
|
|
78
74
|
* a dated suffix (`claude-opus-4-8-20251101`) being read as the minor version.
|
|
79
75
|
* Version parse, not price: `claude-opus-4-1` ($15/M) costs more than
|
|
@@ -83,8 +79,39 @@ function isAnthropicFrontier(model: Model<Api>): boolean {
|
|
|
83
79
|
if (model.provider !== "anthropic" || model.id.includes("haiku")) return false;
|
|
84
80
|
const version = parseClaudeVersion(model.id);
|
|
85
81
|
if (!version) return false;
|
|
86
|
-
if (version.family === "
|
|
87
|
-
return version.major >
|
|
82
|
+
if (version.family === "fable") return true;
|
|
83
|
+
if (version.family === "sonnet") return version.major > 5 || (version.major === 5 && version.minor >= 5);
|
|
84
|
+
return version.major > 4 || (version.major === 4 && version.minor >= 8);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* First-party Haiku 5 and later take the frontier prompt and tool forms
|
|
89
|
+
* (findings §68), but nothing else that reads the tier: model selection, the
|
|
90
|
+
* permission gate's fast paths and the doctor judge a Haiku by its catalog
|
|
91
|
+
* tier, so the cost algorithm never offers one as an Opus-class substitute.
|
|
92
|
+
*/
|
|
93
|
+
function isHaikuWithFrontierForms(model: Model<Api>): boolean {
|
|
94
|
+
if (model.provider !== "anthropic") return false;
|
|
95
|
+
const major = model.id.match(/^claude-haiku-(\d+)(?:-|$)/)?.[1];
|
|
96
|
+
return major !== undefined && Number(major) >= 5;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** What a tier is for: the session's prompt and tool forms, or judging a model's capability. */
|
|
100
|
+
export type TierPurpose = "prompt" | "capability";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* OpenAI first-party frontier gate: the GPT-6 Astra and Sol lines and later
|
|
104
|
+
* majors (`gpt-6-astra`, `gpt-6-sol`, `gpt-6.1-sol-pro`), served by OpenAI
|
|
105
|
+
* itself (the `openai`, `openai-codex` and `azure` providers; `azure-openai-responses`
|
|
106
|
+
* before pi 1.0.3).
|
|
107
|
+
* GPT-5.x Sol and Terra, and every Luna, stay below it (decided 2026-09-25:
|
|
108
|
+
* Astra and Sol 6 are the OpenAI models comparable to Fable and Opus 5). A
|
|
109
|
+
* gateway-proxied copy can't be verified, the same rule as Claude above.
|
|
110
|
+
*/
|
|
111
|
+
function isOpenAIFrontier(model: Model<Api>): boolean {
|
|
112
|
+
if (providerPolicy(model.provider).kind !== "direct" || modelIdentity(model).profile !== "openai") return false;
|
|
113
|
+
const match = model.id.match(/^gpt-(\d+)(?:\.\d+)?-(?:astra|sol)(?:-|$)/);
|
|
114
|
+
return match !== null && Number(match[1]) >= 6;
|
|
88
115
|
}
|
|
89
116
|
|
|
90
117
|
/**
|
|
@@ -92,7 +119,7 @@ function isAnthropicFrontier(model: Model<Api>): boolean {
|
|
|
92
119
|
* `claude-sonnet-5`, `claude-opus-4-5-20251101`): family, major, minor. A
|
|
93
120
|
* dated suffix is never read as the minor version (the `length < 8` guard).
|
|
94
121
|
* Undefined for anything else. Shared by the frontier gate here and the
|
|
95
|
-
*
|
|
122
|
+
* server-tools gate in `anthropic-server-tools.ts`.
|
|
96
123
|
*/
|
|
97
124
|
export function parseClaudeVersion(id: string): { family: "opus" | "sonnet" | "fable"; major: number; minor: number } | undefined {
|
|
98
125
|
const version = id.match(/^claude-(opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
|
|
@@ -104,103 +131,7 @@ export function parseClaudeVersion(id: string): { family: "opus" | "sonnet" | "f
|
|
|
104
131
|
};
|
|
105
132
|
}
|
|
106
133
|
|
|
107
|
-
|
|
108
|
-
* Curated anchor map for well-known third-party families, checked in order
|
|
109
|
-
* (specific before general). A vetted allowlist — distinct from heuristic
|
|
110
|
-
* substring matching — that fixes cases where price/name alone misroute a
|
|
111
|
-
* flagship, and encodes the ratified overrides (GPT-5-mini→cheap,
|
|
112
|
-
* GPT-5.6-Luna→cheap, GPT-5.x-codex-spark→cheap; the GPT-5-full→workhorse one
|
|
113
|
-
* was retired 2026-09-11 once the coding index contradicted it). A match here is authoritative for the
|
|
114
|
-
* name class (the human already accounted for the name and its generation), so
|
|
115
|
-
* it wins outright — only the two-generations-behind rule still demotes it to
|
|
116
|
-
* tiny (`classifyModelTier`). Shrink it whenever the catalog-wide snapshot
|
|
117
|
-
* (`test/unit/model-tier-catalog.test.ts`) shows the generic rules reproduce an
|
|
118
|
-
* entry; refresh the reference table with `tools/model-tiers/model_tiers.py`.
|
|
119
|
-
*/
|
|
120
|
-
const ANCHOR_MAP: Array<[RegExp, PromptTier]> = [
|
|
121
|
-
// OpenAI GPT-5 family — order matters: variant suffixes before the generic.
|
|
122
|
-
[/(?:^|[-/.])gpt-5[.\d]*-?nano/i, "tiny"],
|
|
123
|
-
[/(?:^|[-/.])gpt-5[.\d]*-?mini/i, "cheap"], // override: price would say tiny
|
|
124
|
-
[/(?:^|[-/.])gpt-5[.\d]*-?luna/i, "cheap"], // override: OpenAI's cheap line despite a high benchmark
|
|
125
|
-
// Spark: OpenAI's fast/small Codex line; no Artificial Analysis row either way, so it
|
|
126
|
-
// is placed with the other lean lines rather than inheriting the family's workhorse
|
|
127
|
-
// name class (2026-09-11 — it had been the cheapest "workhorse" on every Codex session).
|
|
128
|
-
[/(?:^|[-/.])gpt-5[.\d]*-codex-spark/i, "cheap"],
|
|
129
|
-
// No blanket `gpt-5 → workhorse` anchor any more (2026-09-11): it existed to guard
|
|
130
|
-
// the base GPT-5 variant against a benchmark collision, but the coding index now
|
|
131
|
-
// reads base gpt-5 at 37.8 and gpt-5.1 at 49.4 — the anchor was the lie, not the
|
|
132
|
-
// score. Unsuffixed gpt-5.x rows take the name class (workhorse) and let their
|
|
133
|
-
// generation and measured score demote them.
|
|
134
|
-
[/(?:^|[-/.])o[34](?:[-/.]|$)/i, "cheap"], // o3, o3-pro, o4-mini
|
|
135
|
-
[/(?:^|[-/.])gpt-4/i, "tiny"], // gpt-4o, gpt-4.1, gpt-4-turbo — prior generation
|
|
136
|
-
// Anthropic reaching this path is gateway-proxied (first-party handled above);
|
|
137
|
-
// a proxied model can't be version-verified for frontier, so Opus/Sonnet → workhorse.
|
|
138
|
-
[/claude[-/.].*haiku/i, "cheap"],
|
|
139
|
-
[/claude[-/.].*(?:sonnet|opus)/i, "workhorse"],
|
|
140
|
-
// Google Gemini needs no anchor: `lite` is a tiny name, `pro` a capable one, and
|
|
141
|
-
// bare flash is cheap by name class (generation decides 2.5 vs 3.x) — removed
|
|
142
|
-
// 2026-09-10 once the catalog-wide snapshot showed the generic rules reproduce it.
|
|
143
|
-
// Other capable flagships whose price may dip below the workhorse floor.
|
|
144
|
-
[/(?:^|[-/.])grok-4/i, "workhorse"],
|
|
145
|
-
// DeepSeek — priced ~10x below the vendors the price floors were calibrated
|
|
146
|
-
// on, so on the no-facts path the floors alone put every V4 Flash row in tiny
|
|
147
|
-
// and let R1-0528 ($0.50, on the cheap boundary) win automatic selection
|
|
148
|
-
// (2026-09-10). With release-date facts the generic rules reproduce the V4
|
|
149
|
-
// rows; the block stays for rows models.dev does not know yet (V4.1 Flash on
|
|
150
|
-
// its release day) and for R1/V3.x/`deepseek-chat`, which a one-generation
|
|
151
|
-
// demotion would leave at cheap rather than tiny.
|
|
152
|
-
[/(?:^|[-/.])deepseek[-.]v4[.\d]*-flash/i, "cheap"],
|
|
153
|
-
[/(?:^|[-/.])deepseek[-.]v4[.\d]*-pro/i, "workhorse"],
|
|
154
|
-
[/(?:^|[-/.])deepseek[-.](?:r1|chat|v3)(?:[-.:]|$)/i, "tiny"], // `deepseek.v3` is Bedrock's spelling
|
|
155
|
-
];
|
|
156
|
-
|
|
157
|
-
function anchorTier(id: string): PromptTier | undefined {
|
|
158
|
-
const base = baseModelId(id);
|
|
159
|
-
for (const [pattern, tier] of ANCHOR_MAP) if (pattern.test(base)) return tier;
|
|
160
|
-
return undefined;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Name-class CAP: a lean/fast model class raises scaffolding (never lowers it).
|
|
165
|
-
* Delimited tokens — a bare `/mini/` matches inside "geMINI", dragging every
|
|
166
|
-
* Gemini model to cheap. `\d+b` catches size tags (8b, 70b) as the strongest
|
|
167
|
-
* (tiny) signal. Returns the ceiling tier the name justifies.
|
|
168
|
-
*/
|
|
169
|
-
const TINY_NAME_HINT = /(?:^|[-/.])(?:nano|micro|lite|tiny|xs|distill|instant|\d+b)(?:[-/.]|$)/i;
|
|
170
|
-
const CHEAP_NAME_HINT = /(?:^|[-/.])(?:flash|mini|small|air)(?:[-/.]|$)/i;
|
|
171
|
-
function nameClassCap(id: string): PromptTier {
|
|
172
|
-
const base = baseModelId(id); // `flash:batch` must still read as flash
|
|
173
|
-
if (TINY_NAME_HINT.test(base)) return "tiny";
|
|
174
|
-
if (CHEAP_NAME_HINT.test(base)) return "cheap";
|
|
175
|
-
return "workhorse";
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* A capable-class name marks the flagship of a family (deepseek-v4-pro,
|
|
180
|
-
* qwen3-max) — keeps a cheap or unpriced flagship at workhorse rather than being
|
|
181
|
-
* dragged down by the price floor. Delimited so it fires on `-pro`/`max-` but not
|
|
182
|
-
* `prometheus`/`improved`. Only applies when no lean-name cap contradicts it.
|
|
183
|
-
*/
|
|
184
|
-
const CAPABLE_NAME_HINT = /(?:^|[-/.])(?:pro|max|ultra|large)(?:[-/.]|$)/i;
|
|
185
|
-
|
|
186
|
-
/** Input price (USD/1M) floors: capable flagships sit ≥ $1/M; mini/flash under $0.5. */
|
|
187
|
-
const WORKHORSE_MIN_INPUT_COST = 1.0;
|
|
188
|
-
const CHEAP_MIN_INPUT_COST = 0.5;
|
|
189
|
-
|
|
190
|
-
/** Price/containment floor for an unanchored third-party model, biased low. */
|
|
191
|
-
function priceFloorTier(model: Model<Api>): PromptTier {
|
|
192
|
-
if (modelIdentity(model).confidence === "opaque") return "tiny"; // local/self-hosted → max scaffolding
|
|
193
|
-
const price = pricedInput(model);
|
|
194
|
-
if (price === undefined) return "tiny"; // unpriced/unknown → maximum scaffolding
|
|
195
|
-
if (price >= WORKHORSE_MIN_INPUT_COST) return "workhorse";
|
|
196
|
-
if (price >= CHEAP_MIN_INPUT_COST) return "cheap";
|
|
197
|
-
return "tiny";
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** One step more scaffolded: workhorse → cheap → tiny (frontier never enters). */
|
|
201
|
-
function demote(tier: PromptTier): PromptTier {
|
|
202
|
-
return tier === "workhorse" ? "cheap" : "tiny";
|
|
203
|
-
}
|
|
134
|
+
export { claudeFamily, parseClaudeId } from "./claude-id.ts";
|
|
204
135
|
|
|
205
136
|
export interface TierClassification {
|
|
206
137
|
tier: PromptTier;
|
|
@@ -210,110 +141,128 @@ export interface TierClassification {
|
|
|
210
141
|
|
|
211
142
|
/**
|
|
212
143
|
* The tier and the rule that produced it. `resolveModelTier` is the plain form.
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* 3. without facts, the pre-facts heuristics: name-class cap, the capable-name
|
|
225
|
-
* hint (verifiable providers only), and the absolute price floor, combined
|
|
226
|
-
* as the more scaffolded of the two.
|
|
144
|
+
* In order:
|
|
145
|
+
* 1. `CC_PROMPT_TIER` (the session's register only; selection ignores it);
|
|
146
|
+
* 2. the user's `modelTiers` setting for this model;
|
|
147
|
+
* 3. the Claude Code parity gate: first-party Opus ≥ 4.8, Sonnet ≥ 5.5,
|
|
148
|
+
* Fable, and OpenAI's GPT-6+ Astra and Sol are frontier; for the prompt
|
|
149
|
+
* (`purpose: "prompt"`, the default) first-party Haiku ≥ 5 is too;
|
|
150
|
+
* 4. the catalog tier (`model-catalog.ts`): tiny by size, workhorse or cheap
|
|
151
|
+
* by size or price against the same vendor's current models, demoted one
|
|
152
|
+
* step by age;
|
|
153
|
+
* 5. a model no catalog knows: tiny when its id names a small size or it is
|
|
154
|
+
* unpriced or on a custom provider, cheap otherwise.
|
|
227
155
|
*/
|
|
228
|
-
export function classifyModelTier(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
156
|
+
export function classifyModelTier(
|
|
157
|
+
model: Model<Api> | undefined,
|
|
158
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
159
|
+
purpose: TierPurpose = "prompt",
|
|
160
|
+
): TierClassification {
|
|
161
|
+
const forced = tierOverride(env);
|
|
162
|
+
if (forced) return { tier: forced, reason: "CC_PROMPT_TIER" };
|
|
163
|
+
if (!model) return { tier: "tiny", reason: "no model" }; // unknown model → maximum scaffolding
|
|
164
|
+
const configured = configuredModelTier(model);
|
|
165
|
+
if (configured) return { tier: configured, reason: "modelTiers setting" };
|
|
166
|
+
if (isAnthropicFrontier(model)) return { tier: "frontier", reason: "anthropic frontier allowlist" };
|
|
167
|
+
if (purpose === "prompt" && isHaikuWithFrontierForms(model)) return { tier: "frontier", reason: "anthropic frontier allowlist (prompt and tool forms)" };
|
|
168
|
+
if (isOpenAIFrontier(model)) return { tier: "frontier", reason: "openai frontier allowlist" };
|
|
169
|
+
const entry = catalogModelFor(model);
|
|
170
|
+
if (entry) return { tier: entry.tier, reason: `catalog ${entry.id}: ${entry.reason}` };
|
|
171
|
+
const id = baseModelId(model.id);
|
|
172
|
+
const size = sizeFromName(id);
|
|
173
|
+
if (size !== undefined && size < TINY_PARAMS) return { tier: "tiny", reason: "not in the catalogs; a small size in its id" };
|
|
174
|
+
if (size === undefined && smallByName(id)) return { tier: "tiny", reason: "not in the catalogs; a small-model name" };
|
|
175
|
+
if (modelIdentity(model).confidence === "opaque" && !(model.provider in BUILTIN_PROVIDER_POLICIES)) {
|
|
176
|
+
return { tier: "tiny", reason: "custom provider, not in the catalogs" };
|
|
177
|
+
}
|
|
178
|
+
if (pricedInput(model) === undefined) return { tier: "tiny", reason: "unpriced, not in the catalogs" };
|
|
179
|
+
return { tier: "cheap", reason: "not in the catalogs" };
|
|
242
180
|
}
|
|
243
181
|
|
|
244
182
|
/**
|
|
245
|
-
* The
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* Undefined without a snapshot, a confirmed score, or a reference score — the
|
|
249
|
-
* name-led tier then stands, as before the snapshot existed.
|
|
183
|
+
* The user's own tier for a model from `modelTiers` in `~/.onecode/settings.json`,
|
|
184
|
+
* keyed by `provider/id` or by bare id (`{ "openrouter/qwen/qwen3.8-max": "workhorse" }`).
|
|
185
|
+
* User settings only: a project cannot loosen its own permission gate.
|
|
250
186
|
*/
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
const score = scoreFor(snapshot, model, "default");
|
|
255
|
-
const reference = referenceScore(snapshot, "default");
|
|
256
|
-
if (!score || !reference) return undefined;
|
|
257
|
-
const tinyLine = reference.coding * REGISTER_TINY_RATIO;
|
|
258
|
-
if (score.coding < tinyLine) return { cap: "tiny", coding: score.coding, threshold: tinyLine };
|
|
259
|
-
const cheapLine = reference.coding * REGISTER_CHEAP_RATIO;
|
|
260
|
-
if (score.coding < cheapLine) return { cap: "cheap", coding: score.coding, threshold: cheapLine };
|
|
261
|
-
return undefined;
|
|
187
|
+
function configuredModelTier(model: { provider: string; id: string }): PromptTier | undefined {
|
|
188
|
+
const tiers = modelTierOverrides();
|
|
189
|
+
return tiers[`${model.provider}/${model.id}`] ?? tiers[model.id];
|
|
262
190
|
}
|
|
263
191
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const forced = tierOverride(env);
|
|
267
|
-
if (forced) return { tier: forced, reason: "CC_PROMPT_TIER" };
|
|
268
|
-
if (!model) return { tier: "tiny", reason: "no model" }; // unknown model → maximum scaffolding
|
|
192
|
+
let overrideMemo: { path: string; at: number; mtimeMs: number; tiers: Record<string, PromptTier> } | undefined;
|
|
193
|
+
let overridesPinned: Record<string, PromptTier> | undefined;
|
|
269
194
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// automatic selection still skips prior-generation rows.
|
|
276
|
-
return model.id.includes("haiku") ? { tier: "cheap", reason: "anthropic haiku" } : { tier: "workhorse", reason: "anthropic first-party" };
|
|
277
|
-
}
|
|
195
|
+
/** Test seam: pin the `modelTiers` map (`undefined` reads the settings file again). */
|
|
196
|
+
export function setModelTierOverridesForTest(tiers: Record<string, PromptTier> | undefined): void {
|
|
197
|
+
overridesPinned = tiers;
|
|
198
|
+
overrideMemo = undefined;
|
|
199
|
+
}
|
|
278
200
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
201
|
+
/**
|
|
202
|
+
* The validated `modelTiers` map. Stat-checked at most once a second and
|
|
203
|
+
* re-parsed only when the settings file's mtime moved: the permission gate asks
|
|
204
|
+
* on every tool call.
|
|
205
|
+
*/
|
|
206
|
+
export function modelTierOverrides(): Record<string, PromptTier> {
|
|
207
|
+
if (overridesPinned) return overridesPinned;
|
|
208
|
+
const path = oneCodeSettingsPath(homedir());
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
if (overrideMemo && overrideMemo.path === path && now - overrideMemo.at < 1000) return overrideMemo.tiers;
|
|
211
|
+
let mtimeMs = -1;
|
|
212
|
+
try {
|
|
213
|
+
mtimeMs = statSync(path).mtimeMs;
|
|
214
|
+
} catch {
|
|
215
|
+
// No settings file: no overrides.
|
|
216
|
+
}
|
|
217
|
+
if (overrideMemo && overrideMemo.path === path && overrideMemo.mtimeMs === mtimeMs) {
|
|
218
|
+
overrideMemo.at = now;
|
|
219
|
+
return overrideMemo.tiers;
|
|
296
220
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return
|
|
221
|
+
const raw = mtimeMs < 0 ? undefined : readJsonFile<{ modelTiers?: unknown }>(path)?.modelTiers;
|
|
222
|
+
const tiers: Record<string, PromptTier> = {};
|
|
223
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
224
|
+
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
|
225
|
+
if (isPromptTier(value)) tiers[key] = value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
overrideMemo = { path, at: now, mtimeMs, tiers };
|
|
229
|
+
return tiers;
|
|
306
230
|
}
|
|
307
231
|
|
|
308
232
|
export function resolveModelTier(model: Model<Api> | undefined, env: NodeJS.ProcessEnv = process.env): PromptTier {
|
|
309
233
|
return classifyModelTier(model, env).tier;
|
|
310
234
|
}
|
|
311
235
|
|
|
236
|
+
const TRUTHY_ENV = new Set(["1", "true", "yes", "on"]);
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Whether the session model gets the task tools (`task_create` and family),
|
|
240
|
+
* Claude Code's model gate for a foreground session: its current models run
|
|
241
|
+
* without a task list, which `CLAUDE_CODE_ENABLE_TODO_TOOLS` turns back on
|
|
242
|
+
* (findings §31). Off for the frontier tier and first-party Sonnet 5 or later;
|
|
243
|
+
* on for everything else, including an unknown model (Claude Code keeps the
|
|
244
|
+
* tools for a model it does not recognise). Claude Code also turns them on in
|
|
245
|
+
* a backgrounded session, which One Code does not have.
|
|
246
|
+
*/
|
|
247
|
+
export function taskToolsEnabled(
|
|
248
|
+
model: Model<Api> | undefined,
|
|
249
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
250
|
+
/** Request-surface callers pass the session's frozen tier (session-model-tier.ts). */
|
|
251
|
+
tier?: PromptTier,
|
|
252
|
+
): boolean {
|
|
253
|
+
if (TRUTHY_ENV.has(env.CLAUDE_CODE_ENABLE_TODO_TOOLS?.trim().toLowerCase() ?? "")) return true;
|
|
254
|
+
if (!model) return true;
|
|
255
|
+
if ((tier ?? resolveModelTier(model, env)) === "frontier") return false;
|
|
256
|
+
if (model.provider !== "anthropic") return true;
|
|
257
|
+
const version = parseClaudeVersion(model.id);
|
|
258
|
+
return !(version?.family === "sonnet" && version.major >= 5);
|
|
259
|
+
}
|
|
260
|
+
|
|
312
261
|
|
|
313
262
|
/**
|
|
314
263
|
* The ordered candidate chain for an automatic *secondary* model — the same
|
|
315
|
-
* mechanism the auto-mode classifier and
|
|
316
|
-
*
|
|
264
|
+
* mechanism the auto-mode classifier, subagents and readers consume before
|
|
265
|
+
* their own budget and tier rules decide which models qualify.
|
|
317
266
|
*
|
|
318
267
|
* The chain is:
|
|
319
268
|
* - **contained** to the session's provider (and route/family on gateways):
|
|
@@ -321,32 +270,31 @@ export function resolveModelTier(model: Model<Api> | undefined, env: NodeJS.Proc
|
|
|
321
270
|
* the parent transcript, so leaving the session's provider silently is a
|
|
322
271
|
* privacy leak — only an explicit setting may cross (see the two model-select
|
|
323
272
|
* modules);
|
|
324
|
-
* - never `tiny`: a
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
* `resolveModelTier`, so this excludes it too. On a provider with no usable
|
|
334
|
-
* prices the chain is empty and callers degrade to the session model
|
|
335
|
-
* (correct, merely not cheap).
|
|
273
|
+
* - never `tiny`: a small model is a weak security boundary and a weak coding
|
|
274
|
+
* worker, so automatic selection stops at `cheap`;
|
|
275
|
+
* - known to the catalogs and selectable there (`model-catalog.ts`): not
|
|
276
|
+
* superseded by a newer model of its vendor in the same price band that
|
|
277
|
+
* this pool also offers, not two years behind its vendor's newest, not
|
|
278
|
+
* deprecated, able to call tools, a text model. A row no catalog knows is
|
|
279
|
+
* never picked automatically;
|
|
280
|
+
* - priced only: on a provider with no usable prices the chain is empty and
|
|
281
|
+
* callers degrade to the session model (correct, merely not cheap).
|
|
336
282
|
*
|
|
337
|
-
* Sorted by
|
|
338
|
-
*
|
|
339
|
-
* model
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
283
|
+
* Sorted by input price, the more capable tier first at equal price, so the
|
|
284
|
+
* head is "the cheapest usable model this provider offers." Price leads, not
|
|
285
|
+
* tier: a frontier model cheaper than every cheap one (Haiku 5.5 against Haiku
|
|
286
|
+
* 4.5) is the better pick on both counts. The session model
|
|
287
|
+
* may appear in the chain; the budget-gated `cheaperContainedCandidates` drops
|
|
288
|
+
* it. Budget ceilings and tier floors are caller policy — this function ranks,
|
|
289
|
+
* it does not gate. A caller that already computed the containment set may
|
|
290
|
+
* pass it as `contained` to skip the O(catalog) recompute.
|
|
343
291
|
*
|
|
344
292
|
* `requireImageInput` (default false) drops every candidate that cannot take
|
|
345
293
|
* image input — the modality gate a subagent selection sets when the session
|
|
346
294
|
* model is image-capable, so a delegated worker can still read an image or PDF
|
|
347
295
|
* the session may feed it. The classifier and the readers leave it false: the
|
|
348
296
|
* classifier renders the transcript to text and the readers strip images, so
|
|
349
|
-
* neither ever sends one. See `docs/decisions/model-policy.md`.
|
|
297
|
+
* neither ever sends one. See `working-docs/decisions/model-policy.md`.
|
|
350
298
|
*/
|
|
351
299
|
export function economicalContainedCandidates(
|
|
352
300
|
available: Model<Api>[],
|
|
@@ -355,37 +303,66 @@ export function economicalContainedCandidates(
|
|
|
355
303
|
requireImageInput = false,
|
|
356
304
|
): Model<Api>[] {
|
|
357
305
|
const pool = contained ?? modelsContainedToSession(available, sessionModel);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
306
|
+
// Modality gate: when the session works with images/PDFs a subagent may
|
|
307
|
+
// need to read, a text-only worker cannot serve — drop it before ranking.
|
|
308
|
+
const usable = requireImageInput ? pool.filter(supportsImageInput) : pool;
|
|
309
|
+
// A superseded model is skipped only for a successor this pool can pick.
|
|
310
|
+
const served = servedCatalogIds(usable);
|
|
311
|
+
return usable
|
|
362
312
|
// A dated snapshot whose undated alias is also listed is the same model
|
|
363
313
|
// twice; rank the alias so every automatic pick (classifier, subagent,
|
|
364
314
|
// reader, presets) names the model the way the user sees it in /model.
|
|
365
315
|
.filter((model) => !isDatedDuplicate(model, pool))
|
|
366
|
-
|
|
367
|
-
// picked automatically however it prices (the R1-over-V4-Flash incident),
|
|
368
|
-
// and a subagent or screener must be able to call tools at all.
|
|
369
|
-
.filter((model) => !isPriorGeneration(model) && !lacksToolCalls(model))
|
|
316
|
+
.filter((model) => isAutoSelectable(model, served))
|
|
370
317
|
// Classify by the model's INTRINSIC tier — never `process.env`: CC_PROMPT_TIER
|
|
371
318
|
// forces the *session's* prompt-scaffolding register, and honoring it here
|
|
372
319
|
// would collapse every candidate to one tier and let a `tiny` model through
|
|
373
320
|
// the security floor. Selection must judge each model on its own merits.
|
|
374
|
-
.map((model) => ({ model, tier:
|
|
321
|
+
.map((model) => ({ model, tier: intrinsicTier(model), price: pricedInput(model) }))
|
|
375
322
|
.filter(
|
|
376
|
-
(entry): entry is { model: Model<Api>; tier:
|
|
323
|
+
(entry): entry is { model: Model<Api>; tier: PromptTier; price: number } =>
|
|
377
324
|
entry.tier !== "tiny" && entry.price !== undefined,
|
|
378
325
|
)
|
|
379
|
-
.sort((a, b) =>
|
|
326
|
+
.sort((a, b) => a.price - b.price || TIER_RANK[a.tier] - TIER_RANK[b.tier])
|
|
380
327
|
.map((entry) => entry.model);
|
|
381
328
|
}
|
|
382
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Whether automatic selection may pick `model` at all: the catalogs know it and
|
|
332
|
+
* do not mark it legacy, deprecated, tool-less or non-text, it is not an
|
|
333
|
+
* experimental build (`isExperimentalBuild`), and it is not superseded by a
|
|
334
|
+
* model `served` holds (`model-catalog.ts autoSelectable`; `served` is
|
|
335
|
+
* `servedCatalogIds` of the session's contained pool). A model the user tiered
|
|
336
|
+
* by hand in `modelTiers` still needs a catalog entry: the setting speaks to
|
|
337
|
+
* its tier, not to whether it is current.
|
|
338
|
+
*/
|
|
339
|
+
export function isAutoSelectable(model: Model<Api>, served: ReadonlySet<string>): boolean {
|
|
340
|
+
// The pi id too: a row can name an experimental build its catalog entry does not.
|
|
341
|
+
if (isExperimentalBuild(model.id)) return false;
|
|
342
|
+
const entry = catalogModelFor(model);
|
|
343
|
+
return entry !== undefined && autoSelectable(entry, served);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** The catalog models a pool of pi rows serves: where a successor must be for supersession to skip a model. */
|
|
347
|
+
export function servedCatalogIds(pool: readonly Model<Api>[]): Set<string> {
|
|
348
|
+
const ids = new Set<string>();
|
|
349
|
+
for (const model of pool) {
|
|
350
|
+
const entry = catalogModelFor(model);
|
|
351
|
+
if (entry) ids.add(entry.id);
|
|
352
|
+
}
|
|
353
|
+
return ids;
|
|
354
|
+
}
|
|
355
|
+
|
|
383
356
|
/** No CC_PROMPT_TIER override: automatic model *selection* always uses intrinsic tiers. */
|
|
384
357
|
const INTRINSIC_TIER_ENV: NodeJS.ProcessEnv = Object.freeze({}) as NodeJS.ProcessEnv;
|
|
385
358
|
|
|
386
|
-
/**
|
|
359
|
+
/**
|
|
360
|
+
* A model's own capability tier, CC_PROMPT_TIER ignored — what selection, the
|
|
361
|
+
* permission gate and the doctor judge a model by. It differs from the prompt
|
|
362
|
+
* tier only for Haiku 5 and later (`isHaikuWithFrontierForms`).
|
|
363
|
+
*/
|
|
387
364
|
export function intrinsicTier(model: Model<Api>): PromptTier {
|
|
388
|
-
return
|
|
365
|
+
return classifyModelTier(model, INTRINSIC_TIER_ENV, "capability").tier;
|
|
389
366
|
}
|
|
390
367
|
|
|
391
368
|
/** Whether `tier` is at least as capable as `floor` (frontier ≥ workhorse ≥ cheap ≥ tiny). */
|
|
@@ -393,114 +370,65 @@ export function atLeastTier(tier: PromptTier, floor: PromptTier): boolean {
|
|
|
393
370
|
return TIER_RANK[tier] <= TIER_RANK[floor];
|
|
394
371
|
}
|
|
395
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Whether the permission gate gives `model`'s calls Claude Code's fast paths.
|
|
375
|
+
* Frontier and workhorse do; cheap and tiny keep One Code's stricter gate
|
|
376
|
+
* (working-docs/decisions/auto-mode.md, "Two gates by model tier"). Judged on
|
|
377
|
+
* the intrinsic tier, so `CC_PROMPT_TIER` can never loosen the gate; no model
|
|
378
|
+
* gets the stricter gate.
|
|
379
|
+
*/
|
|
380
|
+
export function usesClaudeCodeFastPaths(model: Model<Api> | undefined): boolean {
|
|
381
|
+
return !!model && atLeastTier(intrinsicTier(model), "workhorse");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** A model's price for comparing candidates, or undefined when it has none. */
|
|
385
|
+
export type ModelPrice = (model: Model<Api>) => number | undefined;
|
|
386
|
+
|
|
396
387
|
/**
|
|
397
388
|
* The budget-gated form the automatic secondary-model pickers all share: same as
|
|
398
389
|
* `economicalContainedCandidates`, minus the session model itself and anything
|
|
399
|
-
* dearer than it. `strict` requires *strictly* cheaper (
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
390
|
+
* dearer than it. `strict` requires *strictly* cheaper (the subagent default and
|
|
391
|
+
* the classifier: a same-price model saves nothing); non-strict allows equal
|
|
392
|
+
* price (the reader). With the session price unknown there is no demonstrable
|
|
393
|
+
* saving, so `strict` yields nothing while non-strict keeps the tier-ranked
|
|
394
|
+
* list. `contained` is forwarded to skip the containment recompute. `price`
|
|
395
|
+
* replaces the list input price (the classifier judges per-call cost).
|
|
404
396
|
*/
|
|
405
397
|
export function cheaperContainedCandidates(
|
|
406
398
|
available: Model<Api>[],
|
|
407
399
|
sessionModel: Model<Api>,
|
|
408
|
-
opts: { strict?: boolean; contained?: Model<Api>[];
|
|
400
|
+
opts: { strict?: boolean; contained?: Model<Api>[]; requireImageInput?: boolean; price?: ModelPrice } = {},
|
|
409
401
|
): Model<Api>[] {
|
|
402
|
+
const priceOf = opts.price ?? pricedInput;
|
|
410
403
|
const sessionSpec = modelSpec(sessionModel);
|
|
411
|
-
const sessionPrice =
|
|
412
|
-
|
|
404
|
+
const sessionPrice = priceOf(sessionModel);
|
|
405
|
+
return economicalContainedCandidates(available, sessionModel, opts.contained, opts.requireImageInput).filter((model) => {
|
|
413
406
|
if (modelSpec(model) === sessionSpec) return false;
|
|
414
407
|
if (sessionPrice === undefined) return !opts.strict;
|
|
415
|
-
const price =
|
|
408
|
+
const price = priceOf(model);
|
|
416
409
|
return price !== undefined && (opts.strict ? price < sessionPrice : price <= sessionPrice);
|
|
417
410
|
});
|
|
418
|
-
return opts.role ? rankByCapability(cheaper, sessionModel, opts.role).map((entry) => entry.model) : cheaper;
|
|
419
411
|
}
|
|
420
412
|
|
|
421
413
|
/**
|
|
422
|
-
* The
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
* model (docs/decisions/model-policy.md).
|
|
414
|
+
* The subagent default and the classifier's automatic pick: the strictly
|
|
415
|
+
* cheaper contained candidates (`cheaperContainedCandidates`) in the session's
|
|
416
|
+
* own tier or above, cheapest first. A workhorse session delegates to the cheapest workhorse-or-better
|
|
417
|
+
* model, a frontier session to the cheapest frontier one; a tiny session, whose
|
|
418
|
+
* tier automatic selection never picks, to the cheapest cheap-or-better one.
|
|
419
|
+
* The head is "the cheapest model this provider offers that is not weaker than
|
|
420
|
+
* the session's".
|
|
430
421
|
*/
|
|
431
|
-
export function
|
|
432
|
-
const tier = intrinsicTier(sessionModel);
|
|
433
|
-
return tier === "frontier" || tier === "workhorse" ? "workhorse" : "cheap";
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* The floor-gated form the classifier and the subagent default share: the
|
|
438
|
-
* cheaper contained candidates (`cheaperContainedCandidates`) that either
|
|
439
|
-
* measurably reach the role's capability floor (an Artificial Analysis
|
|
440
|
-
* snapshot, when one exists — `capability-index.ts`) or, unscored, sit at or
|
|
441
|
-
* above `automaticTierFloor(sessionModel)` by name class. Measured failures are
|
|
442
|
-
* already dropped upstream. The head is "the cheapest model this provider
|
|
443
|
-
* offers that is capable enough for the session".
|
|
444
|
-
*/
|
|
445
|
-
export function capableContainedCandidates(
|
|
422
|
+
export function sameTierContainedCandidates(
|
|
446
423
|
available: Model<Api>[],
|
|
447
424
|
sessionModel: Model<Api>,
|
|
448
|
-
|
|
449
|
-
opts: { strict?: boolean; contained?: Model<Api>[]; requireImageInput?: boolean } = {},
|
|
425
|
+
opts: { contained?: Model<Api>[]; requireImageInput?: boolean; price?: ModelPrice } = {},
|
|
450
426
|
): Model<Api>[] {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
interface RankedCandidate {
|
|
458
|
-
model: Model<Api>;
|
|
459
|
-
/** "pass": measurably reaches the floor; "unscored": no snapshot or no confirmed score (the caller's name-class rule decides). */
|
|
460
|
-
measured: "pass" | "unscored";
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* `cheaperContainedCandidates` with the measured verdict kept on each entry, so a
|
|
465
|
-
* caller that must tell a measured pass from an unscored candidate (the
|
|
466
|
-
* classifier's name-class fallback) reads it instead of recomputing it.
|
|
467
|
-
*/
|
|
468
|
-
function rankedContainedCandidates(
|
|
469
|
-
available: Model<Api>[],
|
|
470
|
-
sessionModel: Model<Api>,
|
|
471
|
-
role: FloorRole,
|
|
472
|
-
opts: { strict?: boolean; contained?: Model<Api>[]; requireImageInput?: boolean } = {},
|
|
473
|
-
): RankedCandidate[] {
|
|
474
|
-
return rankByCapability(cheaperContainedCandidates(available, sessionModel, opts), sessionModel, role);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* The measured capability floor (`capability-index.ts`) over a tier-ranked
|
|
479
|
-
* candidate list: candidates the Artificial Analysis snapshot shows reaching
|
|
480
|
-
* min(session, Sonnet 5) come first, cheapest first — capability per dollar is
|
|
481
|
-
* the question here, and a flash-class model that measurably matches the
|
|
482
|
-
* session model should beat a dearer "workhorse"-named one. Measured failures
|
|
483
|
-
* are dropped. Unscored candidates (no key, no confirmed match) follow in their
|
|
484
|
-
* tier order, for the caller's name-class rule to judge. The score never admits
|
|
485
|
-
* anything the tier gate already refused (tiny, prior generation, no tools).
|
|
486
|
-
*/
|
|
487
|
-
function rankByCapability(candidates: Model<Api>[], sessionModel: Model<Api>, role: FloorRole): RankedCandidate[] {
|
|
488
|
-
const snapshot = currentCapabilitySnapshot();
|
|
489
|
-
if (!snapshot) return candidates.map((model) => ({ model, measured: "unscored" }));
|
|
490
|
-
const measured: RankedCandidate[] = [];
|
|
491
|
-
const unscored: RankedCandidate[] = [];
|
|
492
|
-
for (const model of candidates) {
|
|
493
|
-
const verdict = capabilityFloor(snapshot, model, sessionModel, role).verdict;
|
|
494
|
-
if (verdict === "pass") measured.push({ model, measured: "pass" });
|
|
495
|
-
else if (verdict === "unscored") unscored.push({ model, measured: "unscored" });
|
|
496
|
-
}
|
|
497
|
-
const price = (entry: RankedCandidate) => pricedInput(entry.model) ?? Number.POSITIVE_INFINITY;
|
|
498
|
-
return [...measured.sort((a, b) => price(a) - price(b)), ...unscored];
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/** The cached Artificial Analysis snapshot, if a key has ever produced one (`capability-index.ts`). */
|
|
502
|
-
export function currentCapabilitySnapshot(): CapabilitySnapshot | undefined {
|
|
503
|
-
return loadCapabilitySnapshot(oneCodeStateDir());
|
|
427
|
+
const priceOf = opts.price ?? pricedInput;
|
|
428
|
+
const floor = intrinsicTier(sessionModel);
|
|
429
|
+
return cheaperContainedCandidates(available, sessionModel, { ...opts, strict: true })
|
|
430
|
+
.filter((model) => atLeastTier(intrinsicTier(model), floor))
|
|
431
|
+
.sort((a, b) => priceOf(a)! - priceOf(b)!);
|
|
504
432
|
}
|
|
505
433
|
|
|
506
434
|
export interface EconomicalModelChoice {
|
|
@@ -509,7 +437,7 @@ export interface EconomicalModelChoice {
|
|
|
509
437
|
}
|
|
510
438
|
|
|
511
439
|
/**
|
|
512
|
-
* The cheapest
|
|
440
|
+
* The cheapest usable same-provider model, else the session model itself. Used
|
|
513
441
|
* for low-stakes one-shot jobs over untrusted content (the web_fetch reader,
|
|
514
442
|
* the recap) — the content goes to the model, so containment applies. Never
|
|
515
443
|
* dearer than the session model, and never a `tiny`-tier model
|
|
@@ -521,6 +449,6 @@ export function pickEconomicalContainedModel(
|
|
|
521
449
|
sessionModel: Model<Api> | undefined,
|
|
522
450
|
): EconomicalModelChoice | undefined {
|
|
523
451
|
if (!sessionModel) return undefined;
|
|
524
|
-
const cheaper = cheaperContainedCandidates(available, sessionModel
|
|
452
|
+
const cheaper = cheaperContainedCandidates(available, sessionModel)[0];
|
|
525
453
|
return cheaper ? { model: cheaper, via: "tier" } : { model: sessionModel, via: "session" };
|
|
526
454
|
}
|