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
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model pairing table (pure): for the models people run most, which model
|
|
3
|
+
* a session hands its subagents and its auto-mode classifier. Each row comes
|
|
4
|
+
* from measured data, not from tiers or price alone: Artificial Analysis's
|
|
5
|
+
* Intelligence Index (maximum effort for subagents; low effort for the
|
|
6
|
+
* classifier, which runs with thinking off) and list prices, with OpenRouter's
|
|
7
|
+
* usage rankings choosing which models get a row. The snapshot and the numbers
|
|
8
|
+
* behind every row are in `working-docs/decisions/model-policy.md` ("Model
|
|
9
|
+
* pairing table").
|
|
10
|
+
*
|
|
11
|
+
* A pick is the cheapest same-vendor model within 15 points at maximum effort
|
|
12
|
+
* (subagent) or 7 points at low effort (classifier); `"session"` keeps the
|
|
13
|
+
* session's own model, which also overrides the cost algorithm. A model the
|
|
14
|
+
* table does not name goes to the cost algorithm (`model-tier.ts`
|
|
15
|
+
* `sameTierContainedCandidates`).
|
|
16
|
+
*
|
|
17
|
+
* The table knows only what existed on PAIRINGS_AS_OF. When the session's
|
|
18
|
+
* provider serves a model of the session's vendor released after that date,
|
|
19
|
+
* the table is skipped for that vendor: a new model may change every pairing,
|
|
20
|
+
* and the algorithm judges it where the table cannot.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
24
|
+
import { catalogModelFor } from "./model-catalog.ts";
|
|
25
|
+
import { isAliasOrVariantId, isDatedDuplicate, modelsContainedToSession, pricedInput, supportsImageInput, vendorProfile } from "./model-policy.ts";
|
|
26
|
+
import { isAutoSelectable, type ModelPrice, servedCatalogIds } from "./model-tier.ts";
|
|
27
|
+
|
|
28
|
+
export type PairingRole = "subagent" | "classifier";
|
|
29
|
+
|
|
30
|
+
interface PairingRow {
|
|
31
|
+
/** Session models, as catalog ids, all of one vendor. */
|
|
32
|
+
sessions: readonly string[];
|
|
33
|
+
/** A catalog id (`vendor/id`), or SESSION for the session's own model. */
|
|
34
|
+
subagent: string;
|
|
35
|
+
classifier: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The data date: Artificial Analysis and OpenRouter rankings, 2026-10-13. */
|
|
39
|
+
export const PAIRINGS_AS_OF = "2026-10-13";
|
|
40
|
+
|
|
41
|
+
const SESSION = "session";
|
|
42
|
+
|
|
43
|
+
export const PAIRING_TABLE: readonly PairingRow[] = [
|
|
44
|
+
// Anthropic. Sonnet 5.5 is within 2 points of Opus 5.5 at half the price and above Fable 5.1.
|
|
45
|
+
{
|
|
46
|
+
sessions: ["anthropic/claude-opus-5-5", "anthropic/claude-opus-5", "anthropic/claude-opus-4-8", "anthropic/claude-fable-5", "anthropic/claude-fable-5-1"],
|
|
47
|
+
subagent: "anthropic/claude-sonnet-5-5",
|
|
48
|
+
classifier: "anthropic/claude-sonnet-5-5",
|
|
49
|
+
},
|
|
50
|
+
{ sessions: ["anthropic/claude-sonnet-5-5", "anthropic/claude-sonnet-5"], subagent: "anthropic/claude-haiku-5-5", classifier: "anthropic/claude-haiku-5-5" },
|
|
51
|
+
{ sessions: ["anthropic/claude-haiku-5-5"], subagent: SESSION, classifier: SESSION },
|
|
52
|
+
// OpenAI. GPT-6.1 Sol is within a point of Astra at a fifth of the price; Luna drops 20 points at low effort.
|
|
53
|
+
{ sessions: ["openai/gpt-6-astra"], subagent: "openai/gpt-6.1-sol", classifier: "openai/gpt-6.1-sol" },
|
|
54
|
+
{ sessions: ["openai/gpt-6.1-sol", "openai/gpt-6-sol"], subagent: "openai/gpt-6-luna", classifier: SESSION },
|
|
55
|
+
{ sessions: ["openai/gpt-5.6-sol"], subagent: "openai/gpt-6.1-sol", classifier: "openai/gpt-6.1-sol" },
|
|
56
|
+
{ sessions: ["openai/gpt-5.6-terra", "openai/gpt-5.6-luna"], subagent: "openai/gpt-6-luna", classifier: "openai/gpt-6-luna" },
|
|
57
|
+
{ sessions: ["openai/gpt-6-luna"], subagent: SESSION, classifier: SESSION },
|
|
58
|
+
// Google. Gemini 3.8 Flash outscores 3.1 Pro; Argon's classifier pick is the user's call (2026-10-13).
|
|
59
|
+
{
|
|
60
|
+
sessions: ["google/gemini-4-argon", "google/gemini-3.1-pro-preview", "google/gemini-3.7-flash", "google/gemini-3.6-flash", "google/gemini-3.5-flash"],
|
|
61
|
+
subagent: "google/gemini-3.8-flash",
|
|
62
|
+
classifier: "google/gemini-3.8-flash",
|
|
63
|
+
},
|
|
64
|
+
{ sessions: ["google/gemini-3.8-flash"], subagent: SESSION, classifier: SESSION },
|
|
65
|
+
// DeepSeek. V4.1 Flash outscores V4 Pro at under a quarter of its price.
|
|
66
|
+
{
|
|
67
|
+
sessions: ["deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-pro-0813", "deepseek/deepseek-v4-flash", "deepseek/deepseek-v4-flash-0731"],
|
|
68
|
+
subagent: "deepseek/deepseek-v4.1-flash",
|
|
69
|
+
classifier: "deepseek/deepseek-v4.1-flash",
|
|
70
|
+
},
|
|
71
|
+
{ sessions: ["deepseek/deepseek-v4.1-flash"], subagent: SESSION, classifier: SESSION },
|
|
72
|
+
// Z.ai, Xiaomi, Qwen: each vendor's flash model.
|
|
73
|
+
{ sessions: ["zhipuai/glm-5.3"], subagent: "zhipuai/glm-5.3-flash", classifier: "zhipuai/glm-5.3-flash" },
|
|
74
|
+
{ sessions: ["zhipuai/glm-5.3-flash"], subagent: SESSION, classifier: SESSION },
|
|
75
|
+
{ sessions: ["xiaomi/mimo-v2.6-pro"], subagent: "xiaomi/mimo-v2.6-flash", classifier: SESSION },
|
|
76
|
+
{ sessions: ["xiaomi/mimo-v2.6-flash"], subagent: SESSION, classifier: SESSION },
|
|
77
|
+
{ sessions: ["alibaba/qwen3.8-max", "alibaba/qwen3.8-max-0902"], subagent: "alibaba/qwen3.8-flash", classifier: "alibaba/qwen3.8-flash" },
|
|
78
|
+
{ sessions: ["alibaba/qwen3.8-flash"], subagent: SESSION, classifier: SESSION },
|
|
79
|
+
// No same-vendor model within 15 points: the session model serves both roles.
|
|
80
|
+
{ sessions: ["moonshotai/kimi-k3"], subagent: SESSION, classifier: SESSION },
|
|
81
|
+
{ sessions: ["xai/grok-4.7"], subagent: SESSION, classifier: SESSION },
|
|
82
|
+
{ sessions: ["meta/muse-spark-1.3"], subagent: SESSION, classifier: SESSION },
|
|
83
|
+
{ sessions: ["minimax/MiniMax-M3"], subagent: SESSION, classifier: SESSION },
|
|
84
|
+
{ sessions: ["stepfun/step-5-preview"], subagent: SESSION, classifier: SESSION },
|
|
85
|
+
{ sessions: ["mistral/mistral-large-4"], subagent: SESSION, classifier: SESSION },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const ROW_BY_SESSION: ReadonlyMap<string, PairingRow> = new Map(PAIRING_TABLE.flatMap((row) => row.sessions.map((id) => [id.toLowerCase(), row] as const)));
|
|
89
|
+
const AS_OF_TIME = Date.parse(`${PAIRINGS_AS_OF}T23:59:59Z`);
|
|
90
|
+
|
|
91
|
+
export type Pairing = { kind: "session" } | { kind: "model"; model: Model<Api> };
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The table's pick for `role` on `sessionModel`, or undefined when the table
|
|
95
|
+
* does not decide: no row names the session model, the session's provider
|
|
96
|
+
* serves a model of its vendor released after PAIRINGS_AS_OF, or no model in
|
|
97
|
+
* the session's contained pool serves the pick, costs no more than the
|
|
98
|
+
* session model on that route, and passes the caller's gates
|
|
99
|
+
* (`requireImageInput`, and `accept`: the classifier's context window). The
|
|
100
|
+
* caller then runs the cost algorithm.
|
|
101
|
+
*
|
|
102
|
+
* Both checks read the pool the cost algorithm starts from (no dated
|
|
103
|
+
* duplicates, only auto-selectable models) but need no price: the table's
|
|
104
|
+
* picks are measured, so they hold on a route with no list prices. Where both
|
|
105
|
+
* are priced, a pick dearer than the session is skipped: a route can price an
|
|
106
|
+
* older model below its successor (OpenRouter's DeepSeek V4 Flash), and an
|
|
107
|
+
* automatic pick never costs more than the session (`price`, list input by
|
|
108
|
+
* default; the classifier passes its per-call price).
|
|
109
|
+
*/
|
|
110
|
+
export function pairedModel(
|
|
111
|
+
role: PairingRole,
|
|
112
|
+
available: Model<Api>[],
|
|
113
|
+
sessionModel: Model<Api>,
|
|
114
|
+
opts: { contained?: Model<Api>[]; requireImageInput?: boolean; price?: ModelPrice; accept?: (model: Model<Api>) => boolean } = {},
|
|
115
|
+
): Pairing | undefined {
|
|
116
|
+
const sessionEntry = catalogModelFor(sessionModel);
|
|
117
|
+
const row = sessionEntry && ROW_BY_SESSION.get(sessionEntry.id.toLowerCase());
|
|
118
|
+
if (!sessionEntry || !row) return undefined;
|
|
119
|
+
const pool = opts.contained ?? modelsContainedToSession(available, sessionModel);
|
|
120
|
+
const served = servedCatalogIds(pool);
|
|
121
|
+
const selectable = pool.filter((model) => isAutoSelectable(model, served));
|
|
122
|
+
// The table predates this vendor's newest model: it no longer speaks for the
|
|
123
|
+
// vendor. A new dated snapshot counts, even beside the undated alias.
|
|
124
|
+
const vendor = vendorProfile(sessionEntry.vendor);
|
|
125
|
+
const newer = selectable.some((model) => {
|
|
126
|
+
const entry = catalogModelFor(model);
|
|
127
|
+
return entry !== undefined && vendorProfile(entry.vendor) === vendor && entry.released !== undefined && entry.released > AS_OF_TIME;
|
|
128
|
+
});
|
|
129
|
+
if (newer) return undefined;
|
|
130
|
+
const usable = selectable.filter((model) => !isDatedDuplicate(model, pool));
|
|
131
|
+
const pick = row[role];
|
|
132
|
+
if (pick === SESSION) return { kind: "session" };
|
|
133
|
+
const wanted = pick.toLowerCase();
|
|
134
|
+
const accept = opts.accept ?? (() => true);
|
|
135
|
+
const priceOf = opts.price ?? pricedInput;
|
|
136
|
+
const sessionPrice = priceOf(sessionModel);
|
|
137
|
+
const notDearer = (model: Model<Api>) => {
|
|
138
|
+
const price = priceOf(model);
|
|
139
|
+
return sessionPrice === undefined || price === undefined || price <= sessionPrice;
|
|
140
|
+
};
|
|
141
|
+
const serving = usable
|
|
142
|
+
.filter(
|
|
143
|
+
(model) =>
|
|
144
|
+
catalogModelFor(model)?.id.toLowerCase() === wanted &&
|
|
145
|
+
(!opts.requireImageInput || supportsImageInput(model)) &&
|
|
146
|
+
notDearer(model) &&
|
|
147
|
+
accept(model),
|
|
148
|
+
)
|
|
149
|
+
// A plain row before a gateway variant (`…:batch`) of the same model.
|
|
150
|
+
.sort((a, b) => Number(isAliasOrVariantId(a.id)) - Number(isAliasOrVariantId(b.id)) || a.id.length - b.id.length);
|
|
151
|
+
return serving[0] ? { kind: "model", model: serving[0] } : undefined;
|
|
152
|
+
}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Shared interactive model picker (pure).
|
|
3
3
|
*
|
|
4
|
-
* Filtering, key decoding, selection movement, and rendering
|
|
5
|
-
*
|
|
6
|
-
* unit-testable. The thin `ctx.ui.custom` component in the permissions
|
|
7
|
-
* extension owns nothing but mutable state and repaint calls.
|
|
8
|
-
*
|
|
9
|
-
* A picker exists because the alternative was worse in practice: choosing the
|
|
10
|
-
* classifier automatically took three rounds of fault-fixing against real
|
|
11
|
-
* catalogs, and when auto-selection picks badly the only recourse was
|
|
12
|
-
* hand-editing settings.json with a model id the user has to guess. Naming a
|
|
13
|
-
* model here is the explicit consent `autoMode.classifierModel` stands for —
|
|
14
|
-
* including consent to another provider, which auto-selection must never
|
|
15
|
-
* assume (see model-select.ts).
|
|
4
|
+
* Filtering, key decoding, selection movement, and rendering stay free of pi
|
|
5
|
+
* imports so command wiring can own only mutable state and repaint calls.
|
|
16
6
|
*/
|
|
17
7
|
|
|
18
|
-
import { truncateLine } from "
|
|
8
|
+
import { truncateLine } from "./tui-render.ts";
|
|
9
|
+
import { keyId, keyText } from "./key-input.ts";
|
|
19
10
|
|
|
20
11
|
export interface PickerEntry {
|
|
21
12
|
provider: string;
|
|
@@ -68,26 +59,23 @@ export type PickerKey =
|
|
|
68
59
|
* shortcuts: every printable character belongs to the filter query.
|
|
69
60
|
*/
|
|
70
61
|
export function decodePickerKey(data: string): PickerKey | undefined {
|
|
71
|
-
switch (data) {
|
|
72
|
-
case "
|
|
73
|
-
case "\x1bOA":
|
|
62
|
+
switch (keyId(data)) {
|
|
63
|
+
case "up":
|
|
74
64
|
return { kind: "up" };
|
|
75
|
-
case "
|
|
76
|
-
case "\x1bOB":
|
|
65
|
+
case "down":
|
|
77
66
|
return { kind: "down" };
|
|
78
|
-
case "
|
|
79
|
-
case "\n":
|
|
67
|
+
case "enter":
|
|
80
68
|
return { kind: "confirm" };
|
|
81
|
-
case "
|
|
82
|
-
case "
|
|
69
|
+
case "escape":
|
|
70
|
+
case "ctrl+c": // same intent as escape while the picker is focused
|
|
83
71
|
return { kind: "cancel" };
|
|
84
|
-
case "
|
|
85
|
-
case "
|
|
72
|
+
case "backspace":
|
|
73
|
+
case "ctrl+backspace":
|
|
86
74
|
return { kind: "backspace" };
|
|
87
75
|
default: {
|
|
88
|
-
// Printable text only (covers paste); control sequences
|
|
89
|
-
const text =
|
|
90
|
-
return text
|
|
76
|
+
// Printable text only (covers paste); control sequences type nothing.
|
|
77
|
+
const text = keyText(data);
|
|
78
|
+
return text ? { kind: "type", text } : undefined;
|
|
91
79
|
}
|
|
92
80
|
}
|
|
93
81
|
}
|
|
@@ -109,9 +97,9 @@ export interface PickerView {
|
|
|
109
97
|
/** The currently configured spec, marked in the listing. */
|
|
110
98
|
current?: string;
|
|
111
99
|
maxVisible?: number;
|
|
112
|
-
/** Picker header
|
|
113
|
-
title
|
|
114
|
-
subtitle
|
|
100
|
+
/** Picker header and explanatory subtitle. */
|
|
101
|
+
title: string;
|
|
102
|
+
subtitle: string;
|
|
115
103
|
}
|
|
116
104
|
|
|
117
105
|
const formatPrice = (price?: number): string => (price === undefined ? "" : `$${price}/M in`);
|
|
@@ -130,15 +118,13 @@ export function toPickerEntries(models: { provider: string; id: string; cost?: {
|
|
|
130
118
|
export interface PickerComponentOptions {
|
|
131
119
|
entries: PickerEntry[];
|
|
132
120
|
current?: string;
|
|
133
|
-
title
|
|
134
|
-
subtitle
|
|
121
|
+
title: string;
|
|
122
|
+
subtitle: string;
|
|
135
123
|
}
|
|
136
124
|
|
|
137
125
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* (structurally typed, no pi imports) so both commands share one keyboard
|
|
141
|
-
* loop instead of drifting copies.
|
|
126
|
+
* A `ctx.ui.custom` model picker: filter-as-you-type over the entries, enter
|
|
127
|
+
* confirms, esc cancels. Kept here with no pi imports.
|
|
142
128
|
*/
|
|
143
129
|
export function modelPickerComponent(
|
|
144
130
|
options: PickerComponentOptions,
|
|
@@ -191,7 +177,7 @@ export function modelPickerComponent(
|
|
|
191
177
|
if (key.kind === "cancel") return done(null);
|
|
192
178
|
if (key.kind === "confirm") return done(filtered[index] ?? null);
|
|
193
179
|
if (key.kind === "up") index = Math.max(0, index - 1);
|
|
194
|
-
else if (key.kind === "down") index = Math.min(filtered.length - 1, index + 1);
|
|
180
|
+
else if (key.kind === "down") index = Math.max(0, Math.min(filtered.length - 1, index + 1));
|
|
195
181
|
else {
|
|
196
182
|
query = key.kind === "backspace" ? query.slice(0, -1) : query + key.text;
|
|
197
183
|
filtered = filterEntries(options.entries, query);
|
|
@@ -208,9 +194,6 @@ export function modelPickerComponent(
|
|
|
208
194
|
|
|
209
195
|
/** Key hint, kept on its own line so it survives at narrow widths. */
|
|
210
196
|
const PICKER_HINT = "type to filter · ↑/↓ · enter · esc";
|
|
211
|
-
/** Default privacy note for the classifier picker (the key hint rides its own line below). */
|
|
212
|
-
const CLASSIFIER_SUBTITLE = "It reads your prompts and CLAUDE.md — picking another provider sends them there.";
|
|
213
|
-
|
|
214
197
|
export function renderModelPicker(view: PickerView, paint: Paint, width = Infinity): string[] {
|
|
215
198
|
const maxVisible = view.maxVisible ?? 10;
|
|
216
199
|
// Every line is cut to `width` — pi-tui crashes the whole app on a rendered
|
|
@@ -218,8 +201,8 @@ export function renderModelPicker(view: PickerView, paint: Paint, width = Infini
|
|
|
218
201
|
// lines uncut (truncateLine returns them unchanged) for unit tests.
|
|
219
202
|
const cut = (line: string): string => truncateLine(line, width);
|
|
220
203
|
const lines: string[] = [];
|
|
221
|
-
lines.push(cut(paint("accent", view.title
|
|
222
|
-
lines.push(cut(paint("dim", view.subtitle
|
|
204
|
+
lines.push(cut(paint("accent", view.title)));
|
|
205
|
+
lines.push(cut(paint("dim", view.subtitle)));
|
|
223
206
|
// The key hint is always its own line, so a narrow terminal never clips the
|
|
224
207
|
// only place the keys are documented.
|
|
225
208
|
lines.push(cut(paint("dim", ` ${PICKER_HINT}`)));
|
|
@@ -18,7 +18,7 @@ export function isClaudeFamilyModel(model: { provider: string; id: string }): bo
|
|
|
18
18
|
* subagent opens either. A model whose modality is unknown (undefined `input`, as
|
|
19
19
|
* on some opaque/self-hosted providers, or a minimal test stub, or no model at all) reads as
|
|
20
20
|
* text-only here — the safe direction for a gate that only ever ADDS a
|
|
21
|
-
* requirement, never relaxes one. See `docs/decisions/model-policy.md`.
|
|
21
|
+
* requirement, never relaxes one. See `working-docs/decisions/model-policy.md`.
|
|
22
22
|
*/
|
|
23
23
|
export function supportsImageInput(model: { input?: readonly string[] } | undefined): boolean {
|
|
24
24
|
return Array.isArray(model?.input) && model.input.includes("image");
|
|
@@ -30,6 +30,8 @@ export interface ProviderPolicy {
|
|
|
30
30
|
kind: ProviderPolicyKind;
|
|
31
31
|
/** Fixed profile for direct vendors and stable hosted catalogs. */
|
|
32
32
|
profile?: string;
|
|
33
|
+
/** The provider also serves other vendors' models: a row whose id names its vendor takes that vendor's profile. */
|
|
34
|
+
profileFromId?: boolean;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
const direct = (profile: string): ProviderPolicy => ({ kind: "direct", profile });
|
|
@@ -38,7 +40,9 @@ const gateway = (): ProviderPolicy => ({ kind: "gateway" });
|
|
|
38
40
|
const opaque = (): ProviderPolicy => ({ kind: "opaque" });
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
|
-
* Every built-in language-model provider in pi
|
|
43
|
+
* Every built-in language-model provider in pi 1.1.0 (`model-policy.test.ts`
|
|
44
|
+
* checks the list against pi's own), plus the names older pi still in the
|
|
45
|
+
* tested range uses. Unknown/custom providers
|
|
42
46
|
* get the opaque policy below, so a pi upgrade fails safe rather than widening
|
|
43
47
|
* automatic routing.
|
|
44
48
|
*/
|
|
@@ -49,6 +53,9 @@ export const BUILTIN_PROVIDER_POLICIES: Readonly<Record<string, ProviderPolicy>>
|
|
|
49
53
|
google: direct("google"),
|
|
50
54
|
"google-vertex": direct("google"),
|
|
51
55
|
openai: direct("openai"),
|
|
56
|
+
// Also serves Foundry models from other vendors (`deepseek-v4-pro`).
|
|
57
|
+
azure: { ...direct("openai"), profileFromId: true },
|
|
58
|
+
// pi before 1.0.3 named `azure` this way.
|
|
52
59
|
"azure-openai-responses": direct("openai"),
|
|
53
60
|
"openai-codex": direct("openai"),
|
|
54
61
|
radius: opaque(),
|
|
@@ -78,6 +85,8 @@ export const BUILTIN_PROVIDER_POLICIES: Readonly<Record<string, ProviderPolicy>>
|
|
|
78
85
|
"cloudflare-ai-gateway": gateway(),
|
|
79
86
|
"qwen-token-plan": hosted("host:qwen-plan"),
|
|
80
87
|
"qwen-token-plan-cn": hosted("host:qwen-plan"),
|
|
88
|
+
"qwen-token-plan-individual": hosted("host:qwen-plan"),
|
|
89
|
+
meta: direct("meta"),
|
|
81
90
|
xiaomi: direct("xiaomi"),
|
|
82
91
|
"xiaomi-token-plan-cn": direct("xiaomi"),
|
|
83
92
|
"xiaomi-token-plan-ams": direct("xiaomi"),
|
|
@@ -95,6 +104,7 @@ const VENDOR_ALIASES: Readonly<Record<string, string>> = {
|
|
|
95
104
|
deepseek: "deepseek",
|
|
96
105
|
zai: "zai",
|
|
97
106
|
"z-ai": "zai",
|
|
107
|
+
zhipuai: "zai",
|
|
98
108
|
qwen: "host:qwen-plan",
|
|
99
109
|
alibaba: "host:qwen-plan",
|
|
100
110
|
moonshotai: "moonshot",
|
|
@@ -115,12 +125,21 @@ export interface ModelIdentity {
|
|
|
115
125
|
confidence: "exact" | "family" | "opaque";
|
|
116
126
|
}
|
|
117
127
|
|
|
118
|
-
const providerPolicy = (provider: string): ProviderPolicy => BUILTIN_PROVIDER_POLICIES[provider] ?? opaque();
|
|
128
|
+
export const providerPolicy = (provider: string): ProviderPolicy => BUILTIN_PROVIDER_POLICIES[provider] ?? opaque();
|
|
119
129
|
|
|
120
130
|
function canonicalVendor(value: string): string | undefined {
|
|
121
131
|
return VENDOR_ALIASES[value.toLowerCase()];
|
|
122
132
|
}
|
|
123
133
|
|
|
134
|
+
/**
|
|
135
|
+
* The identity profile a catalog vendor name stands for (`z-ai`, `zhipuai` and
|
|
136
|
+
* `zai` are one vendor), else the name itself: the key `model-catalog.ts`
|
|
137
|
+
* matches a row to its vendor's model under.
|
|
138
|
+
*/
|
|
139
|
+
export function vendorProfile(vendor: string): string {
|
|
140
|
+
return canonicalVendor(vendor) ?? vendor.toLowerCase();
|
|
141
|
+
}
|
|
142
|
+
|
|
124
143
|
function prefixedIdentity(model: Model<Api>, prefix: string): ModelIdentity {
|
|
125
144
|
const raw = model.id.startsWith("~") ? model.id.slice(1) : model.id;
|
|
126
145
|
const slash = raw.indexOf("/");
|
|
@@ -153,8 +172,13 @@ function bareFamily(id: string): string | undefined {
|
|
|
153
172
|
}
|
|
154
173
|
|
|
155
174
|
function bedrockIdentity(model: Model<Api>): ModelIdentity {
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
// A cross-region inference profile prefixes the vendor with its geography
|
|
176
|
+
// (`us.`, `jp.`, `us-gov.`, …): a first segment that is not a vendor, followed
|
|
177
|
+
// by one that is. Reading it that way needs no list of AWS region codes.
|
|
178
|
+
const [first, second] = model.id.split(".");
|
|
179
|
+
const regional = second !== undefined && !canonicalVendor(first) && canonicalVendor(second) !== undefined;
|
|
180
|
+
const geography = regional ? first.toLowerCase() : "in-region";
|
|
181
|
+
const raw = regional ? model.id.slice(first.length + 1) : model.id;
|
|
158
182
|
const dot = raw.indexOf(".");
|
|
159
183
|
if (dot <= 0 || raw.startsWith("arn:")) {
|
|
160
184
|
return { containment: `${model.provider}:opaque:${model.id}`, normalizedId: model.id, confidence: "opaque" };
|
|
@@ -209,7 +233,7 @@ export function modelIdentity(model: Model<Api>): ModelIdentity {
|
|
|
209
233
|
}
|
|
210
234
|
return {
|
|
211
235
|
containment: model.provider,
|
|
212
|
-
profile: policy.profile,
|
|
236
|
+
profile: (policy.profileFromId && bareFamily(model.id)) || policy.profile,
|
|
213
237
|
normalizedId: model.id,
|
|
214
238
|
confidence: "exact",
|
|
215
239
|
};
|
|
@@ -219,8 +243,8 @@ const UNSUITABLE_VARIANT = /:(batch|free|online|thinking)$/i;
|
|
|
219
243
|
|
|
220
244
|
/**
|
|
221
245
|
* The id without OpenRouter's `~` redirect-alias marker and without a
|
|
222
|
-
* `:batch`-style endpoint variant — the spelling the
|
|
223
|
-
*
|
|
246
|
+
* `:batch`-style endpoint variant — the spelling the model-catalog lookups key
|
|
247
|
+
* on (`~deepseek/deepseek-v4-flash-latest`
|
|
224
248
|
* → `deepseek/deepseek-v4-flash-latest`, `gpt-5-mini:batch` → `gpt-5-mini`).
|
|
225
249
|
*/
|
|
226
250
|
export function baseModelId(id: string): string {
|
|
@@ -237,20 +261,88 @@ export function isAliasOrVariantId(id: string): boolean {
|
|
|
237
261
|
* and OpenRouter's `~vendor/family-latest` redirect aliases ("always redirects to
|
|
238
262
|
* the latest model in the family"): the model behind one moves without notice,
|
|
239
263
|
* so it has no stable identity to tier, date or price — and the classifier is a
|
|
240
|
-
* permission boundary. The alias stays available by hand
|
|
241
|
-
*
|
|
264
|
+
* permission boundary. The alias stays available by hand through `/model` or
|
|
265
|
+
* an explicit `subagentModel` setting.
|
|
242
266
|
*/
|
|
243
267
|
export function isSelectableVariant(model: Model<Api>): boolean {
|
|
244
268
|
return !UNSUITABLE_VARIANT.test(model.id) && !model.id.startsWith("~");
|
|
245
269
|
}
|
|
246
270
|
|
|
247
|
-
/**
|
|
271
|
+
/**
|
|
272
|
+
* A moving alias: an id that names whichever model its vendor points it at
|
|
273
|
+
* today (`mistral-large-latest`, `gpt-daybreak-blue-latest`, OpenRouter's
|
|
274
|
+
* `~vendor/…-latest` redirects). What answers can change without notice, so
|
|
275
|
+
* it never supersedes a dated model, is never suggested as an upgrade, and is
|
|
276
|
+
* never picked as the auto-mode classifier (a permission boundary). Other
|
|
277
|
+
* automatic picks may still use one: Mistral serves most models only so.
|
|
278
|
+
*/
|
|
279
|
+
export function isMovingAlias(id: string): boolean {
|
|
280
|
+
return id.startsWith("~") || /-latest(?=$|[-:@])/i.test(id);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* An experimental build (`deepseek-v4-flash-vision-exp`, `tev1-4b-experimental`):
|
|
285
|
+
* a research variant, never picked automatically (as a screener or the
|
|
286
|
+
* subagent default) or suggested as an upgrade. `preview` is deliberately not matched: Google ships mainline
|
|
287
|
+
* Gemini models under it (`gemini-3-flash-preview`).
|
|
288
|
+
*/
|
|
289
|
+
export function isExperimentalBuild(id: string): boolean {
|
|
290
|
+
return /-exp(?:$|[-:])|experimental/i.test(id);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** A positive finite catalog context window, the only value safe to compare windows by. */
|
|
294
|
+
export function hasCatalogContextWindow(model: { contextWindow?: number }): model is { contextWindow: number } {
|
|
295
|
+
return Number.isFinite(model.contextWindow) && (model.contextWindow as number) > 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** One day in milliseconds, for the release-date arithmetic in model-catalog.ts. */
|
|
248
299
|
export const DAY_MS = 86_400_000;
|
|
249
300
|
|
|
250
|
-
/**
|
|
301
|
+
/** A catalog price, or undefined: non-positive values are sentinels/unpriced, not evidence of being free. */
|
|
302
|
+
function catalogPrice(value: number | undefined): number | undefined {
|
|
303
|
+
return typeof value === "number" && value > 0 ? value : undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** The model's list input price, or undefined when it is unpriced. */
|
|
251
307
|
export function pricedInput(model: Model<Api>): number | undefined {
|
|
252
|
-
|
|
253
|
-
|
|
308
|
+
return catalogPrice(model.cost?.input);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** A classifier call's measured shape: the cached ruleset and transcript, the new turn, the verdict. */
|
|
312
|
+
export const CLASSIFIER_CALL_TOKENS = { cachedInput: 28_000, freshInput: 2_000, output: 700 } as const;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* One classifier call's estimated cost in dollars at `CLASSIFIER_CALL_TOKENS`,
|
|
316
|
+
* or undefined for a model with no input price. A model without a cache-read
|
|
317
|
+
* price pays its input price for the cached part, and one without an output
|
|
318
|
+
* price its input price for the verdict: both overstate, never understate.
|
|
319
|
+
*/
|
|
320
|
+
export function classifierCallCost(model: Model<Api>): number | undefined {
|
|
321
|
+
const input = pricedInput(model);
|
|
322
|
+
if (input === undefined) return undefined;
|
|
323
|
+
const priced = (value: number | undefined) => catalogPrice(value) ?? input;
|
|
324
|
+
const { cachedInput, freshInput, output } = CLASSIFIER_CALL_TOKENS;
|
|
325
|
+
return (priced(model.cost?.cacheRead) * cachedInput + input * freshInput + priced(model.cost?.output) * output) / 1_000_000;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* How classifier candidates are priced against `base` (the session model):
|
|
330
|
+
* per classifier call, unless `base` lists no cache-read price. Then its
|
|
331
|
+
* per-call cost would be overstated (a quarter of OpenRouter's priced models
|
|
332
|
+
* list none) and a dearer model could win, so both sides compare by list
|
|
333
|
+
* input price. The selector and the cost warnings share this, so "cheaper per
|
|
334
|
+
* call" means one thing everywhere.
|
|
335
|
+
*/
|
|
336
|
+
export function classifierPrice(base: Model<Api>): (model: Model<Api>) => number | undefined {
|
|
337
|
+
return catalogPrice(base.cost?.cacheRead) === undefined ? pricedInput : classifierCallCost;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** How many times `model`'s classifier call costs `base`'s (priced by `classifierPrice(base)`), or undefined when either price is unknown. */
|
|
341
|
+
export function classifierCostRatio(model: Model<Api>, base: Model<Api>): number | undefined {
|
|
342
|
+
const price = classifierPrice(base);
|
|
343
|
+
const cost = price(model);
|
|
344
|
+
const baseCost = price(base);
|
|
345
|
+
return cost !== undefined && baseCost !== undefined && baseCost > 0 ? cost / baseCost : undefined;
|
|
254
346
|
}
|
|
255
347
|
|
|
256
348
|
/** A dated snapshot id (`claude-haiku-4-5-20251001`): the trailing `-YYYYMMDD` marks a pinned alias of an undated model. */
|
|
@@ -339,7 +431,12 @@ export function forcedReasoningLevel(model: Model<Api>): ThinkingLevel | undefin
|
|
|
339
431
|
* A false positive only costs one retry with minimal thinking.
|
|
340
432
|
*/
|
|
341
433
|
export function isReasoningMandatoryError(message: string): boolean {
|
|
342
|
-
return
|
|
434
|
+
return (
|
|
435
|
+
/(reasoning|thinking)[^.]*\b(mandatory|cannot be disabled|can't be disabled|must be enabled|is required)\b/i.test(message) ||
|
|
436
|
+
// Anthropic's: Opus 5.5 ("\"thinking.type.disabled\" is not supported for this model")
|
|
437
|
+
// and Sonnet 5.5 ("To turn thinking off on this model, send … between_tools"), findings §43.
|
|
438
|
+
/thinking\.type\.disabled\W+is not supported|to turn thinking off on this model/i.test(message)
|
|
439
|
+
);
|
|
343
440
|
}
|
|
344
441
|
|
|
345
442
|
/** The level to retry with after isReasoningMandatoryError: the lowest real level the model supports. */
|