@gajae-code/coding-agent 0.11.0 → 0.11.2
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/CHANGELOG.md +63 -1
- package/dist/types/async/job-manager.d.ts +46 -7
- package/dist/types/cli/config-cli.d.ts +11 -1
- package/dist/types/cli/notify-cli.d.ts +1 -0
- package/dist/types/cli/session-picker.d.ts +2 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/mcp-serve.d.ts +33 -0
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/commands/notify.d.ts +0 -2
- package/dist/types/commands/sdk.d.ts +1 -1
- package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
- package/dist/types/config/file-lock.d.ts +14 -4
- package/dist/types/config/keybindings.d.ts +42 -1
- package/dist/types/config/model-auth.d.ts +3 -0
- package/dist/types/config/model-bindings-applier.d.ts +13 -0
- package/dist/types/config/model-discovery-manager.d.ts +49 -0
- package/dist/types/config/model-equivalence.d.ts +20 -0
- package/dist/types/config/model-registry.d.ts +16 -15
- package/dist/types/config/model-resolver.d.ts +28 -18
- package/dist/types/config/models-config-schema.d.ts +11 -0
- package/dist/types/config/settings-schema.d.ts +56 -1
- package/dist/types/config/settings.d.ts +14 -11
- package/dist/types/coordinator-mcp/policy.d.ts +5 -0
- package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +0 -6
- package/dist/types/edit/index.d.ts +3 -0
- package/dist/types/eval/py/env.d.ts +9 -0
- package/dist/types/extensibility/extensions/loader.d.ts +2 -1
- package/dist/types/extensibility/extensions/runner.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +34 -3
- package/dist/types/extensibility/hooks/runner.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
- package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
- package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
- package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
- package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
- package/dist/types/gjc-runtime/team-store.d.ts +160 -0
- package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +11 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
- package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
- package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/goals/state.d.ts +8 -0
- package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
- package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/state.d.ts +11 -4
- package/dist/types/lsp/lspmux.d.ts +5 -3
- package/dist/types/lsp/path-trust.d.ts +1 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memories/storage.d.ts +1 -0
- package/dist/types/modes/action-registry.d.ts +29 -0
- package/dist/types/modes/components/chrome.d.ts +14 -0
- package/dist/types/modes/components/command-palette.d.ts +27 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
- package/dist/types/modes/components/index.d.ts +2 -1
- package/dist/types/modes/components/pet-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
- package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
- package/dist/types/modes/components/queue-pane.d.ts +12 -0
- package/dist/types/modes/components/read-tool-group.d.ts +6 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
- package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
- package/dist/types/modes/components/settings-selector.d.ts +1 -1
- package/dist/types/modes/components/show-images-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +1 -94
- package/dist/types/modes/components/tasks-pane.d.ts +15 -0
- package/dist/types/modes/components/theme-selector.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +15 -0
- package/dist/types/modes/components/tool-status-header.d.ts +111 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -2
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
- package/dist/types/modes/controllers/input-controller.d.ts +8 -1
- package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
- package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
- package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
- package/dist/types/modes/interactive-mode.d.ts +21 -19
- package/dist/types/modes/jobs-observer.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +21 -2
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
- package/dist/types/modes/tasks-aggregator.d.ts +39 -0
- package/dist/types/modes/transcript-item-registry.d.ts +60 -0
- package/dist/types/modes/types.d.ts +22 -15
- package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +78 -12
- package/dist/types/sdk/broker/ensure.d.ts +9 -0
- package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
- package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
- package/dist/types/sdk/broker/session-index.d.ts +0 -1
- package/dist/types/sdk/broker/state-version.d.ts +2 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
- package/dist/types/sdk/bus/config-commands.d.ts +1 -1
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +5 -0
- package/dist/types/sdk/bus/html-format.d.ts +3 -2
- package/dist/types/sdk/bus/index.d.ts +27 -0
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/notification-service.d.ts +20 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
- package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
- package/dist/types/sdk/bus/rich-document.d.ts +7 -0
- package/dist/types/sdk/bus/rich-render.d.ts +18 -41
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +69 -3
- package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/session-directory.d.ts +56 -0
- package/dist/types/session/agent-session.d.ts +82 -47
- package/dist/types/session/agent-storage.d.ts +7 -0
- package/dist/types/session/fallback-chain-controller.d.ts +10 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
- package/dist/types/session/messages.d.ts +16 -0
- package/dist/types/session/session-manager-internal.d.ts +14 -0
- package/dist/types/session/session-manager.d.ts +82 -12
- package/dist/types/session/session-storage.d.ts +43 -3
- package/dist/types/session/volatile-context-pruning.d.ts +15 -0
- package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
- package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
- package/dist/types/system-prompt.d.ts +2 -11
- package/dist/types/task/executor.d.ts +3 -1
- package/dist/types/thinking.d.ts +7 -0
- package/dist/types/tool-discovery/tool-index.d.ts +1 -50
- package/dist/types/tools/ask.d.ts +64 -2
- package/dist/types/tools/bash.d.ts +3 -0
- package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
- package/dist/types/tools/cron.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +1 -0
- package/dist/types/tools/index.d.ts +30 -15
- package/dist/types/tools/output-meta.d.ts +1 -0
- package/dist/types/tools/read.d.ts +3 -0
- package/dist/types/tools/search-tool-bm25.d.ts +3 -4
- package/dist/types/tools/subagent-render.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +6 -1
- package/dist/types/utils/event-bus.d.ts +1 -1
- package/dist/types/web/search/index.d.ts +3 -1
- package/package.json +21 -9
- package/scripts/build-sdk-package-smoke.ts +39 -1
- package/scripts/generate-hotkeys-docs.ts +74 -0
- package/scripts/generate-sdk-operation-inventory.ts +64 -20
- package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
- package/src/async/job-manager.ts +455 -69
- package/src/cli/config-cli.ts +75 -2
- package/src/cli/notify-cli.ts +92 -26
- package/src/cli/session-picker.ts +20 -3
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/harness.ts +34 -106
- package/src/commands/launch.ts +6 -4
- package/src/commands/mcp-serve.ts +85 -8
- package/src/commands/mcp.ts +2 -2
- package/src/commands/notify.ts +8 -4
- package/src/commands/sdk.ts +61 -40
- package/src/commands/ultragoal.ts +4 -1
- package/src/config/atomic-yaml-patch.ts +80 -13
- package/src/config/file-lock.ts +105 -43
- package/src/config/keybindings.ts +64 -20
- package/src/config/model-auth.ts +9 -0
- package/src/config/model-bindings-applier.ts +139 -0
- package/src/config/model-discovery-manager.ts +224 -0
- package/src/config/model-equivalence.ts +66 -0
- package/src/config/model-registry.ts +169 -302
- package/src/config/model-resolver.ts +361 -369
- package/src/config/models-config-schema.ts +5 -4
- package/src/config/settings-schema.ts +159 -2
- package/src/config/settings.ts +458 -482
- package/src/coordinator-mcp/policy.ts +76 -11
- package/src/coordinator-mcp/question-gate-codec.ts +274 -0
- package/src/coordinator-mcp/question-state.ts +778 -0
- package/src/coordinator-mcp/server.ts +1702 -238
- package/src/debug/index.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
- package/src/defaults/gjc/skills/team/SKILL.md +16 -0
- package/src/discovery/helpers.ts +1 -50
- package/src/edit/index.ts +64 -0
- package/src/eval/py/env.ts +26 -0
- package/src/eval/py/kernel.ts +5 -3
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +12 -3
- package/src/extensibility/extensions/types.ts +39 -1
- package/src/extensibility/gjc-plugins/installer.ts +16 -16
- package/src/extensibility/hooks/runner.ts +2 -2
- package/src/extensibility/runtime-skill-discovery.ts +33 -13
- package/src/extensibility/shared-events.ts +2 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
- package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
- package/src/gjc-runtime/deep-interview-state.ts +317 -5
- package/src/gjc-runtime/goal-mode-request.ts +55 -8
- package/src/gjc-runtime/launch-tmux.ts +2 -5
- package/src/gjc-runtime/launch-worktree.ts +5 -5
- package/src/gjc-runtime/linux-proc.ts +71 -0
- package/src/gjc-runtime/ralplan-runtime.ts +74 -90
- package/src/gjc-runtime/session-resolution.ts +18 -3
- package/src/gjc-runtime/session-state-sidecar.ts +6 -107
- package/src/gjc-runtime/state-migrations.ts +1 -1
- package/src/gjc-runtime/state-runtime.ts +320 -249
- package/src/gjc-runtime/state-writer.ts +36 -40
- package/src/gjc-runtime/team-launch.ts +319 -0
- package/src/gjc-runtime/team-notify.ts +326 -0
- package/src/gjc-runtime/team-runtime.ts +1099 -1482
- package/src/gjc-runtime/team-store.ts +828 -0
- package/src/gjc-runtime/team-workers.ts +962 -0
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
- package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
- package/src/gjc-runtime/tmux-sessions.ts +4 -21
- package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
- package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
- package/src/gjc-runtime/ultragoal-guard.ts +83 -12
- package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +102 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +156 -1319
- package/src/gjc-runtime/workflow-cli-common.ts +30 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +18 -0
- package/src/goals/continuation-timeout-guard.ts +79 -0
- package/src/goals/runtime.ts +7 -5
- package/src/goals/state.ts +18 -0
- package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
- package/src/harness-control-plane/phase-rollup.ts +4 -7
- package/src/harness-control-plane/receipt-ingest.ts +5 -0
- package/src/harness-control-plane/receipt-spool.ts +51 -7
- package/src/harness-control-plane/session-lease.ts +12 -130
- package/src/hindsight/backend.ts +26 -27
- package/src/hindsight/state.ts +101 -32
- package/src/hooks/skill-state.ts +17 -11
- package/src/internal-urls/docs-index.generated.ts +21 -18
- package/src/lsp/client.ts +1 -1
- package/src/lsp/config.ts +145 -26
- package/src/lsp/index.ts +1 -1
- package/src/lsp/lspmux.ts +39 -10
- package/src/lsp/path-trust.ts +81 -0
- package/src/main.ts +28 -8
- package/src/memories/index.ts +1 -0
- package/src/memories/storage.ts +6 -2
- package/src/modes/action-registry.ts +165 -0
- package/src/modes/components/chrome.ts +40 -0
- package/src/modes/components/command-palette.ts +168 -0
- package/src/modes/components/custom-editor.ts +1 -1
- package/src/modes/components/custom-provider-wizard.ts +51 -14
- package/src/modes/components/index.ts +2 -1
- package/src/modes/components/pet-selector.ts +11 -17
- package/src/modes/components/plan-preview-overlay.ts +295 -0
- package/src/modes/components/queue-mode-selector.ts +10 -27
- package/src/modes/components/queue-pane.ts +79 -0
- package/src/modes/components/read-tool-group.ts +13 -0
- package/src/modes/components/session-observer-overlay.ts +207 -772
- package/src/modes/components/sessions-dashboard.ts +181 -0
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +10 -24
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +1 -969
- package/src/modes/components/tasks-pane.ts +73 -0
- package/src/modes/components/theme-selector.ts +11 -31
- package/src/modes/components/tool-execution.ts +22 -0
- package/src/modes/components/tool-status-header.ts +1056 -0
- package/src/modes/components/transcript-viewer-overlay.ts +384 -0
- package/src/modes/controllers/btw-controller.ts +1 -0
- package/src/modes/controllers/command-controller.ts +9 -15
- package/src/modes/controllers/event-controller.ts +3 -2
- package/src/modes/controllers/extension-ui-controller.ts +72 -25
- package/src/modes/controllers/goal-mode-controller.ts +474 -0
- package/src/modes/controllers/input-controller.ts +764 -229
- package/src/modes/controllers/mode-gate.ts +24 -0
- package/src/modes/controllers/plan-mode-controller.ts +517 -0
- package/src/modes/controllers/selector-controller.ts +225 -10
- package/src/modes/controllers/stt-controller.ts +125 -0
- package/src/modes/interactive-mode.ts +302 -1151
- package/src/modes/jobs-observer.ts +3 -0
- package/src/modes/runtime-init.ts +8 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +78 -19
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
- package/src/modes/tasks-aggregator.ts +199 -0
- package/src/modes/transcript-item-registry.ts +229 -0
- package/src/modes/types.ts +47 -20
- package/src/modes/utils/ui-helpers.ts +17 -9
- package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
- package/src/prompts/agent-fragments/restricted-bash.md +1 -0
- package/src/prompts/agents/architect.md +2 -8
- package/src/prompts/agents/critic.md +2 -8
- package/src/prompts/agents/executor.md +3 -0
- package/src/prompts/agents/planner.md +4 -12
- package/src/prompts/system/custom-system-prompt.md +1 -23
- package/src/prompts/system/plan-mode-approved.md +8 -0
- package/src/prompts/system/system-prompt.md +17 -96
- package/src/prompts/tools/bash.md +4 -4
- package/src/prompts/tools/browser.md +15 -52
- package/src/prompts/tools/find.md +2 -2
- package/src/prompts/tools/hashline.md +13 -55
- package/src/prompts/tools/read.md +3 -6
- package/src/prompts/tools/search-tool-bm25.md +0 -2
- package/src/prompts/tools/search.md +2 -4
- package/src/prompts/tools/skill-discovery.md +1 -2
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-mcp/manager.ts +1 -1
- package/src/sdk/broker/broker.ts +206 -78
- package/src/sdk/broker/discovery.ts +21 -2
- package/src/sdk/broker/ensure.ts +85 -25
- package/src/sdk/broker/lifecycle-codec.ts +11 -0
- package/src/sdk/broker/lifecycle-ledger.ts +371 -74
- package/src/sdk/broker/lifecycle.ts +1399 -142
- package/src/sdk/broker/process-incarnation.ts +5 -13
- package/src/sdk/broker/session-index.ts +145 -56
- package/src/sdk/broker/state-version.ts +21 -0
- package/src/sdk/broker/transport.ts +1 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -28
- package/src/sdk/bus/chat-effect-journal.ts +14 -4
- package/src/sdk/bus/config-commands.ts +5 -4
- package/src/sdk/bus/config.ts +98 -0
- package/src/sdk/bus/conversation-store.ts +5 -29
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/discord-daemon.ts +25 -8
- package/src/sdk/bus/html-format.ts +20 -3
- package/src/sdk/bus/index.ts +834 -113
- package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
- package/src/sdk/bus/native-runtime-compatibility.ts +1 -1
- package/src/sdk/bus/natives-augment.d.ts +26 -1
- package/src/sdk/bus/notification-service.ts +168 -20
- package/src/sdk/bus/rate-limit-pool.ts +58 -14
- package/src/sdk/bus/recent-activity.ts +188 -74
- package/src/sdk/bus/rich-document.ts +258 -0
- package/src/sdk/bus/rich-render.ts +62 -70
- package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon.ts +1491 -247
- package/src/sdk/bus/threaded-inbound.ts +8 -3
- package/src/sdk/bus/threaded-render.ts +57 -1
- package/src/sdk/bus/topic-registry.ts +120 -13
- package/src/sdk/host/control/dispatch.ts +14 -2
- package/src/sdk/host/control/operations.ts +6 -1
- package/src/sdk/host/host.ts +31 -1
- package/src/sdk/host/query/handlers.ts +20 -22
- package/src/sdk/host/query/revision-store.ts +80 -57
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
- package/src/sdk/session-directory.ts +141 -0
- package/src/sdk/session.ts +458 -479
- package/src/session/agent-session.ts +1901 -637
- package/src/session/agent-storage.ts +18 -0
- package/src/session/cache-economics.ts +25 -8
- package/src/session/fallback-chain-controller.ts +53 -3
- package/src/session/internal/managed-session-scope.ts +2226 -0
- package/src/session/internal/managed-session-storage.ts +453 -0
- package/src/session/messages.ts +91 -2
- package/src/session/session-manager-internal.ts +33 -0
- package/src/session/session-manager.ts +1112 -333
- package/src/session/session-storage.ts +444 -39
- package/src/session/volatile-context-pruning.ts +78 -0
- package/src/setup/credential-auto-import.ts +2 -1
- package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
- package/src/setup/provider-onboarding.ts +36 -61
- package/src/setup/provider-presets.json +5 -0
- package/src/skill-state/active-state.ts +3 -13
- package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
- package/src/skill-state/workflow-state-contract.ts +6 -41
- package/src/slash-commands/builtin-registry.ts +46 -11
- package/src/system-prompt.ts +37 -44
- package/src/task/agents.ts +19 -1
- package/src/task/executor.ts +90 -6
- package/src/task/index.ts +62 -37
- package/src/thinking.ts +21 -6
- package/src/tool-discovery/tool-index.ts +2 -134
- package/src/tools/ask.ts +157 -21
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/bash.ts +38 -0
- package/src/tools/browser/dead-tab-recovery.ts +78 -0
- package/src/tools/browser/tab-supervisor.ts +282 -36
- package/src/tools/computer.ts +10 -3
- package/src/tools/cron.ts +30 -16
- package/src/tools/eval.ts +1 -2
- package/src/tools/fetch.ts +11 -4
- package/src/tools/index.ts +76 -24
- package/src/tools/output-meta.ts +13 -5
- package/src/tools/read.ts +37 -1
- package/src/tools/resource-gc.ts +2 -2
- package/src/tools/search-tool-bm25.ts +11 -74
- package/src/tools/subagent-render.ts +19 -8
- package/src/tools/subagent.ts +84 -32
- package/src/utils/event-bus.ts +9 -4
- package/src/web/search/index.ts +3 -3
- package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
- package/src/discovery/plugin-dir-roots.ts +0 -28
- package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
6
6
|
import { type Api, DEFAULT_MODEL_PER_PROVIDER, type KnownProvider, type Model, modelsAreEqual } from "@gajae-code/ai";
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
import { logger } from "@gajae-code/utils";
|
|
9
9
|
import chalk from "chalk";
|
|
10
|
-
import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking";
|
|
11
|
-
import {
|
|
10
|
+
import { parseThinkingLevel, resolveThinkingLevelForModel, splitSelectorThinkingSuffix } from "../thinking";
|
|
11
|
+
import { isAuthenticatedOrKeyless } from "./model-auth";
|
|
12
|
+
import { compareEquivalentModelVariants } from "./model-equivalence";
|
|
13
|
+
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
|
12
14
|
import { type ModelSelectorValue, normalizeModelSelectorValue } from "./model-selector-value";
|
|
13
15
|
import type { Settings } from "./settings";
|
|
14
16
|
|
|
@@ -35,6 +37,9 @@ export interface ScopedModel extends ScopedModelSelection {
|
|
|
35
37
|
explicitThinkingLevel: boolean;
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
export type { SelectorThinkingSuffix } from "../thinking";
|
|
41
|
+
export { splitSelectorThinkingSuffix } from "../thinking";
|
|
42
|
+
|
|
38
43
|
/**
|
|
39
44
|
* Parse a model string in "provider/modelId" format.
|
|
40
45
|
* Returns undefined if the format is invalid.
|
|
@@ -44,18 +49,11 @@ export function parseModelString(
|
|
|
44
49
|
): { provider: string; id: string; thinkingLevel?: ThinkingLevel } | undefined {
|
|
45
50
|
const slashIdx = modelStr.indexOf("/");
|
|
46
51
|
if (slashIdx <= 0) return undefined;
|
|
47
|
-
const id = modelStr.slice(slashIdx + 1);
|
|
48
52
|
const provider = modelStr.slice(0, slashIdx);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const thinkingLevel = parseThinkingLevel(suffix);
|
|
54
|
-
if (thinkingLevel) {
|
|
55
|
-
return { provider, id: id.slice(0, colonIdx), thinkingLevel };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return { provider, id };
|
|
53
|
+
const suffix = splitSelectorThinkingSuffix(modelStr);
|
|
54
|
+
return suffix.thinkingLevel
|
|
55
|
+
? { provider, id: suffix.selector.slice(slashIdx + 1), thinkingLevel: suffix.thinkingLevel }
|
|
56
|
+
: { provider, id: modelStr.slice(slashIdx + 1) };
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
/**
|
|
@@ -123,15 +121,22 @@ function cloneModelWithRequestedId(model: Model<Api>, requestedId: string): Mode
|
|
|
123
121
|
};
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
[
|
|
129
|
-
|
|
124
|
+
const providerModelIndexes = new WeakMap<
|
|
125
|
+
readonly Model<Api>[],
|
|
126
|
+
{ fingerprint: string; models: readonly Model<Api>[]; index: Map<string, Model<Api> | null> }
|
|
127
|
+
>();
|
|
128
|
+
|
|
129
|
+
function modelFingerprint(availableModels: readonly Model<Api>[]): string {
|
|
130
|
+
return availableModels.map(model => `${model.provider}\u0000${model.id}`).join("\u0001");
|
|
131
|
+
}
|
|
130
132
|
|
|
131
133
|
function getProviderModelIndex(availableModels: readonly Model<Api>[]): Map<string, Model<Api> | null> {
|
|
132
|
-
const
|
|
133
|
-
const cached =
|
|
134
|
-
if (cached)
|
|
134
|
+
const fingerprint = modelFingerprint(availableModels);
|
|
135
|
+
const cached = providerModelIndexes.get(availableModels);
|
|
136
|
+
if (cached?.fingerprint === fingerprint && cached.models.every((model, index) => model === availableModels[index])) {
|
|
137
|
+
return cached.index;
|
|
138
|
+
}
|
|
139
|
+
|
|
135
140
|
const index = new Map<string, Model<Api> | null>();
|
|
136
141
|
for (const m of availableModels) {
|
|
137
142
|
const key = `${m.provider.toLowerCase()}\u0000${m.id.toLowerCase()}`;
|
|
@@ -141,7 +146,8 @@ function getProviderModelIndex(availableModels: readonly Model<Api>[]): Map<stri
|
|
|
141
146
|
index.set(key, m);
|
|
142
147
|
}
|
|
143
148
|
}
|
|
144
|
-
|
|
149
|
+
providerModelIndexes.set(availableModels, { fingerprint, models: [...availableModels], index });
|
|
150
|
+
|
|
145
151
|
return index;
|
|
146
152
|
}
|
|
147
153
|
|
|
@@ -158,13 +164,10 @@ export function resolveProviderModelReference(
|
|
|
158
164
|
return undefined;
|
|
159
165
|
}
|
|
160
166
|
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
// ambiguous for fuzzy/case-insensitive lookup, but an exact selector should
|
|
166
|
-
// still resolve deterministically.
|
|
167
|
-
const caseExact = availableModels.find(m => m.provider === trimmedProvider && m.id === trimmedModelId);
|
|
167
|
+
// Preserve exact provider/id replacement semantics before case-insensitive
|
|
168
|
+
// ambiguity handling. Custom entries can intentionally replace a bundled
|
|
169
|
+
// model while a differently-cased catalog alias remains present.
|
|
170
|
+
const caseExact = availableModels.find(model => model.provider === trimmedProvider && model.id === trimmedModelId);
|
|
168
171
|
if (caseExact) return caseExact;
|
|
169
172
|
|
|
170
173
|
const index = getProviderModelIndex(availableModels);
|
|
@@ -201,12 +204,12 @@ export interface ModelMatchPreferences {
|
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
export type CanonicalModelRegistry = Partial<
|
|
204
|
-
Pick<ModelRegistry, "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId">
|
|
207
|
+
Pick<ModelRegistry, "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId" | "seedCanonicalVariant">
|
|
205
208
|
>;
|
|
206
209
|
export type ModelLookupRegistry = Pick<ModelRegistry, "getAvailable"> & Partial<CanonicalModelRegistry>;
|
|
207
210
|
type CliModelRegistry = Pick<ModelRegistry, "getAll"> & Partial<CanonicalModelRegistry>;
|
|
208
|
-
type InitialModelRegistry = Pick<ModelRegistry, "getAvailable"
|
|
209
|
-
type RestorableModelRegistry = Pick<ModelRegistry, "getAvailable" | "
|
|
211
|
+
type InitialModelRegistry = Pick<ModelRegistry, "getAvailable">;
|
|
212
|
+
type RestorableModelRegistry = Pick<ModelRegistry, "getAvailable" | "getApiKey">;
|
|
210
213
|
|
|
211
214
|
interface ModelPreferenceContext {
|
|
212
215
|
modelUsageRank: Map<string, number>;
|
|
@@ -215,68 +218,99 @@ interface ModelPreferenceContext {
|
|
|
215
218
|
modelOrder: Map<string, number>;
|
|
216
219
|
}
|
|
217
220
|
|
|
221
|
+
const preferenceContexts = new WeakMap<
|
|
222
|
+
readonly Model<Api>[],
|
|
223
|
+
{
|
|
224
|
+
fingerprint: string;
|
|
225
|
+
models: readonly Model<Api>[];
|
|
226
|
+
cacheKey: string;
|
|
227
|
+
context: ModelPreferenceContext;
|
|
228
|
+
}
|
|
229
|
+
>();
|
|
230
|
+
|
|
231
|
+
function modelPreferenceKey(model: Pick<Model<Api>, "provider" | "id">): string {
|
|
232
|
+
return `${model.provider}/${model.id}`.toLowerCase();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function preferenceCacheKey(preferences: ModelMatchPreferences | undefined): string {
|
|
236
|
+
return JSON.stringify([preferences?.usageOrder ?? [], preferences?.deprioritizeProviders ?? ["openrouter"]]);
|
|
237
|
+
}
|
|
238
|
+
|
|
218
239
|
function buildPreferenceContext(
|
|
219
240
|
availableModels: Model<Api>[],
|
|
220
241
|
preferences: ModelMatchPreferences | undefined,
|
|
221
242
|
): ModelPreferenceContext {
|
|
243
|
+
const fingerprint = modelFingerprint(availableModels);
|
|
244
|
+
const cacheKey = preferenceCacheKey(preferences);
|
|
245
|
+
const cached = preferenceContexts.get(availableModels);
|
|
246
|
+
if (
|
|
247
|
+
cached?.fingerprint === fingerprint &&
|
|
248
|
+
cached.cacheKey === cacheKey &&
|
|
249
|
+
cached.models.every((model, index) => model === availableModels[index])
|
|
250
|
+
) {
|
|
251
|
+
return cached.context;
|
|
252
|
+
}
|
|
253
|
+
|
|
222
254
|
const modelUsageRank = new Map<string, number>();
|
|
223
255
|
const providerUsageRank = new Map<string, number>();
|
|
224
256
|
const usageOrder = preferences?.usageOrder ?? [];
|
|
225
257
|
for (let i = 0; i < usageOrder.length; i += 1) {
|
|
226
258
|
const key = usageOrder[i];
|
|
227
|
-
if (!modelUsageRank.has(key))
|
|
228
|
-
modelUsageRank.set(key, i);
|
|
229
|
-
}
|
|
259
|
+
if (!modelUsageRank.has(key.toLowerCase())) modelUsageRank.set(key.toLowerCase(), i);
|
|
230
260
|
const parsed = parseModelString(key);
|
|
231
|
-
if (parsed && !providerUsageRank.has(parsed.provider)) {
|
|
232
|
-
providerUsageRank.set(parsed.provider, i);
|
|
261
|
+
if (parsed && !providerUsageRank.has(parsed.provider.toLowerCase())) {
|
|
262
|
+
providerUsageRank.set(parsed.provider.toLowerCase(), i);
|
|
233
263
|
}
|
|
234
264
|
}
|
|
235
265
|
|
|
236
|
-
const deprioritizedProviders = new Set(
|
|
266
|
+
const deprioritizedProviders = new Set(
|
|
267
|
+
(preferences?.deprioritizeProviders ?? ["openrouter"]).map(provider => provider.toLowerCase()),
|
|
268
|
+
);
|
|
269
|
+
|
|
237
270
|
const modelOrder = new Map<string, number>();
|
|
238
271
|
for (let i = 0; i < availableModels.length; i += 1) {
|
|
239
272
|
modelOrder.set(formatModelString(availableModels[i]), i);
|
|
240
273
|
}
|
|
274
|
+
const context = { modelUsageRank, providerUsageRank, deprioritizedProviders, modelOrder };
|
|
275
|
+
preferenceContexts.set(availableModels, {
|
|
276
|
+
fingerprint,
|
|
277
|
+
models: [...availableModels],
|
|
278
|
+
cacheKey,
|
|
279
|
+
context,
|
|
280
|
+
});
|
|
241
281
|
|
|
242
|
-
return
|
|
282
|
+
return context;
|
|
243
283
|
}
|
|
244
284
|
|
|
245
285
|
function pickPreferredModel(candidates: Model<Api>[], context: ModelPreferenceContext): Model<Api> {
|
|
246
286
|
if (candidates.length <= 1) return candidates[0];
|
|
247
287
|
return [...candidates].sort((a, b) => {
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
const bUsage = context.modelUsageRank.get(bKey);
|
|
288
|
+
const aUsage = context.modelUsageRank.get(modelPreferenceKey(a));
|
|
289
|
+
const bUsage = context.modelUsageRank.get(modelPreferenceKey(b));
|
|
290
|
+
|
|
252
291
|
if (aUsage !== undefined || bUsage !== undefined) {
|
|
253
292
|
return (aUsage ?? Number.POSITIVE_INFINITY) - (bUsage ?? Number.POSITIVE_INFINITY);
|
|
254
293
|
}
|
|
255
294
|
|
|
256
|
-
const aProviderUsage = context.providerUsageRank.get(a.provider);
|
|
257
|
-
const bProviderUsage = context.providerUsageRank.get(b.provider);
|
|
295
|
+
const aProviderUsage = context.providerUsageRank.get(a.provider.toLowerCase());
|
|
296
|
+
const bProviderUsage = context.providerUsageRank.get(b.provider.toLowerCase());
|
|
297
|
+
|
|
258
298
|
if (aProviderUsage !== undefined || bProviderUsage !== undefined) {
|
|
259
299
|
return (aProviderUsage ?? Number.POSITIVE_INFINITY) - (bProviderUsage ?? Number.POSITIVE_INFINITY);
|
|
260
300
|
}
|
|
261
301
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const aVision = a.input.includes("image") ? 0 : 1;
|
|
266
|
-
const bVision = b.input.includes("image") ? 0 : 1;
|
|
267
|
-
if (aVision !== bVision) {
|
|
268
|
-
return aVision - bVision;
|
|
302
|
+
const variantRank = compareEquivalentModelVariants(a, b, { finalize: false });
|
|
303
|
+
if (variantRank !== 0) {
|
|
304
|
+
return variantRank;
|
|
269
305
|
}
|
|
270
306
|
|
|
271
|
-
const aDeprioritized = context.deprioritizedProviders.has(a.provider);
|
|
272
|
-
const bDeprioritized = context.deprioritizedProviders.has(b.provider);
|
|
307
|
+
const aDeprioritized = context.deprioritizedProviders.has(a.provider.toLowerCase());
|
|
308
|
+
const bDeprioritized = context.deprioritizedProviders.has(b.provider.toLowerCase());
|
|
273
309
|
if (aDeprioritized !== bDeprioritized) {
|
|
274
310
|
return aDeprioritized ? 1 : -1;
|
|
275
311
|
}
|
|
276
312
|
|
|
277
|
-
|
|
278
|
-
const bOrder = context.modelOrder.get(bKey) ?? 0;
|
|
279
|
-
return aOrder - bOrder;
|
|
313
|
+
return compareEquivalentModelVariants(a, b, { modelOrder: context.modelOrder });
|
|
280
314
|
})[0];
|
|
281
315
|
}
|
|
282
316
|
|
|
@@ -321,6 +355,7 @@ function findExactCanonicalModelMatch(
|
|
|
321
355
|
modelReference: string,
|
|
322
356
|
availableModels: Model<Api>[],
|
|
323
357
|
modelRegistry: CanonicalModelRegistry | undefined,
|
|
358
|
+
sessionId?: string,
|
|
324
359
|
): Model<Api> | undefined {
|
|
325
360
|
if (!modelRegistry) {
|
|
326
361
|
return undefined;
|
|
@@ -332,6 +367,35 @@ function findExactCanonicalModelMatch(
|
|
|
332
367
|
return modelRegistry.resolveCanonicalModel?.(trimmedReference, {
|
|
333
368
|
availableOnly: false,
|
|
334
369
|
candidates: availableModels,
|
|
370
|
+
sessionId,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Resolve an unqualified exact alias through its canonical record so it uses
|
|
376
|
+
* the registry's equivalent-variant ranking rather than generic fuzzy policy.
|
|
377
|
+
*/
|
|
378
|
+
function findExactEquivalentModelMatch(
|
|
379
|
+
modelReference: string,
|
|
380
|
+
availableModels: Model<Api>[],
|
|
381
|
+
modelRegistry: CanonicalModelRegistry | undefined,
|
|
382
|
+
sessionId?: string,
|
|
383
|
+
): Model<Api> | undefined {
|
|
384
|
+
if (!modelRegistry?.getCanonicalId || !modelRegistry.resolveCanonicalModel) return undefined;
|
|
385
|
+
const trimmedReference = modelReference.trim();
|
|
386
|
+
if (!trimmedReference || trimmedReference.includes("/")) return undefined;
|
|
387
|
+
const exactMatches = availableModels.filter(model => model.id.toLowerCase() === trimmedReference.toLowerCase());
|
|
388
|
+
if (exactMatches.length === 0) return undefined;
|
|
389
|
+
const canonicalIds = new Set(
|
|
390
|
+
exactMatches
|
|
391
|
+
.map(model => modelRegistry.getCanonicalId!(model))
|
|
392
|
+
.filter((canonicalId): canonicalId is string => Boolean(canonicalId)),
|
|
393
|
+
);
|
|
394
|
+
if (canonicalIds.size !== 1) return undefined;
|
|
395
|
+
return modelRegistry.resolveCanonicalModel([...canonicalIds][0]!, {
|
|
396
|
+
availableOnly: false,
|
|
397
|
+
candidates: availableModels,
|
|
398
|
+
sessionId,
|
|
335
399
|
});
|
|
336
400
|
}
|
|
337
401
|
|
|
@@ -343,7 +407,7 @@ function tryMatchModel(
|
|
|
343
407
|
modelPattern: string,
|
|
344
408
|
availableModels: Model<Api>[],
|
|
345
409
|
context: ModelPreferenceContext,
|
|
346
|
-
options?: { modelRegistry?: CanonicalModelRegistry },
|
|
410
|
+
options?: { modelRegistry?: CanonicalModelRegistry; sessionId?: string },
|
|
347
411
|
): Model<Api> | undefined {
|
|
348
412
|
// Explicit provider/model selectors always bypass canonical coalescing.
|
|
349
413
|
const exactRefMatch = findExactModelReferenceMatch(modelPattern, availableModels);
|
|
@@ -352,11 +416,26 @@ function tryMatchModel(
|
|
|
352
416
|
}
|
|
353
417
|
|
|
354
418
|
// Exact canonical ids coalesce provider variants before bare-id matching.
|
|
355
|
-
const exactCanonicalMatch = findExactCanonicalModelMatch(
|
|
419
|
+
const exactCanonicalMatch = findExactCanonicalModelMatch(
|
|
420
|
+
modelPattern,
|
|
421
|
+
availableModels,
|
|
422
|
+
options?.modelRegistry,
|
|
423
|
+
options?.sessionId,
|
|
424
|
+
);
|
|
356
425
|
if (exactCanonicalMatch) {
|
|
357
426
|
return exactCanonicalMatch;
|
|
358
427
|
}
|
|
359
428
|
|
|
429
|
+
const exactEquivalentMatch = findExactEquivalentModelMatch(
|
|
430
|
+
modelPattern,
|
|
431
|
+
availableModels,
|
|
432
|
+
options?.modelRegistry,
|
|
433
|
+
options?.sessionId,
|
|
434
|
+
);
|
|
435
|
+
if (exactEquivalentMatch) {
|
|
436
|
+
return exactEquivalentMatch;
|
|
437
|
+
}
|
|
438
|
+
|
|
360
439
|
// Exact ID match (case-insensitive) — this must happen before provider-scoped
|
|
361
440
|
// fuzzy matching so raw IDs that contain slashes (for example OpenRouter model
|
|
362
441
|
// IDs like "openai/gpt-4o:extended") still resolve as IDs instead of being
|
|
@@ -365,41 +444,19 @@ function tryMatchModel(
|
|
|
365
444
|
if (exactMatches.length > 0) {
|
|
366
445
|
return pickPreferredModel(exactMatches, context);
|
|
367
446
|
}
|
|
368
|
-
//
|
|
447
|
+
// Provider-qualified selectors are exact-only. Case-only duplicate catalog
|
|
448
|
+
// entries still rank deterministically within the explicitly named provider.
|
|
369
449
|
const slashIndex = modelPattern.indexOf("/");
|
|
370
450
|
if (slashIndex !== -1) {
|
|
371
|
-
const provider = modelPattern.
|
|
372
|
-
const modelId = modelPattern
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
.filter(entry => entry.match.matches);
|
|
381
|
-
if (scored.length === 0) {
|
|
382
|
-
return undefined;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
scored.sort((a, b) => {
|
|
386
|
-
if (a.match.score !== b.match.score) return a.match.score - b.match.score;
|
|
387
|
-
const aKey = formatModelString(a.model);
|
|
388
|
-
const bKey = formatModelString(b.model);
|
|
389
|
-
const aUsage = context.modelUsageRank.get(aKey) ?? Number.POSITIVE_INFINITY;
|
|
390
|
-
const bUsage = context.modelUsageRank.get(bKey) ?? Number.POSITIVE_INFINITY;
|
|
391
|
-
if (aUsage !== bUsage) return aUsage - bUsage;
|
|
392
|
-
|
|
393
|
-
const aProviderUsage = context.providerUsageRank.get(a.model.provider) ?? Number.POSITIVE_INFINITY;
|
|
394
|
-
const bProviderUsage = context.providerUsageRank.get(b.model.provider) ?? Number.POSITIVE_INFINITY;
|
|
395
|
-
if (aProviderUsage !== bProviderUsage) return aProviderUsage - bProviderUsage;
|
|
396
|
-
|
|
397
|
-
const aOrder = context.modelOrder.get(aKey) ?? 0;
|
|
398
|
-
const bOrder = context.modelOrder.get(bKey) ?? 0;
|
|
399
|
-
return aOrder - bOrder;
|
|
400
|
-
});
|
|
401
|
-
return scored[0]?.model;
|
|
402
|
-
}
|
|
451
|
+
const provider = modelPattern.slice(0, slashIndex).trim().toLowerCase();
|
|
452
|
+
const modelId = modelPattern
|
|
453
|
+
.slice(slashIndex + 1)
|
|
454
|
+
.trim()
|
|
455
|
+
.toLowerCase();
|
|
456
|
+
const providerExactMatches = availableModels.filter(
|
|
457
|
+
model => model.provider.toLowerCase() === provider && model.id.toLowerCase() === modelId,
|
|
458
|
+
);
|
|
459
|
+
return providerExactMatches.length > 0 ? pickPreferredModel(providerExactMatches, context) : undefined;
|
|
403
460
|
}
|
|
404
461
|
|
|
405
462
|
// No exact match - fall back to partial matching
|
|
@@ -441,84 +498,80 @@ export interface ParsedModelResult {
|
|
|
441
498
|
explicitThinkingLevel: boolean;
|
|
442
499
|
}
|
|
443
500
|
|
|
501
|
+
export interface ResolveSelectorOptions {
|
|
502
|
+
allowInvalidThinkingSelectorFallback?: boolean;
|
|
503
|
+
modelRegistry?: CanonicalModelRegistry;
|
|
504
|
+
preferences?: ModelMatchPreferences;
|
|
505
|
+
sessionId?: string;
|
|
506
|
+
}
|
|
507
|
+
|
|
444
508
|
/**
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
* Algorithm:
|
|
449
|
-
* 1. Try to match full pattern as a model
|
|
450
|
-
* 2. If found, return it with undefined thinking level
|
|
451
|
-
* 3. If not found and has colons, split on last colon:
|
|
452
|
-
* - If suffix is valid thinking level, use it and recurse on prefix
|
|
453
|
-
* - If suffix is invalid, warn and recurse on prefix
|
|
454
|
-
*
|
|
455
|
-
* @internal Exported for testing
|
|
509
|
+
* Resolve one selector through ordered exact, canonical, bare-id, provider-fuzzy,
|
|
510
|
+
* substring/alias, and glob stages. Thinking is split only after a full selector
|
|
511
|
+
* cannot resolve, which preserves OpenRouter route suffixes in concrete IDs.
|
|
456
512
|
*/
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
options?: { allowInvalidThinkingSelectorFallback?: boolean; modelRegistry?: CanonicalModelRegistry },
|
|
513
|
+
export function resolveSelector(
|
|
514
|
+
selector: string,
|
|
515
|
+
candidates: Model<Api>[],
|
|
516
|
+
options?: ResolveSelectorOptions,
|
|
462
517
|
): ParsedModelResult {
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
518
|
+
const context = buildPreferenceContext(candidates, options?.preferences);
|
|
519
|
+
const exact = tryMatchModel(selector, candidates, context, options);
|
|
520
|
+
if (exact) return { model: exact, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
521
|
+
const glob = findGlobMatch(selector, candidates, context);
|
|
522
|
+
if (glob) return { model: glob, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
523
|
+
|
|
524
|
+
const suffix = splitSelectorThinkingSuffix(selector);
|
|
525
|
+
if (
|
|
526
|
+
suffix.selector === selector ||
|
|
527
|
+
(suffix.invalidSuffix !== undefined && !(options?.allowInvalidThinkingSelectorFallback ?? true))
|
|
528
|
+
) {
|
|
473
529
|
return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
474
530
|
}
|
|
475
531
|
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
const result = parseModelPatternWithContext(prefix, availableModels, context, options);
|
|
483
|
-
if (result.model) {
|
|
484
|
-
// Only use this thinking level if no warning from inner recursion
|
|
485
|
-
const explicitThinkingLevel = !result.warning;
|
|
486
|
-
return {
|
|
487
|
-
model: result.model,
|
|
488
|
-
thinkingLevel: explicitThinkingLevel ? parsedThinkingLevel : undefined,
|
|
489
|
-
warning: result.warning,
|
|
490
|
-
explicitThinkingLevel,
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
return result;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const allowFallback = options?.allowInvalidThinkingSelectorFallback ?? true;
|
|
497
|
-
if (!allowFallback) {
|
|
498
|
-
return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
532
|
+
const model =
|
|
533
|
+
tryMatchModel(suffix.selector, candidates, context, options) ??
|
|
534
|
+
findGlobMatch(suffix.selector, candidates, context);
|
|
535
|
+
if (!model) return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
536
|
+
if (suffix.thinkingLevel) {
|
|
537
|
+
return { model, thinkingLevel: suffix.thinkingLevel, warning: undefined, explicitThinkingLevel: true };
|
|
499
538
|
}
|
|
539
|
+
return {
|
|
540
|
+
model,
|
|
541
|
+
thinkingLevel: undefined,
|
|
542
|
+
warning: `Invalid thinking level "${suffix.invalidSuffix}" in pattern "${selector}". Using default instead.`,
|
|
543
|
+
explicitThinkingLevel: false,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
500
546
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
547
|
+
function findGlobMatch(
|
|
548
|
+
selector: string,
|
|
549
|
+
candidates: Model<Api>[],
|
|
550
|
+
context: ModelPreferenceContext,
|
|
551
|
+
): Model<Api> | undefined {
|
|
552
|
+
if (!selector.includes("*") && !selector.includes("?") && !selector.includes("[")) return undefined;
|
|
553
|
+
const glob = new Bun.Glob(selector.toLowerCase());
|
|
554
|
+
return pickPreferredModel(
|
|
555
|
+
candidates.filter(
|
|
556
|
+
model => glob.match(formatModelString(model).toLowerCase()) || glob.match(model.id.toLowerCase()),
|
|
557
|
+
),
|
|
558
|
+
context,
|
|
559
|
+
);
|
|
512
560
|
}
|
|
513
561
|
|
|
562
|
+
/** @internal Exported for testing and legacy adapters. */
|
|
563
|
+
|
|
514
564
|
export function parseModelPattern(
|
|
515
565
|
pattern: string,
|
|
516
566
|
availableModels: Model<Api>[],
|
|
517
567
|
preferences?: ModelMatchPreferences,
|
|
518
|
-
options?: {
|
|
568
|
+
options?: {
|
|
569
|
+
allowInvalidThinkingSelectorFallback?: boolean;
|
|
570
|
+
modelRegistry?: CanonicalModelRegistry;
|
|
571
|
+
sessionId?: string;
|
|
572
|
+
},
|
|
519
573
|
): ParsedModelResult {
|
|
520
|
-
|
|
521
|
-
return parseModelPatternWithContext(pattern, availableModels, context, options);
|
|
574
|
+
return resolveSelector(pattern, availableModels, { ...options, preferences });
|
|
522
575
|
}
|
|
523
576
|
|
|
524
577
|
const PREFIX_MODEL_ROLE = "pi/";
|
|
@@ -547,11 +600,11 @@ function resolveConfiguredRolePattern(value: string, settings?: Settings): strin
|
|
|
547
600
|
const normalized = value.trim();
|
|
548
601
|
if (!normalized) return undefined;
|
|
549
602
|
|
|
550
|
-
const
|
|
551
|
-
const thinkingLevel =
|
|
552
|
-
|
|
553
|
-
const aliasCandidate = thinkingLevel ? normalized.slice(0, lastColonIndex) : normalized;
|
|
603
|
+
const suffix = splitSelectorThinkingSuffix(normalized);
|
|
604
|
+
const thinkingLevel = suffix.thinkingLevel;
|
|
605
|
+
const aliasCandidate = thinkingLevel ? suffix.selector : normalized;
|
|
554
606
|
const role = getModelRoleAlias(aliasCandidate);
|
|
607
|
+
|
|
555
608
|
if (!role) return [normalized];
|
|
556
609
|
|
|
557
610
|
const configured = settings?.getModelRole(role);
|
|
@@ -626,7 +679,12 @@ export interface ResolvedModelRoleValue {
|
|
|
626
679
|
export function resolveModelRoleValue(
|
|
627
680
|
roleValue: ModelSelectorValue | undefined,
|
|
628
681
|
availableModels: Model<Api>[],
|
|
629
|
-
options?: {
|
|
682
|
+
options?: {
|
|
683
|
+
settings?: Settings;
|
|
684
|
+
matchPreferences?: ModelMatchPreferences;
|
|
685
|
+
modelRegistry?: CanonicalModelRegistry;
|
|
686
|
+
sessionId?: string;
|
|
687
|
+
},
|
|
630
688
|
): ResolvedModelRoleValue {
|
|
631
689
|
const effectivePatterns = normalizeModelPatternList(roleValue).flatMap(
|
|
632
690
|
pattern => resolveConfiguredRolePattern(pattern, options?.settings) ?? [],
|
|
@@ -639,6 +697,7 @@ export function resolveModelRoleValue(
|
|
|
639
697
|
for (const effectivePattern of effectivePatterns) {
|
|
640
698
|
const resolved = parseModelPattern(effectivePattern, availableModels, options?.matchPreferences, {
|
|
641
699
|
modelRegistry: options?.modelRegistry,
|
|
700
|
+
sessionId: options?.sessionId,
|
|
642
701
|
});
|
|
643
702
|
if (resolved.model) {
|
|
644
703
|
return {
|
|
@@ -669,9 +728,8 @@ export function extractExplicitThinkingSelector(
|
|
|
669
728
|
let current = normalized;
|
|
670
729
|
while (!visited.has(current)) {
|
|
671
730
|
visited.add(current);
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
lastColonIndex > PREFIX_MODEL_ROLE.length ? parseThinkingLevel(current.slice(lastColonIndex + 1)) : undefined;
|
|
731
|
+
const thinkingSelector = splitSelectorThinkingSuffix(current).thinkingLevel;
|
|
732
|
+
|
|
675
733
|
if (thinkingSelector) {
|
|
676
734
|
return thinkingSelector;
|
|
677
735
|
}
|
|
@@ -693,12 +751,7 @@ export function resolveModelFromString(
|
|
|
693
751
|
matchPreferences?: ModelMatchPreferences,
|
|
694
752
|
modelRegistry?: CanonicalModelRegistry,
|
|
695
753
|
): Model<Api> | undefined {
|
|
696
|
-
|
|
697
|
-
if (parsed) {
|
|
698
|
-
const exact = available.find(model => model.provider === parsed.provider && model.id === parsed.id);
|
|
699
|
-
if (exact) return exact;
|
|
700
|
-
}
|
|
701
|
-
return parseModelPattern(value, available, matchPreferences, { modelRegistry }).model;
|
|
754
|
+
return resolveSelector(value, available, { preferences: matchPreferences, modelRegistry }).model;
|
|
702
755
|
}
|
|
703
756
|
|
|
704
757
|
/**
|
|
@@ -735,6 +788,7 @@ export function resolveModelOverride(
|
|
|
735
788
|
modelPatterns: string[],
|
|
736
789
|
modelRegistry: ModelLookupRegistry,
|
|
737
790
|
settings?: Settings,
|
|
791
|
+
sessionId?: string,
|
|
738
792
|
): { model?: Model<Api>; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } {
|
|
739
793
|
if (modelPatterns.length === 0) return { explicitThinkingLevel: false };
|
|
740
794
|
const availableModels = modelRegistry.getAvailable();
|
|
@@ -744,6 +798,7 @@ export function resolveModelOverride(
|
|
|
744
798
|
settings,
|
|
745
799
|
matchPreferences,
|
|
746
800
|
modelRegistry,
|
|
801
|
+
sessionId,
|
|
747
802
|
});
|
|
748
803
|
if (model) {
|
|
749
804
|
return { model, thinkingLevel, explicitThinkingLevel };
|
|
@@ -781,7 +836,12 @@ export async function resolveModelChainWithAuth(
|
|
|
781
836
|
const skips: Array<{ selector: string; reason: string }> = [];
|
|
782
837
|
for (let activeIndex = 0; activeIndex < modelPatterns.length; activeIndex += 1) {
|
|
783
838
|
const selector = modelPatterns[activeIndex];
|
|
784
|
-
const candidate = resolveModelRoleValue(selector, availableModels, {
|
|
839
|
+
const candidate = resolveModelRoleValue(selector, availableModels, {
|
|
840
|
+
settings,
|
|
841
|
+
matchPreferences,
|
|
842
|
+
modelRegistry,
|
|
843
|
+
sessionId,
|
|
844
|
+
});
|
|
785
845
|
if (!candidate.model) {
|
|
786
846
|
skips.push({ selector, reason: "unknown_model" });
|
|
787
847
|
continue;
|
|
@@ -794,7 +854,7 @@ export async function resolveModelChainWithAuth(
|
|
|
794
854
|
}
|
|
795
855
|
}
|
|
796
856
|
const key = await modelRegistry.getApiKey(candidate.model, sessionId);
|
|
797
|
-
if (
|
|
857
|
+
if (isAuthenticatedOrKeyless(key)) {
|
|
798
858
|
return { ...candidate, activeIndex, skips };
|
|
799
859
|
}
|
|
800
860
|
skips.push({ selector, reason: "unauthenticated" });
|
|
@@ -828,8 +888,9 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
828
888
|
parentActiveModelPattern: string | undefined,
|
|
829
889
|
modelRegistry: ModelLookupRegistry & Pick<ModelRegistry, "getApiKey">,
|
|
830
890
|
settings?: Settings,
|
|
831
|
-
|
|
891
|
+
authSessionId?: string,
|
|
832
892
|
options?: ModelChainResolutionOptions,
|
|
893
|
+
canonicalSessionId?: string,
|
|
833
894
|
): Promise<{
|
|
834
895
|
model?: Model<Api>;
|
|
835
896
|
thinkingLevel?: ThinkingLevel;
|
|
@@ -847,8 +908,20 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
847
908
|
let requestedResolution: ResolvedModelRoleValue | undefined;
|
|
848
909
|
const skips: Array<{ selector: string; reason: string }> = [];
|
|
849
910
|
let activeIndex = 0;
|
|
911
|
+
const canonicalScope = canonicalSessionId ?? authSessionId;
|
|
912
|
+
if (canonicalScope && parentActiveModelPattern) {
|
|
913
|
+
const parentActiveModel = resolveModelOverride([parentActiveModelPattern], modelRegistry, settings).model;
|
|
914
|
+
if (parentActiveModel) {
|
|
915
|
+
modelRegistry.seedCanonicalVariant?.(canonicalScope, parentActiveModel);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
850
918
|
for (const pattern of modelPatterns) {
|
|
851
|
-
const candidate = resolveModelRoleValue(pattern, availableModels, {
|
|
919
|
+
const candidate = resolveModelRoleValue(pattern, availableModels, {
|
|
920
|
+
settings,
|
|
921
|
+
matchPreferences,
|
|
922
|
+
modelRegistry,
|
|
923
|
+
sessionId: canonicalScope,
|
|
924
|
+
});
|
|
852
925
|
if (!requestedModel && candidate.model) {
|
|
853
926
|
requestedModel = candidate.model;
|
|
854
927
|
requestedResolution = candidate;
|
|
@@ -866,19 +939,19 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
866
939
|
continue;
|
|
867
940
|
}
|
|
868
941
|
}
|
|
869
|
-
const key = await modelRegistry.getApiKey(candidate.model,
|
|
870
|
-
if (
|
|
942
|
+
const key = await modelRegistry.getApiKey(candidate.model, authSessionId);
|
|
943
|
+
if (isAuthenticatedOrKeyless(key)) {
|
|
871
944
|
return { ...candidate, requestedModel: candidate.model, authFallbackUsed: false, activeIndex, skips };
|
|
872
945
|
}
|
|
873
946
|
skips.push({ selector: pattern, reason: "unauthenticated" });
|
|
874
947
|
activeIndex += 1;
|
|
875
948
|
}
|
|
876
949
|
const fallback = parentActiveModelPattern
|
|
877
|
-
? resolveModelOverride([parentActiveModelPattern], modelRegistry, settings)
|
|
950
|
+
? resolveModelOverride([parentActiveModelPattern], modelRegistry, settings, authSessionId)
|
|
878
951
|
: { explicitThinkingLevel: false };
|
|
879
952
|
if (fallback.model) {
|
|
880
|
-
const fallbackKey = await modelRegistry.getApiKey(fallback.model,
|
|
881
|
-
if (
|
|
953
|
+
const fallbackKey = await modelRegistry.getApiKey(fallback.model, authSessionId);
|
|
954
|
+
if (isAuthenticatedOrKeyless(fallbackKey)) {
|
|
882
955
|
const isParentSubstitution = requestedModel === undefined || !modelsAreEqual(fallback.model, requestedModel);
|
|
883
956
|
return {
|
|
884
957
|
...fallback,
|
|
@@ -923,21 +996,20 @@ function resolveExactCanonicalScopePattern(
|
|
|
923
996
|
modelRegistry: Pick<ModelRegistry, "getCanonicalVariants">,
|
|
924
997
|
availableModels: Model<Api>[],
|
|
925
998
|
): { models: Model<Api>[]; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } | undefined {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const parsedThinkingLevel = parseThinkingLevel(suffix);
|
|
934
|
-
if (parsedThinkingLevel) {
|
|
935
|
-
canonicalId = pattern.substring(0, lastColonIndex);
|
|
936
|
-
thinkingLevel = parsedThinkingLevel;
|
|
937
|
-
explicitThinkingLevel = true;
|
|
938
|
-
}
|
|
999
|
+
// Exact concrete selectors must win before a suffix is interpreted as thinking.
|
|
1000
|
+
// This preserves canonical/OpenRouter model IDs that themselves contain colons.
|
|
1001
|
+
if (
|
|
1002
|
+
findExactModelReferenceMatch(pattern, availableModels) ||
|
|
1003
|
+
(pattern.includes(":") && availableModels.some(model => model.id.toLowerCase() === pattern.trim().toLowerCase()))
|
|
1004
|
+
) {
|
|
1005
|
+
return undefined;
|
|
939
1006
|
}
|
|
940
1007
|
|
|
1008
|
+
const suffix = splitSelectorThinkingSuffix(pattern);
|
|
1009
|
+
const canonicalId = suffix.thinkingLevel ? suffix.selector : pattern;
|
|
1010
|
+
const thinkingLevel = suffix.thinkingLevel;
|
|
1011
|
+
const explicitThinkingLevel = thinkingLevel !== undefined;
|
|
1012
|
+
|
|
941
1013
|
const variants = modelRegistry
|
|
942
1014
|
.getCanonicalVariants(canonicalId, { availableOnly: true, candidates: availableModels })
|
|
943
1015
|
.map(variant => variant.model);
|
|
@@ -949,43 +1021,39 @@ function resolveExactCanonicalScopePattern(
|
|
|
949
1021
|
}
|
|
950
1022
|
|
|
951
1023
|
/**
|
|
952
|
-
* Resolve model patterns to actual Model objects with optional thinking levels
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
* Supports models with colons in their IDs (e.g., OpenRouter's model:exacto).
|
|
959
|
-
* The algorithm tries to match the full pattern first, then progressively
|
|
960
|
-
* strips colon-suffixes to find a match.
|
|
1024
|
+
* Resolve model patterns to actual Model objects with optional thinking levels.
|
|
1025
|
+
* A `:level` suffix is interpreted only after the complete selector fails to
|
|
1026
|
+
* resolve, preserving concrete model IDs that contain colon-bearing route suffixes.
|
|
1027
|
+
* For each non-glob pattern, alias IDs are preferred over dated versions; otherwise
|
|
1028
|
+
* the latest dated version is selected.
|
|
961
1029
|
*/
|
|
1030
|
+
|
|
962
1031
|
export async function resolveModelScope(
|
|
963
1032
|
patterns: string[],
|
|
964
1033
|
modelRegistry: Pick<ModelRegistry, "getAvailable" | "getCanonicalVariants">,
|
|
965
1034
|
preferences?: ModelMatchPreferences,
|
|
966
1035
|
): Promise<ScopedModel[]> {
|
|
967
1036
|
const availableModels = modelRegistry.getAvailable();
|
|
968
|
-
|
|
1037
|
+
|
|
969
1038
|
const scopedModels: ScopedModel[] = [];
|
|
1039
|
+
const addScopedModel = (selection: ScopedModel): void => {
|
|
1040
|
+
const duplicate = scopedModels.some(
|
|
1041
|
+
existing =>
|
|
1042
|
+
modelsAreEqual(existing.model, selection.model) &&
|
|
1043
|
+
existing.thinkingLevel === selection.thinkingLevel &&
|
|
1044
|
+
existing.explicitThinkingLevel === selection.explicitThinkingLevel,
|
|
1045
|
+
);
|
|
1046
|
+
if (!duplicate) scopedModels.push(selection);
|
|
1047
|
+
};
|
|
970
1048
|
|
|
971
1049
|
for (const pattern of patterns) {
|
|
972
1050
|
// Check if pattern contains glob characters
|
|
973
1051
|
if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
|
|
974
|
-
// Extract optional thinking
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
if (colonIdx !== -1) {
|
|
981
|
-
const suffix = pattern.substring(colonIdx + 1);
|
|
982
|
-
const parsedThinkingLevel = parseThinkingLevel(suffix);
|
|
983
|
-
if (parsedThinkingLevel) {
|
|
984
|
-
thinkingLevel = parsedThinkingLevel;
|
|
985
|
-
explicitThinkingLevel = true;
|
|
986
|
-
globPattern = pattern.substring(0, colonIdx);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
1052
|
+
// Extract the optional thinking suffix once, preserving route suffixes.
|
|
1053
|
+
const suffix = splitSelectorThinkingSuffix(pattern);
|
|
1054
|
+
const globPattern = suffix.thinkingLevel ? suffix.selector : pattern;
|
|
1055
|
+
const thinkingLevel = suffix.thinkingLevel;
|
|
1056
|
+
const explicitThinkingLevel = thinkingLevel !== undefined;
|
|
989
1057
|
|
|
990
1058
|
// Match against "provider/modelId" format OR just model ID
|
|
991
1059
|
// This allows "*sonnet*" to match without requiring "anthropic/*sonnet*"
|
|
@@ -1001,42 +1069,37 @@ export async function resolveModelScope(
|
|
|
1001
1069
|
}
|
|
1002
1070
|
|
|
1003
1071
|
for (const model of matchingModels) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
thinkingLevel
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1072
|
+
addScopedModel({
|
|
1073
|
+
model,
|
|
1074
|
+
thinkingLevel: explicitThinkingLevel
|
|
1075
|
+
? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
|
|
1076
|
+
: thinkingLevel,
|
|
1077
|
+
explicitThinkingLevel,
|
|
1078
|
+
});
|
|
1013
1079
|
}
|
|
1080
|
+
|
|
1014
1081
|
continue;
|
|
1015
1082
|
}
|
|
1016
1083
|
|
|
1017
1084
|
const exactCanonical = resolveExactCanonicalScopePattern(pattern, modelRegistry, availableModels);
|
|
1018
1085
|
if (exactCanonical) {
|
|
1019
1086
|
for (const model of exactCanonical.models) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
thinkingLevel
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
explicitThinkingLevel: exactCanonical.explicitThinkingLevel,
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1087
|
+
addScopedModel({
|
|
1088
|
+
model,
|
|
1089
|
+
thinkingLevel: exactCanonical.explicitThinkingLevel
|
|
1090
|
+
? (resolveThinkingLevelForModel(model, exactCanonical.thinkingLevel) ?? exactCanonical.thinkingLevel)
|
|
1091
|
+
: exactCanonical.thinkingLevel,
|
|
1092
|
+
explicitThinkingLevel: exactCanonical.explicitThinkingLevel,
|
|
1093
|
+
});
|
|
1030
1094
|
}
|
|
1095
|
+
|
|
1031
1096
|
continue;
|
|
1032
1097
|
}
|
|
1033
1098
|
|
|
1034
|
-
const { model, thinkingLevel, warning, explicitThinkingLevel } =
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
{ modelRegistry },
|
|
1039
|
-
);
|
|
1099
|
+
const { model, thinkingLevel, warning, explicitThinkingLevel } = resolveSelector(pattern, availableModels, {
|
|
1100
|
+
modelRegistry,
|
|
1101
|
+
preferences,
|
|
1102
|
+
});
|
|
1040
1103
|
|
|
1041
1104
|
if (warning) {
|
|
1042
1105
|
logger.warn(warning);
|
|
@@ -1047,16 +1110,13 @@ export async function resolveModelScope(
|
|
|
1047
1110
|
continue;
|
|
1048
1111
|
}
|
|
1049
1112
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
model,
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
explicitThinkingLevel,
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1113
|
+
addScopedModel({
|
|
1114
|
+
model,
|
|
1115
|
+
thinkingLevel: explicitThinkingLevel
|
|
1116
|
+
? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
|
|
1117
|
+
: thinkingLevel,
|
|
1118
|
+
explicitThinkingLevel,
|
|
1119
|
+
});
|
|
1060
1120
|
}
|
|
1061
1121
|
|
|
1062
1122
|
return scopedModels;
|
|
@@ -1100,9 +1160,7 @@ export interface ResolveCliModelResult {
|
|
|
1100
1160
|
error: string | undefined;
|
|
1101
1161
|
}
|
|
1102
1162
|
|
|
1103
|
-
/**
|
|
1104
|
-
* Resolve a single model from CLI flags.
|
|
1105
|
-
*/
|
|
1163
|
+
/** Resolve a single model from CLI flags through the staged selector resolver. */
|
|
1106
1164
|
export function resolveCliModel(options: {
|
|
1107
1165
|
cliProvider?: string;
|
|
1108
1166
|
cliModel?: string;
|
|
@@ -1110,10 +1168,7 @@ export function resolveCliModel(options: {
|
|
|
1110
1168
|
preferences?: ModelMatchPreferences;
|
|
1111
1169
|
}): ResolveCliModelResult {
|
|
1112
1170
|
const { cliProvider, cliModel, modelRegistry, preferences } = options;
|
|
1113
|
-
|
|
1114
|
-
if (!cliModel) {
|
|
1115
|
-
return { model: undefined, selector: undefined, warning: undefined, error: undefined };
|
|
1116
|
-
}
|
|
1171
|
+
if (!cliModel) return { model: undefined, selector: undefined, warning: undefined, error: undefined };
|
|
1117
1172
|
|
|
1118
1173
|
const availableModels = modelRegistry.getAll();
|
|
1119
1174
|
if (availableModels.length === 0) {
|
|
@@ -1125,12 +1180,9 @@ export function resolveCliModel(options: {
|
|
|
1125
1180
|
};
|
|
1126
1181
|
}
|
|
1127
1182
|
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
let provider = cliProvider ? providerMap.get(cliProvider.toLowerCase()) : undefined;
|
|
1183
|
+
const provider = cliProvider
|
|
1184
|
+
? availableModels.find(model => model.provider.toLowerCase() === cliProvider.toLowerCase())?.provider
|
|
1185
|
+
: undefined;
|
|
1134
1186
|
if (cliProvider && !provider) {
|
|
1135
1187
|
return {
|
|
1136
1188
|
model: undefined,
|
|
@@ -1140,117 +1192,55 @@ export function resolveCliModel(options: {
|
|
|
1140
1192
|
};
|
|
1141
1193
|
}
|
|
1142
1194
|
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
};
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
if (!exact) {
|
|
1170
|
-
exact = availableModels.find(
|
|
1171
|
-
model => model.id.toLowerCase() === lower || `${model.provider}/${model.id}`.toLowerCase() === lower,
|
|
1172
|
-
);
|
|
1173
|
-
}
|
|
1174
|
-
if (exact) {
|
|
1175
|
-
return {
|
|
1176
|
-
model: exact,
|
|
1177
|
-
selector: formatModelString(exact),
|
|
1178
|
-
warning: undefined,
|
|
1179
|
-
thinkingLevel: undefined,
|
|
1180
|
-
error: undefined,
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
let pattern = trimmedModel;
|
|
1186
|
-
|
|
1187
|
-
if (!provider) {
|
|
1188
|
-
const slashIndex = cliModel.indexOf("/");
|
|
1189
|
-
if (slashIndex !== -1) {
|
|
1190
|
-
const maybeProvider = cliModel.substring(0, slashIndex);
|
|
1191
|
-
const canonical = providerMap.get(maybeProvider.toLowerCase());
|
|
1192
|
-
if (canonical) {
|
|
1193
|
-
provider = canonical;
|
|
1194
|
-
pattern = cliModel.substring(slashIndex + 1);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
} else {
|
|
1198
|
-
const prefix = `${provider}/`;
|
|
1199
|
-
if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
1200
|
-
pattern = cliModel.substring(prefix.length);
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
if (provider) {
|
|
1205
|
-
const exactProviderMatch = resolveProviderModelReference(provider, pattern, availableModels);
|
|
1206
|
-
if (exactProviderMatch) {
|
|
1207
|
-
return {
|
|
1208
|
-
model: exactProviderMatch,
|
|
1209
|
-
selector: formatModelString(exactProviderMatch),
|
|
1210
|
-
warning: undefined,
|
|
1211
|
-
thinkingLevel: undefined,
|
|
1212
|
-
error: undefined,
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
|
|
1218
|
-
const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, preferences, {
|
|
1219
|
-
allowInvalidThinkingSelectorFallback: false,
|
|
1220
|
-
modelRegistry,
|
|
1221
|
-
});
|
|
1195
|
+
const modelInput = cliModel.trim();
|
|
1196
|
+
const providerPrefix = provider ? `${provider}/` : "";
|
|
1197
|
+
const pattern =
|
|
1198
|
+
provider && modelInput.toLowerCase().startsWith(providerPrefix.toLowerCase())
|
|
1199
|
+
? modelInput.slice(providerPrefix.length)
|
|
1200
|
+
: modelInput;
|
|
1201
|
+
const candidates = provider
|
|
1202
|
+
? availableModels.filter(model => model.provider.toLowerCase() === provider.toLowerCase())
|
|
1203
|
+
: availableModels;
|
|
1204
|
+
const selectorInput = provider ? `${provider}/${pattern}` : pattern;
|
|
1205
|
+
const exactProviderResolved = provider
|
|
1206
|
+
? resolveSelector(selectorInput, candidates, {
|
|
1207
|
+
allowInvalidThinkingSelectorFallback: false,
|
|
1208
|
+
preferences,
|
|
1209
|
+
})
|
|
1210
|
+
: undefined;
|
|
1211
|
+
const resolved = exactProviderResolved?.model
|
|
1212
|
+
? exactProviderResolved
|
|
1213
|
+
: resolveSelector(provider ? pattern : selectorInput, candidates, {
|
|
1214
|
+
allowInvalidThinkingSelectorFallback: false,
|
|
1215
|
+
modelRegistry: provider ? undefined : modelRegistry,
|
|
1216
|
+
preferences,
|
|
1217
|
+
});
|
|
1222
1218
|
|
|
1223
|
-
if (!model) {
|
|
1224
|
-
const display = provider ? `${provider}/${pattern}` : cliModel;
|
|
1219
|
+
if (!resolved.model) {
|
|
1225
1220
|
return {
|
|
1226
1221
|
model: undefined,
|
|
1227
1222
|
selector: undefined,
|
|
1228
1223
|
thinkingLevel: undefined,
|
|
1229
|
-
warning,
|
|
1230
|
-
error: `Model "${
|
|
1224
|
+
warning: resolved.warning,
|
|
1225
|
+
error: `Model "${selectorInput}" not found. Use --list-models to see available models.`,
|
|
1231
1226
|
};
|
|
1232
1227
|
}
|
|
1233
1228
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
? pattern.substring(0, lastColonIndex)
|
|
1240
|
-
: pattern;
|
|
1241
|
-
if (!canonicalCandidate.includes("/")) {
|
|
1242
|
-
const canonicalResolved = modelRegistry.resolveCanonicalModel?.(canonicalCandidate, { availableOnly: false });
|
|
1243
|
-
if (canonicalResolved && canonicalResolved.provider === model.provider && canonicalResolved.id === model.id) {
|
|
1244
|
-
selector = modelRegistry.getCanonicalId?.(canonicalResolved) ?? canonicalCandidate;
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1229
|
+
const suffix = splitSelectorThinkingSuffix(selectorInput);
|
|
1230
|
+
const canonicalSelector = suffix.selector.includes("/") ? undefined : suffix.selector;
|
|
1231
|
+
const canonicalModel = canonicalSelector
|
|
1232
|
+
? modelRegistry.resolveCanonicalModel?.(canonicalSelector, { availableOnly: false, candidates: availableModels })
|
|
1233
|
+
: undefined;
|
|
1249
1234
|
return {
|
|
1250
|
-
model,
|
|
1251
|
-
selector
|
|
1252
|
-
|
|
1253
|
-
|
|
1235
|
+
model: resolved.model,
|
|
1236
|
+
selector:
|
|
1237
|
+
canonicalModel &&
|
|
1238
|
+
canonicalModel.provider === resolved.model.provider &&
|
|
1239
|
+
canonicalModel.id === resolved.model.id
|
|
1240
|
+
? (modelRegistry.getCanonicalId?.(canonicalModel) ?? canonicalSelector)
|
|
1241
|
+
: formatModelString(resolved.model),
|
|
1242
|
+
thinkingLevel: resolved.thinkingLevel,
|
|
1243
|
+
warning: resolved.warning,
|
|
1254
1244
|
error: undefined,
|
|
1255
1245
|
};
|
|
1256
1246
|
}
|
|
@@ -1295,7 +1285,8 @@ export async function findInitialModel(options: {
|
|
|
1295
1285
|
|
|
1296
1286
|
// 1. CLI args take priority
|
|
1297
1287
|
if (cliProvider && cliModel) {
|
|
1298
|
-
const found =
|
|
1288
|
+
const found = resolveProviderModelReference(cliProvider, cliModel, modelRegistry.getAvailable());
|
|
1289
|
+
|
|
1299
1290
|
if (!found) {
|
|
1300
1291
|
console.error(chalk.red(`Model ${cliProvider}/${cliModel} not found`));
|
|
1301
1292
|
process.exit(1);
|
|
@@ -1319,7 +1310,8 @@ export async function findInitialModel(options: {
|
|
|
1319
1310
|
|
|
1320
1311
|
// 3. Try saved default from settings
|
|
1321
1312
|
if (defaultProvider && defaultModelId) {
|
|
1322
|
-
const found =
|
|
1313
|
+
const found = resolveProviderModelReference(defaultProvider, defaultModelId, modelRegistry.getAvailable());
|
|
1314
|
+
|
|
1323
1315
|
if (found) {
|
|
1324
1316
|
model = found;
|
|
1325
1317
|
thinkingLevel = resolveThinkingLevelForModel(found, defaultThinkingSelector);
|
|
@@ -1358,7 +1350,7 @@ export async function restoreModelFromSession(
|
|
|
1358
1350
|
shouldPrintMessages: boolean,
|
|
1359
1351
|
modelRegistry: RestorableModelRegistry,
|
|
1360
1352
|
): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
|
|
1361
|
-
const restoredModel =
|
|
1353
|
+
const restoredModel = resolveProviderModelReference(savedProvider, savedModelId, modelRegistry.getAvailable());
|
|
1362
1354
|
|
|
1363
1355
|
// Check if restored model exists and has a valid API key
|
|
1364
1356
|
const hasApiKey = restoredModel ? !!(await modelRegistry.getApiKey(restoredModel)) : false;
|