@prometheus-ai/agent 0.5.4 → 0.5.8
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 +30 -0
- package/dist/cli.js +25110 -0
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/async/job-manager.d.ts +33 -0
- package/dist/types/autolearn/controller.d.ts +25 -0
- package/dist/types/autolearn/managed-skills.d.ts +45 -0
- package/dist/types/autoresearch/state.d.ts +1 -1
- package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/run-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/update-notes.d.ts +1 -1
- package/dist/types/autoresearch/types.d.ts +1 -1
- package/dist/types/capability/context-file.d.ts +0 -13
- package/dist/types/capability/mcp.d.ts +1 -0
- package/dist/types/capability/rule-buckets.d.ts +1 -1
- package/dist/types/capability/rule.d.ts +6 -1
- package/dist/types/capability/types.d.ts +0 -4
- package/dist/types/cli/args.d.ts +23 -3
- package/dist/types/cli/bench-cli.d.ts +78 -0
- package/dist/types/cli/claude-trace-cli.d.ts +7 -0
- package/dist/types/cli/dry-balance-cli.d.ts +16 -2
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +55 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/cli/grievances-cli.d.ts +1 -1
- package/dist/types/cli/list-models.d.ts +6 -14
- package/dist/types/cli/models-cli.d.ts +49 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/cli/setup-model-picker.d.ts +14 -0
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +13 -40
- package/dist/types/cli/usage-cli.d.ts +81 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/collab/crypto.d.ts +7 -0
- package/dist/types/collab/guest.d.ts +37 -0
- package/dist/types/collab/host.d.ts +29 -0
- package/dist/types/collab/protocol.d.ts +119 -0
- package/dist/types/collab/relay-client.d.ts +22 -0
- package/dist/types/commands/bench.d.ts +29 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/commands/install.d.ts +1 -1
- package/dist/types/commands/join.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +8 -4
- package/dist/types/commands/models.d.ts +33 -0
- package/dist/types/commands/read.d.ts +1 -1
- package/dist/types/commands/say.d.ts +24 -0
- package/dist/types/commands/token.d.ts +25 -0
- package/dist/types/commands/usage.d.ts +34 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-overview.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +1 -1
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +1 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +1 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +1 -1
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +3 -3
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/commit/shared-llm.d.ts +1 -1
- package/dist/types/config/api-key-resolver.d.ts +43 -0
- package/dist/types/config/append-only-context-mode.d.ts +2 -1
- package/dist/types/config/keybindings.d.ts +12 -7
- package/dist/types/config/model-discovery.d.ts +57 -0
- package/dist/types/config/model-equivalence.d.ts +1 -1
- package/dist/types/config/model-registry.d.ts +86 -222
- package/dist/types/config/model-resolver.d.ts +43 -12
- package/dist/types/config/model-roles.d.ts +29 -0
- package/dist/types/config/models-config-schema.d.ts +536 -43
- package/dist/types/config/models-config.d.ts +391 -0
- package/dist/types/config/settings-schema.d.ts +1202 -324
- package/dist/types/config/settings.d.ts +15 -3
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/log-viewer.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +1 -1
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/debug/terminal-info.d.ts +0 -1
- package/dist/types/discovery/at-imports.d.ts +15 -0
- package/dist/types/discovery/prometheus-extension-roots.d.ts +7 -7
- package/dist/types/edit/diff.d.ts +3 -2
- package/dist/types/edit/file-snapshot-store.d.ts +18 -0
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/edit/hashline/params.d.ts +1 -1
- package/dist/types/edit/index.d.ts +0 -1
- package/dist/types/edit/modes/apply-patch.d.ts +1 -1
- package/dist/types/edit/modes/patch.d.ts +1 -1
- package/dist/types/edit/modes/replace.d.ts +1 -1
- package/dist/types/edit/renderer.d.ts +1 -0
- package/dist/types/eval/__tests__/completion-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
- package/dist/types/eval/__tests__/js-context-manager.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +7 -2
- package/dist/types/eval/bridge-timeout.d.ts +1 -1
- package/dist/types/eval/completion-bridge.d.ts +25 -0
- package/dist/types/eval/idle-timeout.d.ts +1 -5
- package/dist/types/eval/js/context-manager.d.ts +1 -0
- package/dist/types/eval/js/executor.d.ts +2 -0
- package/dist/types/eval/js/index.d.ts +1 -1
- package/dist/types/eval/js/shared/helpers.d.ts +7 -1
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
- package/dist/types/eval/js/shared/runtime.d.ts +6 -1
- package/dist/types/eval/js/worker-protocol.d.ts +6 -0
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/eval/py/executor.d.ts +12 -0
- package/dist/types/eval/py/index.d.ts +1 -1
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exa/index.d.ts +1 -19
- package/dist/types/exa/mcp-client.d.ts +10 -3
- package/dist/types/exa/types.d.ts +0 -83
- package/dist/types/exec/bash-executor.d.ts +7 -0
- package/dist/types/export/custom-share.d.ts +1 -2
- package/dist/types/export/html/index.d.ts +39 -0
- package/dist/types/export/html/template-js.d.ts +2 -0
- package/dist/types/export/share.d.ts +61 -0
- package/dist/types/export/ttsr.d.ts +14 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +9 -4
- package/dist/types/extensibility/custom-tools/loader.d.ts +30 -4
- package/dist/types/extensibility/custom-tools/types.d.ts +16 -8
- package/dist/types/extensibility/extensions/index.d.ts +1 -1
- package/dist/types/extensibility/extensions/loader.d.ts +20 -1
- package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
- package/dist/types/extensibility/extensions/runner.d.ts +5 -2
- package/dist/types/extensibility/extensions/types.d.ts +72 -11
- package/dist/types/extensibility/hooks/index.d.ts +2 -1
- package/dist/types/extensibility/hooks/loader.d.ts +1 -1
- package/dist/types/extensibility/hooks/types.d.ts +11 -5
- package/dist/types/extensibility/{legacy-pi-ai-shim.d.ts → legacy-package-ai-shim.d.ts} +2 -2
- package/dist/types/extensibility/plugins/{legacy-pi-compat.d.ts → legacy-package-compat.d.ts} +20 -3
- package/dist/types/extensibility/plugins/loader.d.ts +11 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/extensibility/plugins/types.d.ts +2 -2
- package/dist/types/extensibility/shared-events.d.ts +3 -3
- package/dist/types/extensibility/skills.d.ts +10 -0
- package/dist/types/extensibility/slash-commands.d.ts +1 -11
- package/dist/types/goals/guided-setup.d.ts +18 -0
- package/dist/types/goals/state.d.ts +1 -1
- package/dist/types/goals/tools/goal-tool.d.ts +1 -1
- package/dist/types/hindsight/mental-models.d.ts +17 -8
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/local-protocol.d.ts +14 -2
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +79 -0
- package/dist/types/lib/xai-http.d.ts +1 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/config.d.ts +2 -2
- package/dist/types/lsp/edits.d.ts +9 -0
- package/dist/types/lsp/format-options.d.ts +32 -0
- package/dist/types/lsp/index.d.ts +2 -7
- package/dist/types/lsp/types.d.ts +13 -1
- package/dist/types/lsp/utils.d.ts +6 -2
- package/dist/types/main.d.ts +23 -8
- package/dist/types/mcp/json-rpc.d.ts +5 -0
- package/dist/types/mcp/manager.d.ts +8 -0
- package/dist/types/mcp/oauth-discovery.d.ts +6 -1
- package/dist/types/mcp/oauth-flow.d.ts +13 -3
- package/dist/types/mcp/startup-events.d.ts +11 -0
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/mcp/transports/stdio.d.ts +13 -0
- package/dist/types/mcp/types.d.ts +2 -0
- package/dist/types/memories/index.d.ts +7 -15
- package/dist/types/memories/storage.d.ts +0 -10
- package/dist/types/memory-backend/index.d.ts +3 -1
- package/dist/types/memory-backend/local-backend.d.ts +4 -3
- package/dist/types/memory-backend/resolve.d.ts +2 -2
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +67 -2
- package/dist/types/mnemopi/config.d.ts +31 -1
- package/dist/types/mnemopi/state.d.ts +40 -2
- package/dist/types/modes/acp/acp-agent.d.ts +1 -2
- package/dist/types/modes/components/agent-dashboard.d.ts +17 -1
- package/dist/types/modes/components/agent-hub.d.ts +82 -0
- package/dist/types/modes/components/assistant-message.d.ts +5 -12
- package/dist/types/modes/components/bash-execution.d.ts +1 -1
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/collab-prompt-message.d.ts +10 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +25 -5
- package/dist/types/modes/components/copy-selector.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +49 -2
- package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
- package/dist/types/modes/components/dynamic-border.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
- package/dist/types/modes/components/footer.d.ts +1 -1
- package/dist/types/modes/components/hook-editor.d.ts +5 -0
- package/dist/types/modes/components/hook-input.d.ts +4 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -7
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/late-diagnostics-message.d.ts +20 -0
- package/dist/types/modes/components/logout-account-selector.d.ts +8 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -1
- package/dist/types/modes/components/oauth-selector.d.ts +10 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +61 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +8 -0
- package/dist/types/modes/components/reset-usage-selector.d.ts +12 -0
- package/dist/types/modes/components/segment-track.d.ts +11 -6
- package/dist/types/modes/components/session-selector.d.ts +18 -9
- package/dist/types/modes/components/settings-defs.d.ts +9 -2
- package/dist/types/modes/components/settings-selector.d.ts +17 -4
- package/dist/types/modes/components/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/modes/components/status-line/component.d.ts +61 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +47 -3
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +49 -2
- package/dist/types/modes/components/transcript-container.d.ts +76 -26
- package/dist/types/modes/components/tree-selector.d.ts +2 -2
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/usage-row.d.ts +3 -0
- package/dist/types/modes/components/user-message-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/components/visual-truncate.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +12 -2
- package/dist/types/modes/controllers/command-controller.d.ts +3 -2
- package/dist/types/modes/controllers/event-controller.d.ts +7 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +25 -3
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +8 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +5 -2
- package/dist/types/modes/controllers/session-focus-controller.d.ts +31 -0
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +43 -0
- package/dist/types/modes/gradient-highlight.d.ts +9 -4
- package/dist/types/modes/image-references.d.ts +14 -3
- package/dist/types/modes/index.d.ts +8 -7
- package/dist/types/modes/interactive-mode.d.ts +92 -16
- package/dist/types/modes/magic-keywords.d.ts +14 -2
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +48 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +67 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +113 -1
- package/dist/types/modes/runtime-init.d.ts +4 -0
- package/dist/types/modes/session-observer-registry.d.ts +9 -0
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +7 -2
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +4 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +11 -2
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +6 -2
- package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +42 -7
- package/dist/types/modes/types.d.ts +62 -13
- package/dist/types/modes/utils/context-usage.d.ts +6 -1
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +4 -4
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +33 -5
- package/dist/types/sdk.d.ts +46 -4
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +9 -3
- package/dist/types/session/agent-session.d.ts +136 -66
- package/dist/types/session/agent-storage.d.ts +2 -1
- package/dist/types/session/auth-broker-config.d.ts +4 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/codex-auto-reset.d.ts +111 -0
- package/dist/types/session/indexed-session-storage.d.ts +3 -3
- package/dist/types/session/messages.d.ts +26 -15
- package/dist/types/session/session-context.d.ts +39 -0
- package/dist/types/session/session-entries.d.ts +159 -0
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-listing.d.ts +69 -0
- package/dist/types/session/session-loader.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +107 -440
- package/dist/types/session/session-migrations.d.ts +12 -0
- package/dist/types/session/session-paths.d.ts +25 -0
- package/dist/types/session/session-persistence.d.ts +8 -0
- package/dist/types/session/session-storage.d.ts +11 -7
- package/dist/types/session/snapcompact-inline.d.ts +145 -0
- package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
- package/dist/types/session/streaming-output.d.ts +46 -0
- package/dist/types/session/tool-choice-queue.d.ts +6 -6
- package/dist/types/session/yield-queue.d.ts +10 -1
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/available-commands.d.ts +34 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +10 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +14 -0
- package/dist/types/slash-commands/helpers/logout.d.ts +15 -0
- package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
- package/dist/types/slash-commands/types.d.ts +5 -9
- package/dist/types/ssh/connection-manager.d.ts +8 -0
- package/dist/types/stt/asr-client.d.ts +90 -0
- package/dist/types/stt/asr-protocol.d.ts +97 -0
- package/dist/types/stt/asr-worker.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +38 -0
- package/dist/types/stt/endpointer.d.ts +59 -0
- package/dist/types/stt/index.d.ts +5 -1
- package/dist/types/stt/models.d.ts +120 -0
- package/dist/types/stt/recorder.d.ts +17 -0
- package/dist/types/stt/stt-controller.d.ts +6 -0
- package/dist/types/stt/transcriber.d.ts +5 -7
- package/dist/types/stt/wav.d.ts +29 -0
- package/dist/types/system-prompt.d.ts +9 -1
- package/dist/types/task/commands.d.ts +1 -1
- package/dist/types/task/discovery.d.ts +1 -2
- package/dist/types/task/executor.d.ts +61 -2
- package/dist/types/task/index.d.ts +37 -6
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/parallel.d.ts +2 -2
- package/dist/types/task/prometheus-command.d.ts +2 -2
- package/dist/types/task/render.d.ts +20 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +109 -52
- package/dist/types/task/worktree.d.ts +2 -0
- package/dist/types/telemetry-export.d.ts +2 -2
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/models.d.ts +1 -1
- package/dist/types/tiny/title-client.d.ts +12 -1
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ask.d.ts +6 -1
- package/dist/types/tools/ast-edit.d.ts +4 -1
- package/dist/types/tools/ast-grep.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +5 -2
- package/dist/types/tools/browser/attach.d.ts +4 -4
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +202 -0
- package/dist/types/tools/browser/cmux/rpc.d.ts +70 -0
- package/dist/types/tools/browser/cmux/socket-client.d.ts +19 -0
- package/dist/types/tools/browser/registry.d.ts +17 -3
- package/dist/types/tools/browser/render.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +16 -4
- package/dist/types/tools/browser/tab-worker.d.ts +18 -1
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/checkpoint.d.ts +1 -1
- package/dist/types/tools/conflict-detect.d.ts +16 -0
- package/dist/types/tools/debug.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -8
- package/dist/types/tools/eval.d.ts +9 -1
- package/dist/types/tools/fetch.d.ts +17 -8
- package/dist/types/tools/find.d.ts +1 -8
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/gh.d.ts +4 -1
- package/dist/types/tools/github-cache.d.ts +19 -0
- package/dist/types/tools/grouped-file-output.d.ts +46 -12
- package/dist/types/tools/image-gen.d.ts +1 -1
- package/dist/types/tools/index.d.ts +89 -8
- package/dist/types/tools/inspect-image.d.ts +1 -1
- package/dist/types/tools/irc.d.ts +79 -39
- package/dist/types/tools/job.d.ts +8 -2
- package/dist/types/tools/learn.d.ts +51 -0
- package/dist/types/tools/manage-skill.d.ts +40 -0
- package/dist/types/tools/memory-edit.d.ts +2 -2
- package/dist/types/tools/memory-recall.d.ts +1 -1
- package/dist/types/tools/memory-reflect.d.ts +1 -1
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/memory-retain.d.ts +1 -1
- package/dist/types/tools/path-utils.d.ts +17 -5
- package/dist/types/tools/plan-mode-guard.d.ts +18 -9
- package/dist/types/tools/read.d.ts +3 -2
- package/dist/types/tools/render-mermaid.d.ts +1 -1
- package/dist/types/tools/render-utils.d.ts +47 -27
- package/dist/types/tools/renderers.d.ts +10 -2
- package/dist/types/tools/report-tool-issue.d.ts +6 -1
- package/dist/types/tools/resolve.d.ts +1 -1
- package/dist/types/tools/review.d.ts +1 -1
- package/dist/types/tools/search-tool-bm25.d.ts +1 -1
- package/dist/types/tools/search.d.ts +7 -3
- package/dist/types/tools/sqlite-reader.d.ts +4 -0
- package/dist/types/tools/ssh.d.ts +2 -1
- package/dist/types/tools/todo.d.ts +7 -15
- package/dist/types/tools/tool-result.d.ts +2 -0
- package/dist/types/tools/tool-timeouts.d.ts +1 -1
- package/dist/types/tools/tts.d.ts +26 -1
- package/dist/types/tools/write.d.ts +6 -3
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tts/downloader.d.ts +20 -0
- package/dist/types/tts/index.d.ts +8 -0
- package/dist/types/tts/models.d.ts +82 -0
- package/dist/types/tts/player.d.ts +32 -0
- package/dist/types/tts/runtime.d.ts +6 -0
- package/dist/types/tts/streaming-player.d.ts +41 -0
- package/dist/types/tts/tts-client.d.ts +93 -0
- package/dist/types/tts/tts-protocol.d.ts +95 -0
- package/dist/types/tts/tts-worker.d.ts +2 -0
- package/dist/types/tts/vocalizer.d.ts +41 -0
- package/dist/types/tts/wav.d.ts +8 -0
- package/dist/types/tui/code-cell.d.ts +0 -2
- package/dist/types/tui/hyperlink.d.ts +13 -7
- package/dist/types/tui/output-block.d.ts +16 -22
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/block-context.d.ts +35 -0
- package/dist/types/utils/changelog.d.ts +8 -0
- package/dist/types/utils/clipboard.d.ts +4 -3
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/utils/file-mentions.d.ts +7 -0
- package/dist/types/utils/git.d.ts +22 -3
- package/dist/types/utils/image-loading.d.ts +30 -1
- package/dist/types/utils/session-color.d.ts +15 -3
- package/dist/types/utils/thinking-display.d.ts +17 -0
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/dist/types/utils/tool-choice.d.ts +8 -0
- package/dist/types/utils/tools-manager.d.ts +2 -1
- package/dist/types/web/kagi.d.ts +2 -2
- package/dist/types/web/parallel.d.ts +3 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
- package/dist/types/web/scrapers/types.d.ts +12 -0
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/providers/anthropic.d.ts +2 -1
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/brave.d.ts +2 -1
- package/dist/types/web/search/providers/codex.d.ts +2 -1
- package/dist/types/web/search/providers/exa.d.ts +2 -1
- package/dist/types/web/search/providers/gemini.d.ts +10 -6
- package/dist/types/web/search/providers/jina.d.ts +7 -2
- package/dist/types/web/search/providers/kagi.d.ts +7 -2
- package/dist/types/web/search/providers/kimi.d.ts +7 -2
- package/dist/types/web/search/providers/parallel.d.ts +2 -1
- package/dist/types/web/search/providers/perplexity.d.ts +10 -2
- package/dist/types/web/search/providers/searxng.d.ts +2 -1
- package/dist/types/web/search/providers/synthetic.d.ts +7 -3
- package/dist/types/web/search/providers/tavily.d.ts +2 -1
- package/dist/types/web/search/providers/zai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/examples/extensions/api-demo.ts +2 -2
- package/package.json +41 -15
- package/scripts/bench-guard.ts +71 -0
- package/scripts/build-binary.ts +24 -25
- package/scripts/bundle-dist.ts +97 -0
- package/scripts/generate-share-viewer.ts +34 -0
- package/scripts/prometheus +42 -0
- package/scripts/prometheus.ts +20 -0
- package/src/async/index.ts +0 -1
- package/src/async/job-manager.ts +106 -3
- package/src/auto-thinking/classifier.ts +2 -1
- package/src/autolearn/controller.ts +139 -0
- package/src/autolearn/managed-skills.ts +257 -0
- package/src/autoresearch/dashboard.ts +1 -1
- package/src/autoresearch/prompt-setup.md +6 -6
- package/src/autoresearch/prompt.md +6 -6
- package/src/autoresearch/state.ts +1 -1
- package/src/autoresearch/storage.ts +2 -1
- package/src/autoresearch/tools/init-experiment.ts +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +1 -1
- package/src/autoresearch/types.ts +1 -1
- package/src/capability/context-file.ts +0 -14
- package/src/capability/fs.ts +10 -0
- package/src/capability/index.ts +1 -6
- package/src/capability/mcp.ts +1 -0
- package/src/capability/rule-buckets.ts +4 -2
- package/src/capability/rule.ts +10 -1
- package/src/capability/types.ts +0 -4
- package/src/cli/args.ts +66 -13
- package/src/cli/auth-broker-cli.ts +6 -7
- package/src/cli/auth-gateway-cli.ts +8 -9
- package/src/cli/bench-cli.ts +437 -0
- package/src/cli/claude-trace-cli.ts +28 -50
- package/src/cli/completion-gen.ts +28 -28
- package/src/cli/dry-balance-cli.ts +56 -23
- package/src/cli/gallery-cli.ts +231 -0
- package/src/cli/gallery-fixtures/agentic.ts +407 -0
- package/src/cli/gallery-fixtures/codeintel.ts +187 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +220 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +57 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli/grievances-cli.ts +1 -1
- package/src/cli/list-models.ts +16 -174
- package/src/cli/models-cli.ts +429 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +148 -47
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/cli/update-cli.ts +144 -272
- package/src/cli/usage-cli.ts +774 -0
- package/src/cli-commands.ts +36 -0
- package/src/cli.ts +141 -32
- package/src/collab/crypto.ts +63 -0
- package/src/collab/guest.ts +451 -0
- package/src/collab/host.ts +565 -0
- package/src/collab/protocol.ts +241 -0
- package/src/collab/relay-client.ts +216 -0
- package/src/commands/bench.ts +42 -0
- package/src/commands/complete.ts +1 -1
- package/src/commands/gallery.ts +52 -0
- package/src/commands/install.ts +1 -1
- package/src/commands/join.ts +39 -0
- package/src/commands/launch.ts +8 -4
- package/src/commands/models.ts +61 -0
- package/src/commands/read.ts +6 -3
- package/src/commands/say.ts +102 -0
- package/src/commands/setup.ts +1 -1
- package/src/commands/token.ts +89 -0
- package/src/commands/usage.ts +43 -0
- package/src/commit/agentic/agent.ts +2 -1
- package/src/commit/agentic/tools/analyze-file.ts +42 -20
- package/src/commit/agentic/tools/git-file-diff.ts +1 -1
- package/src/commit/agentic/tools/git-hunk.ts +1 -1
- package/src/commit/agentic/tools/git-overview.ts +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +1 -1
- package/src/commit/agentic/tools/propose-commit.ts +1 -1
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -1
- package/src/commit/agentic/tools/split-commit.ts +9 -2
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +3 -3
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +35 -12
- package/src/commit/pipeline.ts +4 -4
- package/src/commit/shared-llm.ts +1 -1
- package/src/config/api-key-resolver.ts +67 -0
- package/src/config/append-only-context-mode.ts +6 -12
- package/src/config/keybindings.ts +9 -4
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-discovery.ts +574 -0
- package/src/config/model-equivalence.ts +5 -4
- package/src/config/model-registry.ts +659 -1093
- package/src/config/model-resolver.ts +374 -174
- package/src/config/model-roles.ts +88 -0
- package/src/config/models-config-schema.ts +61 -9
- package/src/config/models-config.ts +130 -0
- package/src/config/settings-schema.ts +1441 -387
- package/src/config/settings.ts +261 -69
- package/src/dap/client.ts +138 -53
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +263 -161
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +50 -60
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/protocol-probe.ts +1 -1
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/raw-sse.ts +1 -1
- package/src/debug/report-bundle.ts +9 -0
- package/src/debug/terminal-info.ts +0 -3
- package/src/discovery/agents-md.ts +25 -21
- package/src/discovery/agents.ts +9 -15
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-rules/index.ts +4 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/builtin.ts +45 -23
- package/src/discovery/claude-plugins.ts +44 -5
- package/src/discovery/helpers.ts +50 -9
- package/src/discovery/prometheus-extension-roots.ts +10 -10
- package/src/discovery/prometheus-plugins.ts +10 -10
- package/src/edit/diff.ts +191 -4
- package/src/edit/file-snapshot-store.ts +34 -1
- package/src/edit/hashline/block-resolver.ts +20 -1
- package/src/edit/hashline/diff.ts +123 -2
- package/src/edit/hashline/execute.ts +60 -4
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/hashline/params.ts +1 -1
- package/src/edit/index.ts +47 -18
- package/src/edit/modes/apply-patch.ts +1 -1
- package/src/edit/modes/patch.ts +59 -3
- package/src/edit/modes/replace.ts +58 -24
- package/src/edit/notebook.ts +22 -2
- package/src/edit/renderer.ts +315 -151
- package/src/eval/__tests__/agent-bridge.test.ts +105 -39
- package/src/eval/__tests__/budget-bridge.test.ts +1 -1
- package/src/eval/__tests__/completion-bridge.test.ts +412 -0
- package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
- package/src/eval/__tests__/js-context-manager.test.ts +241 -0
- package/src/eval/__tests__/llm-bridge.test.ts +6 -4
- package/src/eval/__tests__/shared-executors.test.ts +34 -92
- package/src/eval/agent-bridge.ts +39 -23
- package/src/eval/backend.ts +15 -2
- package/src/eval/bridge-timeout.ts +1 -1
- package/src/eval/completion-bridge.ts +203 -0
- package/src/eval/idle-timeout.ts +3 -10
- package/src/eval/js/context-manager.ts +108 -31
- package/src/eval/js/executor.ts +9 -2
- package/src/eval/js/index.ts +7 -3
- package/src/eval/js/shared/helpers.ts +59 -13
- package/src/eval/js/shared/local-module-loader.ts +2 -2
- package/src/eval/js/shared/prelude.txt +167 -30
- package/src/eval/js/shared/rewrite-imports.ts +58 -34
- package/src/eval/js/shared/runtime.ts +24 -16
- package/src/eval/js/tool-bridge.ts +4 -0
- package/src/eval/js/worker-core.ts +1 -0
- package/src/eval/js/worker-entry.ts +6 -0
- package/src/eval/js/worker-protocol.ts +6 -0
- package/src/eval/llm-bridge.ts +2 -1
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +70 -26
- package/src/eval/py/index.ts +13 -4
- package/src/eval/py/kernel.ts +48 -9
- package/src/eval/py/prelude.py +73 -24
- package/src/eval/py/runner.py +133 -28
- package/src/eval/py/runtime.ts +38 -1
- package/src/exa/index.ts +1 -26
- package/src/exa/mcp-client.ts +10 -10
- package/src/exa/types.ts +0 -97
- package/src/exec/bash-executor.ts +104 -7
- package/src/export/custom-share.ts +1 -1
- package/src/export/html/index.ts +119 -17
- package/src/export/html/share-loader.js +102 -0
- package/src/export/html/template-js.ts +6 -0
- package/src/export/html/template.css +745 -459
- package/src/export/html/template.css.d.ts +2 -0
- package/src/export/html/template.html +6 -3
- package/src/export/html/template.js +277 -891
- package/src/export/html/tool-views.generated.d.ts +2 -0
- package/src/export/html/tool-views.generated.js +38 -0
- package/src/export/share.ts +269 -0
- package/src/export/ttsr.ts +122 -1
- package/src/extensibility/custom-commands/loader.ts +7 -4
- package/src/extensibility/custom-commands/types.ts +9 -4
- package/src/extensibility/custom-tools/loader.ts +51 -23
- package/src/extensibility/custom-tools/types.ts +16 -8
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/loader.ts +70 -20
- package/src/extensibility/extensions/model-api.ts +41 -0
- package/src/extensibility/extensions/runner.ts +12 -2
- package/src/extensibility/extensions/types.ts +83 -11
- package/src/extensibility/extensions/wrapper.ts +41 -5
- package/src/extensibility/hooks/index.ts +2 -1
- package/src/extensibility/hooks/loader.ts +6 -3
- package/src/extensibility/hooks/types.ts +11 -5
- package/src/extensibility/{legacy-pi-ai-shim.ts → legacy-package-ai-shim.ts} +2 -2
- package/src/extensibility/plugins/doctor.ts +1 -2
- package/src/extensibility/plugins/installer.ts +2 -2
- package/src/extensibility/plugins/{legacy-pi-compat.ts → legacy-package-compat.ts} +165 -77
- package/src/extensibility/plugins/loader.ts +34 -23
- package/src/extensibility/plugins/manager.ts +226 -95
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/extensibility/plugins/types.ts +3 -3
- package/src/extensibility/shared-events.ts +3 -3
- package/src/extensibility/skills.ts +113 -9
- package/src/extensibility/slash-commands.ts +1 -97
- package/src/goals/guided-setup.ts +133 -0
- package/src/goals/state.ts +1 -1
- package/src/goals/tools/goal-tool.ts +38 -28
- package/src/hindsight/bank.ts +17 -2
- package/src/hindsight/client.ts +27 -2
- package/src/hindsight/mental-models.ts +59 -12
- package/src/hindsight/state.ts +12 -3
- package/src/hindsight/transcript.ts +1 -1
- package/src/index.ts +5 -0
- package/src/internal-urls/artifact-protocol.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +9 -7
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/issue-pr-protocol.ts +22 -9
- package/src/internal-urls/local-protocol.ts +42 -7
- package/src/internal-urls/memory-protocol.ts +4 -31
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +303 -0
- package/src/lib/xai-http.ts +3 -3
- package/src/lsp/client.ts +245 -104
- package/src/lsp/clients/biome-client.ts +101 -39
- package/src/lsp/clients/lsp-linter-client.ts +2 -10
- package/src/lsp/config.ts +15 -5
- package/src/lsp/defaults.json +6 -0
- package/src/lsp/edits.ts +143 -95
- package/src/lsp/format-options.ts +119 -0
- package/src/lsp/index.ts +233 -93
- package/src/lsp/render.ts +11 -35
- package/src/lsp/types.ts +13 -1
- package/src/lsp/utils.ts +31 -12
- package/src/main.ts +396 -216
- package/src/mcp/config-writer.ts +7 -3
- package/src/mcp/json-rpc.ts +35 -5
- package/src/mcp/manager.ts +31 -16
- package/src/mcp/oauth-discovery.ts +34 -4
- package/src/mcp/oauth-flow.ts +61 -8
- package/src/mcp/render.ts +7 -1
- package/src/mcp/startup-events.ts +21 -0
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/mcp/transports/stdio.ts +224 -4
- package/src/mcp/types.ts +2 -0
- package/src/memories/index.ts +174 -1128
- package/src/memories/storage.ts +2 -41
- package/src/memory-backend/index.ts +14 -1
- package/src/memory-backend/local-backend.ts +18 -3
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/resolve.ts +4 -6
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +82 -2
- package/src/mnemopi/backend.ts +220 -28
- package/src/mnemopi/config.ts +138 -33
- package/src/mnemopi/state.ts +91 -11
- package/src/modes/acp/acp-agent.ts +149 -142
- package/src/modes/acp/acp-event-mapper.ts +5 -1
- package/src/modes/components/agent-dashboard.ts +17 -11
- package/src/modes/components/agent-hub.ts +1346 -0
- package/src/modes/components/assistant-message.ts +190 -80
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/collab-prompt-message.ts +30 -0
- package/src/modes/components/compaction-summary-message.ts +168 -33
- package/src/modes/components/copy-selector.ts +2 -45
- package/src/modes/components/custom-editor.test.ts +96 -0
- package/src/modes/components/custom-editor.ts +405 -118
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/diff.ts +13 -2
- package/src/modes/components/dynamic-border.ts +12 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/extensions/extension-dashboard.ts +8 -5
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +7 -3
- package/src/modes/components/extensions/state-manager.ts +36 -41
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +8 -0
- package/src/modes/components/hook-input.ts +8 -0
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/hook-selector.ts +6 -7
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +14 -1
- package/src/modes/components/model-selector.ts +177 -75
- package/src/modes/components/oauth-selector.ts +102 -16
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +845 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/plugin-settings.ts +22 -5
- package/src/modes/components/read-tool-group.ts +442 -39
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/segment-track.ts +44 -7
- package/src/modes/components/session-selector.ts +97 -37
- package/src/modes/components/settings-defs.ts +28 -6
- package/src/modes/components/settings-selector.ts +541 -93
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/snapcompact-shape-preview-doc.md +11 -0
- package/src/modes/components/snapcompact-shape-preview.ts +193 -0
- package/src/modes/components/{status-line.ts → status-line/component.ts} +205 -168
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/presets.ts +3 -3
- package/src/modes/components/status-line/segments.ts +26 -7
- package/src/modes/components/status-line/types.ts +40 -9
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/tips.txt +7 -3
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +236 -103
- package/src/modes/components/transcript-container.ts +724 -99
- package/src/modes/components/tree-selector.ts +19 -4
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/usage-row.ts +18 -0
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +28 -12
- package/src/modes/components/visual-truncate.ts +1 -1
- package/src/modes/components/welcome.ts +80 -22
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +210 -180
- package/src/modes/controllers/event-controller.ts +352 -142
- package/src/modes/controllers/extension-ui-controller.ts +167 -208
- package/src/modes/controllers/input-controller.ts +778 -162
- package/src/modes/controllers/mcp-command-controller.ts +232 -80
- package/src/modes/controllers/selector-controller.ts +284 -145
- package/src/modes/controllers/session-focus-controller.ts +112 -0
- package/src/modes/controllers/ssh-command-controller.ts +2 -2
- package/src/modes/controllers/streaming-reveal.ts +295 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/controllers/tool-args-reveal.ts +174 -0
- package/src/modes/gradient-highlight.ts +21 -9
- package/src/modes/image-references.ts +33 -7
- package/src/modes/index.ts +8 -25
- package/src/modes/interactive-mode.ts +840 -186
- package/src/modes/magic-keywords.ts +28 -6
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +186 -3
- package/src/modes/rpc/rpc-mode.ts +318 -24
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +111 -2
- package/src/modes/runtime-init.ts +28 -3
- package/src/modes/session-observer-registry.ts +72 -3
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +16 -4
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +25 -7
- package/src/modes/setup-wizard/scenes/providers.ts +45 -12
- package/src/modes/setup-wizard/scenes/sign-in.ts +14 -13
- package/src/modes/setup-wizard/scenes/splash.ts +1 -1
- package/src/modes/setup-wizard/scenes/theme.ts +29 -2
- package/src/modes/setup-wizard/scenes/types.ts +11 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +26 -9
- package/src/modes/setup-wizard/wizard-overlay.ts +40 -3
- package/src/modes/shared.ts +2 -0
- package/src/modes/theme/defaults/dark-poimandres.json +1 -1
- package/src/modes/theme/defaults/light-poimandres.json +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +342 -82
- package/src/modes/types.ts +60 -18
- package/src/modes/utils/context-usage.ts +88 -8
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/hotkeys-markdown.ts +3 -2
- package/src/modes/utils/ui-helpers.ts +191 -110
- package/src/modes/workflow.ts +10 -10
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/priority.json +5 -1
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +3 -3
- package/src/prompts/agents/librarian.md +2 -3
- package/src/prompts/agents/oracle.md +2 -2
- package/src/prompts/agents/plan.md +6 -6
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +6 -5
- package/src/prompts/bench.md +12 -0
- package/src/prompts/ci-green-request.md +5 -7
- package/src/prompts/goals/goal-budget-limit.md +2 -2
- package/src/prompts/goals/goal-continuation.md +4 -4
- package/src/prompts/goals/goal-mode-active.md +1 -1
- package/src/prompts/goals/guided-goal-interview.md +8 -0
- package/src/prompts/goals/guided-goal-system.md +12 -0
- package/src/prompts/memories/consolidation.md +2 -7
- package/src/prompts/memories/consolidation_system.md +4 -0
- package/src/prompts/memories/identity_review.md +2 -2
- package/src/prompts/memories/read-path.md +11 -10
- package/src/prompts/memories/stage_one_system.md +2 -2
- package/src/prompts/review-custom-request.md +1 -1
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/auto-continue.md +1 -1
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge.md +3 -0
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/btw-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +13 -1
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +11 -6
- package/src/prompts/system/empty-stop-retry.md +4 -6
- package/src/prompts/system/irc-autoreply.md +6 -0
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/manual-continue.md +7 -0
- package/src/prompts/system/omfg-user.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +10 -10
- package/src/prompts/system/personalities/default.md +26 -0
- package/src/prompts/system/personalities/friendly.md +17 -0
- package/src/prompts/system/personalities/pragmatic.md +15 -0
- package/src/prompts/system/plan-mode-active.md +70 -77
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +4 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-system-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +7 -8
- package/src/prompts/system/system-prompt.md +28 -57
- package/src/prompts/system/tiny-title-system.md +1 -1
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system-marker.md +16 -0
- package/src/prompts/system/title-system.md +16 -3
- package/src/prompts/system/ttsr-tool-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +4 -4
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +2 -2
- package/src/prompts/tools/bash.md +16 -8
- package/src/prompts/tools/browser.md +33 -43
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/eval.md +31 -51
- package/src/prompts/tools/find.md +0 -1
- package/src/prompts/tools/github.md +8 -7
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/inspect-image-system.md +1 -1
- package/src/prompts/tools/irc.md +39 -31
- package/src/prompts/tools/job.md +2 -1
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/memory-edit.md +1 -1
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/read.md +31 -39
- package/src/prompts/tools/recall.md +1 -1
- package/src/prompts/tools/reflect.md +1 -1
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/replace.md +4 -10
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +1 -9
- package/src/prompts/tools/search.md +0 -1
- package/src/prompts/tools/ssh.md +0 -4
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +47 -31
- package/src/prompts/tools/todo.md +6 -3
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +46 -5
- package/src/sdk.ts +692 -219
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +40 -19
- package/src/session/agent-session.ts +1577 -806
- package/src/session/agent-storage.ts +18 -9
- package/src/session/auth-broker-config.ts +30 -1
- package/src/session/auth-storage.ts +6 -0
- package/src/session/codex-auto-reset.ts +202 -0
- package/src/session/history-storage.ts +3 -2
- package/src/session/indexed-session-storage.ts +7 -10
- package/src/session/messages.ts +59 -95
- package/src/session/session-context.ts +352 -0
- package/src/session/session-dump-format.ts +12 -3
- package/src/session/session-entries.ts +194 -0
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-listing.ts +588 -0
- package/src/session/session-loader.ts +106 -0
- package/src/session/session-manager.ts +1003 -2920
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +193 -0
- package/src/session/session-persistence.ts +131 -0
- package/src/session/session-storage.ts +91 -30
- package/src/session/snapcompact-inline.ts +542 -0
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/streaming-output.ts +248 -11
- package/src/session/tool-choice-queue.ts +23 -11
- package/src/session/yield-queue.ts +20 -2
- package/src/slash-commands/acp-builtins.ts +25 -1
- package/src/slash-commands/available-commands.ts +105 -0
- package/src/slash-commands/builtin-registry.ts +575 -49
- package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
- package/src/slash-commands/helpers/context-report.ts +28 -1
- package/src/slash-commands/helpers/logout.ts +88 -0
- package/src/slash-commands/helpers/reset-usage.ts +66 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/slash-commands/helpers/usage-report.ts +38 -3
- package/src/slash-commands/types.ts +5 -9
- package/src/ssh/connection-manager.ts +27 -0
- package/src/ssh/ssh-executor.ts +60 -4
- package/src/stt/asr-client.ts +520 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +790 -0
- package/src/stt/downloader.ts +107 -47
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +5 -1
- package/src/stt/models.ts +150 -0
- package/src/stt/recorder.ts +254 -67
- package/src/stt/stt-controller.ts +201 -22
- package/src/stt/transcriber.ts +37 -68
- package/src/stt/wav.ts +173 -0
- package/src/system-prompt.ts +52 -10
- package/src/task/agents.ts +3 -4
- package/src/task/commands.ts +3 -2
- package/src/task/discovery.ts +17 -24
- package/src/task/executor.ts +1054 -529
- package/src/task/index.ts +862 -757
- package/src/task/output-manager.ts +0 -11
- package/src/task/parallel.ts +3 -3
- package/src/task/prometheus-command.ts +2 -2
- package/src/task/render.ts +529 -182
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +144 -66
- package/src/task/worktree.ts +64 -56
- package/src/telemetry-export.ts +27 -9
- package/src/thinking.ts +9 -7
- package/src/tiny/models.ts +2 -2
- package/src/tiny/text.ts +5 -1
- package/src/tiny/title-client.ts +72 -20
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +23 -99
- package/src/tool-discovery/tool-index.ts +2 -0
- package/src/tools/archive-reader.ts +94 -2
- package/src/tools/ask.ts +234 -177
- package/src/tools/ast-edit.ts +136 -80
- package/src/tools/ast-grep.ts +41 -45
- package/src/tools/auto-generated-guard.ts +20 -3
- package/src/tools/bash-interactive.ts +28 -8
- package/src/tools/bash.ts +198 -35
- package/src/tools/browser/attach.ts +26 -7
- package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
- package/src/tools/browser/cmux/rpc.ts +156 -0
- package/src/tools/browser/cmux/socket-client.ts +309 -0
- package/src/tools/browser/launch.ts +11 -2
- package/src/tools/browser/readable.ts +19 -2
- package/src/tools/browser/registry.ts +52 -5
- package/src/tools/browser/render.ts +13 -5
- package/src/tools/browser/tab-protocol.ts +2 -0
- package/src/tools/browser/tab-supervisor.ts +256 -34
- package/src/tools/browser/tab-worker.ts +259 -91
- package/src/tools/browser.ts +44 -2
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/conflict-detect.ts +50 -4
- package/src/tools/debug.ts +27 -12
- package/src/tools/eval-render.ts +32 -35
- package/src/tools/eval.ts +26 -12
- package/src/tools/fetch.ts +450 -99
- package/src/tools/find.ts +182 -142
- package/src/tools/gh-cache-invalidation.ts +255 -0
- package/src/tools/gh-renderer.ts +104 -51
- package/src/tools/gh.ts +232 -37
- package/src/tools/github-cache.ts +97 -7
- package/src/tools/grouped-file-output.ts +159 -52
- package/src/tools/image-gen.ts +237 -132
- package/src/tools/index.ts +147 -26
- package/src/tools/inspect-image-renderer.ts +74 -45
- package/src/tools/inspect-image.ts +12 -6
- package/src/tools/irc.ts +626 -173
- package/src/tools/job.ts +106 -29
- package/src/tools/learn.ts +144 -0
- package/src/tools/manage-skill.ts +104 -0
- package/src/tools/memory-edit.ts +4 -4
- package/src/tools/memory-recall.ts +7 -9
- package/src/tools/memory-reflect.ts +5 -9
- package/src/tools/memory-render.ts +23 -6
- package/src/tools/memory-retain.ts +4 -4
- package/src/tools/path-utils.ts +102 -48
- package/src/tools/plan-mode-guard.ts +101 -40
- package/src/tools/read.ts +475 -120
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/render-utils.ts +132 -76
- package/src/tools/renderers.ts +12 -1
- package/src/tools/report-tool-issue.ts +14 -6
- package/src/tools/resolve.ts +20 -3
- package/src/tools/review.ts +2 -2
- package/src/tools/search-tool-bm25.ts +37 -24
- package/src/tools/search.ts +233 -115
- package/src/tools/sqlite-reader.ts +26 -17
- package/src/tools/ssh.ts +20 -14
- package/src/tools/todo.ts +197 -191
- package/src/tools/tool-result.ts +8 -0
- package/src/tools/tool-timeouts.ts +1 -1
- package/src/tools/tts.ts +205 -74
- package/src/tools/write.ts +291 -155
- package/src/tools/yield.ts +10 -1
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/player.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/streaming-player.ts +266 -0
- package/src/tts/tts-client.ts +647 -0
- package/src/tts/tts-protocol.ts +60 -0
- package/src/tts/tts-worker.ts +505 -0
- package/src/tts/vocalizer.ts +162 -0
- package/src/tts/wav.ts +58 -0
- package/src/tui/code-cell.ts +2 -7
- package/src/tui/hyperlink.ts +40 -26
- package/src/tui/output-block.ts +60 -108
- package/src/tui/status-line.ts +5 -1
- package/src/utils/block-context.ts +312 -0
- package/src/utils/changelog.ts +27 -1
- package/src/utils/clipboard.ts +91 -22
- package/src/utils/commit-message-generator.ts +8 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/file-mentions.ts +3 -1
- package/src/utils/git.ts +315 -15
- package/src/utils/image-loading.ts +65 -4
- package/src/utils/session-color.ts +83 -9
- package/src/utils/thinking-display.ts +37 -0
- package/src/utils/title-generator.ts +73 -10
- package/src/utils/tool-choice.ts +16 -0
- package/src/utils/tools-manager.ts +19 -1
- package/src/web/kagi.ts +28 -26
- package/src/web/parallel.ts +7 -3
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/github.ts +351 -3
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/twitter.ts +2 -1
- package/src/web/scrapers/types.ts +87 -8
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/scrapers/youtube.ts +9 -3
- package/src/web/search/index.ts +15 -2
- package/src/web/search/providers/anthropic.ts +62 -21
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/brave.ts +5 -2
- package/src/web/search/providers/codex.ts +87 -51
- package/src/web/search/providers/exa.ts +101 -10
- package/src/web/search/providers/gemini.ts +49 -24
- package/src/web/search/providers/jina.ts +15 -5
- package/src/web/search/providers/kagi.ts +9 -2
- package/src/web/search/providers/kimi.ts +45 -20
- package/src/web/search/providers/parallel.ts +39 -24
- package/src/web/search/providers/perplexity.ts +226 -63
- package/src/web/search/providers/searxng.ts +19 -3
- package/src/web/search/providers/synthetic.ts +16 -11
- package/src/web/search/providers/tavily.ts +12 -9
- package/src/web/search/providers/zai.ts +22 -9
- package/src/web/search/render.ts +59 -64
- package/src/web/search/types.ts +5 -1
- package/dist/types/discovery/context-files.d.ts +0 -17
- package/dist/types/exa/factory.d.ts +0 -13
- package/dist/types/exa/render.d.ts +0 -19
- package/dist/types/exa/researcher.d.ts +0 -9
- package/dist/types/exa/search.d.ts +0 -9
- package/dist/types/exa/websets.d.ts +0 -9
- package/dist/types/export/html/template.generated.d.ts +0 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/modes/components/status-line.d.ts +0 -77
- package/dist/types/slash-commands/headless-plan.d.ts +0 -3
- package/dist/types/stt/setup.d.ts +0 -18
- package/scripts/generate-template.ts +0 -33
- package/src/discovery/context-files.ts +0 -49
- package/src/exa/factory.ts +0 -60
- package/src/exa/render.ts +0 -244
- package/src/exa/researcher.ts +0 -36
- package/src/exa/search.ts +0 -47
- package/src/exa/websets.ts +0 -248
- package/src/export/html/template.generated.ts +0 -2
- package/src/modes/components/session-observer-overlay.ts +0 -852
- package/src/slash-commands/headless-plan.ts +0 -142
- package/src/stt/setup.ts +0 -52
- package/src/stt/transcribe.py +0 -70
- /package/dist/types/extensibility/{legacy-pi-coding-agent-shim.d.ts → legacy-package-agent-shim.d.ts} +0 -0
- /package/src/extensibility/{legacy-pi-coding-agent-shim.ts → legacy-package-agent-shim.ts} +0 -0
|
@@ -1,96 +1,34 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
1
2
|
import * as path from "node:path";
|
|
3
|
+
import { registerCustomApi, unregisterCustomApis } from "@prometheus-ai/ai/api-registry";
|
|
4
|
+
import type { Api, Context, Model, ModelSpec, SimpleStreamOptions, ThinkingConfig } from "@prometheus-ai/ai/types";
|
|
5
|
+
import type { AssistantMessageEventStream } from "@prometheus-ai/ai/utils/event-stream";
|
|
6
|
+
import { buildModel } from "@prometheus-ai/catalog/build";
|
|
7
|
+
import { isVertexExpressOpenAIUrl } from "@prometheus-ai/catalog/hosts";
|
|
8
|
+
import { readModelCache } from "@prometheus-ai/catalog/model-cache";
|
|
2
9
|
import {
|
|
3
|
-
type Api,
|
|
4
|
-
type AssistantMessageEventStream,
|
|
5
|
-
type Context,
|
|
6
10
|
createModelManager,
|
|
7
|
-
enrichModelThinking,
|
|
8
|
-
getBundledModels,
|
|
9
|
-
getBundledProviders,
|
|
10
|
-
googleAntigravityModelManagerOptions,
|
|
11
|
-
googleGeminiCliModelManagerOptions,
|
|
12
|
-
type Model,
|
|
13
11
|
type ModelManagerOptions,
|
|
14
12
|
type ModelRefreshStrategy,
|
|
13
|
+
} from "@prometheus-ai/catalog/model-manager";
|
|
14
|
+
import { getBundledModels, getBundledProviders } from "@prometheus-ai/catalog/models";
|
|
15
|
+
import {
|
|
16
|
+
googleAntigravityModelManagerOptions,
|
|
17
|
+
googleGeminiCliModelManagerOptions,
|
|
15
18
|
openaiCodexModelManagerOptions,
|
|
16
19
|
PROVIDER_DESCRIPTORS,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
// any provider module at startup. Must match `DEFAULT_LOCAL_TOKEN` in oauth/lm-studio.ts.
|
|
20
|
+
} from "@prometheus-ai/catalog/provider-models";
|
|
21
|
+
import {
|
|
22
|
+
collapseBuiltModelVariants,
|
|
23
|
+
getVariantAliasSources,
|
|
24
|
+
resolveVariantAlias,
|
|
25
|
+
} from "@prometheus-ai/catalog/variant-collapse";
|
|
26
|
+
|
|
27
|
+
// Sentinels for local-only OAuth tokens — declared inline to avoid loading
|
|
28
|
+
// provider modules at startup. Must match packages/ai/src/registry/lm-studio.ts
|
|
29
|
+
// and packages/ai/src/registry/vllm.ts.
|
|
28
30
|
const DEFAULT_LOCAL_TOKEN = "lm-studio-local";
|
|
29
|
-
|
|
30
|
-
// Default cap on `max_tokens` for auto-discovered models that do not advertise
|
|
31
|
-
// their own output limit (OpenAI-models-list, Ollama, llama.cpp, new-api/
|
|
32
|
-
// one-api proxies). 32K matches the upper end of what mainstream
|
|
33
|
-
// OpenAI-compatible providers (DeepSeek, MiMo, OpenRouter, etc.) actually
|
|
34
|
-
// accept and keeps `min(contextWindow, …)` honoring smaller local windows.
|
|
35
|
-
// Conservative caps below this caused providers to drop the connection
|
|
36
|
-
// mid-stream when models hit the cap on legitimate large tool calls (see
|
|
37
|
-
// issue #1528: `write` payloads >~5KB on deepseek-v4-pro surfaced as
|
|
38
|
-
// "socket connection was closed unexpectedly").
|
|
39
|
-
const DISCOVERY_DEFAULT_MAX_TOKENS = 32_768;
|
|
40
|
-
|
|
41
|
-
const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
|
|
42
|
-
const OLLAMA_HOST_DEFAULT_PORT = "11434";
|
|
43
|
-
|
|
44
|
-
function normalizeOllamaHostEnv(value: string | undefined): string | undefined {
|
|
45
|
-
const trimmed = value?.trim();
|
|
46
|
-
if (!trimmed) return undefined;
|
|
47
|
-
const candidate = trimmed.includes("://")
|
|
48
|
-
? trimmed
|
|
49
|
-
: trimmed.startsWith("//")
|
|
50
|
-
? `http:${trimmed}`
|
|
51
|
-
: trimmed.startsWith(":")
|
|
52
|
-
? `http://127.0.0.1${trimmed}`
|
|
53
|
-
: `http://${trimmed}`;
|
|
54
|
-
try {
|
|
55
|
-
const parsed = new URL(candidate);
|
|
56
|
-
if (!parsed.hostname || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
if (!parsed.port && parsed.protocol === "http:") {
|
|
60
|
-
parsed.port = OLLAMA_HOST_DEFAULT_PORT;
|
|
61
|
-
}
|
|
62
|
-
return `${parsed.protocol}//${parsed.host}`;
|
|
63
|
-
} catch {
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function getImplicitOllamaBaseUrl(): string {
|
|
69
|
-
const baseUrl = Bun.env.OLLAMA_BASE_URL?.trim();
|
|
70
|
-
return baseUrl || normalizeOllamaHostEnv(Bun.env.OLLAMA_HOST) || DEFAULT_OLLAMA_BASE_URL;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function getOllamaContextLengthOverride(): number | undefined {
|
|
74
|
-
const value = Bun.env.OLLAMA_CONTEXT_LENGTH?.trim();
|
|
75
|
-
if (!value) return undefined;
|
|
76
|
-
const parsed = Number(value);
|
|
77
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Anthropic-safe variant of the discovery cap. The Anthropic stream converter
|
|
81
|
-
// in `packages/ai/src/providers/anthropic.ts` derives the request limit as
|
|
82
|
-
// `(model.maxTokens / 3) | 0`, so the 32K default would surface as 10,922
|
|
83
|
-
// requested output tokens — above the 8,192 hard cap on classic Claude 3.x
|
|
84
|
-
// Sonnet/Haiku/Opus endpoints. Discovered models routed through
|
|
85
|
-
// `anthropic-messages` (proxy `supported_endpoint_types: ["anthropic"]` or a
|
|
86
|
-
// custom provider with `api: anthropic-messages` + openai-models-list
|
|
87
|
-
// discovery) fall back to this conservative value.
|
|
88
|
-
const DISCOVERY_DEFAULT_MAX_TOKENS_ANTHROPIC = 8_192;
|
|
89
|
-
|
|
90
|
-
/** Routes discovered-model `maxTokens` defaults around Anthropic's 3× output divisor. */
|
|
91
|
-
function discoveryDefaultMaxTokens(api: Api | undefined): number {
|
|
92
|
-
return api === "anthropic-messages" ? DISCOVERY_DEFAULT_MAX_TOKENS_ANTHROPIC : DISCOVERY_DEFAULT_MAX_TOKENS;
|
|
93
|
-
}
|
|
31
|
+
const DEFAULT_VLLM_LOCAL_TOKEN = "vllm-local";
|
|
94
32
|
|
|
95
33
|
const SPECIAL_MODEL_MANAGER_PROVIDER_IDS: readonly string[] = [
|
|
96
34
|
"google-antigravity",
|
|
@@ -103,35 +41,40 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
|
|
|
103
41
|
...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
|
|
104
42
|
];
|
|
105
43
|
|
|
106
|
-
import {
|
|
107
|
-
import
|
|
108
|
-
import {
|
|
109
|
-
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
110
|
-
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
111
|
-
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
112
|
-
import { type ConfigError, ConfigFile } from "./config-file";
|
|
44
|
+
import type { ApiKeyResolver, FetchImpl } from "@prometheus-ai/ai";
|
|
45
|
+
import { registerOAuthProvider, unregisterOAuthProviders } from "@prometheus-ai/ai/oauth";
|
|
46
|
+
import type { OAuthCredentials, OAuthLoginCallbacks } from "@prometheus-ai/ai/oauth/types";
|
|
113
47
|
import {
|
|
114
48
|
buildCanonicalModelIndex,
|
|
49
|
+
buildCanonicalModelOrder,
|
|
50
|
+
buildModelProviderPriorityRank,
|
|
115
51
|
type CanonicalModelIndex,
|
|
116
52
|
type CanonicalModelRecord,
|
|
117
53
|
type CanonicalModelVariant,
|
|
54
|
+
type CanonicalVariantPreferences,
|
|
118
55
|
formatCanonicalVariantSelector,
|
|
56
|
+
getBundledCanonicalReferenceData,
|
|
57
|
+
getBundledModelReferenceIndex,
|
|
119
58
|
type ModelEquivalenceConfig,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
} from "./
|
|
59
|
+
resolveCanonicalVariant,
|
|
60
|
+
resolveModelReference,
|
|
61
|
+
} from "@prometheus-ai/catalog/identity";
|
|
62
|
+
import { isRecord, logger } from "@prometheus-ai/utils";
|
|
63
|
+
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
64
|
+
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
65
|
+
import { type ApiKeyResolverModel, type ApiKeyResolverOptions, createApiKeyResolver } from "./api-key-resolver";
|
|
66
|
+
import type { ConfigError, ConfigFile } from "./config-file";
|
|
127
67
|
import {
|
|
128
|
-
|
|
129
|
-
type
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
68
|
+
DISCOVERY_DEFAULT_MAX_TOKENS,
|
|
69
|
+
type DiscoveryContext,
|
|
70
|
+
type DiscoveryProviderConfig,
|
|
71
|
+
discoverModelsByProviderType,
|
|
72
|
+
getImplicitOllamaBaseUrl,
|
|
73
|
+
getOllamaContextLengthOverride,
|
|
74
|
+
} from "./model-discovery";
|
|
75
|
+
import { ModelsConfigFile, type ProviderValidationModel, validateProviderConfiguration } from "./models-config";
|
|
76
|
+
import type { ModelOverride, ModelsConfig, ProviderAuthMode } from "./models-config-schema";
|
|
77
|
+
import { settings } from "./settings";
|
|
135
78
|
|
|
136
79
|
export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig };
|
|
137
80
|
|
|
@@ -141,196 +84,17 @@ export function isAuthenticated(apiKey: string | undefined | null): apiKey is st
|
|
|
141
84
|
return Boolean(apiKey) && apiKey !== kNoAuth;
|
|
142
85
|
}
|
|
143
86
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
export interface ModelRoleInfo {
|
|
147
|
-
tag?: string;
|
|
148
|
-
name: string;
|
|
149
|
-
color?: ThemeColor;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
|
|
153
|
-
default: { tag: "DEFAULT", name: "Default", color: "success" },
|
|
154
|
-
smol: { tag: "SMOL", name: "Fast", color: "warning" },
|
|
155
|
-
slow: { tag: "SLOW", name: "Thinking", color: "accent" },
|
|
156
|
-
vision: { tag: "VISION", name: "Vision", color: "error" },
|
|
157
|
-
plan: { tag: "PLAN", name: "Architect", color: "muted" },
|
|
158
|
-
designer: { tag: "DESIGNER", name: "Designer", color: "muted" },
|
|
159
|
-
commit: { tag: "COMMIT", name: "Commit", color: "dim" },
|
|
160
|
-
task: { tag: "TASK", name: "Subtask", color: "muted" },
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "vision", "plan", "designer", "commit", "task"];
|
|
164
|
-
|
|
165
|
-
/** Alias for ModelRoleInfo - used for both built-in and custom roles */
|
|
166
|
-
export type RoleInfo = ModelRoleInfo;
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Return the canonical set of known roles for selector/carousel UI.
|
|
170
|
-
*
|
|
171
|
-
* Built-ins always come first. Configured cycle order, model assignments, and
|
|
172
|
-
* tag metadata can introduce additional custom roles without requiring duplicate
|
|
173
|
-
* entries across settings.
|
|
174
|
-
*/
|
|
175
|
-
export function getKnownRoleIds(settings: Settings): string[] {
|
|
176
|
-
const roles = [...MODEL_ROLE_IDS] as string[];
|
|
177
|
-
const seen = new Set<string>(roles);
|
|
178
|
-
const addRole = (role: string) => {
|
|
179
|
-
if (seen.has(role)) return;
|
|
180
|
-
seen.add(role);
|
|
181
|
-
roles.push(role);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
for (const role of settings.get("cycleOrder")) addRole(role);
|
|
185
|
-
for (const role of Object.keys(settings.getModelRoles())) addRole(role);
|
|
186
|
-
for (const role of Object.keys(settings.get("modelTags"))) addRole(role);
|
|
187
|
-
|
|
188
|
-
return roles;
|
|
87
|
+
function isDiscoveryBearerApiKey(apiKey: string | undefined | null): apiKey is string {
|
|
88
|
+
return isAuthenticated(apiKey) && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== DEFAULT_VLLM_LOCAL_TOKEN;
|
|
189
89
|
}
|
|
190
90
|
|
|
191
|
-
/**
|
|
192
|
-
* Get role info for a role name (built-in or custom).
|
|
193
|
-
* Configured metadata overrides built-in defaults when present.
|
|
194
|
-
*/
|
|
195
|
-
export function getRoleInfo(role: string, settings: Settings): RoleInfo {
|
|
196
|
-
const builtIn = role in MODEL_ROLES ? MODEL_ROLES[role as ModelRole] : undefined;
|
|
197
|
-
const configured = settings.get("modelTags")[role];
|
|
198
|
-
|
|
199
|
-
if (configured) {
|
|
200
|
-
return {
|
|
201
|
-
tag: builtIn?.tag,
|
|
202
|
-
name: configured.name || builtIn?.name || role,
|
|
203
|
-
color: configured.color && isValidThemeColor(configured.color) ? configured.color : builtIn?.color,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (builtIn) return builtIn;
|
|
208
|
-
|
|
209
|
-
return { name: role, color: "muted" };
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
type ProviderValidationMode = "models-config" | "runtime-register";
|
|
213
|
-
|
|
214
|
-
interface ProviderValidationModel {
|
|
215
|
-
id: string;
|
|
216
|
-
api?: Api;
|
|
217
|
-
contextWindow?: number;
|
|
218
|
-
maxTokens?: number;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
interface ProviderValidationConfig {
|
|
222
|
-
baseUrl?: string;
|
|
223
|
-
headers?: Record<string, string>;
|
|
224
|
-
apiKey?: string;
|
|
225
|
-
api?: Api;
|
|
226
|
-
auth?: ProviderAuthMode;
|
|
227
|
-
oauthConfigured?: boolean;
|
|
228
|
-
discovery?: ProviderDiscovery;
|
|
229
|
-
compat?: Model<Api>["compat"];
|
|
230
|
-
disableStrictTools?: boolean;
|
|
231
|
-
modelOverrides?: Record<string, unknown>;
|
|
232
|
-
models: ProviderValidationModel[];
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function validateProviderConfiguration(
|
|
236
|
-
providerName: string,
|
|
237
|
-
config: ProviderValidationConfig,
|
|
238
|
-
mode: ProviderValidationMode,
|
|
239
|
-
): void {
|
|
240
|
-
const hasProviderApi = !!config.api;
|
|
241
|
-
const models = config.models;
|
|
242
|
-
|
|
243
|
-
if (models.length === 0) {
|
|
244
|
-
if (mode === "models-config") {
|
|
245
|
-
const hasModelOverrides = config.modelOverrides && Object.keys(config.modelOverrides).length > 0;
|
|
246
|
-
if (
|
|
247
|
-
!config.baseUrl &&
|
|
248
|
-
!config.headers &&
|
|
249
|
-
!config.compat &&
|
|
250
|
-
!config.apiKey &&
|
|
251
|
-
!config.disableStrictTools &&
|
|
252
|
-
!hasModelOverrides &&
|
|
253
|
-
!config.discovery
|
|
254
|
-
) {
|
|
255
|
-
throw new Error(
|
|
256
|
-
`Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
} else {
|
|
261
|
-
if (!config.baseUrl) {
|
|
262
|
-
throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
|
|
263
|
-
}
|
|
264
|
-
const requiresAuth =
|
|
265
|
-
mode === "runtime-register"
|
|
266
|
-
? !config.apiKey && !config.oauthConfigured
|
|
267
|
-
: !config.apiKey && (config.auth ?? "apiKey") !== "none";
|
|
268
|
-
if (requiresAuth) {
|
|
269
|
-
throw new Error(
|
|
270
|
-
mode === "runtime-register"
|
|
271
|
-
? `Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`
|
|
272
|
-
: `Provider ${providerName}: "apiKey" is required when defining custom models unless auth is "none".`,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (mode === "models-config" && config.discovery && !config.api && config.discovery.type !== "proxy") {
|
|
278
|
-
throw new Error(`Provider ${providerName}: "api" is required when discovery is enabled at provider level.`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
for (const modelDef of models) {
|
|
282
|
-
if (!hasProviderApi && !modelDef.api) {
|
|
283
|
-
throw new Error(
|
|
284
|
-
mode === "runtime-register"
|
|
285
|
-
? `Provider ${providerName}, model ${modelDef.id}: no "api" specified.`
|
|
286
|
-
: `Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
if (!modelDef.id) {
|
|
290
|
-
throw new Error(`Provider ${providerName}: model missing "id"`);
|
|
291
|
-
}
|
|
292
|
-
if (mode === "models-config") {
|
|
293
|
-
if (modelDef.contextWindow !== undefined && modelDef.contextWindow <= 0) {
|
|
294
|
-
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
|
|
295
|
-
}
|
|
296
|
-
if (modelDef.maxTokens !== undefined && modelDef.maxTokens <= 0) {
|
|
297
|
-
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsConfigSchema).withValidation(
|
|
304
|
-
"models",
|
|
305
|
-
config => {
|
|
306
|
-
for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
|
|
307
|
-
validateProviderConfiguration(
|
|
308
|
-
providerName,
|
|
309
|
-
{
|
|
310
|
-
baseUrl: providerConfig.baseUrl,
|
|
311
|
-
headers: providerConfig.headers,
|
|
312
|
-
apiKey: providerConfig.apiKey,
|
|
313
|
-
api: providerConfig.api as Api | undefined,
|
|
314
|
-
auth: (providerConfig.auth ?? "apiKey") as ProviderAuthMode,
|
|
315
|
-
discovery: providerConfig.discovery as ProviderDiscovery | undefined,
|
|
316
|
-
compat: providerConfig.compat,
|
|
317
|
-
disableStrictTools: providerConfig.disableStrictTools,
|
|
318
|
-
modelOverrides: providerConfig.modelOverrides,
|
|
319
|
-
models: (providerConfig.models ?? []) as ProviderValidationModel[],
|
|
320
|
-
},
|
|
321
|
-
"models-config",
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
);
|
|
326
|
-
|
|
327
91
|
/** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
|
|
328
92
|
interface ProviderOverride {
|
|
329
93
|
baseUrl?: string;
|
|
330
94
|
headers?: Record<string, string>;
|
|
331
95
|
apiKey?: string;
|
|
332
96
|
authHeader?: boolean;
|
|
333
|
-
compat?:
|
|
97
|
+
compat?: ModelSpec<Api>["compat"];
|
|
334
98
|
transport?: Model<Api>["transport"];
|
|
335
99
|
}
|
|
336
100
|
|
|
@@ -344,9 +108,19 @@ interface ProviderOverride {
|
|
|
344
108
|
* `token-plan-sgp.xiaomimimo.com` at discovery time)
|
|
345
109
|
* 3. Existing bundled baseUrl (the host baked into `models.json`)
|
|
346
110
|
*
|
|
111
|
+
* `transport` resolution priority:
|
|
112
|
+
* 1. `providerOverride.transport` (e.g. `prometheus-native` for auth-gateway users)
|
|
113
|
+
* 2. `existing.transport` (carried over from boot-time override application)
|
|
114
|
+
* 3. `model.transport` (rarely set — discovery defaults omit it)
|
|
115
|
+
*
|
|
347
116
|
* Without (1), the user's override would lose to discovery; without (2)
|
|
348
117
|
* preferred over (3), the bundled `api.xiaomimimo.com` would shadow the
|
|
349
118
|
* tp- token-plan host and produce 401s on the first stream call.
|
|
119
|
+
* Without explicit transport propagation, an openrouter (or any) entry
|
|
120
|
+
* marked `transport: prometheus-native` in models.yml silently reverts to the
|
|
121
|
+
* default openai-completions transport after the background catalog
|
|
122
|
+
* refresh — so the first `/model` switch after boot hits the raw OpenAI
|
|
123
|
+
* chat-completions URL instead of the gateway's `/v1/pi/stream` (#2555).
|
|
350
124
|
* See `xiaomi-tp-discovery-merge.test.ts` and the `refresh()` baseUrl-override
|
|
351
125
|
* regression in `model-registry.test.ts`.
|
|
352
126
|
*/
|
|
@@ -356,19 +130,22 @@ export function mergeDiscoveredModel<TApi extends Api>(
|
|
|
356
130
|
providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "transport">,
|
|
357
131
|
): Model<TApi> {
|
|
358
132
|
if (existing) {
|
|
359
|
-
return {
|
|
133
|
+
return buildModel({
|
|
360
134
|
...model,
|
|
361
135
|
baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
|
|
362
136
|
headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
|
|
363
|
-
|
|
137
|
+
transport: providerOverride?.transport ?? existing.transport ?? model.transport,
|
|
138
|
+
compat: model.compatConfig,
|
|
139
|
+
} as ModelSpec<TApi>);
|
|
364
140
|
}
|
|
365
141
|
if (providerOverride) {
|
|
366
|
-
return {
|
|
142
|
+
return buildModel({
|
|
367
143
|
...model,
|
|
368
144
|
baseUrl: providerOverride.baseUrl ?? model.baseUrl,
|
|
369
145
|
headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
|
|
370
146
|
...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
|
|
371
|
-
|
|
147
|
+
compat: model.compatConfig,
|
|
148
|
+
} as ModelSpec<TApi>);
|
|
372
149
|
}
|
|
373
150
|
return model;
|
|
374
151
|
}
|
|
@@ -383,7 +160,7 @@ function isAuthoritativeProjectCatalogModel(model: Model<Api>): boolean {
|
|
|
383
160
|
return (
|
|
384
161
|
model.provider === "google-vertex" &&
|
|
385
162
|
model.api === "openai-completions" &&
|
|
386
|
-
model.baseUrl
|
|
163
|
+
isVertexExpressOpenAIUrl(model.baseUrl)
|
|
387
164
|
);
|
|
388
165
|
}
|
|
389
166
|
|
|
@@ -401,14 +178,32 @@ function dropProviderModels(models: readonly Model<Api>[], providers: ReadonlySe
|
|
|
401
178
|
return models.filter(model => !providers.has(model.provider));
|
|
402
179
|
}
|
|
403
180
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Merge `incoming` entries into a copy of `base`, keyed by `provider`+`id`.
|
|
183
|
+
* Matches are replaced with `combine(existing, entry)`; new entries are
|
|
184
|
+
* appended as `combine(undefined, entry)`.
|
|
185
|
+
*/
|
|
186
|
+
function mergeByModelKey<T extends { provider: string; id: string }>(
|
|
187
|
+
base: readonly Model<Api>[],
|
|
188
|
+
incoming: readonly T[],
|
|
189
|
+
combine: (existing: Model<Api> | undefined, entry: T) => Model<Api>,
|
|
190
|
+
): Model<Api>[] {
|
|
191
|
+
const merged = [...base];
|
|
192
|
+
const indexByKey = new Map<string, number>();
|
|
193
|
+
for (let i = 0; i < merged.length; i += 1) {
|
|
194
|
+
indexByKey.set(`${merged[i].provider}\u0000${merged[i].id}`, i);
|
|
195
|
+
}
|
|
196
|
+
for (const entry of incoming) {
|
|
197
|
+
const key = `${entry.provider}\u0000${entry.id}`;
|
|
198
|
+
const existingIndex = indexByKey.get(key);
|
|
199
|
+
if (existingIndex !== undefined) {
|
|
200
|
+
merged[existingIndex] = combine(merged[existingIndex], entry);
|
|
201
|
+
} else {
|
|
202
|
+
merged.push(combine(undefined, entry));
|
|
203
|
+
indexByKey.set(key, merged.length - 1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return merged;
|
|
412
207
|
}
|
|
413
208
|
|
|
414
209
|
interface BuiltInDiscoveryResult {
|
|
@@ -433,6 +228,12 @@ export interface CanonicalModelQueryOptions {
|
|
|
433
228
|
candidates?: readonly Model<Api>[];
|
|
434
229
|
}
|
|
435
230
|
|
|
231
|
+
/** A canonical record (with query-filtered variants) plus the variant model selected for it. */
|
|
232
|
+
export interface CanonicalModelSelection {
|
|
233
|
+
record: CanonicalModelRecord;
|
|
234
|
+
model: Model<Api>;
|
|
235
|
+
}
|
|
236
|
+
|
|
436
237
|
/** Result of loading custom models from models.json */
|
|
437
238
|
interface CustomModelsResult {
|
|
438
239
|
models?: CustomModelOverlay[];
|
|
@@ -446,78 +247,50 @@ interface CustomModelsResult {
|
|
|
446
247
|
found: boolean;
|
|
447
248
|
}
|
|
448
249
|
|
|
449
|
-
|
|
450
|
-
reasoning: boolean;
|
|
451
|
-
input: ("text" | "image")[];
|
|
452
|
-
contextWindow?: number;
|
|
453
|
-
};
|
|
250
|
+
const commandValueCache = new Map<string, string>();
|
|
454
251
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
input?: ("text" | "image")[];
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Resolve an API key config value to an actual key.
|
|
462
|
-
* Checks environment variable first, then treats as literal.
|
|
463
|
-
*/
|
|
464
|
-
function resolveApiKeyConfig(keyConfig: string): string | undefined {
|
|
465
|
-
const envValue = Bun.env[keyConfig];
|
|
466
|
-
if (envValue) return envValue;
|
|
467
|
-
return keyConfig;
|
|
252
|
+
function isCommandConfigValue(valueConfig: string | undefined): valueConfig is string {
|
|
253
|
+
return valueConfig?.startsWith("!") === true;
|
|
468
254
|
}
|
|
469
255
|
|
|
470
|
-
function
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
if (
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
return undefined;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function extractOllamaContextWindow(payload: Record<string, unknown>): number | undefined {
|
|
484
|
-
const modelInfo = payload.model_info;
|
|
485
|
-
if (isRecord(modelInfo)) {
|
|
486
|
-
for (const [key, value] of Object.entries(modelInfo)) {
|
|
487
|
-
if (key === "context_length" || key.endsWith(".context_length")) {
|
|
488
|
-
const contextWindow = toPositiveNumberOrUndefined(value);
|
|
489
|
-
if (contextWindow !== undefined) {
|
|
490
|
-
return contextWindow;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const parameters = payload.parameters;
|
|
497
|
-
if (typeof parameters !== "string") {
|
|
256
|
+
function resolveCommandConfig(command: string): string | undefined {
|
|
257
|
+
const cached = commandValueCache.get(command);
|
|
258
|
+
if (cached !== undefined) return cached;
|
|
259
|
+
try {
|
|
260
|
+
const stdout = execSync(command, { encoding: "utf8", timeout: 10_000, windowsHide: true });
|
|
261
|
+
const trimmed = stdout.trim();
|
|
262
|
+
if (trimmed.length === 0) return undefined;
|
|
263
|
+
commandValueCache.set(command, trimmed);
|
|
264
|
+
return trimmed;
|
|
265
|
+
} catch {
|
|
498
266
|
return undefined;
|
|
499
267
|
}
|
|
500
|
-
const match = parameters.match(/(?:^|\n)\s*num_ctx\s+(\d+)\s*(?:$|\n)/m);
|
|
501
|
-
return match ? toPositiveNumberOrUndefined(match[1]) : undefined;
|
|
502
268
|
}
|
|
503
269
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
270
|
+
interface CommandApiKeyResolution {
|
|
271
|
+
configured: boolean;
|
|
272
|
+
value?: string;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Resolve a models.yml secret/config value to an actual value.
|
|
276
|
+
* `!cmd` runs a shell command and returns trimmed stdout, otherwise env vars are
|
|
277
|
+
* checked first and the input falls back to a literal value.
|
|
278
|
+
*/
|
|
279
|
+
function resolveConfigValue(valueConfig: string): string | undefined {
|
|
280
|
+
if (valueConfig.startsWith("!")) return resolveCommandConfig(valueConfig.slice(1).trim());
|
|
281
|
+
const envValue = Bun.env[valueConfig];
|
|
282
|
+
if (envValue) return envValue;
|
|
283
|
+
return valueConfig;
|
|
513
284
|
}
|
|
514
285
|
|
|
515
|
-
function
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
286
|
+
function resolveConfigHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {
|
|
287
|
+
if (!headers) return undefined;
|
|
288
|
+
const resolved: Record<string, string> = {};
|
|
289
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
290
|
+
const next = resolveConfigValue(value);
|
|
291
|
+
if (next) resolved[key] = next;
|
|
519
292
|
}
|
|
520
|
-
return
|
|
293
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
521
294
|
}
|
|
522
295
|
|
|
523
296
|
function extractGoogleOAuthToken(value: string | undefined): string | undefined {
|
|
@@ -578,73 +351,99 @@ function mergeCompat<TBase extends object, TOverride extends object>(
|
|
|
578
351
|
return merged as TBase & TOverride;
|
|
579
352
|
}
|
|
580
353
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
|
|
589
|
-
if (override.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = override.omitMaxOutputTokens;
|
|
590
|
-
if (override.contextPromotionTarget !== undefined) result.contextPromotionTarget = override.contextPromotionTarget;
|
|
591
|
-
if (override.premiumMultiplier !== undefined) result.premiumMultiplier = override.premiumMultiplier;
|
|
592
|
-
if (override.cost) {
|
|
593
|
-
result.cost = {
|
|
594
|
-
input: override.cost.input ?? model.cost.input,
|
|
595
|
-
output: override.cost.output ?? model.cost.output,
|
|
596
|
-
cacheRead: override.cost.cacheRead ?? model.cost.cacheRead,
|
|
597
|
-
cacheWrite: override.cost.cacheWrite ?? model.cost.cacheWrite,
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
if (override.headers) {
|
|
601
|
-
result.headers = { ...model.headers, ...override.headers };
|
|
602
|
-
}
|
|
603
|
-
result.compat = mergeCompat(model.compat, override.compat);
|
|
604
|
-
return enrichModelThinking(result);
|
|
354
|
+
/**
|
|
355
|
+
* Project a built model back to spec shape for the model-manager/cache
|
|
356
|
+
* boundary: sparse compat comes from `compatConfig`, never from the resolved
|
|
357
|
+
* record.
|
|
358
|
+
*/
|
|
359
|
+
function toModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi> {
|
|
360
|
+
return { ...model, compat: model.compatConfig } as ModelSpec<TApi>;
|
|
605
361
|
}
|
|
606
362
|
|
|
607
|
-
|
|
608
|
-
|
|
363
|
+
/**
|
|
364
|
+
* The patchable subset of `Model` fields shared by `modelOverrides` entries,
|
|
365
|
+
* custom model definitions, and parsed custom-model overlays. `undefined`
|
|
366
|
+
* always means "leave the base value alone".
|
|
367
|
+
*/
|
|
368
|
+
interface ModelPatch {
|
|
609
369
|
name?: string;
|
|
610
|
-
api?: Api;
|
|
611
|
-
baseUrl?: string;
|
|
612
370
|
reasoning?: boolean;
|
|
613
371
|
thinking?: ThinkingConfig;
|
|
614
372
|
input?: ("text" | "image")[];
|
|
615
|
-
cost?:
|
|
373
|
+
cost?: Partial<Model<Api>["cost"]>;
|
|
616
374
|
contextWindow?: number;
|
|
617
375
|
maxTokens?: number;
|
|
618
376
|
omitMaxOutputTokens?: boolean;
|
|
619
377
|
headers?: Record<string, string>;
|
|
620
|
-
compat?:
|
|
378
|
+
compat?: ModelSpec<Api>["compat"];
|
|
621
379
|
contextPromotionTarget?: string;
|
|
622
380
|
premiumMultiplier?: number;
|
|
623
381
|
}
|
|
624
382
|
|
|
383
|
+
/**
|
|
384
|
+
* How a patch treats the base model's transport metadata (headers/compat):
|
|
385
|
+
* - `merge`: fold the patch into the base's (modelOverrides semantics).
|
|
386
|
+
* - `replace`: the patch owns transport wholesale — same-id custom definitions
|
|
387
|
+
* already folded provider-level headers/compat in during parsing, so bundled
|
|
388
|
+
* transport metadata must not be re-merged (see `#mergeCustomModels`).
|
|
389
|
+
*/
|
|
390
|
+
type ModelTransportPolicy = "merge" | "replace";
|
|
391
|
+
|
|
392
|
+
function applyModelPatch(base: Model<Api>, patch: ModelPatch, transport: ModelTransportPolicy): Model<Api> {
|
|
393
|
+
const result = { ...base };
|
|
394
|
+
if (patch.name !== undefined) result.name = patch.name;
|
|
395
|
+
if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
|
|
396
|
+
if (patch.thinking !== undefined) result.thinking = patch.thinking;
|
|
397
|
+
if (patch.input !== undefined) result.input = patch.input;
|
|
398
|
+
if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
|
|
399
|
+
if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
|
|
400
|
+
if (patch.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = patch.omitMaxOutputTokens;
|
|
401
|
+
if (patch.contextPromotionTarget !== undefined) result.contextPromotionTarget = patch.contextPromotionTarget;
|
|
402
|
+
if (patch.premiumMultiplier !== undefined) result.premiumMultiplier = patch.premiumMultiplier;
|
|
403
|
+
if (patch.cost) {
|
|
404
|
+
result.cost = {
|
|
405
|
+
input: patch.cost.input ?? base.cost.input,
|
|
406
|
+
output: patch.cost.output ?? base.cost.output,
|
|
407
|
+
cacheRead: patch.cost.cacheRead ?? base.cost.cacheRead,
|
|
408
|
+
cacheWrite: patch.cost.cacheWrite ?? base.cost.cacheWrite,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
let compat: ModelSpec<Api>["compat"];
|
|
412
|
+
if (transport === "merge") {
|
|
413
|
+
if (patch.headers) {
|
|
414
|
+
result.headers = { ...base.headers, ...patch.headers };
|
|
415
|
+
}
|
|
416
|
+
compat = mergeCompat(base.compatConfig, patch.compat);
|
|
417
|
+
} else {
|
|
418
|
+
result.headers = patch.headers;
|
|
419
|
+
compat = patch.compat;
|
|
420
|
+
}
|
|
421
|
+
return buildModel({ ...result, compat } as ModelSpec<Api>);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {
|
|
425
|
+
return applyModelPatch(model, override as ModelPatch, "merge");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
interface CustomModelDefinitionLike extends ModelPatch {
|
|
429
|
+
id: string;
|
|
430
|
+
api?: Api;
|
|
431
|
+
baseUrl?: string;
|
|
432
|
+
cost?: Model<Api>["cost"];
|
|
433
|
+
}
|
|
434
|
+
|
|
625
435
|
interface CustomModelBuildOptions {
|
|
626
436
|
useDefaults: boolean;
|
|
627
437
|
}
|
|
628
438
|
|
|
629
|
-
|
|
439
|
+
interface CustomModelOverlay extends ModelPatch {
|
|
630
440
|
id: string;
|
|
631
441
|
provider: string;
|
|
632
442
|
api: Api;
|
|
633
443
|
baseUrl: string;
|
|
634
|
-
|
|
635
|
-
reasoning?: boolean;
|
|
636
|
-
thinking?: ThinkingConfig;
|
|
637
|
-
input?: ("text" | "image")[];
|
|
638
|
-
cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
639
|
-
contextWindow?: number;
|
|
640
|
-
maxTokens?: number;
|
|
641
|
-
omitMaxOutputTokens?: boolean;
|
|
642
|
-
headers?: Record<string, string>;
|
|
643
|
-
compat?: Model<Api>["compat"];
|
|
644
|
-
contextPromotionTarget?: string;
|
|
645
|
-
premiumMultiplier?: number;
|
|
444
|
+
cost?: Model<Api>["cost"];
|
|
646
445
|
isOAuth?: boolean;
|
|
647
|
-
}
|
|
446
|
+
}
|
|
648
447
|
|
|
649
448
|
function mergeCustomModelHeaders(
|
|
650
449
|
providerHeaders: Record<string, string> | undefined,
|
|
@@ -652,7 +451,8 @@ function mergeCustomModelHeaders(
|
|
|
652
451
|
authHeader: boolean | undefined,
|
|
653
452
|
apiKeyConfig: string | undefined,
|
|
654
453
|
): Record<string, string> | undefined {
|
|
655
|
-
|
|
454
|
+
const resolvedModelHeaders = resolveConfigHeaders(modelHeaders);
|
|
455
|
+
return mergeAuthHeader({ ...providerHeaders, ...resolvedModelHeaders }, authHeader, apiKeyConfig);
|
|
656
456
|
}
|
|
657
457
|
|
|
658
458
|
function mergeAuthHeader(
|
|
@@ -664,7 +464,7 @@ function mergeAuthHeader(
|
|
|
664
464
|
if (!authHeader || !apiKeyConfig) {
|
|
665
465
|
return nextHeaders;
|
|
666
466
|
}
|
|
667
|
-
const resolvedKey =
|
|
467
|
+
const resolvedKey = resolveConfigValue(apiKeyConfig);
|
|
668
468
|
return resolvedKey ? { ...nextHeaders, Authorization: `Bearer ${resolvedKey}` } : nextHeaders;
|
|
669
469
|
}
|
|
670
470
|
|
|
@@ -691,7 +491,7 @@ function buildCustomModelOverlay(
|
|
|
691
491
|
providerHeaders: Record<string, string> | undefined,
|
|
692
492
|
providerApiKey: string | undefined,
|
|
693
493
|
authHeader: boolean | undefined,
|
|
694
|
-
providerCompat:
|
|
494
|
+
providerCompat: ModelSpec<Api>["compat"] | undefined,
|
|
695
495
|
providerAuth: ProviderAuthMode | undefined,
|
|
696
496
|
modelDef: CustomModelDefinitionLike,
|
|
697
497
|
): CustomModelOverlay | undefined {
|
|
@@ -704,8 +504,8 @@ function buildCustomModelOverlay(
|
|
|
704
504
|
baseUrl: modelDef.baseUrl ?? providerBaseUrl,
|
|
705
505
|
name: modelDef.name,
|
|
706
506
|
reasoning: modelDef.reasoning,
|
|
707
|
-
thinking: modelDef.thinking
|
|
708
|
-
input: modelDef.input
|
|
507
|
+
thinking: modelDef.thinking,
|
|
508
|
+
input: modelDef.input,
|
|
709
509
|
cost: modelDef.cost,
|
|
710
510
|
contextWindow: modelDef.contextWindow,
|
|
711
511
|
maxTokens: modelDef.maxTokens,
|
|
@@ -718,125 +518,6 @@ function buildCustomModelOverlay(
|
|
|
718
518
|
};
|
|
719
519
|
}
|
|
720
520
|
|
|
721
|
-
// Custom provider entries often front a known upstream model through a local proxy.
|
|
722
|
-
// Use bundled metadata for missing pricing/capability fields, but keep the custom transport.
|
|
723
|
-
function shouldReplaceCustomReference(existing: Model<Api> | undefined, candidate: Model<Api>): boolean {
|
|
724
|
-
if (!existing) return true;
|
|
725
|
-
if (candidate.contextWindow !== existing.contextWindow) {
|
|
726
|
-
return candidate.contextWindow > existing.contextWindow;
|
|
727
|
-
}
|
|
728
|
-
if (candidate.maxTokens !== existing.maxTokens) {
|
|
729
|
-
return candidate.maxTokens > existing.maxTokens;
|
|
730
|
-
}
|
|
731
|
-
const existingHasCachePricing = existing.cost.cacheRead > 0 || existing.cost.cacheWrite > 0;
|
|
732
|
-
const candidateHasCachePricing = candidate.cost.cacheRead > 0 || candidate.cost.cacheWrite > 0;
|
|
733
|
-
if (candidateHasCachePricing !== existingHasCachePricing) {
|
|
734
|
-
return candidateHasCachePricing;
|
|
735
|
-
}
|
|
736
|
-
return existing.provider !== "openai" && candidate.provider === "openai";
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function normalizeCustomReferenceKey(value: string): string {
|
|
740
|
-
return value.trim().toLowerCase();
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
function buildCustomReferenceMap(): Map<string, Model<Api>> {
|
|
744
|
-
const references = new Map<string, Model<Api>>();
|
|
745
|
-
for (const provider of getBundledProviders()) {
|
|
746
|
-
for (const model of getBundledModels(provider as Parameters<typeof getBundledModels>[0])) {
|
|
747
|
-
const candidate = model as Model<Api>;
|
|
748
|
-
const key = normalizeCustomReferenceKey(candidate.id);
|
|
749
|
-
if (shouldReplaceCustomReference(references.get(key), candidate)) {
|
|
750
|
-
references.set(key, candidate);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
return references;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
function buildCustomReferenceSuffixAliasMap(exactReferences: ReadonlyMap<string, Model<Api>>): Map<string, Model<Api>> {
|
|
758
|
-
const aliases = new Map<string, Model<Api>>();
|
|
759
|
-
for (const reference of exactReferences.values()) {
|
|
760
|
-
const slashIndex = reference.id.lastIndexOf("/");
|
|
761
|
-
if (slashIndex === -1) {
|
|
762
|
-
continue;
|
|
763
|
-
}
|
|
764
|
-
const suffix = reference.id.slice(slashIndex + 1);
|
|
765
|
-
const alias = getLongestModelLikeIdSegment(suffix);
|
|
766
|
-
if (!alias) {
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
if (shouldReplaceCustomReference(aliases.get(alias), reference)) {
|
|
770
|
-
aliases.set(alias, reference);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
return aliases;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const customReferenceMap = buildCustomReferenceMap();
|
|
777
|
-
const customReferenceSuffixAliasMap = buildCustomReferenceSuffixAliasMap(customReferenceMap);
|
|
778
|
-
|
|
779
|
-
const CUSTOM_REFERENCE_TRAILING_MARKER_PATTERN =
|
|
780
|
-
/[-:](?:thinking|customtools|high|low|medium|minimal|xhigh|free|cloud|exacto|nitro|original|optimized|nvfp4|fp8|fp4|bf16|int8|int4|search)$/i;
|
|
781
|
-
|
|
782
|
-
function stripCustomReferenceTrailingMarker(candidate: string): string | undefined {
|
|
783
|
-
const match = CUSTOM_REFERENCE_TRAILING_MARKER_PATTERN.exec(candidate);
|
|
784
|
-
return match ? candidate.slice(0, match.index) : undefined;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
function getCustomReferenceCandidateIds(modelId: string): string[] {
|
|
788
|
-
const candidates = new Set<string>();
|
|
789
|
-
const queue = [modelId];
|
|
790
|
-
for (let index = 0; index < queue.length; index += 1) {
|
|
791
|
-
const candidate = queue[index]?.trim();
|
|
792
|
-
if (!candidate || candidates.has(candidate)) continue;
|
|
793
|
-
candidates.add(candidate);
|
|
794
|
-
|
|
795
|
-
for (const stripped of getBracketStrippedModelIdCandidates(candidate)) {
|
|
796
|
-
queue.push(stripped);
|
|
797
|
-
}
|
|
798
|
-
for (const segment of getModelLikeIdSegments(candidate)) {
|
|
799
|
-
queue.push(segment);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
for (const suffix of [":cloud", "-cloud"] as const) {
|
|
803
|
-
if (candidate.toLowerCase().endsWith(suffix)) {
|
|
804
|
-
queue.push(candidate.slice(0, -suffix.length));
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const slashIndex = candidate.lastIndexOf("/");
|
|
809
|
-
if (slashIndex !== -1) {
|
|
810
|
-
queue.push(candidate.slice(slashIndex + 1));
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
const colonToDash = candidate.replace(/:/g, "-");
|
|
814
|
-
if (colonToDash !== candidate) {
|
|
815
|
-
queue.push(colonToDash);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
const lowercased = candidate.toLowerCase();
|
|
819
|
-
if (lowercased !== candidate) {
|
|
820
|
-
queue.push(lowercased);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const strippedMarker = stripCustomReferenceTrailingMarker(candidate);
|
|
824
|
-
if (strippedMarker) {
|
|
825
|
-
queue.push(strippedMarker);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
return [...candidates];
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
function resolveCustomModelReference(modelId: string): Model<Api> | undefined {
|
|
832
|
-
for (const candidate of getCustomReferenceCandidateIds(modelId)) {
|
|
833
|
-
const key = normalizeCustomReferenceKey(candidate);
|
|
834
|
-
const reference = customReferenceMap.get(key) ?? customReferenceSuffixAliasMap.get(key);
|
|
835
|
-
if (reference) return reference;
|
|
836
|
-
}
|
|
837
|
-
return undefined;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
521
|
function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomModelOverlay {
|
|
841
522
|
if (model.id !== "gpt-5.4" || model.provider === "github-copilot" || model.contextWindow !== undefined) {
|
|
842
523
|
return model;
|
|
@@ -846,13 +527,15 @@ function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomMo
|
|
|
846
527
|
|
|
847
528
|
function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuildOptions): Model<Api> {
|
|
848
529
|
const resolvedModel = options.useDefaults ? applyStandaloneCustomModelPolicies(model) : model;
|
|
849
|
-
const reference = options.useDefaults
|
|
530
|
+
const reference = options.useDefaults
|
|
531
|
+
? resolveModelReference(resolvedModel.id, getBundledModelReferenceIndex())
|
|
532
|
+
: undefined;
|
|
850
533
|
const cost =
|
|
851
534
|
resolvedModel.cost ??
|
|
852
535
|
reference?.cost ??
|
|
853
536
|
(options.useDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
|
|
854
537
|
const input = resolvedModel.input ?? reference?.input ?? (options.useDefaults ? ["text"] : undefined);
|
|
855
|
-
return
|
|
538
|
+
return buildModel({
|
|
856
539
|
id: resolvedModel.id,
|
|
857
540
|
name: resolvedModel.name ?? (options.useDefaults ? resolvedModel.id : undefined),
|
|
858
541
|
api: resolvedModel.api,
|
|
@@ -862,16 +545,15 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
|
|
|
862
545
|
thinking: resolvedModel.thinking ?? reference?.thinking,
|
|
863
546
|
input: input as ("text" | "image")[],
|
|
864
547
|
cost,
|
|
865
|
-
contextWindow:
|
|
866
|
-
|
|
867
|
-
maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : undefined),
|
|
548
|
+
contextWindow: resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : null),
|
|
549
|
+
maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : null),
|
|
868
550
|
headers: resolvedModel.headers,
|
|
869
551
|
omitMaxOutputTokens: resolvedModel.omitMaxOutputTokens ?? reference?.omitMaxOutputTokens,
|
|
870
|
-
compat: mergeCompat(reference?.
|
|
552
|
+
compat: mergeCompat(reference?.compatConfig, resolvedModel.compat),
|
|
871
553
|
contextPromotionTarget: resolvedModel.contextPromotionTarget,
|
|
872
554
|
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
873
555
|
isOAuth: resolvedModel.isOAuth,
|
|
874
|
-
} as
|
|
556
|
+
} as ModelSpec<Api>);
|
|
875
557
|
}
|
|
876
558
|
|
|
877
559
|
function normalizeSuppressedSelector(selector: string): string {
|
|
@@ -879,7 +561,37 @@ function normalizeSuppressedSelector(selector: string): string {
|
|
|
879
561
|
if (!trimmed) return trimmed;
|
|
880
562
|
const parsed = parseModelString(trimmed);
|
|
881
563
|
if (!parsed) return trimmed;
|
|
882
|
-
|
|
564
|
+
// Retired effort-tier variant ids normalize to their collapsed logical id
|
|
565
|
+
// so persisted suppressions keyed by raw member ids still bind.
|
|
566
|
+
const aliasId = resolveVariantAlias(parsed.provider, parsed.id);
|
|
567
|
+
return `${parsed.provider}/${aliasId ?? parsed.id}`;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Look up a model's override, falling back to entries keyed by retired
|
|
572
|
+
* effort-tier variant ids (models.yml authored before collapsing). A raw key
|
|
573
|
+
* only re-binds when no live model holds that id.
|
|
574
|
+
*/
|
|
575
|
+
function resolveModelOverrideWithAliases(
|
|
576
|
+
overrides: Map<string, ModelOverride>,
|
|
577
|
+
model: Model<Api>,
|
|
578
|
+
hasLiveModel: (provider: string, id: string) => boolean,
|
|
579
|
+
): ModelOverride | undefined {
|
|
580
|
+
const direct = overrides.get(model.id);
|
|
581
|
+
if (direct) return direct;
|
|
582
|
+
for (const rawId of getVariantAliasSources(model.provider, model.id)) {
|
|
583
|
+
if (hasLiveModel(model.provider, rawId)) continue;
|
|
584
|
+
const remapped = overrides.get(rawId);
|
|
585
|
+
if (remapped) {
|
|
586
|
+
logger.debug("model override re-keyed through variant alias", {
|
|
587
|
+
provider: model.provider,
|
|
588
|
+
from: rawId,
|
|
589
|
+
to: model.id,
|
|
590
|
+
});
|
|
591
|
+
return remapped;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return undefined;
|
|
883
595
|
}
|
|
884
596
|
|
|
885
597
|
function getDisabledProviderIdsFromSettings(): Set<string> {
|
|
@@ -904,6 +616,7 @@ function getConfiguredProviderOrderFromSettings(): string[] {
|
|
|
904
616
|
export class ModelRegistry {
|
|
905
617
|
#models: Model<Api>[] = [];
|
|
906
618
|
#canonicalIndex: CanonicalModelIndex = { records: [], byId: new Map(), bySelector: new Map() };
|
|
619
|
+
#canonicalIndexDirty: boolean = true;
|
|
907
620
|
#customProviderApiKeys: Map<string, string> = new Map();
|
|
908
621
|
#keylessProviders: Set<string> = new Set();
|
|
909
622
|
#discoverableProviders: DiscoveryProviderConfig[] = [];
|
|
@@ -927,8 +640,34 @@ export class ModelRegistry {
|
|
|
927
640
|
#runtimeProviderOverrides: Map<string, ProviderOverride> = new Map();
|
|
928
641
|
#runtimeProvidersBySource: Map<string, Set<string>> = new Map();
|
|
929
642
|
#runtimeProviderSourceByName: Map<string, string> = new Map();
|
|
643
|
+
// Runtime model managers registered by extensions via fetchDynamicModels.
|
|
644
|
+
// Keyed by provider name; use the same SQLite cache path as builtins.
|
|
645
|
+
#runtimeModelManagers: Map<string, { options: ModelManagerOptions<Api>; sourceId: string }> = new Map();
|
|
930
646
|
#rebuildPending: boolean = false;
|
|
931
647
|
#rebuildSuspended: number = 0;
|
|
648
|
+
#fetch: FetchImpl;
|
|
649
|
+
|
|
650
|
+
#resolveCommandBackedApiKey(provider: string): CommandApiKeyResolution {
|
|
651
|
+
const keyConfig = this.#customProviderApiKeys.get(provider);
|
|
652
|
+
if (!isCommandConfigValue(keyConfig)) return { configured: false };
|
|
653
|
+
const value = resolveConfigValue(keyConfig);
|
|
654
|
+
if (value) {
|
|
655
|
+
this.authStorage.setConfigApiKey(provider, value);
|
|
656
|
+
return { configured: true, value };
|
|
657
|
+
}
|
|
658
|
+
this.authStorage.removeConfigApiKey(provider);
|
|
659
|
+
return { configured: true };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
#installProviderApiKey(provider: string, keyConfig: string): void {
|
|
663
|
+
this.#customProviderApiKeys.set(provider, keyConfig);
|
|
664
|
+
const resolved = resolveConfigValue(keyConfig);
|
|
665
|
+
if (resolved) {
|
|
666
|
+
this.authStorage.setConfigApiKey(provider, resolved);
|
|
667
|
+
} else if (isCommandConfigValue(keyConfig)) {
|
|
668
|
+
this.authStorage.removeConfigApiKey(provider);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
932
671
|
|
|
933
672
|
/**
|
|
934
673
|
* @param authStorage - Auth storage for API key resolution
|
|
@@ -942,16 +681,16 @@ export class ModelRegistry {
|
|
|
942
681
|
constructor(
|
|
943
682
|
readonly authStorage: AuthStorage,
|
|
944
683
|
modelsPath?: string,
|
|
684
|
+
options?: { fetch?: FetchImpl },
|
|
945
685
|
) {
|
|
686
|
+
this.#fetch = options?.fetch ?? fetch;
|
|
946
687
|
this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
|
|
947
688
|
this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
|
|
948
689
|
// Set up fallback resolver for custom provider API keys
|
|
949
690
|
this.authStorage.setFallbackResolver(provider => {
|
|
950
691
|
const keyConfig = this.#customProviderApiKeys.get(provider);
|
|
951
|
-
if (keyConfig)
|
|
952
|
-
|
|
953
|
-
}
|
|
954
|
-
return undefined;
|
|
692
|
+
if (!keyConfig) return undefined;
|
|
693
|
+
return resolveConfigValue(keyConfig);
|
|
955
694
|
});
|
|
956
695
|
// Load models synchronously in constructor.
|
|
957
696
|
this.#loadModels();
|
|
@@ -1004,6 +743,27 @@ export class ModelRegistry {
|
|
|
1004
743
|
}
|
|
1005
744
|
}
|
|
1006
745
|
|
|
746
|
+
/**
|
|
747
|
+
* Discover models for providers registered at runtime via `fetchDynamicModels`
|
|
748
|
+
* (extension providers). Merges the discovered catalog into the existing model
|
|
749
|
+
* set without reloading static models, so dynamically-discovered models from
|
|
750
|
+
* other providers are preserved. No-op when no runtime providers are registered.
|
|
751
|
+
*
|
|
752
|
+
* Drives the same SQLite model cache as built-in providers, so the default
|
|
753
|
+
* `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
|
|
754
|
+
*/
|
|
755
|
+
async refreshRuntimeProviders(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
|
|
756
|
+
if (this.#runtimeModelManagers.size === 0) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
this.#suspendRebuild();
|
|
760
|
+
try {
|
|
761
|
+
await this.#refreshRuntimeDiscoveries(strategy, new Set(this.#runtimeModelManagers.keys()));
|
|
762
|
+
} finally {
|
|
763
|
+
this.#resumeRebuild();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
1007
767
|
#reloadStaticModels(): void {
|
|
1008
768
|
const currentMtime = this.#modelsConfigFile.getMtimeMs();
|
|
1009
769
|
if (currentMtime !== null && currentMtime === this.#lastStaticLoadMtime) {
|
|
@@ -1021,7 +781,7 @@ export class ModelRegistry {
|
|
|
1021
781
|
// Restore runtime API keys before #loadModels — survives because
|
|
1022
782
|
// #loadModels only calls .set() on #customProviderApiKeys, never reassigns it.
|
|
1023
783
|
for (const [k, v] of this.#runtimeProviderApiKeys) {
|
|
1024
|
-
this.#
|
|
784
|
+
this.#installProviderApiKey(k, v);
|
|
1025
785
|
}
|
|
1026
786
|
this.#providerOverrides.clear();
|
|
1027
787
|
this.#modelOverrides.clear();
|
|
@@ -1089,7 +849,9 @@ export class ModelRegistry {
|
|
|
1089
849
|
const withConfigModels = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
|
|
1090
850
|
// Merge runtime extension models so they survive refresh() cycles
|
|
1091
851
|
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
1092
|
-
|
|
852
|
+
// Custom/config providers bypass the model-manager merge point —
|
|
853
|
+
// collapse effort-tier variants here so X/X-thinking twins fold.
|
|
854
|
+
const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
|
|
1093
855
|
this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
|
|
1094
856
|
this.#rebuildCanonicalIndex();
|
|
1095
857
|
this.#lastStaticLoadMtime = this.#modelsConfigFile.getMtimeMs();
|
|
@@ -1105,84 +867,55 @@ export class ModelRegistry {
|
|
|
1105
867
|
return models.map(m => {
|
|
1106
868
|
if (!providerOverride) return m;
|
|
1107
869
|
const withTransportOverride = this.#applyProviderTransportOverride(m, providerOverride);
|
|
1108
|
-
return {
|
|
870
|
+
return buildModel({
|
|
1109
871
|
...withTransportOverride,
|
|
1110
|
-
compat: mergeCompat(m.
|
|
1111
|
-
};
|
|
872
|
+
compat: mergeCompat(m.compatConfig, providerOverride.compat),
|
|
873
|
+
} as ModelSpec<Api>);
|
|
1112
874
|
});
|
|
1113
875
|
});
|
|
1114
876
|
}
|
|
1115
877
|
|
|
1116
878
|
#mergeResolvedModels(baseModels: Model<Api>[], replacementModels: Model<Api>[]): Model<Api>[] {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
const existingIndex = indexByKey.get(key);
|
|
1126
|
-
if (existingIndex !== undefined) {
|
|
1127
|
-
const existing = merged[existingIndex];
|
|
1128
|
-
merged[existingIndex] = {
|
|
1129
|
-
...replacementModel,
|
|
1130
|
-
contextWindow:
|
|
1131
|
-
replacementModel.contextWindow === UNK_CONTEXT_WINDOW
|
|
1132
|
-
? existing.contextWindow
|
|
1133
|
-
: replacementModel.contextWindow,
|
|
1134
|
-
maxTokens:
|
|
1135
|
-
replacementModel.maxTokens === UNK_MAX_TOKENS ? existing.maxTokens : replacementModel.maxTokens,
|
|
1136
|
-
};
|
|
1137
|
-
} else {
|
|
1138
|
-
merged.push(replacementModel);
|
|
1139
|
-
indexByKey.set(key, merged.length - 1);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
return merged;
|
|
879
|
+
return mergeByModelKey(baseModels, replacementModels, (existing, replacementModel) => {
|
|
880
|
+
if (!existing) return replacementModel;
|
|
881
|
+
return {
|
|
882
|
+
...replacementModel,
|
|
883
|
+
contextWindow: replacementModel.contextWindow ?? existing.contextWindow,
|
|
884
|
+
maxTokens: replacementModel.maxTokens ?? existing.maxTokens,
|
|
885
|
+
};
|
|
886
|
+
});
|
|
1143
887
|
}
|
|
1144
888
|
|
|
1145
889
|
/** Merge custom models with built-in, replacing by provider+id match */
|
|
1146
890
|
#mergeCustomModels(builtInModels: Model<Api>[], customModels: CustomModelOverlay[]): Model<Api>[] {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
for (const customModel of customModels) {
|
|
1154
|
-
const key = `${customModel.provider}\u0000${customModel.id}`;
|
|
1155
|
-
const existingIndex = indexByKey.get(key);
|
|
1156
|
-
if (existingIndex !== undefined) {
|
|
1157
|
-
const existingModel = merged[existingIndex];
|
|
1158
|
-
merged[existingIndex] = enrichModelThinking({
|
|
891
|
+
return mergeByModelKey(builtInModels, customModels, (existingModel, customModel) => {
|
|
892
|
+
if (!existingModel) return finalizeCustomModel(customModel, { useDefaults: true });
|
|
893
|
+
// Same-id custom definitions replace bundled transport behavior, so the
|
|
894
|
+
// patch is applied with the `replace` transport policy.
|
|
895
|
+
return applyModelPatch(
|
|
896
|
+
{
|
|
1159
897
|
...existingModel,
|
|
1160
898
|
id: customModel.id,
|
|
1161
899
|
provider: customModel.provider,
|
|
1162
900
|
api: customModel.api,
|
|
1163
901
|
baseUrl: customModel.baseUrl,
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
headers: customModel.headers,
|
|
1176
|
-
compat: customModel.compat,
|
|
1177
|
-
contextPromotionTarget: customModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
|
|
1178
|
-
premiumMultiplier: customModel.premiumMultiplier ?? existingModel.premiumMultiplier,
|
|
1179
|
-
} as Model<Api>);
|
|
1180
|
-
} else {
|
|
1181
|
-
merged.push(finalizeCustomModel(customModel, { useDefaults: true }));
|
|
1182
|
-
indexByKey.set(key, merged.length - 1);
|
|
1183
|
-
}
|
|
902
|
+
},
|
|
903
|
+
customModel,
|
|
904
|
+
"replace",
|
|
905
|
+
);
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
#resolveStartupModelCacheProviderId(providerId: string): string {
|
|
910
|
+
const descriptor = PROVIDER_DESCRIPTORS.find(candidate => candidate.providerId === providerId);
|
|
911
|
+
if (!descriptor) {
|
|
912
|
+
return providerId;
|
|
1184
913
|
}
|
|
1185
|
-
|
|
914
|
+
const baseUrl =
|
|
915
|
+
this.#runtimeProviderOverrides.get(providerId)?.baseUrl ??
|
|
916
|
+
this.#providerOverrides.get(providerId)?.baseUrl ??
|
|
917
|
+
this.getProviderBaseUrl(providerId);
|
|
918
|
+
return descriptor.createModelManagerOptions({ baseUrl, fetch: this.#fetch }).cacheProviderId ?? providerId;
|
|
1186
919
|
}
|
|
1187
920
|
|
|
1188
921
|
#loadCachedStandardProviderModels(): { models: Model<Api>[]; authoritativeFreshProviders: Set<string> } {
|
|
@@ -1193,7 +926,8 @@ export class ModelRegistry {
|
|
|
1193
926
|
if (configuredDiscoveryProviders.has(providerId)) {
|
|
1194
927
|
continue;
|
|
1195
928
|
}
|
|
1196
|
-
const
|
|
929
|
+
const cacheProviderId = this.#resolveStartupModelCacheProviderId(providerId);
|
|
930
|
+
const cache = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1197
931
|
if (!cache) {
|
|
1198
932
|
continue;
|
|
1199
933
|
}
|
|
@@ -1208,8 +942,13 @@ export class ModelRegistry {
|
|
|
1208
942
|
? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
|
|
1209
943
|
: models;
|
|
1210
944
|
const withCompat = providerOverride?.compat
|
|
1211
|
-
? withTransport.map(model =>
|
|
1212
|
-
|
|
945
|
+
? withTransport.map(model =>
|
|
946
|
+
buildModel({
|
|
947
|
+
...model,
|
|
948
|
+
compat: mergeCompat(model.compat, providerOverride.compat),
|
|
949
|
+
} as ModelSpec<Api>),
|
|
950
|
+
)
|
|
951
|
+
: withTransport.map(model => buildModel(model));
|
|
1213
952
|
cachedModels.push(...this.#applyProviderModelOverrides(providerId, withCompat));
|
|
1214
953
|
}
|
|
1215
954
|
return { models: cachedModels, authoritativeFreshProviders };
|
|
@@ -1218,7 +957,12 @@ export class ModelRegistry {
|
|
|
1218
957
|
#loadCachedDiscoverableModels(): Model<Api>[] {
|
|
1219
958
|
const cachedModels: Model<Api>[] = [];
|
|
1220
959
|
for (const providerConfig of this.#discoverableProviders) {
|
|
1221
|
-
const cache = readModelCache<Api>(
|
|
960
|
+
const cache = readModelCache<Api>(
|
|
961
|
+
this.#configuredDiscoveryCacheProviderId(providerConfig),
|
|
962
|
+
24 * 60 * 60 * 1000,
|
|
963
|
+
Date.now,
|
|
964
|
+
this.#cacheDbPath,
|
|
965
|
+
);
|
|
1222
966
|
if (!cache) {
|
|
1223
967
|
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
1224
968
|
provider: providerConfig.provider,
|
|
@@ -1233,7 +977,10 @@ export class ModelRegistry {
|
|
|
1233
977
|
providerConfig.provider,
|
|
1234
978
|
this.#normalizeDiscoverableModels(
|
|
1235
979
|
providerConfig,
|
|
1236
|
-
this.#applyProviderCompat(
|
|
980
|
+
this.#applyProviderCompat(
|
|
981
|
+
providerConfig.compat,
|
|
982
|
+
cache.models.map(model => buildModel(model)),
|
|
983
|
+
),
|
|
1237
984
|
),
|
|
1238
985
|
);
|
|
1239
986
|
cachedModels.push(...models);
|
|
@@ -1249,9 +996,11 @@ export class ModelRegistry {
|
|
|
1249
996
|
return cachedModels;
|
|
1250
997
|
}
|
|
1251
998
|
|
|
1252
|
-
#applyProviderCompat(compat:
|
|
999
|
+
#applyProviderCompat(compat: ModelSpec<Api>["compat"] | undefined, models: Model<Api>[]): Model<Api>[] {
|
|
1253
1000
|
if (!compat) return models;
|
|
1254
|
-
return models.map(model =>
|
|
1001
|
+
return models.map(model =>
|
|
1002
|
+
buildModel({ ...model, compat: mergeCompat(model.compatConfig, compat) } as ModelSpec<Api>),
|
|
1003
|
+
);
|
|
1255
1004
|
}
|
|
1256
1005
|
|
|
1257
1006
|
#normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
|
|
@@ -1261,7 +1010,14 @@ export class ModelRegistry {
|
|
|
1261
1010
|
|
|
1262
1011
|
const contextLengthOverride = getOllamaContextLengthOverride();
|
|
1263
1012
|
return models.map(model => {
|
|
1264
|
-
const normalized =
|
|
1013
|
+
const normalized =
|
|
1014
|
+
model.api === "openai-completions"
|
|
1015
|
+
? buildModel({
|
|
1016
|
+
...model,
|
|
1017
|
+
api: "openai-responses" as const,
|
|
1018
|
+
compat: model.compatConfig,
|
|
1019
|
+
} as ModelSpec<Api>)
|
|
1020
|
+
: model;
|
|
1265
1021
|
if (contextLengthOverride === undefined) {
|
|
1266
1022
|
return normalized;
|
|
1267
1023
|
}
|
|
@@ -1344,10 +1100,11 @@ export class ModelRegistry {
|
|
|
1344
1100
|
const configuredProviders = new Set(Object.keys(value.providers ?? {}));
|
|
1345
1101
|
|
|
1346
1102
|
for (const [providerName, providerConfig] of providerEntries) {
|
|
1103
|
+
const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
|
|
1347
1104
|
// Always set overrides when baseUrl/headers/apiKey/authHeader/compat/disableStrictTools/transport are present
|
|
1348
1105
|
if (
|
|
1349
1106
|
providerConfig.baseUrl ||
|
|
1350
|
-
|
|
1107
|
+
resolvedProviderHeaders ||
|
|
1351
1108
|
providerConfig.apiKey ||
|
|
1352
1109
|
providerConfig.authHeader !== undefined ||
|
|
1353
1110
|
providerConfig.compat ||
|
|
@@ -1357,7 +1114,7 @@ export class ModelRegistry {
|
|
|
1357
1114
|
const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
|
|
1358
1115
|
overrides.set(providerName, {
|
|
1359
1116
|
baseUrl: providerConfig.baseUrl,
|
|
1360
|
-
headers:
|
|
1117
|
+
headers: resolvedProviderHeaders,
|
|
1361
1118
|
apiKey: providerConfig.apiKey,
|
|
1362
1119
|
authHeader: providerConfig.authHeader,
|
|
1363
1120
|
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
@@ -1379,7 +1136,7 @@ export class ModelRegistry {
|
|
|
1379
1136
|
// fallback for entries that don't advertise one.
|
|
1380
1137
|
api: (providerConfig.api ?? "openai-completions") as Api,
|
|
1381
1138
|
baseUrl: providerConfig.baseUrl,
|
|
1382
|
-
headers:
|
|
1139
|
+
headers: resolvedProviderHeaders,
|
|
1383
1140
|
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
1384
1141
|
discovery: providerConfig.discovery,
|
|
1385
1142
|
optional: false,
|
|
@@ -1391,16 +1148,17 @@ export class ModelRegistry {
|
|
|
1391
1148
|
// bearer in models.yml (e.g. for an auth-gateway baseUrl), that bearer
|
|
1392
1149
|
// must authenticate the outbound request.
|
|
1393
1150
|
if (providerConfig.apiKey) {
|
|
1394
|
-
this.#
|
|
1395
|
-
const resolved = resolveApiKeyConfig(providerConfig.apiKey);
|
|
1396
|
-
if (resolved) this.authStorage.setConfigApiKey(providerName, resolved);
|
|
1151
|
+
this.#installProviderApiKey(providerName, providerConfig.apiKey);
|
|
1397
1152
|
}
|
|
1398
1153
|
|
|
1399
1154
|
// Parse per-model overrides
|
|
1400
1155
|
if (providerConfig.modelOverrides) {
|
|
1401
1156
|
const perModel = new Map<string, ModelOverride>();
|
|
1402
1157
|
for (const [modelId, override] of Object.entries(providerConfig.modelOverrides)) {
|
|
1403
|
-
perModel.set(
|
|
1158
|
+
perModel.set(
|
|
1159
|
+
modelId,
|
|
1160
|
+
override.headers ? { ...override, headers: resolveConfigHeaders(override.headers) } : override,
|
|
1161
|
+
);
|
|
1404
1162
|
}
|
|
1405
1163
|
allModelOverrides.set(providerName, perModel);
|
|
1406
1164
|
}
|
|
@@ -1461,16 +1219,24 @@ export class ModelRegistry {
|
|
|
1461
1219
|
const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
|
|
1462
1220
|
// Merge runtime extension models so they survive online discovery completion
|
|
1463
1221
|
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
1464
|
-
const withModelOverrides = this.#applyModelOverrides(combined, this.#modelOverrides);
|
|
1222
|
+
const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
|
|
1465
1223
|
this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
|
|
1466
1224
|
this.#rebuildCanonicalIndex();
|
|
1467
1225
|
}
|
|
1468
1226
|
|
|
1227
|
+
#configuredDiscoveryCacheProviderId(providerConfig: DiscoveryProviderConfig): string {
|
|
1228
|
+
if (providerConfig.discovery.type === "openai-models-list") {
|
|
1229
|
+
return `${providerConfig.provider}:openai-models-list-context-v2`;
|
|
1230
|
+
}
|
|
1231
|
+
return providerConfig.provider;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1469
1234
|
async #discoverProviderModels(
|
|
1470
1235
|
providerConfig: DiscoveryProviderConfig,
|
|
1471
1236
|
strategy: ModelRefreshStrategy,
|
|
1472
1237
|
): Promise<Model<Api>[]> {
|
|
1473
|
-
const
|
|
1238
|
+
const cacheProviderId = this.#configuredDiscoveryCacheProviderId(providerConfig);
|
|
1239
|
+
const cached = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1474
1240
|
const requiresAuth = !this.#keylessProviders.has(providerConfig.provider);
|
|
1475
1241
|
if (requiresAuth) {
|
|
1476
1242
|
const apiKey = await this.#peekApiKeyForProvider(providerConfig.provider);
|
|
@@ -1484,17 +1250,20 @@ export class ModelRegistry {
|
|
|
1484
1250
|
models: cached?.models.map(model => model.id) ?? [],
|
|
1485
1251
|
});
|
|
1486
1252
|
this.#lastDiscoveryWarnings.delete(providerConfig.provider);
|
|
1487
|
-
return cached
|
|
1253
|
+
return cached ? cached.models.map(model => buildModel(model)) : [];
|
|
1488
1254
|
}
|
|
1489
1255
|
}
|
|
1490
1256
|
|
|
1491
1257
|
const providerId = providerConfig.provider;
|
|
1492
1258
|
let discoveryError: string | undefined;
|
|
1493
|
-
const fetchDynamicModels = async (): Promise<readonly
|
|
1259
|
+
const fetchDynamicModels = async (): Promise<readonly ModelSpec<Api>[] | null> => {
|
|
1494
1260
|
try {
|
|
1495
|
-
const models =
|
|
1261
|
+
const models = this.#applyProviderModelOverrides(
|
|
1262
|
+
providerId,
|
|
1263
|
+
await discoverModelsByProviderType(providerConfig, this.#discoveryContext()),
|
|
1264
|
+
);
|
|
1496
1265
|
this.#lastDiscoveryWarnings.delete(providerId);
|
|
1497
|
-
return models;
|
|
1266
|
+
return models.map(toModelSpec);
|
|
1498
1267
|
} catch (error) {
|
|
1499
1268
|
discoveryError = error instanceof Error ? error.message : String(error);
|
|
1500
1269
|
return null;
|
|
@@ -1505,6 +1274,7 @@ export class ModelRegistry {
|
|
|
1505
1274
|
providerId,
|
|
1506
1275
|
staticModels: [],
|
|
1507
1276
|
cacheDbPath: this.#cacheDbPath,
|
|
1277
|
+
cacheProviderId,
|
|
1508
1278
|
cacheTtlMs: 24 * 60 * 60 * 1000,
|
|
1509
1279
|
fetchDynamicModels,
|
|
1510
1280
|
});
|
|
@@ -1541,18 +1311,17 @@ export class ModelRegistry {
|
|
|
1541
1311
|
);
|
|
1542
1312
|
}
|
|
1543
1313
|
|
|
1544
|
-
#
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
return this
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
}
|
|
1314
|
+
#discoveryContext(): DiscoveryContext {
|
|
1315
|
+
return {
|
|
1316
|
+
fetch: this.#fetch,
|
|
1317
|
+
getBearerApiKeyResolver: async provider => {
|
|
1318
|
+
const apiKey = await this.getApiKeyForProvider(provider);
|
|
1319
|
+
if (!isDiscoveryBearerApiKey(apiKey)) {
|
|
1320
|
+
return undefined;
|
|
1321
|
+
}
|
|
1322
|
+
return this.resolver(provider);
|
|
1323
|
+
},
|
|
1324
|
+
};
|
|
1556
1325
|
}
|
|
1557
1326
|
|
|
1558
1327
|
#warnProviderDiscoveryFailure(providerConfig: DiscoveryProviderConfig, error: string): void {
|
|
@@ -1610,6 +1379,7 @@ export class ModelRegistry {
|
|
|
1610
1379
|
googleAntigravityModelManagerOptions({
|
|
1611
1380
|
oauthToken,
|
|
1612
1381
|
endpoint: this.getProviderBaseUrl("google-antigravity"),
|
|
1382
|
+
fetch: this.#fetch,
|
|
1613
1383
|
}),
|
|
1614
1384
|
},
|
|
1615
1385
|
{
|
|
@@ -1619,6 +1389,7 @@ export class ModelRegistry {
|
|
|
1619
1389
|
googleGeminiCliModelManagerOptions({
|
|
1620
1390
|
oauthToken,
|
|
1621
1391
|
endpoint: this.getProviderBaseUrl("google-gemini-cli"),
|
|
1392
|
+
fetch: this.#fetch,
|
|
1622
1393
|
}),
|
|
1623
1394
|
},
|
|
1624
1395
|
{
|
|
@@ -1652,11 +1423,21 @@ export class ModelRegistry {
|
|
|
1652
1423
|
for (let i = 0; i < standardProviderDescriptors.length; i++) {
|
|
1653
1424
|
const descriptor = standardProviderDescriptors[i];
|
|
1654
1425
|
const apiKey = standardProviderKeys[i];
|
|
1655
|
-
|
|
1426
|
+
const hasExplicitVllmConfig =
|
|
1427
|
+
descriptor.providerId === "vllm" &&
|
|
1428
|
+
(this.#runtimeProviderOverrides.has(descriptor.providerId) ||
|
|
1429
|
+
this.#providerOverrides.has(descriptor.providerId) ||
|
|
1430
|
+
this.#keylessProviders.has(descriptor.providerId));
|
|
1431
|
+
if (isAuthenticated(apiKey) || descriptor.allowUnauthenticated || hasExplicitVllmConfig) {
|
|
1432
|
+
const discoveryBaseUrl =
|
|
1433
|
+
this.#runtimeProviderOverrides.get(descriptor.providerId)?.baseUrl ??
|
|
1434
|
+
this.#providerOverrides.get(descriptor.providerId)?.baseUrl ??
|
|
1435
|
+
this.getProviderBaseUrl(descriptor.providerId);
|
|
1656
1436
|
options.push(
|
|
1657
1437
|
descriptor.createModelManagerOptions({
|
|
1658
|
-
apiKey:
|
|
1659
|
-
baseUrl:
|
|
1438
|
+
apiKey: isDiscoveryBearerApiKey(apiKey) ? apiKey : undefined,
|
|
1439
|
+
baseUrl: discoveryBaseUrl,
|
|
1440
|
+
fetch: this.#fetch,
|
|
1660
1441
|
}),
|
|
1661
1442
|
);
|
|
1662
1443
|
}
|
|
@@ -1670,6 +1451,10 @@ export class ModelRegistry {
|
|
|
1670
1451
|
}
|
|
1671
1452
|
options.push(descriptor.createOptions(key));
|
|
1672
1453
|
}
|
|
1454
|
+
// Append runtime model managers registered by extensions via fetchDynamicModels.
|
|
1455
|
+
for (const { options: managerOpts } of this.#runtimeModelManagers.values()) {
|
|
1456
|
+
options.push(managerOpts);
|
|
1457
|
+
}
|
|
1673
1458
|
return options;
|
|
1674
1459
|
}
|
|
1675
1460
|
|
|
@@ -1697,366 +1482,16 @@ export class ModelRegistry {
|
|
|
1697
1482
|
}
|
|
1698
1483
|
}
|
|
1699
1484
|
|
|
1700
|
-
async #discoverOllamaModelMetadata(
|
|
1701
|
-
endpoint: string,
|
|
1702
|
-
modelId: string,
|
|
1703
|
-
headers: Record<string, string> | undefined,
|
|
1704
|
-
): Promise<OllamaDiscoveredModelMetadata | null> {
|
|
1705
|
-
const showUrl = `${endpoint}/api/show`;
|
|
1706
|
-
try {
|
|
1707
|
-
const response = await fetch(showUrl, {
|
|
1708
|
-
method: "POST",
|
|
1709
|
-
headers: { ...(headers ?? {}), "Content-Type": "application/json" },
|
|
1710
|
-
body: JSON.stringify({ model: modelId }),
|
|
1711
|
-
signal: AbortSignal.timeout(150),
|
|
1712
|
-
});
|
|
1713
|
-
if (!response.ok) {
|
|
1714
|
-
return null;
|
|
1715
|
-
}
|
|
1716
|
-
const payload = (await response.json()) as unknown;
|
|
1717
|
-
if (!isRecord(payload)) {
|
|
1718
|
-
return null;
|
|
1719
|
-
}
|
|
1720
|
-
const contextWindow = extractOllamaContextWindow(payload);
|
|
1721
|
-
const capabilities = payload.capabilities;
|
|
1722
|
-
if (Array.isArray(capabilities)) {
|
|
1723
|
-
const normalized = new Set(
|
|
1724
|
-
capabilities.flatMap(capability => (typeof capability === "string" ? [capability.toLowerCase()] : [])),
|
|
1725
|
-
);
|
|
1726
|
-
const supportsVision = normalized.has("vision") || normalized.has("image");
|
|
1727
|
-
return {
|
|
1728
|
-
reasoning: normalized.has("thinking"),
|
|
1729
|
-
input: supportsVision ? ["text", "image"] : ["text"],
|
|
1730
|
-
contextWindow,
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
if (!isRecord(capabilities)) {
|
|
1734
|
-
return {
|
|
1735
|
-
reasoning: false,
|
|
1736
|
-
input: ["text"],
|
|
1737
|
-
contextWindow,
|
|
1738
|
-
};
|
|
1739
|
-
}
|
|
1740
|
-
const supportsVision = capabilities.vision === true || capabilities.image === true;
|
|
1741
|
-
return {
|
|
1742
|
-
reasoning: capabilities.thinking === true,
|
|
1743
|
-
input: supportsVision ? ["text", "image"] : ["text"],
|
|
1744
|
-
contextWindow,
|
|
1745
|
-
};
|
|
1746
|
-
} catch {
|
|
1747
|
-
return null;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
async #discoverOllamaModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
1752
|
-
const endpoint = this.#normalizeOllamaBaseUrl(providerConfig.baseUrl);
|
|
1753
|
-
const tagsUrl = `${endpoint}/api/tags`;
|
|
1754
|
-
const headers = { ...(providerConfig.headers ?? {}) };
|
|
1755
|
-
const response = await fetch(tagsUrl, {
|
|
1756
|
-
headers,
|
|
1757
|
-
signal: AbortSignal.timeout(250),
|
|
1758
|
-
});
|
|
1759
|
-
if (!response.ok) {
|
|
1760
|
-
throw new Error(`HTTP ${response.status} from ${tagsUrl}`);
|
|
1761
|
-
}
|
|
1762
|
-
const payload = (await response.json()) as { models?: Array<{ name?: string; model?: string }> };
|
|
1763
|
-
const entries = (payload.models ?? []).flatMap(item => {
|
|
1764
|
-
const id = item.model || item.name;
|
|
1765
|
-
return id ? [{ id, name: item.name || id }] : [];
|
|
1766
|
-
});
|
|
1767
|
-
const metadataById = new Map(
|
|
1768
|
-
await Promise.all(
|
|
1769
|
-
entries.map(
|
|
1770
|
-
async entry => [entry.id, await this.#discoverOllamaModelMetadata(endpoint, entry.id, headers)] as const,
|
|
1771
|
-
),
|
|
1772
|
-
),
|
|
1773
|
-
);
|
|
1774
|
-
const discovered = entries.map(entry => {
|
|
1775
|
-
const metadata = metadataById.get(entry.id);
|
|
1776
|
-
return enrichModelThinking({
|
|
1777
|
-
id: entry.id,
|
|
1778
|
-
name: entry.name,
|
|
1779
|
-
api: providerConfig.api,
|
|
1780
|
-
provider: providerConfig.provider,
|
|
1781
|
-
baseUrl: `${endpoint}/v1`,
|
|
1782
|
-
reasoning: metadata?.reasoning ?? false,
|
|
1783
|
-
input: metadata?.input ?? ["text"],
|
|
1784
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1785
|
-
contextWindow: metadata?.contextWindow ?? 128000,
|
|
1786
|
-
maxTokens: Math.min(metadata?.contextWindow ?? Number.POSITIVE_INFINITY, DISCOVERY_DEFAULT_MAX_TOKENS),
|
|
1787
|
-
headers: providerConfig.headers,
|
|
1788
|
-
});
|
|
1789
|
-
});
|
|
1790
|
-
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
async #discoverLlamaCppServerMetadata(
|
|
1794
|
-
baseUrl: string,
|
|
1795
|
-
headers: Record<string, string> | undefined,
|
|
1796
|
-
): Promise<LlamaCppDiscoveredServerMetadata | null> {
|
|
1797
|
-
const propsUrl = `${this.#toLlamaCppNativeBaseUrl(baseUrl)}/props`;
|
|
1798
|
-
try {
|
|
1799
|
-
const response = await fetch(propsUrl, {
|
|
1800
|
-
headers,
|
|
1801
|
-
signal: AbortSignal.timeout(150),
|
|
1802
|
-
});
|
|
1803
|
-
if (!response.ok) {
|
|
1804
|
-
return null;
|
|
1805
|
-
}
|
|
1806
|
-
const payload = (await response.json()) as unknown;
|
|
1807
|
-
if (!isRecord(payload)) {
|
|
1808
|
-
return null;
|
|
1809
|
-
}
|
|
1810
|
-
return {
|
|
1811
|
-
contextWindow: extractLlamaCppContextWindow(payload),
|
|
1812
|
-
input: extractLlamaCppInputCapabilities(payload),
|
|
1813
|
-
};
|
|
1814
|
-
} catch {
|
|
1815
|
-
return null;
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
async #discoverLlamaCppModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
1820
|
-
const baseUrl = this.#normalizeLlamaCppBaseUrl(providerConfig.baseUrl);
|
|
1821
|
-
const modelsUrl = `${baseUrl}/models`;
|
|
1822
|
-
|
|
1823
|
-
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
1824
|
-
const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
|
|
1825
|
-
if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
|
|
1826
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
const [response, serverMetadata] = await Promise.all([
|
|
1830
|
-
fetch(modelsUrl, {
|
|
1831
|
-
headers,
|
|
1832
|
-
signal: AbortSignal.timeout(250),
|
|
1833
|
-
}),
|
|
1834
|
-
this.#discoverLlamaCppServerMetadata(baseUrl, headers),
|
|
1835
|
-
]);
|
|
1836
|
-
if (!response.ok) {
|
|
1837
|
-
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
1838
|
-
}
|
|
1839
|
-
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
|
1840
|
-
const models = payload.data ?? [];
|
|
1841
|
-
const discovered: Model<Api>[] = [];
|
|
1842
|
-
for (const item of models) {
|
|
1843
|
-
const id = item.id;
|
|
1844
|
-
if (!id) continue;
|
|
1845
|
-
discovered.push(
|
|
1846
|
-
enrichModelThinking({
|
|
1847
|
-
id,
|
|
1848
|
-
name: id,
|
|
1849
|
-
api: providerConfig.api,
|
|
1850
|
-
provider: providerConfig.provider,
|
|
1851
|
-
baseUrl,
|
|
1852
|
-
reasoning: false,
|
|
1853
|
-
input: serverMetadata?.input ?? ["text"],
|
|
1854
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1855
|
-
contextWindow: serverMetadata?.contextWindow ?? 128000,
|
|
1856
|
-
maxTokens: Math.min(
|
|
1857
|
-
serverMetadata?.contextWindow ?? Number.POSITIVE_INFINITY,
|
|
1858
|
-
DISCOVERY_DEFAULT_MAX_TOKENS,
|
|
1859
|
-
),
|
|
1860
|
-
headers,
|
|
1861
|
-
compat: {
|
|
1862
|
-
supportsStore: false,
|
|
1863
|
-
supportsDeveloperRole: false,
|
|
1864
|
-
supportsReasoningEffort: false,
|
|
1865
|
-
},
|
|
1866
|
-
}),
|
|
1867
|
-
);
|
|
1868
|
-
}
|
|
1869
|
-
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
async #discoverOpenAIModelsList(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
1873
|
-
const baseUrl = this.#normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
|
|
1874
|
-
const modelsUrl = `${baseUrl}/models`;
|
|
1875
|
-
|
|
1876
|
-
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
1877
|
-
const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
|
|
1878
|
-
if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
|
|
1879
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
const response = await fetch(modelsUrl, {
|
|
1883
|
-
headers,
|
|
1884
|
-
signal: AbortSignal.timeout(10_000),
|
|
1885
|
-
});
|
|
1886
|
-
if (!response.ok) {
|
|
1887
|
-
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
1888
|
-
}
|
|
1889
|
-
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
|
1890
|
-
const models = payload.data ?? [];
|
|
1891
|
-
const discovered: Model<Api>[] = [];
|
|
1892
|
-
for (const item of models) {
|
|
1893
|
-
const id = item.id;
|
|
1894
|
-
if (!id) continue;
|
|
1895
|
-
discovered.push(
|
|
1896
|
-
enrichModelThinking({
|
|
1897
|
-
id,
|
|
1898
|
-
name: id,
|
|
1899
|
-
api: providerConfig.api,
|
|
1900
|
-
provider: providerConfig.provider,
|
|
1901
|
-
baseUrl,
|
|
1902
|
-
reasoning: false,
|
|
1903
|
-
input: ["text"],
|
|
1904
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1905
|
-
contextWindow: 128000,
|
|
1906
|
-
maxTokens: discoveryDefaultMaxTokens(providerConfig.api),
|
|
1907
|
-
headers,
|
|
1908
|
-
compat: {
|
|
1909
|
-
supportsStore: false,
|
|
1910
|
-
supportsDeveloperRole: false,
|
|
1911
|
-
supportsReasoningEffort: false,
|
|
1912
|
-
},
|
|
1913
|
-
}),
|
|
1914
|
-
);
|
|
1915
|
-
}
|
|
1916
|
-
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
/**
|
|
1920
|
-
* Discover models from an Anthropic+OpenAI-compatible reseller proxy that
|
|
1921
|
-
* exposes both `/v1/messages` and `/v1/chat/completions`, advertising each
|
|
1922
|
-
* model's wire capabilities through `supported_endpoint_types` on
|
|
1923
|
-
* `GET /v1/models` (new-api / one-api-style proxies).
|
|
1924
|
-
*
|
|
1925
|
-
* Routing per model:
|
|
1926
|
-
* supported_endpoint_types: ["anthropic", ...] -> api: "anthropic-messages"
|
|
1927
|
-
* supported_endpoint_types: ["openai"] -> api: "openai-completions"
|
|
1928
|
-
* missing / neither -> provider-level api fallback
|
|
1929
|
-
*
|
|
1930
|
-
* Anthropic models share the same baseUrl; the Anthropic SDK strips a
|
|
1931
|
-
* trailing `/v1` itself before appending `/v1/messages`, so the discovery
|
|
1932
|
-
* URL (which ends in `/v1`) round-trips correctly.
|
|
1933
|
-
*/
|
|
1934
|
-
async #discoverProxyModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
1935
|
-
const baseUrl = this.#normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
|
|
1936
|
-
const modelsUrl = `${baseUrl}/models`;
|
|
1937
|
-
|
|
1938
|
-
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
1939
|
-
const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
|
|
1940
|
-
if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
|
|
1941
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
|
-
const response = await fetch(modelsUrl, {
|
|
1945
|
-
headers,
|
|
1946
|
-
signal: AbortSignal.timeout(10_000),
|
|
1947
|
-
});
|
|
1948
|
-
if (!response.ok) {
|
|
1949
|
-
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
1950
|
-
}
|
|
1951
|
-
const payload = (await response.json()) as {
|
|
1952
|
-
data?: Array<{ id?: string; name?: string; supported_endpoint_types?: string[] }>;
|
|
1953
|
-
};
|
|
1954
|
-
const items = payload.data ?? [];
|
|
1955
|
-
const discovered: Model<Api>[] = [];
|
|
1956
|
-
for (const item of items) {
|
|
1957
|
-
const id = item.id;
|
|
1958
|
-
if (!id) continue;
|
|
1959
|
-
const endpoints = item.supported_endpoint_types ?? [];
|
|
1960
|
-
const api: Api | undefined = endpoints.includes("anthropic")
|
|
1961
|
-
? "anthropic-messages"
|
|
1962
|
-
: endpoints.includes("openai")
|
|
1963
|
-
? "openai-completions"
|
|
1964
|
-
: providerConfig.api;
|
|
1965
|
-
if (!api) continue;
|
|
1966
|
-
const isAnthropic = api === "anthropic-messages";
|
|
1967
|
-
const reference = resolveCustomModelReference(id);
|
|
1968
|
-
const discoveryName = typeof item.name === "string" ? item.name.trim() : "";
|
|
1969
|
-
const displayName =
|
|
1970
|
-
reference?.name ??
|
|
1971
|
-
(discoveryName && discoveryName !== id ? discoveryName : undefined) ??
|
|
1972
|
-
stripBracketedModelIdAffixes(id) ??
|
|
1973
|
-
id;
|
|
1974
|
-
discovered.push(
|
|
1975
|
-
enrichModelThinking({
|
|
1976
|
-
id,
|
|
1977
|
-
name: displayName,
|
|
1978
|
-
api,
|
|
1979
|
-
provider: providerConfig.provider,
|
|
1980
|
-
baseUrl,
|
|
1981
|
-
reasoning: reference?.reasoning ?? false,
|
|
1982
|
-
thinking: reference?.thinking,
|
|
1983
|
-
input: reference?.input ?? ["text"],
|
|
1984
|
-
// Proxy pricing is provider-specific and usually does not match
|
|
1985
|
-
// upstream bundled catalogs, so keep costs local-unknown even when
|
|
1986
|
-
// we successfully recover the upstream model identity.
|
|
1987
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1988
|
-
contextWindow: reference?.contextWindow ?? 128000,
|
|
1989
|
-
maxTokens: reference?.maxTokens ?? discoveryDefaultMaxTokens(api),
|
|
1990
|
-
headers,
|
|
1991
|
-
// OpenAI-compat fields are no-ops on anthropic models; the
|
|
1992
|
-
// Anthropic SDK ignores them. Provider-level disableStrictTools
|
|
1993
|
-
// flows in via #applyProviderCompat for the third-party-Anthropic
|
|
1994
|
-
// path. Cross-wire bundled compat is intentionally not copied:
|
|
1995
|
-
// request-shaping fields are provider-wire specific.
|
|
1996
|
-
compat: isAnthropic
|
|
1997
|
-
? undefined
|
|
1998
|
-
: {
|
|
1999
|
-
supportsStore: false,
|
|
2000
|
-
supportsDeveloperRole: false,
|
|
2001
|
-
supportsReasoningEffort: false,
|
|
2002
|
-
},
|
|
2003
|
-
}),
|
|
2004
|
-
);
|
|
2005
|
-
}
|
|
2006
|
-
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
#normalizeLlamaCppBaseUrl(baseUrl?: string): string {
|
|
2010
|
-
const defaultBaseUrl = "http://127.0.0.1:8080";
|
|
2011
|
-
const raw = baseUrl || defaultBaseUrl;
|
|
2012
|
-
try {
|
|
2013
|
-
const parsed = new URL(raw);
|
|
2014
|
-
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
2015
|
-
return `${parsed.protocol}//${parsed.host}${trimmedPath}`;
|
|
2016
|
-
} catch {
|
|
2017
|
-
return raw;
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
#toLlamaCppNativeBaseUrl(baseUrl: string): string {
|
|
2022
|
-
try {
|
|
2023
|
-
const parsed = new URL(baseUrl);
|
|
2024
|
-
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
2025
|
-
parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath.slice(0, -3) || "/" : trimmedPath || "/";
|
|
2026
|
-
const normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
2027
|
-
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
2028
|
-
} catch {
|
|
2029
|
-
return baseUrl.endsWith("/v1") ? baseUrl.slice(0, -3) : baseUrl;
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
#normalizeOpenAIModelsListBaseUrl(baseUrl?: string): string {
|
|
2034
|
-
const defaultBaseUrl = "http://127.0.0.1:1234/v1";
|
|
2035
|
-
const raw = baseUrl || defaultBaseUrl;
|
|
2036
|
-
try {
|
|
2037
|
-
const parsed = new URL(raw);
|
|
2038
|
-
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
2039
|
-
parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath || "/v1" : `${trimmedPath}/v1`;
|
|
2040
|
-
return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
2041
|
-
} catch {
|
|
2042
|
-
return raw;
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
#normalizeOllamaBaseUrl(baseUrl?: string): string {
|
|
2046
|
-
const raw = baseUrl || DEFAULT_OLLAMA_BASE_URL;
|
|
2047
|
-
try {
|
|
2048
|
-
const parsed = new URL(raw);
|
|
2049
|
-
return `${parsed.protocol}//${parsed.host}`;
|
|
2050
|
-
} catch {
|
|
2051
|
-
return DEFAULT_OLLAMA_BASE_URL;
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
1485
|
#applyProviderModelOverrides(provider: string, models: Model<Api>[]): Model<Api>[] {
|
|
2056
1486
|
const overrides = this.#modelOverrides.get(provider);
|
|
2057
1487
|
if (!overrides || overrides.size === 0) return models;
|
|
1488
|
+
let liveIds: Set<string> | null = null;
|
|
1489
|
+
const hasLiveModel = (_provider: string, id: string) => {
|
|
1490
|
+
liveIds ??= new Set(models.map(m => m.id));
|
|
1491
|
+
return liveIds.has(id);
|
|
1492
|
+
};
|
|
2058
1493
|
return models.map(model => {
|
|
2059
|
-
const override = overrides
|
|
1494
|
+
const override = resolveModelOverrideWithAliases(overrides, model, hasLiveModel);
|
|
2060
1495
|
if (!override) return model;
|
|
2061
1496
|
return applyModelOverride(model, override);
|
|
2062
1497
|
});
|
|
@@ -2100,10 +1535,15 @@ export class ModelRegistry {
|
|
|
2100
1535
|
}
|
|
2101
1536
|
#applyModelOverrides(models: Model<Api>[], overrides: Map<string, Map<string, ModelOverride>>): Model<Api>[] {
|
|
2102
1537
|
if (overrides.size === 0) return models;
|
|
1538
|
+
let liveKeys: Set<string> | null = null;
|
|
1539
|
+
const hasLiveModel = (provider: string, id: string) => {
|
|
1540
|
+
liveKeys ??= new Set(models.map(m => `${m.provider}\u0000${m.id}`));
|
|
1541
|
+
return liveKeys.has(`${provider}\u0000${id}`);
|
|
1542
|
+
};
|
|
2103
1543
|
return models.map(model => {
|
|
2104
1544
|
const providerOverrides = overrides.get(model.provider);
|
|
2105
1545
|
if (!providerOverrides) return model;
|
|
2106
|
-
const override = providerOverrides
|
|
1546
|
+
const override = resolveModelOverrideWithAliases(providerOverrides, model, hasLiveModel);
|
|
2107
1547
|
if (!override) return model;
|
|
2108
1548
|
return applyModelOverride(model, override);
|
|
2109
1549
|
});
|
|
@@ -2129,10 +1569,25 @@ export class ModelRegistry {
|
|
|
2129
1569
|
this.#rebuildPending = true;
|
|
2130
1570
|
return;
|
|
2131
1571
|
}
|
|
2132
|
-
|
|
1572
|
+
// Defer the catalog-wide index build to first read. Boot model
|
|
1573
|
+
// resolution reads it only when enabledModels or a default-role pattern
|
|
1574
|
+
// is configured; the empty interactive launch never reads it pre-paint,
|
|
1575
|
+
// so the ~200ms build over the full catalog moves off the first-paint
|
|
1576
|
+
// critical path.
|
|
1577
|
+
this.#canonicalIndexDirty = true;
|
|
2133
1578
|
this.#rebuildPending = false;
|
|
2134
1579
|
}
|
|
2135
1580
|
|
|
1581
|
+
#ensureCanonicalIndex(): CanonicalModelIndex {
|
|
1582
|
+
if (this.#canonicalIndexDirty) {
|
|
1583
|
+
this.#canonicalIndex = logger.time("buildCanonicalModelIndex", () =>
|
|
1584
|
+
buildCanonicalModelIndex(this.#models, getBundledCanonicalReferenceData(), this.#equivalenceConfig),
|
|
1585
|
+
);
|
|
1586
|
+
this.#canonicalIndexDirty = false;
|
|
1587
|
+
}
|
|
1588
|
+
return this.#canonicalIndex;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
2136
1591
|
#suspendRebuild(): void {
|
|
2137
1592
|
this.#rebuildSuspended += 1;
|
|
2138
1593
|
}
|
|
@@ -2143,7 +1598,7 @@ export class ModelRegistry {
|
|
|
2143
1598
|
}
|
|
2144
1599
|
if (this.#rebuildSuspended === 0 && this.#rebuildPending) {
|
|
2145
1600
|
this.#rebuildPending = false;
|
|
2146
|
-
this.#
|
|
1601
|
+
this.#canonicalIndexDirty = true;
|
|
2147
1602
|
}
|
|
2148
1603
|
}
|
|
2149
1604
|
|
|
@@ -2153,10 +1608,9 @@ export class ModelRegistry {
|
|
|
2153
1608
|
for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
|
|
2154
1609
|
const modelDefs = providerConfig.models ?? [];
|
|
2155
1610
|
if (modelDefs.length === 0) continue; // Override-only, no custom models
|
|
1611
|
+
const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
|
|
2156
1612
|
if (providerConfig.apiKey) {
|
|
2157
|
-
this.#
|
|
2158
|
-
const resolved = resolveApiKeyConfig(providerConfig.apiKey);
|
|
2159
|
-
if (resolved) this.authStorage.setConfigApiKey(providerName, resolved);
|
|
1613
|
+
this.#installProviderApiKey(providerName, providerConfig.apiKey);
|
|
2160
1614
|
}
|
|
2161
1615
|
for (const modelDef of modelDefs) {
|
|
2162
1616
|
const providerCompat = providerConfig.disableStrictTools
|
|
@@ -2166,7 +1620,7 @@ export class ModelRegistry {
|
|
|
2166
1620
|
providerName,
|
|
2167
1621
|
providerConfig.baseUrl!,
|
|
2168
1622
|
providerConfig.api as Api | undefined,
|
|
2169
|
-
|
|
1623
|
+
resolvedProviderHeaders,
|
|
2170
1624
|
providerConfig.apiKey,
|
|
2171
1625
|
providerConfig.authHeader,
|
|
2172
1626
|
providerCompat,
|
|
@@ -2188,100 +1642,73 @@ export class ModelRegistry {
|
|
|
2188
1642
|
return this.#models;
|
|
2189
1643
|
}
|
|
2190
1644
|
|
|
2191
|
-
|
|
1645
|
+
/**
|
|
1646
|
+
* Availability predicate with per-provider memoization. Auth lookups
|
|
1647
|
+
* (`authStorage.hasAuth`) and the disabled-provider set are resolved once
|
|
1648
|
+
* per provider instead of once per model, which matters when filtering the
|
|
1649
|
+
* full bundled catalog (thousands of models, ~50 providers).
|
|
1650
|
+
*/
|
|
1651
|
+
#createAvailabilityCheck(): (model: Model<Api>) => boolean {
|
|
2192
1652
|
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
1653
|
+
const byProvider = new Map<string, boolean>();
|
|
1654
|
+
return model => {
|
|
1655
|
+
let available = byProvider.get(model.provider);
|
|
1656
|
+
if (available === undefined) {
|
|
1657
|
+
available =
|
|
1658
|
+
!disabledProviders.has(model.provider) &&
|
|
1659
|
+
(this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider));
|
|
1660
|
+
byProvider.set(model.provider, available);
|
|
1661
|
+
}
|
|
1662
|
+
return available;
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* Build the shared per-query filter state for canonical model queries.
|
|
1668
|
+
* Hoisted out of the per-record loop: building the candidate-selector set
|
|
1669
|
+
* and availability memo once per query instead of once per record is what
|
|
1670
|
+
* keeps `getCanonicalModelSelections` linear instead of O(records × candidates).
|
|
1671
|
+
*/
|
|
1672
|
+
#canonicalQueryFilters(options: CanonicalModelQueryOptions | undefined): {
|
|
1673
|
+
candidateKeys: Set<string> | undefined;
|
|
1674
|
+
isAvailable: ((model: Model<Api>) => boolean) | undefined;
|
|
1675
|
+
} {
|
|
1676
|
+
return {
|
|
1677
|
+
candidateKeys: options?.candidates
|
|
1678
|
+
? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
|
|
1679
|
+
: undefined,
|
|
1680
|
+
isAvailable: options?.availableOnly ? this.#createAvailabilityCheck() : undefined,
|
|
1681
|
+
};
|
|
2197
1682
|
}
|
|
2198
1683
|
|
|
2199
1684
|
#filterCanonicalVariants(
|
|
2200
1685
|
record: CanonicalModelRecord,
|
|
2201
|
-
|
|
1686
|
+
candidateKeys: ReadonlySet<string> | undefined,
|
|
1687
|
+
isAvailable: ((model: Model<Api>) => boolean) | undefined,
|
|
2202
1688
|
): CanonicalModelVariant[] {
|
|
2203
|
-
const candidateKeys = options?.candidates
|
|
2204
|
-
? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
|
|
2205
|
-
: undefined;
|
|
2206
1689
|
return record.variants.filter(variant => {
|
|
2207
1690
|
if (candidateKeys && !candidateKeys.has(variant.selector)) {
|
|
2208
1691
|
return false;
|
|
2209
1692
|
}
|
|
2210
|
-
if (
|
|
1693
|
+
if (isAvailable && !isAvailable(variant.model)) {
|
|
2211
1694
|
return false;
|
|
2212
1695
|
}
|
|
2213
1696
|
return true;
|
|
2214
1697
|
});
|
|
2215
1698
|
}
|
|
2216
1699
|
|
|
2217
|
-
#
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
for (const provider of configuredProviders) {
|
|
2222
|
-
const normalized = provider.trim().toLowerCase();
|
|
2223
|
-
if (!normalized || result.has(normalized)) {
|
|
2224
|
-
continue;
|
|
2225
|
-
}
|
|
2226
|
-
result.set(normalized, nextRank);
|
|
2227
|
-
nextRank += 1;
|
|
2228
|
-
}
|
|
2229
|
-
for (const model of models) {
|
|
2230
|
-
const normalized = model.provider.toLowerCase();
|
|
2231
|
-
if (result.has(normalized)) {
|
|
2232
|
-
continue;
|
|
2233
|
-
}
|
|
2234
|
-
result.set(normalized, nextRank);
|
|
2235
|
-
nextRank += 1;
|
|
2236
|
-
}
|
|
2237
|
-
return result;
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
#resolveCanonicalVariant(
|
|
2241
|
-
variants: readonly CanonicalModelVariant[],
|
|
2242
|
-
allCandidates: readonly Model<Api>[],
|
|
2243
|
-
): CanonicalModelVariant | undefined {
|
|
2244
|
-
if (variants.length === 0) {
|
|
2245
|
-
return undefined;
|
|
2246
|
-
}
|
|
2247
|
-
const providerRank = this.#providerRank(allCandidates);
|
|
2248
|
-
const modelOrder = new Map<string, number>();
|
|
2249
|
-
for (let index = 0; index < allCandidates.length; index += 1) {
|
|
2250
|
-
modelOrder.set(formatCanonicalVariantSelector(allCandidates[index]!), index);
|
|
2251
|
-
}
|
|
2252
|
-
const sourceRank: Record<CanonicalModelVariant["source"], number> = {
|
|
2253
|
-
override: 1,
|
|
2254
|
-
bundled: 1,
|
|
2255
|
-
heuristic: 2,
|
|
2256
|
-
fallback: 3,
|
|
1700
|
+
#variantPreferences(candidates: readonly Model<Api>[]): CanonicalVariantPreferences {
|
|
1701
|
+
return {
|
|
1702
|
+
modelOrder: buildCanonicalModelOrder(candidates),
|
|
1703
|
+
providerRank: buildModelProviderPriorityRank(getConfiguredProviderOrderFromSettings()),
|
|
2257
1704
|
};
|
|
2258
|
-
return [...variants].sort((left, right) => {
|
|
2259
|
-
const leftProviderRank = providerRank.get(left.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
|
|
2260
|
-
const rightProviderRank = providerRank.get(right.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
|
|
2261
|
-
if (leftProviderRank !== rightProviderRank) {
|
|
2262
|
-
return leftProviderRank - rightProviderRank;
|
|
2263
|
-
}
|
|
2264
|
-
const leftExact = left.model.id === left.canonicalId ? 0 : 1;
|
|
2265
|
-
const rightExact = right.model.id === right.canonicalId ? 0 : 1;
|
|
2266
|
-
if (leftExact !== rightExact) {
|
|
2267
|
-
return leftExact - rightExact;
|
|
2268
|
-
}
|
|
2269
|
-
if (sourceRank[left.source] !== sourceRank[right.source]) {
|
|
2270
|
-
return sourceRank[left.source] - sourceRank[right.source];
|
|
2271
|
-
}
|
|
2272
|
-
if (left.model.id.length !== right.model.id.length) {
|
|
2273
|
-
return left.model.id.length - right.model.id.length;
|
|
2274
|
-
}
|
|
2275
|
-
const leftOrder = modelOrder.get(left.selector) ?? Number.MAX_SAFE_INTEGER;
|
|
2276
|
-
const rightOrder = modelOrder.get(right.selector) ?? Number.MAX_SAFE_INTEGER;
|
|
2277
|
-
return leftOrder - rightOrder;
|
|
2278
|
-
})[0];
|
|
2279
1705
|
}
|
|
2280
1706
|
|
|
2281
1707
|
getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[] {
|
|
1708
|
+
const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
|
|
2282
1709
|
const records: CanonicalModelRecord[] = [];
|
|
2283
|
-
for (const record of this.#
|
|
2284
|
-
const variants = this.#filterCanonicalVariants(record,
|
|
1710
|
+
for (const record of this.#ensureCanonicalIndex().records) {
|
|
1711
|
+
const variants = this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
|
|
2285
1712
|
if (variants.length === 0) {
|
|
2286
1713
|
continue;
|
|
2287
1714
|
}
|
|
@@ -2294,12 +1721,42 @@ export class ModelRegistry {
|
|
|
2294
1721
|
return records;
|
|
2295
1722
|
}
|
|
2296
1723
|
|
|
1724
|
+
/**
|
|
1725
|
+
* One-pass equivalent of `getCanonicalModels` + `resolveCanonicalModel` per
|
|
1726
|
+
* record. The per-query state (candidate-selector set, availability memo,
|
|
1727
|
+
* provider rank, candidate order) is built once, so the whole catalog
|
|
1728
|
+
* resolves in O(records + candidates) instead of O(records × candidates).
|
|
1729
|
+
* This is the path the model selector hydrates from synchronously on open.
|
|
1730
|
+
*/
|
|
1731
|
+
getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[] {
|
|
1732
|
+
const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
|
|
1733
|
+
const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
|
|
1734
|
+
const preferences = this.#variantPreferences(candidates);
|
|
1735
|
+
const selections: CanonicalModelSelection[] = [];
|
|
1736
|
+
for (const record of this.#ensureCanonicalIndex().records) {
|
|
1737
|
+
const variants = this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
|
|
1738
|
+
if (variants.length === 0) {
|
|
1739
|
+
continue;
|
|
1740
|
+
}
|
|
1741
|
+
const resolved = resolveCanonicalVariant(variants, preferences);
|
|
1742
|
+
if (!resolved) {
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
selections.push({
|
|
1746
|
+
record: { id: record.id, name: record.name, variants },
|
|
1747
|
+
model: resolved.model,
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
return selections;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
2297
1753
|
getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[] {
|
|
2298
|
-
const record = this.#
|
|
1754
|
+
const record = this.#ensureCanonicalIndex().byId.get(canonicalId.trim().toLowerCase());
|
|
2299
1755
|
if (!record) {
|
|
2300
1756
|
return [];
|
|
2301
1757
|
}
|
|
2302
|
-
|
|
1758
|
+
const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
|
|
1759
|
+
return this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
|
|
2303
1760
|
}
|
|
2304
1761
|
|
|
2305
1762
|
resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined {
|
|
@@ -2308,11 +1765,11 @@ export class ModelRegistry {
|
|
|
2308
1765
|
return undefined;
|
|
2309
1766
|
}
|
|
2310
1767
|
const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
|
|
2311
|
-
return
|
|
1768
|
+
return resolveCanonicalVariant(variants, this.#variantPreferences(candidates))?.model;
|
|
2312
1769
|
}
|
|
2313
1770
|
|
|
2314
1771
|
getCanonicalId(model: Model<Api>): string | undefined {
|
|
2315
|
-
return this.#
|
|
1772
|
+
return this.#ensureCanonicalIndex().bySelector.get(formatCanonicalVariantSelector(model).toLowerCase());
|
|
2316
1773
|
}
|
|
2317
1774
|
|
|
2318
1775
|
/**
|
|
@@ -2320,20 +1777,32 @@ export class ModelRegistry {
|
|
|
2320
1777
|
* This is a fast check that doesn't refresh OAuth tokens.
|
|
2321
1778
|
*/
|
|
2322
1779
|
getAvailable(): Model<Api>[] {
|
|
2323
|
-
return this.#models.filter(
|
|
1780
|
+
return this.#models.filter(this.#createAvailabilityCheck());
|
|
2324
1781
|
}
|
|
2325
1782
|
|
|
2326
1783
|
/**
|
|
2327
1784
|
* Check whether auth is configured for a model's provider.
|
|
2328
1785
|
*
|
|
2329
|
-
* Mirrors the upstream `@mariozechner/
|
|
1786
|
+
* Mirrors the upstream `@mariozechner/agent` API surface so that
|
|
2330
1787
|
* external plugins/extensions and downstream wrappers (e.g. subagent launch
|
|
2331
1788
|
* paths that pre-flight auth before model resolution) can probe a model
|
|
2332
1789
|
* without resolving an API key. Returns true for keyless providers as well
|
|
2333
1790
|
* as providers with stored credentials. See issue #993.
|
|
1791
|
+
*
|
|
1792
|
+
* Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
|
|
1793
|
+
* configured by its presence alone — the program is NOT executed — and OAuth
|
|
1794
|
+
* tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the
|
|
1795
|
+
* model-switch pre-flight off the event loop's hot path; the real key
|
|
1796
|
+
* (command execution + OAuth refresh) is resolved lazily per request via
|
|
1797
|
+
* {@link ModelRegistry.resolver}.
|
|
2334
1798
|
*/
|
|
2335
1799
|
hasConfiguredAuth(model: Model<Api>): boolean {
|
|
2336
|
-
|
|
1800
|
+
const keyConfig = this.#customProviderApiKeys.get(model.provider);
|
|
1801
|
+
return (
|
|
1802
|
+
isCommandConfigValue(keyConfig) ||
|
|
1803
|
+
this.#keylessProviders.has(model.provider) ||
|
|
1804
|
+
this.authStorage.hasAuth(model.provider)
|
|
1805
|
+
);
|
|
2337
1806
|
}
|
|
2338
1807
|
|
|
2339
1808
|
getDiscoverableProviders(): string[] {
|
|
@@ -2365,6 +1834,8 @@ export class ModelRegistry {
|
|
|
2365
1834
|
* Get API key for a model.
|
|
2366
1835
|
*/
|
|
2367
1836
|
async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
|
|
1837
|
+
const commandKey = this.#resolveCommandBackedApiKey(model.provider);
|
|
1838
|
+
if (commandKey.configured) return commandKey.value;
|
|
2368
1839
|
if (this.#keylessProviders.has(model.provider) && !this.authStorage.hasAuth(model.provider)) {
|
|
2369
1840
|
return kNoAuth;
|
|
2370
1841
|
}
|
|
@@ -2373,15 +1844,53 @@ export class ModelRegistry {
|
|
|
2373
1844
|
|
|
2374
1845
|
/**
|
|
2375
1846
|
* Get API key for a provider (e.g., "openai").
|
|
1847
|
+
*
|
|
1848
|
+
* `options.forceRefresh` powers step (b) of the auth-retry policy — it
|
|
1849
|
+
* re-mints the session-sticky OAuth token even when the cached copy still
|
|
1850
|
+
* looks valid. `options.signal` is threaded into any broker-bound refresh.
|
|
2376
1851
|
*/
|
|
2377
|
-
async getApiKeyForProvider(
|
|
1852
|
+
async getApiKeyForProvider(
|
|
1853
|
+
provider: string,
|
|
1854
|
+
sessionId?: string,
|
|
1855
|
+
options?: { baseUrl?: string; modelId?: string; forceRefresh?: boolean; signal?: AbortSignal },
|
|
1856
|
+
): Promise<string | undefined> {
|
|
1857
|
+
const commandKey = this.#resolveCommandBackedApiKey(provider);
|
|
1858
|
+
if (commandKey.configured) return commandKey.value;
|
|
2378
1859
|
if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
|
|
2379
1860
|
return kNoAuth;
|
|
2380
1861
|
}
|
|
2381
|
-
return this.authStorage.getApiKey(provider, sessionId, {
|
|
1862
|
+
return this.authStorage.getApiKey(provider, sessionId, {
|
|
1863
|
+
baseUrl: options?.baseUrl,
|
|
1864
|
+
modelId: options?.modelId,
|
|
1865
|
+
forceRefresh: options?.forceRefresh,
|
|
1866
|
+
signal: options?.signal,
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
|
|
1872
|
+
* policy. Accepts a provider id with options, or a model with an optional
|
|
1873
|
+
* session id (`resolver(model, sessionId)`) which derives `baseUrl`/`modelId`
|
|
1874
|
+
* from the model. Callers that need the initial key for a guard can call
|
|
1875
|
+
* `resolveApiKeyOnce(resolver)`.
|
|
1876
|
+
*/
|
|
1877
|
+
resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
|
|
1878
|
+
resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
|
|
1879
|
+
resolver(target: string | ApiKeyResolverModel, optionsOrSessionId?: ApiKeyResolverOptions | string): ApiKeyResolver {
|
|
1880
|
+
const options = typeof optionsOrSessionId === "string" ? { sessionId: optionsOrSessionId } : optionsOrSessionId;
|
|
1881
|
+
if (typeof target === "string") {
|
|
1882
|
+
return createApiKeyResolver(this, target, options);
|
|
1883
|
+
}
|
|
1884
|
+
return createApiKeyResolver(this, target.provider, {
|
|
1885
|
+
...options,
|
|
1886
|
+
baseUrl: target.baseUrl,
|
|
1887
|
+
modelId: target.id,
|
|
1888
|
+
});
|
|
2382
1889
|
}
|
|
2383
1890
|
|
|
2384
1891
|
async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
|
|
1892
|
+
const commandKey = this.#resolveCommandBackedApiKey(provider);
|
|
1893
|
+
if (commandKey.configured) return commandKey.value;
|
|
2385
1894
|
if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
|
|
2386
1895
|
return kNoAuth;
|
|
2387
1896
|
}
|
|
@@ -2399,6 +1908,7 @@ export class ModelRegistry {
|
|
|
2399
1908
|
this.#runtimeProviderApiKeys.delete(providerName);
|
|
2400
1909
|
this.#runtimeProviderOverrides.delete(providerName);
|
|
2401
1910
|
this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(overlay => overlay.provider !== providerName);
|
|
1911
|
+
this.#runtimeModelManagers.delete(providerName);
|
|
2402
1912
|
this.authStorage.removeConfigApiKey(providerName);
|
|
2403
1913
|
}
|
|
2404
1914
|
|
|
@@ -2504,11 +2014,9 @@ export class ModelRegistry {
|
|
|
2504
2014
|
}
|
|
2505
2015
|
|
|
2506
2016
|
if (config.apiKey) {
|
|
2507
|
-
this.#
|
|
2017
|
+
this.#installProviderApiKey(providerName, config.apiKey);
|
|
2508
2018
|
// Persist runtime API keys so they survive #reloadStaticModels() cycles
|
|
2509
2019
|
this.#runtimeProviderApiKeys.set(providerName, config.apiKey);
|
|
2510
|
-
const resolved = resolveApiKeyConfig(config.apiKey);
|
|
2511
|
-
if (resolved) this.authStorage.setConfigApiKey(providerName, resolved);
|
|
2512
2020
|
}
|
|
2513
2021
|
|
|
2514
2022
|
if (config.models && config.models.length > 0) {
|
|
@@ -2562,6 +2070,47 @@ export class ModelRegistry {
|
|
|
2562
2070
|
return;
|
|
2563
2071
|
}
|
|
2564
2072
|
|
|
2073
|
+
if (config.fetchDynamicModels) {
|
|
2074
|
+
const fetcher = config.fetchDynamicModels;
|
|
2075
|
+
const providerBaseUrl = config.baseUrl ?? "";
|
|
2076
|
+
const providerApi = config.api;
|
|
2077
|
+
const providerHeaders = config.headers;
|
|
2078
|
+
const providerApiKey = config.apiKey;
|
|
2079
|
+
const providerAuthHeader = config.authHeader;
|
|
2080
|
+
const providerCompat = config.compat;
|
|
2081
|
+
const managerOptions: ModelManagerOptions<Api> = {
|
|
2082
|
+
providerId: providerName as Parameters<typeof createModelManager>[0]["providerId"],
|
|
2083
|
+
staticModels: [],
|
|
2084
|
+
cacheDbPath: this.#cacheDbPath,
|
|
2085
|
+
cacheTtlMs: 24 * 60 * 60 * 1000,
|
|
2086
|
+
dynamicModelsAuthoritative: true,
|
|
2087
|
+
fetchDynamicModels: async () => {
|
|
2088
|
+
const apiKey = await this.#peekApiKeyForProvider(providerName);
|
|
2089
|
+
const resolvedKey = isAuthenticated(apiKey) ? apiKey : undefined;
|
|
2090
|
+
const modelDefs = await fetcher(resolvedKey);
|
|
2091
|
+
const results: Model<Api>[] = [];
|
|
2092
|
+
for (const modelDef of modelDefs) {
|
|
2093
|
+
const overlay = buildCustomModelOverlay(
|
|
2094
|
+
providerName,
|
|
2095
|
+
modelDef.baseUrl ?? providerBaseUrl,
|
|
2096
|
+
modelDef.api ?? providerApi,
|
|
2097
|
+
providerHeaders,
|
|
2098
|
+
providerApiKey,
|
|
2099
|
+
providerAuthHeader,
|
|
2100
|
+
providerCompat,
|
|
2101
|
+
undefined,
|
|
2102
|
+
modelDef as CustomModelDefinitionLike,
|
|
2103
|
+
);
|
|
2104
|
+
if (overlay) results.push(finalizeCustomModel(overlay, { useDefaults: true }));
|
|
2105
|
+
}
|
|
2106
|
+
return results.map(toModelSpec);
|
|
2107
|
+
},
|
|
2108
|
+
};
|
|
2109
|
+
this.#runtimeModelManagers.set(providerName, { options: managerOptions, sourceId: sourceId ?? "" });
|
|
2110
|
+
// Discovery is driven by refreshRuntimeProviders() after the drain — not
|
|
2111
|
+
// here, so registration has no network side effect and callers can await.
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2565
2114
|
if (
|
|
2566
2115
|
config.baseUrl ||
|
|
2567
2116
|
config.headers ||
|
|
@@ -2609,6 +2158,14 @@ export class ModelRegistry {
|
|
|
2609
2158
|
}
|
|
2610
2159
|
return true;
|
|
2611
2160
|
}
|
|
2161
|
+
|
|
2162
|
+
/**
|
|
2163
|
+
* Clear all cooldown suppressions recorded via {@link suppressSelector}.
|
|
2164
|
+
* Used to reset retry-fallback cooldown state without a full {@link refresh}.
|
|
2165
|
+
*/
|
|
2166
|
+
clearSuppressedSelectors(): void {
|
|
2167
|
+
this.#suppressedSelectors.clear();
|
|
2168
|
+
}
|
|
2612
2169
|
}
|
|
2613
2170
|
|
|
2614
2171
|
/**
|
|
@@ -2620,7 +2177,7 @@ export interface ProviderConfigInput {
|
|
|
2620
2177
|
api?: Api;
|
|
2621
2178
|
streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
2622
2179
|
headers?: Record<string, string>;
|
|
2623
|
-
compat?:
|
|
2180
|
+
compat?: ModelSpec<Api>["compat"];
|
|
2624
2181
|
authHeader?: boolean;
|
|
2625
2182
|
/** Streaming transport override — see {@link Model.transport}. */
|
|
2626
2183
|
transport?: Model<Api>["transport"];
|
|
@@ -2631,6 +2188,15 @@ export interface ProviderConfigInput {
|
|
|
2631
2188
|
getApiKey?(credentials: OAuthCredentials): string;
|
|
2632
2189
|
modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
|
|
2633
2190
|
};
|
|
2191
|
+
/**
|
|
2192
|
+
* Async factory that fetches the live model list from the provider endpoint.
|
|
2193
|
+
* When present, the result is run through the same SQLite model-cache as
|
|
2194
|
+
* built-in providers (keyed by provider name, default 24 h TTL).
|
|
2195
|
+
* The factory receives the resolved API key (undefined when unauthenticated).
|
|
2196
|
+
*/
|
|
2197
|
+
fetchDynamicModels?: (
|
|
2198
|
+
apiKey: string | undefined,
|
|
2199
|
+
) => Promise<readonly NonNullable<ProviderConfigInput["models"]>[number][]>;
|
|
2634
2200
|
models?: Array<{
|
|
2635
2201
|
id: string;
|
|
2636
2202
|
name: string;
|
|
@@ -2643,7 +2209,7 @@ export interface ProviderConfigInput {
|
|
|
2643
2209
|
contextWindow: number;
|
|
2644
2210
|
maxTokens: number;
|
|
2645
2211
|
headers?: Record<string, string>;
|
|
2646
|
-
compat?:
|
|
2212
|
+
compat?: ModelSpec<Api>["compat"];
|
|
2647
2213
|
contextPromotionTarget?: string;
|
|
2648
2214
|
premiumMultiplier?: number;
|
|
2649
2215
|
}>;
|