@kr78/pi-coding-agent 17.2.10
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 +14861 -0
- package/README.md +35 -0
- package/dist/CHANGELOG-chtdxh1z.md +14861 -0
- package/dist/cli.js +20483 -0
- package/dist/template-c2hyaytt.js +1653 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +104 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/extensions/README.md +142 -0
- package/examples/extensions/api-demo.ts +78 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +31 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +549 -0
- package/examples/extensions/reload-runtime.ts +38 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +17 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +149 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +118 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +46 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +82 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +41 -0
- package/examples/sdk/08-slash-commands.ts +46 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/12-redis-sessions.ts +54 -0
- package/examples/sdk/13-sql-sessions.ts +61 -0
- package/examples/sdk/README.md +169 -0
- package/package.json +587 -0
- package/scripts/bench-guard.ts +71 -0
- package/scripts/bench-title-models.ts +332 -0
- package/scripts/build-binary.ts +117 -0
- package/scripts/bundle-dist.ts +123 -0
- package/scripts/compile-binary.ts +69 -0
- package/scripts/embed-mupdf-wasm.ts +67 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-aria-snapshot.ts +134 -0
- package/scripts/generate-docs-index.ts +58 -0
- package/scripts/generate-share-viewer.ts +34 -0
- package/scripts/legacy-pi-virtual-module.ts +205 -0
- package/scripts/measure-prompt-tokens.ts +63 -0
- package/scripts/omp +51 -0
- package/scripts/omp.ts +19 -0
- package/scripts/security-compare.ts +40 -0
- package/src/advisor/advise-tool.ts +234 -0
- package/src/advisor/config.ts +341 -0
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +6 -0
- package/src/advisor/runtime.ts +1412 -0
- package/src/advisor/transcript-recorder.ts +215 -0
- package/src/advisor/watchdog.ts +135 -0
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +847 -0
- package/src/auto-thinking/classifier.ts +220 -0
- package/src/autolearn/controller.ts +152 -0
- package/src/autolearn/managed-skills.ts +255 -0
- package/src/autoresearch/command-resume.md +14 -0
- package/src/autoresearch/dashboard.ts +436 -0
- package/src/autoresearch/git.ts +331 -0
- package/src/autoresearch/helpers.ts +218 -0
- package/src/autoresearch/index.ts +541 -0
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +103 -0
- package/src/autoresearch/resume-message.md +10 -0
- package/src/autoresearch/state.ts +273 -0
- package/src/autoresearch/storage.ts +700 -0
- package/src/autoresearch/tools/init-experiment.ts +268 -0
- package/src/autoresearch/tools/log-experiment.ts +520 -0
- package/src/autoresearch/tools/run-experiment.ts +407 -0
- package/src/autoresearch/tools/update-notes.ts +109 -0
- package/src/autoresearch/types.ts +168 -0
- package/src/capability/context-file.ts +44 -0
- package/src/capability/extension-module.ts +34 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +117 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +467 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +115 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule-buckets.ts +66 -0
- package/src/capability/rule.ts +298 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +69 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +187 -0
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/agents-cli.ts +138 -0
- package/src/cli/args.ts +368 -0
- package/src/cli/auth-broker-cli.ts +940 -0
- package/src/cli/auth-gateway-cli.ts +674 -0
- package/src/cli/bench-cli.ts +990 -0
- package/src/cli/browser-relay-cli.ts +119 -0
- package/src/cli/classify-install-target.ts +76 -0
- package/src/cli/claude-trace-cli.ts +795 -0
- package/src/cli/command-help.ts +107 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/completion-gen.ts +550 -0
- package/src/cli/config-cli.ts +459 -0
- package/src/cli/dry-balance-cli.ts +864 -0
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/file-processor.ts +132 -0
- package/src/cli/flag-tables.ts +367 -0
- package/src/cli/gallery-cli.ts +272 -0
- package/src/cli/gallery-fixtures/agentic.ts +420 -0
- package/src/cli/gallery-fixtures/codeintel.ts +187 -0
- package/src/cli/gallery-fixtures/edit.ts +254 -0
- package/src/cli/gallery-fixtures/fs.ts +245 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +46 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +177 -0
- package/src/cli/gallery-fixtures/search.ts +135 -0
- package/src/cli/gallery-fixtures/shell.ts +241 -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/gc-cli.ts +1566 -0
- package/src/cli/grep-cli.ts +161 -0
- package/src/cli/grievances-cli.ts +256 -0
- package/src/cli/help-extra.ts +89 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/models-cli.ts +385 -0
- package/src/cli/plugin-cli.ts +996 -0
- package/src/cli/profile-alias.ts +369 -0
- package/src/cli/profile-bootstrap.ts +233 -0
- package/src/cli/read-cli.ts +99 -0
- package/src/cli/session-picker.ts +110 -0
- package/src/cli/setup-cli.ts +312 -0
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli/shell-cli.ts +176 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/startup-cwd.ts +58 -0
- package/src/cli/stats-cli.ts +229 -0
- package/src/cli/thinking-levels.ts +7 -0
- package/src/cli/tiny-models-cli.ts +153 -0
- package/src/cli/ttsr-cli.ts +1013 -0
- package/src/cli/update-cli.ts +1184 -0
- package/src/cli/usage-cli.ts +1084 -0
- package/src/cli/usage-error.ts +7 -0
- package/src/cli/web-search-cli.ts +144 -0
- package/src/cli/worktree-cli.ts +311 -0
- package/src/cli-commands.ts +308 -0
- package/src/cli.ts +428 -0
- package/src/collab/crypto.ts +63 -0
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +764 -0
- package/src/collab/host.ts +692 -0
- package/src/collab/protocol.ts +296 -0
- package/src/collab/relay-client.ts +282 -0
- package/src/collab/replication-shrink.ts +111 -0
- package/src/commands/acp.ts +35 -0
- package/src/commands/agents.ts +58 -0
- package/src/commands/auth-broker.ts +100 -0
- package/src/commands/auth-gateway.ts +70 -0
- package/src/commands/bench.ts +64 -0
- package/src/commands/browser-relay.ts +53 -0
- package/src/commands/cleanse.ts +45 -0
- package/src/commands/commit.ts +47 -0
- package/src/commands/complete.ts +67 -0
- package/src/commands/completions.ts +61 -0
- package/src/commands/config.ts +52 -0
- package/src/commands/dry-balance.ts +43 -0
- package/src/commands/gallery.ts +61 -0
- package/src/commands/gc.ts +47 -0
- package/src/commands/grep.ts +49 -0
- package/src/commands/grievances.ts +52 -0
- package/src/commands/install.ts +107 -0
- package/src/commands/join.ts +40 -0
- package/src/commands/launch-help.ts +116 -0
- package/src/commands/launch.ts +34 -0
- package/src/commands/models.ts +61 -0
- package/src/commands/plugin.ts +79 -0
- package/src/commands/read.ts +39 -0
- package/src/commands/say.ts +146 -0
- package/src/commands/setup.ts +68 -0
- package/src/commands/share.ts +71 -0
- package/src/commands/shell.ts +30 -0
- package/src/commands/ssh.ts +61 -0
- package/src/commands/stats.ts +30 -0
- package/src/commands/tiny-models.ts +36 -0
- package/src/commands/token.ts +164 -0
- package/src/commands/ttsr.ts +125 -0
- package/src/commands/update.ts +34 -0
- package/src/commands/usage.ts +55 -0
- package/src/commands/web-search.ts +43 -0
- package/src/commands/worktree.ts +63 -0
- package/src/commit/agentic/agent.ts +322 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +383 -0
- package/src/commit/agentic/lock-files.ts +107 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +60 -0
- package/src/commit/agentic/tools/analyze-file.ts +148 -0
- package/src/commit/agentic/tools/git-file-diff.ts +191 -0
- package/src/commit/agentic/tools/git-hunk.ts +52 -0
- package/src/commit/agentic/tools/git-overview.ts +62 -0
- package/src/commit/agentic/tools/index.ts +54 -0
- package/src/commit/agentic/tools/propose-changelog.ts +147 -0
- package/src/commit/agentic/tools/propose-commit.ts +109 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/schemas.ts +11 -0
- package/src/commit/agentic/tools/split-commit.ts +241 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +183 -0
- package/src/commit/analysis/conventional.ts +64 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +107 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +40 -0
- package/src/commit/changelog/generate.ts +101 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +85 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +69 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +49 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +95 -0
- package/src/commit/pipeline.ts +244 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/shared-llm.ts +70 -0
- package/src/commit/types.ts +118 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/commit/utils.ts +58 -0
- package/src/config/api-key-resolver.ts +81 -0
- package/src/config/append-only-context-mode.ts +76 -0
- package/src/config/config-file.ts +347 -0
- package/src/config/inline-tool-descriptors-mode.ts +26 -0
- package/src/config/keybindings.ts +703 -0
- package/src/config/mcp-schema.json +252 -0
- package/src/config/model-discovery.ts +1077 -0
- package/src/config/model-registry.ts +2822 -0
- package/src/config/model-resolver.ts +2115 -0
- package/src/config/model-roles.ts +113 -0
- package/src/config/models-config-schema-bundle.ts +315 -0
- package/src/config/models-config-schema.ts +14 -0
- package/src/config/models-config.ts +130 -0
- package/src/config/prompt-templates.ts +205 -0
- package/src/config/provider-globals.ts +25 -0
- package/src/config/resolve-config-value.ts +95 -0
- package/src/config/service-tier.ts +146 -0
- package/src/config/settings-schema.ts +5887 -0
- package/src/config/settings.ts +2394 -0
- package/src/config.ts +242 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +960 -0
- package/src/dap/client.ts +1043 -0
- package/src/dap/config.ts +480 -0
- package/src/dap/defaults.json +212 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1841 -0
- package/src/dap/types.ts +611 -0
- package/src/debug/index.ts +584 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +966 -0
- package/src/debug/profiler.ts +168 -0
- package/src/debug/protocol-probe.ts +267 -0
- package/src/debug/raw-sse-buffer.ts +421 -0
- package/src/debug/raw-sse.ts +312 -0
- package/src/debug/remote-debugger.ts +151 -0
- package/src/debug/report-bundle.ts +411 -0
- package/src/debug/system-info.ts +111 -0
- package/src/debug/terminal-info.ts +124 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +300 -0
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-defaults.ts +39 -0
- package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
- package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
- package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
- package/src/discovery/builtin-rules/go-ioutil.md +37 -0
- package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
- package/src/discovery/builtin-rules/go-new-expr.md +44 -0
- package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
- package/src/discovery/builtin-rules/go-range-int.md +45 -0
- package/src/discovery/builtin-rules/index.ts +74 -0
- package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
- package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
- package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
- package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
- package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
- package/src/discovery/builtin-rules/rs-result-type.md +20 -0
- package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
- package/src/discovery/builtin-rules/ts-import-type.md +43 -0
- package/src/discovery/builtin-rules/ts-no-any.md +66 -0
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
- package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
- package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
- package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/builtin-rules/ts-set-map.md +28 -0
- package/src/discovery/builtin.ts +945 -0
- package/src/discovery/claude-plugins.ts +640 -0
- package/src/discovery/claude.ts +591 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +553 -0
- package/src/discovery/contained-path.ts +78 -0
- package/src/discovery/cursor.ts +223 -0
- package/src/discovery/gemini.ts +386 -0
- package/src/discovery/github.ts +337 -0
- package/src/discovery/helpers.ts +1183 -0
- package/src/discovery/index.ts +82 -0
- package/src/discovery/mcp-json.ts +182 -0
- package/src/discovery/omp-extension-roots.ts +272 -0
- package/src/discovery/omp-plugins.ts +409 -0
- package/src/discovery/opencode.ts +441 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +77 -0
- package/src/discovery/vscode.ts +106 -0
- package/src/discovery/windsurf.ts +149 -0
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +1008 -0
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/file-snapshot-store.ts +148 -0
- package/src/edit/hashline/block-resolver.ts +33 -0
- package/src/edit/hashline/diff.ts +402 -0
- package/src/edit/hashline/execute.ts +313 -0
- package/src/edit/hashline/filesystem.ts +247 -0
- package/src/edit/hashline/index.ts +5 -0
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/hashline/params.ts +12 -0
- package/src/edit/index.ts +699 -0
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +53 -0
- package/src/edit/modes/patch.ts +1958 -0
- package/src/edit/modes/replace.ts +1191 -0
- package/src/edit/normalize.ts +345 -0
- package/src/edit/notebook.ts +242 -0
- package/src/edit/read-file.ts +25 -0
- package/src/edit/renderer.ts +998 -0
- package/src/edit/snapshot-details.ts +77 -0
- package/src/edit/streaming.ts +710 -0
- package/src/eval/agent-bridge.ts +224 -0
- package/src/eval/backend-helpers.ts +48 -0
- package/src/eval/backend.ts +71 -0
- package/src/eval/bridge-timeout.ts +65 -0
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/completion-bridge.ts +212 -0
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/executor-base.ts +562 -0
- package/src/eval/idle-timeout.ts +91 -0
- package/src/eval/index.ts +6 -0
- package/src/eval/jl/executor.ts +551 -0
- package/src/eval/jl/index.ts +54 -0
- package/src/eval/jl/kernel.ts +236 -0
- package/src/eval/jl/prelude.jl +736 -0
- package/src/eval/jl/prelude.ts +3 -0
- package/src/eval/jl/runner.jl +666 -0
- package/src/eval/jl/runtime.ts +118 -0
- package/src/eval/js/context-manager.ts +872 -0
- package/src/eval/js/executor.ts +178 -0
- package/src/eval/js/index.ts +41 -0
- package/src/eval/js/process-entry.ts +31 -0
- package/src/eval/js/shared/helpers.ts +170 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/local-module-loader.ts +364 -0
- package/src/eval/js/shared/prelude.ts +2 -0
- package/src/eval/js/shared/prelude.txt +294 -0
- package/src/eval/js/shared/rewrite-imports.ts +550 -0
- package/src/eval/js/shared/runtime.ts +590 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +163 -0
- package/src/eval/js/worker-core.ts +380 -0
- package/src/eval/js/worker-entry.ts +37 -0
- package/src/eval/js/worker-protocol.ts +47 -0
- package/src/eval/kernel-base.ts +569 -0
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +643 -0
- package/src/eval/py/index.ts +57 -0
- package/src/eval/py/kernel.ts +235 -0
- package/src/eval/py/prelude.py +673 -0
- package/src/eval/py/prelude.ts +3 -0
- package/src/eval/py/runner.py +1425 -0
- package/src/eval/py/runtime.ts +276 -0
- package/src/eval/py/spawn-options.ts +134 -0
- package/src/eval/py/tool-bridge.ts +201 -0
- package/src/eval/rb/executor.ts +511 -0
- package/src/eval/rb/index.ts +54 -0
- package/src/eval/rb/kernel.ts +231 -0
- package/src/eval/rb/prelude.rb +551 -0
- package/src/eval/rb/prelude.ts +3 -0
- package/src/eval/rb/runner.rb +581 -0
- package/src/eval/rb/runtime.ts +132 -0
- package/src/eval/runtime-env.ts +104 -0
- package/src/eval/session-id.ts +8 -0
- package/src/eval/types.ts +48 -0
- package/src/exa/index.ts +2 -0
- package/src/exa/mcp-client.ts +370 -0
- package/src/exa/types.ts +69 -0
- package/src/exec/bash-executor.ts +627 -0
- package/src/exec/direnv.ts +145 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/non-interactive-env.ts +118 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +320 -0
- package/src/export/html/share-loader.js +102 -0
- package/src/export/html/template.css +1355 -0
- package/src/export/html/template.html +55 -0
- package/src/export/html/template.js +1653 -0
- package/src/export/html/tool-views.generated.js +35 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/html/web-palette.ts +126 -0
- package/src/export/share.ts +686 -0
- package/src/export/ttsr.ts +590 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
- package/src/extensibility/custom-commands/index.ts +2 -0
- package/src/extensibility/custom-commands/loader.ts +242 -0
- package/src/extensibility/custom-commands/types.ts +119 -0
- package/src/extensibility/custom-tools/index.ts +7 -0
- package/src/extensibility/custom-tools/loader.ts +301 -0
- package/src/extensibility/custom-tools/types.ts +287 -0
- package/src/extensibility/custom-tools/wrapper.ts +50 -0
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/get-commands-handler.ts +78 -0
- package/src/extensibility/extensions/index.ts +16 -0
- package/src/extensibility/extensions/load-errors.ts +13 -0
- package/src/extensibility/extensions/loader.ts +663 -0
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/model-api.ts +39 -0
- package/src/extensibility/extensions/runner.ts +1396 -0
- package/src/extensibility/extensions/types.ts +1603 -0
- package/src/extensibility/extensions/wrapper.ts +395 -0
- package/src/extensibility/hooks/index.ts +6 -0
- package/src/extensibility/hooks/loader.ts +243 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +124 -0
- package/src/extensibility/hooks/types.ts +612 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
- package/src/extensibility/legacy-typebox.ts +101 -0
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/doctor.ts +65 -0
- package/src/extensibility/plugins/git-url.ts +367 -0
- package/src/extensibility/plugins/index.ts +9 -0
- package/src/extensibility/plugins/installer.ts +201 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
- package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
- package/src/extensibility/plugins/loader.ts +535 -0
- package/src/extensibility/plugins/manager.ts +1142 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +927 -0
- package/src/extensibility/plugins/marketplace/registry.ts +192 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +192 -0
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/extensibility/plugins/parser.ts +107 -0
- package/src/extensibility/plugins/runtime-config.ts +9 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/session-handler-types.ts +21 -0
- package/src/extensibility/shared-events.ts +401 -0
- package/src/extensibility/skills.ts +531 -0
- package/src/extensibility/slash-commands.ts +131 -0
- package/src/extensibility/tool-event-input.ts +80 -0
- package/src/extensibility/tool-proxy.ts +28 -0
- package/src/extensibility/utils.ts +192 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +521 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +251 -0
- package/src/hindsight/backend.ts +354 -0
- package/src/hindsight/bank.ts +156 -0
- package/src/hindsight/client.ts +680 -0
- package/src/hindsight/config.ts +193 -0
- package/src/hindsight/content.ts +266 -0
- package/src/hindsight/index.ts +8 -0
- package/src/hindsight/mental-models.ts +429 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +530 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/index.ts +66 -0
- package/src/internal-urls/agent-protocol.ts +180 -0
- package/src/internal-urls/artifact-protocol.ts +151 -0
- package/src/internal-urls/docs-index.ts +102 -0
- package/src/internal-urls/filesystem-resource.ts +34 -0
- package/src/internal-urls/history-protocol.ts +198 -0
- package/src/internal-urls/index.ts +28 -0
- package/src/internal-urls/issue-pr-protocol.ts +594 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/local-protocol.ts +471 -0
- package/src/internal-urls/mcp-protocol.ts +168 -0
- package/src/internal-urls/memory-protocol.ts +380 -0
- package/src/internal-urls/omp-protocol.ts +94 -0
- package/src/internal-urls/parse.ts +103 -0
- package/src/internal-urls/registry-helpers.ts +132 -0
- package/src/internal-urls/router.ts +153 -0
- package/src/internal-urls/rule-protocol.ts +45 -0
- package/src/internal-urls/security-protocol.ts +261 -0
- package/src/internal-urls/skill-protocol.ts +131 -0
- package/src/internal-urls/ssh-protocol.ts +368 -0
- package/src/internal-urls/types.ts +196 -0
- package/src/internal-urls/vault-protocol.ts +940 -0
- package/src/internal-urls/xd-protocol.ts +46 -0
- package/src/irc/bus.ts +380 -0
- package/src/jsonrpc/message-framing.ts +142 -0
- package/src/launch/broker.ts +1368 -0
- package/src/launch/client.ts +521 -0
- package/src/launch/ensure.ts +73 -0
- package/src/launch/paths.ts +14 -0
- package/src/launch/presence.ts +82 -0
- package/src/launch/protocol.ts +448 -0
- package/src/launch/spawn-options.ts +17 -0
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/launch/terminal-output.ts +46 -0
- package/src/lib/xai-http.ts +150 -0
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +517 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +422 -0
- package/src/live/visualizer.ts +221 -0
- package/src/live/voices.ts +18 -0
- package/src/lsp/client.ts +1465 -0
- package/src/lsp/clients/biome-client.ts +263 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +85 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +549 -0
- package/src/lsp/defaults.json +499 -0
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/edits.ts +288 -0
- package/src/lsp/format-options.ts +119 -0
- package/src/lsp/index.ts +2821 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/mux/daemon.ts +348 -0
- package/src/lsp/mux/protocol.ts +96 -0
- package/src/lsp/mux/server.ts +797 -0
- package/src/lsp/render.ts +668 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +479 -0
- package/src/lsp/utils.ts +747 -0
- package/src/main.ts +1799 -0
- package/src/markit/NOTICE +32 -0
- package/src/markit/converters/docx.ts +56 -0
- package/src/markit/converters/epub.ts +136 -0
- package/src/markit/converters/pdf/columns.ts +103 -0
- package/src/markit/converters/pdf/extract.ts +598 -0
- package/src/markit/converters/pdf/grid.ts +780 -0
- package/src/markit/converters/pdf/headers.ts +106 -0
- package/src/markit/converters/pdf/index.ts +146 -0
- package/src/markit/converters/pdf/render.ts +501 -0
- package/src/markit/converters/pdf/types.ts +84 -0
- package/src/markit/converters/pptx.ts +325 -0
- package/src/markit/converters/xlsx.ts +173 -0
- package/src/markit/index.ts +2 -0
- package/src/markit/registry.ts +59 -0
- package/src/markit/types.ts +35 -0
- package/src/mcp/client.ts +511 -0
- package/src/mcp/config-writer.ts +377 -0
- package/src/mcp/config.ts +385 -0
- package/src/mcp/index.ts +29 -0
- package/src/mcp/json-rpc.ts +122 -0
- package/src/mcp/loader.ts +125 -0
- package/src/mcp/manager.ts +1524 -0
- package/src/mcp/oauth-credentials.ts +104 -0
- package/src/mcp/oauth-discovery.ts +587 -0
- package/src/mcp/oauth-flow.ts +830 -0
- package/src/mcp/render.ts +214 -0
- package/src/mcp/request-id.ts +24 -0
- package/src/mcp/smithery-auth.ts +108 -0
- package/src/mcp/smithery-connect.ts +154 -0
- package/src/mcp/smithery-registry.ts +500 -0
- package/src/mcp/startup-events.ts +116 -0
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/tool-bridge.ts +691 -0
- package/src/mcp/tool-cache.ts +117 -0
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +508 -0
- package/src/mcp/transports/index.ts +7 -0
- package/src/mcp/transports/sse.ts +372 -0
- package/src/mcp/transports/stdio.ts +905 -0
- package/src/mcp/types.ts +467 -0
- package/src/memories/index.ts +1434 -0
- package/src/memories/storage.ts +578 -0
- package/src/memory-backend/index.ts +19 -0
- package/src/memory-backend/local-backend.ts +47 -0
- package/src/memory-backend/messages.ts +19 -0
- package/src/memory-backend/off-backend.ts +25 -0
- package/src/memory-backend/resolve.ts +25 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/memory-backend/types.ts +166 -0
- package/src/mnemopi/backend.ts +629 -0
- package/src/mnemopi/config.ts +267 -0
- package/src/mnemopi/embed-client.ts +293 -0
- package/src/mnemopi/embed-protocol.ts +35 -0
- package/src/mnemopi/embed-worker.ts +114 -0
- package/src/mnemopi/index.ts +3 -0
- package/src/mnemopi/state.ts +940 -0
- package/src/modes/acp/acp-agent.ts +2595 -0
- package/src/modes/acp/acp-client-bridge.ts +154 -0
- package/src/modes/acp/acp-event-mapper.ts +1084 -0
- package/src/modes/acp/acp-mode.ts +48 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/advisor-config.ts +635 -0
- package/src/modes/components/advisor-message.ts +109 -0
- package/src/modes/components/agent-dashboard.ts +1252 -0
- package/src/modes/components/agent-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +1126 -0
- package/src/modes/components/agent-transcript-viewer.ts +647 -0
- package/src/modes/components/ask-dialog.ts +1017 -0
- package/src/modes/components/assistant-message.ts +978 -0
- package/src/modes/components/background-tan-message.ts +36 -0
- package/src/modes/components/bash-execution.ts +233 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/btw-panel.ts +161 -0
- package/src/modes/components/cache-invalidation-marker.ts +110 -0
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/chat-transcript-builder.ts +518 -0
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/collab-prompt-message.ts +32 -0
- package/src/modes/components/compaction-summary-message.ts +221 -0
- package/src/modes/components/copy-selector.ts +218 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.ts +1036 -0
- package/src/modes/components/custom-message.ts +70 -0
- package/src/modes/components/diff.ts +254 -0
- package/src/modes/components/dynamic-border.ts +37 -0
- package/src/modes/components/error-banner.ts +33 -0
- package/src/modes/components/eval-execution.ts +169 -0
- package/src/modes/components/execution-shared.ts +101 -0
- package/src/modes/components/extensions/extension-dashboard.ts +492 -0
- package/src/modes/components/extensions/extension-list.ts +507 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +326 -0
- package/src/modes/components/extensions/state-manager.ts +648 -0
- package/src/modes/components/extensions/types.ts +186 -0
- package/src/modes/components/footer.ts +274 -0
- package/src/modes/components/history-search.ts +268 -0
- package/src/modes/components/hook-editor.ts +213 -0
- package/src/modes/components/hook-input.ts +87 -0
- package/src/modes/components/hook-message.ts +67 -0
- package/src/modes/components/hook-selector.ts +691 -0
- package/src/modes/components/index.ts +42 -0
- package/src/modes/components/keybinding-hints.ts +56 -0
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/login-dialog.ts +197 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1413 -0
- package/src/modes/components/message-frame.ts +98 -0
- package/src/modes/components/model-browser.ts +888 -0
- package/src/modes/components/model-hub.ts +2014 -0
- package/src/modes/components/model-picker.ts +237 -0
- package/src/modes/components/move-overlay.ts +293 -0
- package/src/modes/components/oauth-selector.ts +474 -0
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/overlay-box.ts +109 -0
- package/src/modes/components/pause-screen.ts +208 -0
- package/src/modes/components/plan-review-overlay.ts +1226 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/plugin-selector.ts +100 -0
- package/src/modes/components/plugin-settings.ts +745 -0
- package/src/modes/components/queue-mode-selector.ts +61 -0
- package/src/modes/components/read-tool-group.ts +856 -0
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/segment-track.ts +89 -0
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/selector-helpers.ts +129 -0
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/session-selector.ts +1035 -0
- package/src/modes/components/settings-defs.ts +267 -0
- package/src/modes/components/settings-selector.ts +1445 -0
- package/src/modes/components/show-images-selector.ts +50 -0
- package/src/modes/components/skill-message.ts +110 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/status-line/component.ts +1843 -0
- package/src/modes/components/status-line/context-thresholds.ts +86 -0
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/index.ts +5 -0
- package/src/modes/components/status-line/presets.ts +106 -0
- package/src/modes/components/status-line/segments.ts +710 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/types.ts +159 -0
- package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
- package/src/modes/components/theme-selector.ts +68 -0
- package/src/modes/components/thinking-selector.ts +57 -0
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +26 -0
- package/src/modes/components/todo-reminder.ts +43 -0
- package/src/modes/components/tool-execution.ts +1467 -0
- package/src/modes/components/transcript-container.ts +527 -0
- package/src/modes/components/tree-selector.ts +1006 -0
- package/src/modes/components/ttsr-notification.ts +123 -0
- package/src/modes/components/usage-row.ts +52 -0
- package/src/modes/components/user-message-selector.ts +227 -0
- package/src/modes/components/user-message.ts +167 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +578 -0
- package/src/modes/controllers/btw-controller.ts +246 -0
- package/src/modes/controllers/command-controller-shared.ts +109 -0
- package/src/modes/controllers/command-controller.ts +2024 -0
- package/src/modes/controllers/event-controller.ts +2173 -0
- package/src/modes/controllers/extension-ui-controller.ts +1243 -0
- package/src/modes/controllers/input-controller.ts +2037 -0
- package/src/modes/controllers/live-command-controller.ts +261 -0
- package/src/modes/controllers/mcp-command-controller.ts +2552 -0
- package/src/modes/controllers/omfg-controller.ts +287 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +2058 -0
- package/src/modes/controllers/session-focus-controller.ts +117 -0
- package/src/modes/controllers/ssh-command-controller.ts +385 -0
- package/src/modes/controllers/streaming-reveal.ts +399 -0
- package/src/modes/controllers/tan-command-controller.ts +243 -0
- package/src/modes/controllers/todo-command-controller.ts +487 -0
- package/src/modes/controllers/tool-args-reveal.ts +591 -0
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/github-ref-autocomplete.ts +75 -0
- package/src/modes/gradient-highlight.ts +99 -0
- package/src/modes/image-references.ts +137 -0
- package/src/modes/index.ts +10 -0
- package/src/modes/interactive-mode.ts +5055 -0
- package/src/modes/internal-url-autocomplete.ts +158 -0
- package/src/modes/loop-limit.ts +192 -0
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/magic-keywords.ts +42 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/oauth-manual-input.ts +69 -0
- package/src/modes/orchestrate.ts +43 -0
- package/src/modes/print-mode.ts +282 -0
- package/src/modes/prompt-action-autocomplete.ts +322 -0
- package/src/modes/queue-input.ts +132 -0
- package/src/modes/rpc/host-tools.ts +204 -0
- package/src/modes/rpc/host-uris.ts +241 -0
- package/src/modes/rpc/rpc-client.ts +1213 -0
- package/src/modes/rpc/rpc-frame.ts +316 -0
- package/src/modes/rpc/rpc-input.ts +38 -0
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +1519 -0
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +544 -0
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/runtime-init.ts +144 -0
- package/src/modes/session-observer-registry.ts +223 -0
- package/src/modes/session-teardown.ts +82 -0
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +103 -0
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
- package/src/modes/setup-wizard/scenes/model.ts +132 -0
- package/src/modes/setup-wizard/scenes/outro.ts +35 -0
- package/src/modes/setup-wizard/scenes/providers.ts +105 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
- package/src/modes/setup-wizard/scenes/splash.ts +201 -0
- package/src/modes/setup-wizard/scenes/theme.ts +330 -0
- package/src/modes/setup-wizard/scenes/types.ts +65 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
- package/src/modes/setup-wizard/startup-splash.ts +107 -0
- package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
- package/src/modes/shared.ts +49 -0
- package/src/modes/skill-command.ts +91 -0
- package/src/modes/theme/dark.json +95 -0
- package/src/modes/theme/defaults/alabaster.json +93 -0
- package/src/modes/theme/defaults/amethyst.json +96 -0
- package/src/modes/theme/defaults/anthracite.json +93 -0
- package/src/modes/theme/defaults/basalt.json +91 -0
- package/src/modes/theme/defaults/birch.json +95 -0
- package/src/modes/theme/defaults/dark-abyss.json +91 -0
- package/src/modes/theme/defaults/dark-arctic.json +104 -0
- package/src/modes/theme/defaults/dark-aurora.json +95 -0
- package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
- package/src/modes/theme/defaults/dark-cavern.json +91 -0
- package/src/modes/theme/defaults/dark-copper.json +95 -0
- package/src/modes/theme/defaults/dark-cosmos.json +90 -0
- package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/modes/theme/defaults/dark-dracula.json +98 -0
- package/src/modes/theme/defaults/dark-eclipse.json +91 -0
- package/src/modes/theme/defaults/dark-ember.json +95 -0
- package/src/modes/theme/defaults/dark-equinox.json +90 -0
- package/src/modes/theme/defaults/dark-forest.json +96 -0
- package/src/modes/theme/defaults/dark-github.json +105 -0
- package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
- package/src/modes/theme/defaults/dark-lavender.json +95 -0
- package/src/modes/theme/defaults/dark-lunar.json +89 -0
- package/src/modes/theme/defaults/dark-midnight.json +95 -0
- package/src/modes/theme/defaults/dark-monochrome.json +94 -0
- package/src/modes/theme/defaults/dark-monokai.json +98 -0
- package/src/modes/theme/defaults/dark-nebula.json +90 -0
- package/src/modes/theme/defaults/dark-nord.json +97 -0
- package/src/modes/theme/defaults/dark-ocean.json +101 -0
- package/src/modes/theme/defaults/dark-one.json +100 -0
- package/src/modes/theme/defaults/dark-poimandres.json +143 -0
- package/src/modes/theme/defaults/dark-rainforest.json +91 -0
- package/src/modes/theme/defaults/dark-reef.json +91 -0
- package/src/modes/theme/defaults/dark-retro.json +92 -0
- package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
- package/src/modes/theme/defaults/dark-sakura.json +95 -0
- package/src/modes/theme/defaults/dark-slate.json +95 -0
- package/src/modes/theme/defaults/dark-solarized.json +97 -0
- package/src/modes/theme/defaults/dark-solstice.json +90 -0
- package/src/modes/theme/defaults/dark-starfall.json +91 -0
- package/src/modes/theme/defaults/dark-sunset.json +99 -0
- package/src/modes/theme/defaults/dark-swamp.json +90 -0
- package/src/modes/theme/defaults/dark-synthwave.json +103 -0
- package/src/modes/theme/defaults/dark-taiga.json +91 -0
- package/src/modes/theme/defaults/dark-terminal.json +95 -0
- package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/modes/theme/defaults/dark-tundra.json +91 -0
- package/src/modes/theme/defaults/dark-twilight.json +91 -0
- package/src/modes/theme/defaults/dark-volcanic.json +91 -0
- package/src/modes/theme/defaults/graphite.json +92 -0
- package/src/modes/theme/defaults/index.ts +199 -0
- package/src/modes/theme/defaults/light-arctic.json +107 -0
- package/src/modes/theme/defaults/light-aurora-day.json +91 -0
- package/src/modes/theme/defaults/light-canyon.json +91 -0
- package/src/modes/theme/defaults/light-catppuccin.json +106 -0
- package/src/modes/theme/defaults/light-cirrus.json +90 -0
- package/src/modes/theme/defaults/light-coral.json +95 -0
- package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
- package/src/modes/theme/defaults/light-dawn.json +90 -0
- package/src/modes/theme/defaults/light-dunes.json +91 -0
- package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
- package/src/modes/theme/defaults/light-forest.json +100 -0
- package/src/modes/theme/defaults/light-frost.json +95 -0
- package/src/modes/theme/defaults/light-github.json +115 -0
- package/src/modes/theme/defaults/light-glacier.json +91 -0
- package/src/modes/theme/defaults/light-gruvbox.json +108 -0
- package/src/modes/theme/defaults/light-haze.json +90 -0
- package/src/modes/theme/defaults/light-honeycomb.json +95 -0
- package/src/modes/theme/defaults/light-lagoon.json +91 -0
- package/src/modes/theme/defaults/light-lavender.json +95 -0
- package/src/modes/theme/defaults/light-meadow.json +91 -0
- package/src/modes/theme/defaults/light-mint.json +95 -0
- package/src/modes/theme/defaults/light-monochrome.json +101 -0
- package/src/modes/theme/defaults/light-ocean.json +99 -0
- package/src/modes/theme/defaults/light-one.json +99 -0
- package/src/modes/theme/defaults/light-opal.json +91 -0
- package/src/modes/theme/defaults/light-orchard.json +91 -0
- package/src/modes/theme/defaults/light-paper.json +95 -0
- package/src/modes/theme/defaults/light-poimandres.json +143 -0
- package/src/modes/theme/defaults/light-prism.json +90 -0
- package/src/modes/theme/defaults/light-retro.json +98 -0
- package/src/modes/theme/defaults/light-sand.json +95 -0
- package/src/modes/theme/defaults/light-savanna.json +91 -0
- package/src/modes/theme/defaults/light-solarized.json +102 -0
- package/src/modes/theme/defaults/light-soleil.json +90 -0
- package/src/modes/theme/defaults/light-sunset.json +99 -0
- package/src/modes/theme/defaults/light-synthwave.json +98 -0
- package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
- package/src/modes/theme/defaults/light-wetland.json +91 -0
- package/src/modes/theme/defaults/light-zenith.json +89 -0
- package/src/modes/theme/defaults/limestone.json +94 -0
- package/src/modes/theme/defaults/mahogany.json +97 -0
- package/src/modes/theme/defaults/marble.json +93 -0
- package/src/modes/theme/defaults/obsidian.json +91 -0
- package/src/modes/theme/defaults/onyx.json +91 -0
- package/src/modes/theme/defaults/pearl.json +93 -0
- package/src/modes/theme/defaults/porcelain.json +91 -0
- package/src/modes/theme/defaults/quartz.json +96 -0
- package/src/modes/theme/defaults/sandstone.json +95 -0
- package/src/modes/theme/defaults/titanium.json +90 -0
- package/src/modes/theme/light.json +93 -0
- package/src/modes/theme/mermaid-cache.ts +92 -0
- package/src/modes/theme/shimmer.ts +305 -0
- package/src/modes/theme/theme-schema.json +463 -0
- package/src/modes/theme/theme.ts +3171 -0
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +494 -0
- package/src/modes/ultrathink.ts +42 -0
- package/src/modes/utils/context-usage.ts +518 -0
- package/src/modes/utils/copy-targets.ts +378 -0
- package/src/modes/utils/hotkeys-markdown.ts +65 -0
- package/src/modes/utils/interactive-context-helpers.ts +31 -0
- package/src/modes/utils/keybinding-matchers.ts +86 -0
- package/src/modes/utils/tools-markdown.ts +31 -0
- package/src/modes/utils/transcript-render-helpers.ts +253 -0
- package/src/modes/utils/ui-helpers.ts +976 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/modes/workflow.ts +55 -0
- package/src/plan-mode/approved-plan.ts +194 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/plan-mode/plan-handoff.ts +37 -0
- package/src/plan-mode/plan-protection.ts +31 -0
- package/src/plan-mode/state.ts +6 -0
- package/src/priority.json +60 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/advise-tool.md +3 -0
- package/src/prompts/advisor/context-files.md +8 -0
- package/src/prompts/advisor/system.md +98 -0
- package/src/prompts/agents/designer.md +74 -0
- package/src/prompts/agents/frontmatter.md +12 -0
- package/src/prompts/agents/init.md +33 -0
- package/src/prompts/agents/librarian.md +119 -0
- package/src/prompts/agents/reviewer.md +139 -0
- package/src/prompts/agents/scout.md +58 -0
- package/src/prompts/agents/security-reviewer.md +75 -0
- package/src/prompts/agents/task.md +17 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/bench.md +6 -0
- package/src/prompts/ci-green-request.md +36 -0
- package/src/prompts/dry-balance-bench.md +8 -0
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/goals/goal-mode-context.md +4 -0
- package/src/prompts/goals/goal-todo-context.md +12 -0
- package/src/prompts/goals/guided-goal-interview.md +43 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/consolidation_system.md +4 -0
- package/src/prompts/memories/read-path.md +17 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-custom-request.md +21 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +68 -0
- package/src/prompts/security/scan-coordinator.md +7 -0
- package/src/prompts/security/scan-request.md +21 -0
- package/src/prompts/security/validate-request.md +8 -0
- package/src/prompts/session/launch-completion.md +1 -0
- package/src/prompts/skills/autoload.md +8 -0
- package/src/prompts/skills/user-invocation.md +11 -0
- package/src/prompts/steering/parent-irc.md +5 -0
- package/src/prompts/steering/user-interjection.md +6 -0
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/agent-creation-architect.md +50 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/auto-continue.md +1 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +14 -0
- 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-autocontinue.md +5 -0
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/commit-message-system.md +14 -0
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/custom-system-prompt.md +64 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +18 -0
- package/src/prompts/system/empty-stop-retry.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/irc-autoreply.md +6 -0
- package/src/prompts/system/irc-incoming.md +9 -0
- package/src/prompts/system/manual-continue.md +7 -0
- package/src/prompts/system/mcp-xdev-guidance.md +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/system/mid-run-todo-nudge.md +3 -0
- package/src/prompts/system/omfg-user.md +50 -0
- package/src/prompts/system/orchestrate-notice.md +40 -0
- package/src/prompts/system/personalities/default.md +18 -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 +125 -0
- package/src/prompts/system/plan-mode-approved.md +22 -0
- package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
- package/src/prompts/system/plan-mode-reference.md +10 -0
- package/src/prompts/system/plan-mode-subagent.md +33 -0
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
- package/src/prompts/system/plan-yolo-handoff.md +5 -0
- package/src/prompts/system/prewalk-checklist.md +7 -0
- package/src/prompts/system/prewalk-continue.md +1 -0
- package/src/prompts/system/prewalk-plan.md +13 -0
- package/src/prompts/system/project-prompt.md +61 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/resolve-device-reminder.md +3 -0
- package/src/prompts/system/rewind-report.md +6 -0
- package/src/prompts/system/side-channel-no-tools.md +3 -0
- 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/speech-rewrite.md +15 -0
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/subagent-system-prompt.md +73 -0
- package/src/prompts/system/subagent-user-prompt.md +3 -0
- package/src/prompts/system/subagent-yield-reminder.md +23 -0
- package/src/prompts/system/system-prompt.md +263 -0
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/system/task-label.md +23 -0
- package/src/prompts/system/thinking-loop-redirect.md +10 -0
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system.md +16 -0
- package/src/prompts/system/tool-call-loop-redirect.md +8 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/system/ultrathink-notice.md +3 -0
- package/src/prompts/system/unexpected-stop-classifier.md +17 -0
- package/src/prompts/system/unexpected-stop-retry.md +4 -0
- package/src/prompts/system/vibe-mode-active.md +26 -0
- package/src/prompts/system/web-search.md +25 -0
- package/src/prompts/system/workflow-notice.md +112 -0
- package/src/prompts/system/xdev-mount-notice.md +20 -0
- package/src/prompts/tools/apply-patch.md +65 -0
- package/src/prompts/tools/ask.md +22 -0
- package/src/prompts/tools/ast-edit.md +11 -0
- package/src/prompts/tools/ast-grep.md +19 -0
- package/src/prompts/tools/async-result.md +8 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/browser.md +29 -0
- package/src/prompts/tools/checkpoint.md +15 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/debug.md +3 -0
- package/src/prompts/tools/eval.md +45 -0
- package/src/prompts/tools/github.md +22 -0
- package/src/prompts/tools/glob.md +16 -0
- package/src/prompts/tools/goal.md +11 -0
- package/src/prompts/tools/grep.md +13 -0
- package/src/prompts/tools/hub.md +34 -0
- package/src/prompts/tools/image-attachment-describe-system.md +8 -0
- package/src/prompts/tools/image-attachment-describe.md +10 -0
- package/src/prompts/tools/image-gen.md +7 -0
- package/src/prompts/tools/inspect-image-system.md +20 -0
- package/src/prompts/tools/inspect-image.md +22 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
- package/src/prompts/tools/lsp.md +19 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/memory-edit.md +12 -0
- package/src/prompts/tools/patch.md +57 -0
- package/src/prompts/tools/read.md +27 -0
- package/src/prompts/tools/recall.md +7 -0
- package/src/prompts/tools/reflect.md +5 -0
- package/src/prompts/tools/replace.md +30 -0
- package/src/prompts/tools/retain.md +6 -0
- package/src/prompts/tools/rewind.md +14 -0
- package/src/prompts/tools/security-publish.md +1 -0
- package/src/prompts/tools/security-scan.md +1 -0
- package/src/prompts/tools/task-async-contract.md +1 -0
- package/src/prompts/tools/task-summary.md +20 -0
- package/src/prompts/tools/task.md +83 -0
- package/src/prompts/tools/todo.md +42 -0
- package/src/prompts/tools/vibe-kill.md +3 -0
- package/src/prompts/tools/vibe-list.md +3 -0
- package/src/prompts/tools/vibe-send.md +9 -0
- package/src/prompts/tools/vibe-spawn.md +10 -0
- package/src/prompts/tools/vibe-turn-result.md +19 -0
- package/src/prompts/tools/vibe-wait.md +8 -0
- package/src/prompts/tools/web-search.md +8 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/registry/agent-lifecycle.ts +505 -0
- package/src/registry/agent-registry.ts +287 -0
- package/src/registry/persisted-agents.ts +423 -0
- package/src/sdk.ts +3820 -0
- package/src/secrets/index.ts +375 -0
- package/src/secrets/obfuscator.ts +2629 -0
- package/src/secrets/regex.ts +21 -0
- package/src/security/auth.ts +98 -0
- package/src/security/cloud.ts +686 -0
- package/src/security/comparison.ts +255 -0
- package/src/security/contracts/ids.ts +111 -0
- package/src/security/contracts/index.ts +4 -0
- package/src/security/contracts/schemas.ts +219 -0
- package/src/security/contracts/types.ts +254 -0
- package/src/security/contracts/validation.ts +69 -0
- package/src/security/coordinator.ts +708 -0
- package/src/security/importers/codex-security.ts +387 -0
- package/src/security/importers/index.ts +2 -0
- package/src/security/importers/sarif.ts +357 -0
- package/src/security/index.ts +13 -0
- package/src/security/preflight.ts +405 -0
- package/src/security/provenance.ts +106 -0
- package/src/security/publication.ts +326 -0
- package/src/security/remediation.ts +93 -0
- package/src/security/resource-output.ts +50 -0
- package/src/security/sarif.ts +78 -0
- package/src/security/store.ts +430 -0
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-events.ts +67 -0
- package/src/session/agent-session-types.ts +407 -0
- package/src/session/agent-session.ts +9000 -0
- package/src/session/agent-storage.ts +807 -0
- package/src/session/artifacts.ts +154 -0
- package/src/session/async-job-delivery.ts +82 -0
- package/src/session/auth-broker-config.ts +92 -0
- package/src/session/auth-storage.ts +25 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.ts +295 -0
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/client-bridge.ts +85 -0
- package/src/session/codex-auto-reset.ts +673 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/compact-modes.ts +105 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/eval-runner.ts +219 -0
- package/src/session/exit-diagnostics.ts +310 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/history-storage.ts +329 -0
- package/src/session/indexed-session-storage.ts +553 -0
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/launch-completion.ts +37 -0
- package/src/session/messages.ts +1304 -0
- package/src/session/model-controls.ts +757 -0
- package/src/session/prewalk.ts +279 -0
- package/src/session/provider-image-budget.ts +86 -0
- package/src/session/queued-messages.ts +99 -0
- package/src/session/redis-session-storage.ts +257 -0
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1896 -0
- package/src/session/session-context.ts +584 -0
- package/src/session/session-dump-format.ts +216 -0
- package/src/session/session-entries.ts +306 -0
- package/src/session/session-handoff.ts +325 -0
- package/src/session/session-history-format.ts +459 -0
- package/src/session/session-listing.ts +715 -0
- package/src/session/session-loader.ts +362 -0
- package/src/session/session-maintenance.ts +3115 -0
- package/src/session/session-manager.ts +2747 -0
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-metadata.ts +53 -0
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +280 -0
- package/src/session/session-persistence.ts +293 -0
- package/src/session/session-provider-boundary.ts +306 -0
- package/src/session/session-stats.ts +349 -0
- package/src/session/session-storage.ts +774 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/session-tools.ts +1385 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/settings-stream-fn.ts +79 -0
- package/src/session/shake-types.ts +43 -0
- package/src/session/snapcompact-inline.ts +545 -0
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/sql-session-storage.ts +374 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +1459 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/tool-choice-queue.ts +305 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-persistence.ts +142 -0
- package/src/session/turn-recovery.ts +2005 -0
- package/src/session/unexpected-stop-classifier.ts +141 -0
- package/src/session/yield-queue.ts +288 -0
- package/src/slash-commands/acp-builtins.ts +70 -0
- package/src/slash-commands/available-commands.ts +105 -0
- package/src/slash-commands/builtin-registry.ts +3149 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
- package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
- package/src/slash-commands/helpers/context-report.ts +66 -0
- package/src/slash-commands/helpers/format.ts +46 -0
- package/src/slash-commands/helpers/logout.ts +108 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +533 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/reset-usage.ts +68 -0
- package/src/slash-commands/helpers/security.ts +451 -0
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/ssh.ts +196 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
- package/src/slash-commands/helpers/todo.ts +285 -0
- package/src/slash-commands/helpers/usage-report.ts +198 -0
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/slash-commands/types.ts +139 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +667 -0
- package/src/ssh/file-transfer.ts +214 -0
- package/src/ssh/sshfs-mount.ts +163 -0
- package/src/ssh/utils.ts +51 -0
- package/src/startup-splash.ts +19 -0
- package/src/stt/asr-client.ts +401 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +603 -0
- package/src/stt/downloader.ts +142 -0
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +6 -0
- package/src/stt/models.ts +150 -0
- package/src/stt/sherpa-runtime.ts +71 -0
- package/src/stt/stt-controller.ts +320 -0
- package/src/stt/submit-trigger.ts +74 -0
- package/src/subprocess/worker-client.ts +463 -0
- package/src/subprocess/worker-runtime.ts +494 -0
- package/src/system-prompt.ts +921 -0
- package/src/task/agents.ts +170 -0
- package/src/task/commands.ts +132 -0
- package/src/task/discovery.ts +145 -0
- package/src/task/executor.ts +3431 -0
- package/src/task/index.ts +1515 -0
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/isolation-runner.ts +444 -0
- package/src/task/label.ts +40 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/omp-command.ts +26 -0
- package/src/task/output-manager.ts +115 -0
- package/src/task/parallel.ts +221 -0
- package/src/task/persisted-revive.ts +173 -0
- package/src/task/prewalk.ts +6 -0
- package/src/task/prompt-policy.ts +8 -0
- package/src/task/provider-concurrency.ts +100 -0
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/render.ts +1830 -0
- package/src/task/renderer.ts +14 -0
- package/src/task/repair-args.ts +118 -0
- package/src/task/spawn-policy.ts +72 -0
- package/src/task/structured-subagent.ts +676 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/types.ts +555 -0
- package/src/task/worktree.ts +967 -0
- package/src/task/yield-assembly.ts +198 -0
- package/src/telemetry-export.ts +504 -0
- package/src/thinking.ts +377 -0
- package/src/tiny/device.ts +111 -0
- package/src/tiny/dtype.ts +101 -0
- package/src/tiny/message-preproc.ts +155 -0
- package/src/tiny/models.ts +268 -0
- package/src/tiny/text.ts +290 -0
- package/src/tiny/title-client.ts +460 -0
- package/src/tiny/title-protocol.ts +56 -0
- package/src/tiny/worker.ts +354 -0
- package/src/tools/acp-bridge.ts +125 -0
- package/src/tools/approval.ts +245 -0
- package/src/tools/ask.ts +1459 -0
- package/src/tools/ast-edit.ts +718 -0
- package/src/tools/ast-grep.ts +526 -0
- package/src/tools/auto-generated-guard.ts +335 -0
- package/src/tools/bash-interactive.ts +435 -0
- package/src/tools/bash-interceptor.ts +148 -0
- package/src/tools/bash-pty-selection.ts +14 -0
- package/src/tools/bash-skill-urls.ts +350 -0
- package/src/tools/bash.ts +1826 -0
- package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
- package/src/tools/browser/aria/aria-snapshot.ts +131 -0
- package/src/tools/browser/attach.ts +219 -0
- package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
- package/src/tools/browser/cmux/rpc.ts +200 -0
- package/src/tools/browser/cmux/socket-client.ts +445 -0
- package/src/tools/browser/launch.ts +939 -0
- package/src/tools/browser/readable.ts +112 -0
- package/src/tools/browser/registry.ts +447 -0
- package/src/tools/browser/relay/bridge.ts +945 -0
- package/src/tools/browser/relay/daemon.ts +117 -0
- package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
- package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
- package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
- package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
- package/src/tools/browser/relay/kind.ts +41 -0
- package/src/tools/browser/relay/protocol.ts +54 -0
- package/src/tools/browser/relay/server.ts +141 -0
- package/src/tools/browser/render.ts +229 -0
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/shared-daemon.ts +139 -0
- package/src/tools/browser/tab-protocol.ts +123 -0
- package/src/tools/browser/tab-supervisor.ts +1077 -0
- package/src/tools/browser/tab-worker-entry.ts +29 -0
- package/src/tools/browser/tab-worker.ts +1982 -0
- package/src/tools/browser.ts +487 -0
- package/src/tools/builtin-names.ts +67 -0
- package/src/tools/checkpoint.ts +137 -0
- package/src/tools/computer/exposure.ts +14 -0
- package/src/tools/computer/protocol.ts +69 -0
- package/src/tools/computer/supervisor.ts +409 -0
- package/src/tools/computer/worker-entry.ts +39 -0
- package/src/tools/computer/worker.ts +745 -0
- package/src/tools/computer-renderer.ts +147 -0
- package/src/tools/computer.ts +222 -0
- package/src/tools/conflict-detect.ts +815 -0
- package/src/tools/context.ts +49 -0
- package/src/tools/debug.ts +1121 -0
- package/src/tools/default-renderer.ts +154 -0
- package/src/tools/essential-tools.ts +46 -0
- package/src/tools/eval-backends.ts +34 -0
- package/src/tools/eval-format/index.ts +24 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/julia.ts +446 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval-format/ruby.ts +380 -0
- package/src/tools/eval-render.ts +784 -0
- package/src/tools/eval.ts +774 -0
- package/src/tools/fetch.ts +1889 -0
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gh-cache-invalidation.ts +175 -0
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +484 -0
- package/src/tools/gh.ts +3958 -0
- package/src/tools/github-cache.ts +663 -0
- package/src/tools/glob.ts +691 -0
- package/src/tools/grep.ts +1920 -0
- package/src/tools/grouped-file-output.ts +210 -0
- package/src/tools/hub/index.ts +579 -0
- package/src/tools/hub/jobs.ts +714 -0
- package/src/tools/hub/launch.ts +690 -0
- package/src/tools/hub/messaging.ts +735 -0
- package/src/tools/hub/types.ts +117 -0
- package/src/tools/image-gen.ts +1689 -0
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +736 -0
- package/src/tools/inspect-image-renderer.ts +133 -0
- package/src/tools/inspect-image.ts +316 -0
- package/src/tools/json-tree.ts +260 -0
- package/src/tools/jtd-to-json-schema.ts +219 -0
- package/src/tools/jtd-to-typescript.ts +136 -0
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/learn.ts +141 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/manage-skill.ts +102 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/memory-edit.ts +61 -0
- package/src/tools/memory-recall.ts +102 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +211 -0
- package/src/tools/memory-retain.ts +89 -0
- package/src/tools/output-meta.ts +860 -0
- package/src/tools/output-schema-validator.ts +307 -0
- package/src/tools/path-utils.ts +1489 -0
- package/src/tools/plan-mode-guard.ts +155 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
- package/src/tools/read.ts +3762 -0
- package/src/tools/render-utils.ts +926 -0
- package/src/tools/renderers.ts +133 -0
- package/src/tools/report-tool-issue.ts +568 -0
- package/src/tools/resolve.ts +423 -0
- package/src/tools/review.ts +103 -0
- package/src/tools/run-scope.ts +417 -0
- package/src/tools/security-scan.ts +287 -0
- package/src/tools/shell-tokenize.ts +213 -0
- package/src/tools/sqlite-reader.ts +884 -0
- package/src/tools/terminal-output.ts +141 -0
- package/src/tools/todo.ts +1226 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +102 -0
- package/src/tools/tool-timeouts.ts +38 -0
- package/src/tools/tts.ts +266 -0
- package/src/tools/vibe.ts +608 -0
- package/src/tools/write.ts +1646 -0
- package/src/tools/xdev.ts +560 -0
- package/src/tools/yield.ts +486 -0
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +10 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/speakable.ts +392 -0
- package/src/tts/speech-enhancer.ts +206 -0
- package/src/tts/streaming-player.ts +120 -0
- package/src/tts/tts-client.ts +475 -0
- package/src/tts/tts-protocol.ts +69 -0
- package/src/tts/tts-worker.ts +434 -0
- package/src/tts/vocalizer.ts +419 -0
- package/src/tts/wav.ts +58 -0
- package/src/tui/code-cell.ts +268 -0
- package/src/tui/file-list.ts +55 -0
- package/src/tui/hyperlink.ts +178 -0
- package/src/tui/index.ts +13 -0
- package/src/tui/output-block.ts +268 -0
- package/src/tui/status-line.ts +54 -0
- package/src/tui/tree-list.ts +172 -0
- package/src/tui/types.ts +15 -0
- package/src/tui/utils.ts +103 -0
- package/src/tui/width-aware-text.ts +58 -0
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/block-context.ts +312 -0
- package/src/utils/changelog.ts +373 -0
- package/src/utils/clipboard.ts +361 -0
- package/src/utils/command-args.ts +74 -0
- package/src/utils/commit-message-generator.ts +148 -0
- package/src/utils/cpuprofile.ts +235 -0
- package/src/utils/edit-mode.ts +61 -0
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +80 -0
- package/src/utils/fetch-timeout.ts +10 -0
- package/src/utils/file-display-mode.ts +44 -0
- package/src/utils/file-mentions.ts +293 -0
- package/src/utils/git.ts +2472 -0
- package/src/utils/image-loading.ts +236 -0
- package/src/utils/image-resize.ts +420 -0
- package/src/utils/image-vision-fallback.ts +196 -0
- package/src/utils/inspect-image-mode.ts +39 -0
- package/src/utils/ipc.ts +38 -0
- package/src/utils/jj.ts +422 -0
- package/src/utils/lang-from-path.ts +251 -0
- package/src/utils/late-cleanup.ts +17 -0
- package/src/utils/local-date.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +223 -0
- package/src/utils/mupdf-wasm-embed.ts +12 -0
- package/src/utils/open.ts +126 -0
- package/src/utils/profile-tree.ts +111 -0
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/qrcode.ts +535 -0
- package/src/utils/sample-profile.ts +437 -0
- package/src/utils/session-color.ts +142 -0
- package/src/utils/shell-snapshot-fn-env.sh +63 -0
- package/src/utils/shell-snapshot.ts +326 -0
- package/src/utils/sixel.ts +69 -0
- package/src/utils/thinking-display.ts +163 -0
- package/src/utils/title-generator.ts +597 -0
- package/src/utils/token-rate.ts +72 -0
- package/src/utils/tool-choice.ts +50 -0
- package/src/utils/tools-manager.ts +411 -0
- package/src/utils/turndown.ts +82 -0
- package/src/utils/zip.ts +1106 -0
- package/src/vibe/runtime.ts +1684 -0
- package/src/vibe/state.ts +4 -0
- package/src/web/kagi.ts +305 -0
- package/src/web/parallel.ts +354 -0
- package/src/web/scrapers/artifacthub.ts +207 -0
- package/src/web/scrapers/arxiv.ts +83 -0
- package/src/web/scrapers/aur.ts +162 -0
- package/src/web/scrapers/biorxiv.ts +133 -0
- package/src/web/scrapers/bluesky.ts +262 -0
- package/src/web/scrapers/brew.ts +172 -0
- package/src/web/scrapers/cheatsh.ts +68 -0
- package/src/web/scrapers/chocolatey.ts +196 -0
- package/src/web/scrapers/choosealicense.ts +95 -0
- package/src/web/scrapers/cisa-kev.ts +87 -0
- package/src/web/scrapers/clojars.ts +154 -0
- package/src/web/scrapers/coingecko.ts +177 -0
- package/src/web/scrapers/crates-io.ts +97 -0
- package/src/web/scrapers/crossref.ts +136 -0
- package/src/web/scrapers/devto.ts +147 -0
- package/src/web/scrapers/discogs.ts +306 -0
- package/src/web/scrapers/discourse.ts +197 -0
- package/src/web/scrapers/dockerhub.ts +138 -0
- package/src/web/scrapers/docs-rs.ts +663 -0
- package/src/web/scrapers/fdroid.ts +134 -0
- package/src/web/scrapers/firefox-addons.ts +191 -0
- package/src/web/scrapers/flathub.ts +223 -0
- package/src/web/scrapers/github-gist.ts +58 -0
- package/src/web/scrapers/github.ts +800 -0
- package/src/web/scrapers/gitlab.ts +401 -0
- package/src/web/scrapers/go-pkg.ts +266 -0
- package/src/web/scrapers/hackage.ts +129 -0
- package/src/web/scrapers/hackernews.ts +189 -0
- package/src/web/scrapers/hex.ts +105 -0
- package/src/web/scrapers/huggingface.ts +321 -0
- package/src/web/scrapers/iacr.ts +89 -0
- package/src/web/scrapers/index.ts +252 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
- package/src/web/scrapers/lemmy.ts +203 -0
- package/src/web/scrapers/lobsters.ts +175 -0
- package/src/web/scrapers/mastodon.ts +292 -0
- package/src/web/scrapers/maven.ts +138 -0
- package/src/web/scrapers/mdn.ts +173 -0
- package/src/web/scrapers/metacpan.ts +222 -0
- package/src/web/scrapers/musicbrainz.ts +250 -0
- package/src/web/scrapers/npm.ts +98 -0
- package/src/web/scrapers/nuget.ts +183 -0
- package/src/web/scrapers/nvd.ts +222 -0
- package/src/web/scrapers/ollama.ts +239 -0
- package/src/web/scrapers/open-vsx.ts +106 -0
- package/src/web/scrapers/opencorporates.ts +292 -0
- package/src/web/scrapers/openlibrary.ts +336 -0
- package/src/web/scrapers/orcid.ts +286 -0
- package/src/web/scrapers/osv.ts +176 -0
- package/src/web/scrapers/packagist.ts +160 -0
- package/src/web/scrapers/pub-dev.ts +143 -0
- package/src/web/scrapers/pubmed.ts +211 -0
- package/src/web/scrapers/pypi.ts +112 -0
- package/src/web/scrapers/rawg.ts +110 -0
- package/src/web/scrapers/readthedocs.ts +121 -0
- package/src/web/scrapers/reddit.ts +95 -0
- package/src/web/scrapers/repology.ts +251 -0
- package/src/web/scrapers/rfc.ts +201 -0
- package/src/web/scrapers/rubygems.ts +103 -0
- package/src/web/scrapers/searchcode.ts +189 -0
- package/src/web/scrapers/sec-edgar.ts +261 -0
- package/src/web/scrapers/semantic-scholar.ts +171 -0
- package/src/web/scrapers/snapcraft.ts +187 -0
- package/src/web/scrapers/sourcegraph.ts +336 -0
- package/src/web/scrapers/spdx.ts +108 -0
- package/src/web/scrapers/spotify.ts +198 -0
- package/src/web/scrapers/stackoverflow.ts +120 -0
- package/src/web/scrapers/terraform.ts +277 -0
- package/src/web/scrapers/tldr.ts +47 -0
- package/src/web/scrapers/twitter.ts +94 -0
- package/src/web/scrapers/types.ts +354 -0
- package/src/web/scrapers/utils.ts +109 -0
- package/src/web/scrapers/vimeo.ts +133 -0
- package/src/web/scrapers/vscode-marketplace.ts +187 -0
- package/src/web/scrapers/w3c.ts +156 -0
- package/src/web/scrapers/wikidata.ts +344 -0
- package/src/web/scrapers/wikipedia.ts +84 -0
- package/src/web/scrapers/youtube.ts +325 -0
- package/src/web/search/index.ts +393 -0
- package/src/web/search/provider.ts +272 -0
- package/src/web/search/providers/anthropic.ts +406 -0
- package/src/web/search/providers/base.ts +112 -0
- package/src/web/search/providers/brave.ts +181 -0
- package/src/web/search/providers/browser-headers.ts +82 -0
- package/src/web/search/providers/browser-page.ts +125 -0
- package/src/web/search/providers/codex.ts +788 -0
- package/src/web/search/providers/duckduckgo.ts +382 -0
- package/src/web/search/providers/ecosia.ts +183 -0
- package/src/web/search/providers/exa.ts +513 -0
- package/src/web/search/providers/firecrawl.ts +213 -0
- package/src/web/search/providers/gemini.ts +634 -0
- package/src/web/search/providers/google.ts +195 -0
- package/src/web/search/providers/jina.ts +144 -0
- package/src/web/search/providers/kagi.ts +98 -0
- package/src/web/search/providers/kimi.ts +221 -0
- package/src/web/search/providers/mojeek.ts +220 -0
- package/src/web/search/providers/parallel.ts +186 -0
- package/src/web/search/providers/perplexity-auth.ts +142 -0
- package/src/web/search/providers/perplexity.ts +997 -0
- package/src/web/search/providers/public.ts +199 -0
- package/src/web/search/providers/searxng.ts +467 -0
- package/src/web/search/providers/startpage.ts +225 -0
- package/src/web/search/providers/synthetic.ts +126 -0
- package/src/web/search/providers/tavily.ts +244 -0
- package/src/web/search/providers/tinyfish.ts +166 -0
- package/src/web/search/providers/utils.ts +130 -0
- package/src/web/search/providers/xai.ts +372 -0
- package/src/web/search/providers/zai.ts +452 -0
- package/src/web/search/query.ts +850 -0
- package/src/web/search/render.ts +262 -0
- package/src/web/search/types.ts +518 -0
- package/src/web/search/utils.ts +17 -0
- package/src/workspace-tree.ts +326 -0
|
@@ -0,0 +1,2822 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { registerCustomApi, unregisterCustomApis } from "@oh-my-pi/pi-ai/api-registry";
|
|
4
|
+
import type {
|
|
5
|
+
Api,
|
|
6
|
+
Context,
|
|
7
|
+
Model,
|
|
8
|
+
ModelSpec,
|
|
9
|
+
RemoteCompactionConfig,
|
|
10
|
+
SimpleStreamOptions,
|
|
11
|
+
ThinkingConfig,
|
|
12
|
+
} from "@oh-my-pi/pi-ai/types";
|
|
13
|
+
import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
|
|
14
|
+
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
|
15
|
+
import { isVertexExpressOpenAIUrl } from "@oh-my-pi/pi-catalog/hosts";
|
|
16
|
+
import { readModelCache } from "@oh-my-pi/pi-catalog/model-cache";
|
|
17
|
+
import {
|
|
18
|
+
createModelManager,
|
|
19
|
+
type ModelManagerOptions,
|
|
20
|
+
type ModelRefreshStrategy,
|
|
21
|
+
} from "@oh-my-pi/pi-catalog/model-manager";
|
|
22
|
+
import { getBundledModels, getBundledProviders } from "@oh-my-pi/pi-catalog/models";
|
|
23
|
+
import {
|
|
24
|
+
googleAntigravityModelManagerOptions,
|
|
25
|
+
googleGeminiCliModelManagerOptions,
|
|
26
|
+
type OpenAICodexAccount,
|
|
27
|
+
openaiCodexModelManagerOptions,
|
|
28
|
+
PROVIDER_DESCRIPTORS,
|
|
29
|
+
resolveModelCacheProviderId,
|
|
30
|
+
resolveOllamaModelCacheProviderId,
|
|
31
|
+
} from "@oh-my-pi/pi-catalog/provider-models";
|
|
32
|
+
import {
|
|
33
|
+
collapseBuiltModelVariants,
|
|
34
|
+
getVariantAliasSources,
|
|
35
|
+
resolveVariantAlias,
|
|
36
|
+
} from "@oh-my-pi/pi-catalog/variant-collapse";
|
|
37
|
+
|
|
38
|
+
const SPECIAL_MODEL_MANAGER_PROVIDER_IDS: readonly string[] = [
|
|
39
|
+
"google-antigravity",
|
|
40
|
+
"google-gemini-cli",
|
|
41
|
+
"openai-codex",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
|
|
45
|
+
...PROVIDER_DESCRIPTORS.map(descriptor => descriptor.providerId),
|
|
46
|
+
...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Sentinels for local-only OAuth tokens — declared inline to avoid loading
|
|
50
|
+
// provider modules at startup. Must match packages/ai/src/registry/llama-cpp.ts,
|
|
51
|
+
// packages/ai/src/registry/lm-studio.ts, and packages/ai/src/registry/vllm.ts.
|
|
52
|
+
const LOCAL_PROVIDER_PLACEHOLDERS = new Set<string>(["llama-cpp-local", "lm-studio-local", "vllm-local"]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Hard bound for extension-provided fetchDynamicModels to prevent indefinite hangs
|
|
56
|
+
* during runtime provider discovery. Uses a cancellable manual timer (not AbortSignal.timeout)
|
|
57
|
+
* so a successful fast path does not leave an armed timeout signal for concurrent GC.
|
|
58
|
+
*/
|
|
59
|
+
const RUNTIME_DYNAMIC_MODEL_FETCH_TIMEOUT_MS = 15_000;
|
|
60
|
+
// Built-in discovery preflight mirror of the catalog model-manager's private
|
|
61
|
+
// cache timings (model-manager.ts: DEFAULT_CACHE_TTL_MS / NON_AUTHORITATIVE_RETRY_MS).
|
|
62
|
+
// Built-in descriptors never override cacheTtlMs, so agreeing with these values
|
|
63
|
+
// makes the OAuth-refresh preflight fire exactly when the manager will fetch.
|
|
64
|
+
const BUILT_IN_DISCOVERY_CACHE_TTL_MS = 2 * 60 * 60 * 1000;
|
|
65
|
+
const BUILT_IN_DISCOVERY_NON_AUTHORITATIVE_RETRY_MS = 5 * 60 * 1000;
|
|
66
|
+
|
|
67
|
+
import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai";
|
|
68
|
+
import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
69
|
+
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
|
|
70
|
+
import { setCodexAttestationProvider } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
71
|
+
import { getProviderDefinition } from "@oh-my-pi/pi-ai/registry";
|
|
72
|
+
import {
|
|
73
|
+
getBundledModelReferenceIndex,
|
|
74
|
+
inheritReferenceThinking,
|
|
75
|
+
resolveModelReference,
|
|
76
|
+
} from "@oh-my-pi/pi-catalog/identity";
|
|
77
|
+
import { $envExact, isBunTestRuntime, isRecord, logger, wrapFetchForExtraCa } from "@oh-my-pi/pi-utils";
|
|
78
|
+
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
79
|
+
import { generateCodexAttestation } from "../live/attestation";
|
|
80
|
+
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
81
|
+
import { type ApiKeyResolverModel, type ApiKeyResolverOptions, createApiKeyResolver } from "./api-key-resolver";
|
|
82
|
+
import type { ConfigError, ConfigFile } from "./config-file";
|
|
83
|
+
import {
|
|
84
|
+
applyLlamaCppQwenThinking,
|
|
85
|
+
DISCOVERY_DEFAULT_MAX_TOKENS,
|
|
86
|
+
type DiscoveryContext,
|
|
87
|
+
type DiscoveryProviderConfig,
|
|
88
|
+
discoverLlamaCppModelRuntimeMetadata,
|
|
89
|
+
discoverModelsByProviderType,
|
|
90
|
+
getImplicitOllamaBaseUrl,
|
|
91
|
+
getOllamaContextLengthOverride,
|
|
92
|
+
normalizeLiteLLMDiscoveryBaseUrl,
|
|
93
|
+
} from "./model-discovery";
|
|
94
|
+
import { ModelsConfigFile, type ProviderValidationModel, validateProviderConfiguration } from "./models-config";
|
|
95
|
+
import type { ModelOverride, ModelsConfig, ProviderAuthMode } from "./models-config-schema";
|
|
96
|
+
import { settings } from "./settings";
|
|
97
|
+
|
|
98
|
+
// DeviceCheck attestation (`x-oai-attestation`) for ChatGPT-OAuth Codex
|
|
99
|
+
// requests; the pi-ai provider resolves it just-in-time per request.
|
|
100
|
+
setCodexAttestationProvider(generateCodexAttestation);
|
|
101
|
+
|
|
102
|
+
export const kNoAuth = "N/A";
|
|
103
|
+
|
|
104
|
+
export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
|
|
105
|
+
return Boolean(apiKey) && apiKey !== kNoAuth;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isDiscoveryBearerApiKey(apiKey: string | undefined | null): apiKey is string {
|
|
109
|
+
return isAuthenticated(apiKey) && !LOCAL_PROVIDER_PLACEHOLDERS.has(apiKey);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Wraps an extension-provided fetchDynamicModels call with a hard timeout.
|
|
114
|
+
* Uses a cancellable manual timer (not AbortSignal.timeout) so that a fast
|
|
115
|
+
* successful path does not leave an armed timeout signal for concurrent GC.
|
|
116
|
+
* The inner fetcher does not receive a signal (extension contract has none).
|
|
117
|
+
*/
|
|
118
|
+
async function withRuntimeDynamicModelsTimeout<T>(timeoutMs: number, run: () => Promise<T>): Promise<T> {
|
|
119
|
+
const { promise: timeoutPromise, reject: timeoutReject } = Promise.withResolvers<never>();
|
|
120
|
+
const timer = setTimeout(() => {
|
|
121
|
+
timeoutReject(new Error(`fetchDynamicModels timed out after ${timeoutMs}ms`));
|
|
122
|
+
}, timeoutMs);
|
|
123
|
+
try {
|
|
124
|
+
return await Promise.race([run(), timeoutPromise]);
|
|
125
|
+
} finally {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
|
|
131
|
+
interface ProviderOverride {
|
|
132
|
+
baseUrl?: string;
|
|
133
|
+
headers?: Record<string, string>;
|
|
134
|
+
apiKey?: string;
|
|
135
|
+
authHeader?: boolean;
|
|
136
|
+
compat?: ModelSpec<Api>["compat"];
|
|
137
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
138
|
+
transport?: Model<Api>["transport"];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Merge a freshly discovered model with the matching bundled/configured entry
|
|
143
|
+
* (or a runtime provider override when no bundled entry exists).
|
|
144
|
+
*
|
|
145
|
+
* `baseUrl` resolution priority:
|
|
146
|
+
* 1. User-set `providerOverride.baseUrl` (explicit override in models.json)
|
|
147
|
+
* 2. Discovered baseUrl (xiaomi `tp-` token-plan keys resolve to
|
|
148
|
+
* `token-plan-sgp.xiaomimimo.com` at discovery time)
|
|
149
|
+
* 3. Existing bundled baseUrl (the host baked into `models.json`)
|
|
150
|
+
*
|
|
151
|
+
* `transport` resolution priority:
|
|
152
|
+
* 1. `providerOverride.transport` (e.g. `pi-native` for auth-gateway users)
|
|
153
|
+
* 2. `existing.transport` (carried over from boot-time override application)
|
|
154
|
+
* 3. `model.transport` (rarely set — discovery defaults omit it)
|
|
155
|
+
*
|
|
156
|
+
* Without (1), the user's override would lose to discovery; without (2)
|
|
157
|
+
* preferred over (3), the bundled `api.xiaomimimo.com` would shadow the
|
|
158
|
+
* tp- token-plan host and produce 401s on the first stream call.
|
|
159
|
+
* Without explicit transport propagation, an openrouter (or any) entry
|
|
160
|
+
* marked `transport: pi-native` in models.yml silently reverts to the
|
|
161
|
+
* default openai-completions transport after the background catalog
|
|
162
|
+
* refresh — so the first `/model` switch after boot hits the raw OpenAI
|
|
163
|
+
* chat-completions URL instead of the gateway's `/v1/pi/stream` (#2555).
|
|
164
|
+
* See `xiaomi-tp-discovery-merge.test.ts` and the `refresh()` baseUrl-override
|
|
165
|
+
* regression in `model-registry.test.ts`.
|
|
166
|
+
*/
|
|
167
|
+
export function mergeDiscoveredModel<TApi extends Api>(
|
|
168
|
+
model: Model<TApi>,
|
|
169
|
+
existing: Model<Api> | undefined,
|
|
170
|
+
providerOverride?: Pick<ProviderOverride, "baseUrl" | "compat" | "headers" | "remoteCompaction" | "transport">,
|
|
171
|
+
): Model<TApi> {
|
|
172
|
+
if (existing) {
|
|
173
|
+
const supportsTools = model.supportsTools ?? existing.supportsTools;
|
|
174
|
+
return buildModel({
|
|
175
|
+
...model,
|
|
176
|
+
baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
|
|
177
|
+
headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
|
|
178
|
+
transport: providerOverride?.transport ?? existing.transport ?? model.transport,
|
|
179
|
+
remoteCompaction: mergeProviderRemoteCompactionConfig(
|
|
180
|
+
mergeRemoteCompactionConfig(existing.remoteCompaction, model.remoteCompaction),
|
|
181
|
+
providerOverride?.remoteCompaction,
|
|
182
|
+
),
|
|
183
|
+
...(supportsTools !== undefined ? { supportsTools } : {}),
|
|
184
|
+
compat: mergeCompat(model.compatConfig, providerOverride?.compat),
|
|
185
|
+
} as ModelSpec<TApi>);
|
|
186
|
+
}
|
|
187
|
+
if (providerOverride) {
|
|
188
|
+
return buildModel({
|
|
189
|
+
...model,
|
|
190
|
+
baseUrl: providerOverride.baseUrl ?? model.baseUrl,
|
|
191
|
+
headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
|
|
192
|
+
...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
|
|
193
|
+
remoteCompaction: mergeProviderRemoteCompactionConfig(
|
|
194
|
+
model.remoteCompaction,
|
|
195
|
+
providerOverride.remoteCompaction,
|
|
196
|
+
),
|
|
197
|
+
compat: mergeCompat(model.compatConfig, providerOverride.compat),
|
|
198
|
+
} as ModelSpec<TApi>);
|
|
199
|
+
}
|
|
200
|
+
return model;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS = new Set<string>(
|
|
204
|
+
PROVIDER_DESCRIPTORS.filter(descriptor => descriptor.dynamicModelsAuthoritative).map(
|
|
205
|
+
descriptor => descriptor.providerId,
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
function isAuthoritativeProjectCatalogModel(model: Model<Api>): boolean {
|
|
210
|
+
return (
|
|
211
|
+
model.provider === "google-vertex" &&
|
|
212
|
+
model.api === "openai-completions" &&
|
|
213
|
+
isVertexExpressOpenAIUrl(model.baseUrl)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function providersWithAuthoritativeProjectCatalog(models: readonly Model<Api>[]): Set<string> {
|
|
218
|
+
const providers = new Set<string>();
|
|
219
|
+
for (const model of models) {
|
|
220
|
+
if (isAuthoritativeProjectCatalogModel(model)) {
|
|
221
|
+
providers.add(model.provider);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return providers;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function dropProviderModels(models: readonly Model<Api>[], providers: ReadonlySet<string>): Model<Api>[] {
|
|
228
|
+
return models.filter(model => !providers.has(model.provider));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Merge `incoming` entries into a copy of `base`, keyed by `provider`+`id`.
|
|
233
|
+
* Matches are replaced with `combine(existing, entry)`; new entries are
|
|
234
|
+
* appended as `combine(undefined, entry)`.
|
|
235
|
+
*/
|
|
236
|
+
function mergeByModelKey<T extends { provider: string; id: string }>(
|
|
237
|
+
base: readonly Model<Api>[],
|
|
238
|
+
incoming: readonly T[],
|
|
239
|
+
combine: (existing: Model<Api> | undefined, entry: T) => Model<Api>,
|
|
240
|
+
): Model<Api>[] {
|
|
241
|
+
const merged = [...base];
|
|
242
|
+
const indexByKey = new Map<string, number>();
|
|
243
|
+
for (let i = 0; i < merged.length; i += 1) {
|
|
244
|
+
indexByKey.set(`${merged[i].provider}\u0000${merged[i].id}`, i);
|
|
245
|
+
}
|
|
246
|
+
for (const entry of incoming) {
|
|
247
|
+
const key = `${entry.provider}\u0000${entry.id}`;
|
|
248
|
+
const existingIndex = indexByKey.get(key);
|
|
249
|
+
if (existingIndex !== undefined) {
|
|
250
|
+
merged[existingIndex] = combine(merged[existingIndex], entry);
|
|
251
|
+
} else {
|
|
252
|
+
merged.push(combine(undefined, entry));
|
|
253
|
+
indexByKey.set(key, merged.length - 1);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return merged;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface BuiltInDiscoveryResult {
|
|
260
|
+
models: Model<Api>[];
|
|
261
|
+
authoritativeProviders: Set<string>;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export type ProviderDiscoveryStatus = "idle" | "ok" | "empty" | "cached" | "unavailable" | "unauthenticated";
|
|
265
|
+
|
|
266
|
+
export interface ProviderDiscoveryState {
|
|
267
|
+
provider: string;
|
|
268
|
+
status: ProviderDiscoveryStatus;
|
|
269
|
+
optional: boolean;
|
|
270
|
+
stale: boolean;
|
|
271
|
+
fetchedAt?: number;
|
|
272
|
+
models: string[];
|
|
273
|
+
error?: string;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Result of loading custom models config. */
|
|
277
|
+
interface CustomModelsResult {
|
|
278
|
+
models?: CustomModelOverlay[];
|
|
279
|
+
overrides?: Map<string, ProviderOverride>;
|
|
280
|
+
modelOverrides?: Map<string, Map<string, ModelOverride>>;
|
|
281
|
+
keylessProviders?: Set<string>;
|
|
282
|
+
discoverableProviders?: DiscoveryProviderConfig[];
|
|
283
|
+
configuredProviders?: Set<string>;
|
|
284
|
+
error?: ConfigError;
|
|
285
|
+
found: boolean;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const commandValueCache = new Map<string, string>();
|
|
289
|
+
// Failed `!command` resolutions (non-zero exit, empty stdout) are negative-cached
|
|
290
|
+
// with a TTL instead of forever: a transient failure (locked password manager,
|
|
291
|
+
// network hiccup) must not disable the key until process restart, but re-running
|
|
292
|
+
// the command on every resolution would restore the execSync storm this cache
|
|
293
|
+
// exists to prevent. One probe per TTL window bounds both.
|
|
294
|
+
const COMMAND_FAILURE_RETRY_MS = 30_000;
|
|
295
|
+
const commandFailureRetryAt = new Map<string, number>();
|
|
296
|
+
|
|
297
|
+
function isCommandConfigValue(valueConfig: string | undefined): valueConfig is string {
|
|
298
|
+
return valueConfig?.startsWith("!") === true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function resolveCommandConfig(command: string): string | undefined {
|
|
302
|
+
const cached = commandValueCache.get(command);
|
|
303
|
+
if (cached !== undefined) return cached;
|
|
304
|
+
const retryAt = commandFailureRetryAt.get(command);
|
|
305
|
+
if (retryAt !== undefined && Date.now() < retryAt) return undefined;
|
|
306
|
+
try {
|
|
307
|
+
const stdout = execSync(command, { encoding: "utf8", timeout: 10_000, windowsHide: true });
|
|
308
|
+
const trimmed = stdout.trim();
|
|
309
|
+
if (trimmed.length === 0) {
|
|
310
|
+
commandFailureRetryAt.set(command, Date.now() + COMMAND_FAILURE_RETRY_MS);
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
commandFailureRetryAt.delete(command);
|
|
314
|
+
commandValueCache.set(command, trimmed);
|
|
315
|
+
return trimmed;
|
|
316
|
+
} catch {
|
|
317
|
+
commandFailureRetryAt.set(command, Date.now() + COMMAND_FAILURE_RETRY_MS);
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface CommandApiKeyResolution {
|
|
323
|
+
configured: boolean;
|
|
324
|
+
value?: string;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Resolve a models.yml/models.yaml secret/config value to an actual value.
|
|
328
|
+
* `!cmd` runs a shell command and returns trimmed stdout, otherwise env vars are
|
|
329
|
+
* checked first and the input falls back to a literal value.
|
|
330
|
+
*/
|
|
331
|
+
function resolveConfigValue(valueConfig: string): string | undefined {
|
|
332
|
+
if (valueConfig.startsWith("!")) return resolveCommandConfig(valueConfig.slice(1).trim());
|
|
333
|
+
const envValue = $envExact(valueConfig);
|
|
334
|
+
if (envValue) return envValue;
|
|
335
|
+
return valueConfig;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
type HeaderSource = Record<string, string> | undefined;
|
|
339
|
+
|
|
340
|
+
interface HeaderResolutionOptions {
|
|
341
|
+
authHeader?: boolean;
|
|
342
|
+
apiKeyConfig?: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function materializeConfigHeaderSources(
|
|
346
|
+
sources: readonly HeaderSource[],
|
|
347
|
+
options?: HeaderResolutionOptions,
|
|
348
|
+
): Record<string, string> | undefined {
|
|
349
|
+
const resolved: Record<string, string> = {};
|
|
350
|
+
for (const source of sources) {
|
|
351
|
+
if (!source) continue;
|
|
352
|
+
for (const [key, value] of Object.entries(source)) {
|
|
353
|
+
const next = resolveConfigValue(value);
|
|
354
|
+
if (next) resolved[key] = next;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (options?.authHeader && options.apiKeyConfig) {
|
|
358
|
+
const resolvedKey = resolveConfigValue(options.apiKeyConfig);
|
|
359
|
+
if (resolvedKey) resolved.Authorization = `Bearer ${resolvedKey}`;
|
|
360
|
+
}
|
|
361
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function createLiveConfigHeaders(
|
|
365
|
+
sources: readonly HeaderSource[],
|
|
366
|
+
options?: HeaderResolutionOptions,
|
|
367
|
+
): Record<string, string> | undefined {
|
|
368
|
+
const liveSources = sources.filter((source): source is Record<string, string> => source !== undefined);
|
|
369
|
+
if (liveSources.length === 0 && (!options?.authHeader || !options.apiKeyConfig)) return undefined;
|
|
370
|
+
|
|
371
|
+
const localHeaders: Record<string, string> = {};
|
|
372
|
+
const allSources = [...liveSources, localHeaders];
|
|
373
|
+
const current = () => materializeConfigHeaderSources(allSources, options) ?? {};
|
|
374
|
+
return new Proxy(localHeaders, {
|
|
375
|
+
get(target, property, receiver) {
|
|
376
|
+
if (typeof property !== "string") return Reflect.get(target, property, receiver);
|
|
377
|
+
return current()[property];
|
|
378
|
+
},
|
|
379
|
+
set(target, property, value) {
|
|
380
|
+
if (typeof property !== "string" || typeof value !== "string") return false;
|
|
381
|
+
target[property] = value;
|
|
382
|
+
return true;
|
|
383
|
+
},
|
|
384
|
+
deleteProperty(target, property) {
|
|
385
|
+
if (typeof property !== "string") return false;
|
|
386
|
+
delete target[property];
|
|
387
|
+
return true;
|
|
388
|
+
},
|
|
389
|
+
has(_target, property) {
|
|
390
|
+
if (typeof property !== "string") return false;
|
|
391
|
+
return Object.hasOwn(current(), property);
|
|
392
|
+
},
|
|
393
|
+
ownKeys() {
|
|
394
|
+
return Reflect.ownKeys(current());
|
|
395
|
+
},
|
|
396
|
+
getOwnPropertyDescriptor(_target, property) {
|
|
397
|
+
if (typeof property !== "string") return undefined;
|
|
398
|
+
const headers = current();
|
|
399
|
+
if (!Object.hasOwn(headers, property)) return undefined;
|
|
400
|
+
return {
|
|
401
|
+
configurable: true,
|
|
402
|
+
enumerable: true,
|
|
403
|
+
value: headers[property],
|
|
404
|
+
writable: true,
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function resolveConfigHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {
|
|
411
|
+
return materializeConfigHeaderSources([headers]);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function extractGoogleOAuthToken(value: string | undefined): string | undefined {
|
|
415
|
+
if (!isAuthenticated(value)) return undefined;
|
|
416
|
+
try {
|
|
417
|
+
const parsed = JSON.parse(value) as { token?: unknown };
|
|
418
|
+
if (Object.hasOwn(parsed, "token")) {
|
|
419
|
+
if (typeof parsed.token !== "string") {
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
const token = parsed.token.trim();
|
|
423
|
+
return token.length > 0 ? token : undefined;
|
|
424
|
+
}
|
|
425
|
+
} catch {
|
|
426
|
+
// OAuth values for Google providers are expected to be JSON, but custom setups may already provide raw token.
|
|
427
|
+
}
|
|
428
|
+
return value;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function getOAuthCredentialsForProvider(authStorage: AuthStorage, provider: string): OAuthCredential[] {
|
|
432
|
+
const providerEntry = authStorage.getAll()[provider];
|
|
433
|
+
if (!providerEntry) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
const entries = Array.isArray(providerEntry) ? providerEntry : [providerEntry];
|
|
437
|
+
return entries.filter((entry): entry is OAuthCredential => entry.type === "oauth");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Resolve every configured Codex OAuth account for catalog discovery, refreshing
|
|
442
|
+
* each credential exactly once. Codex `/models` is account-scoped, so discovery
|
|
443
|
+
* must fetch per account and union the results; resolving a single access token
|
|
444
|
+
* (as before) hid models available only through a sibling account (#6265).
|
|
445
|
+
*
|
|
446
|
+
* Returns `null` when any stored account fails to resolve (e.g. a transient
|
|
447
|
+
* refresh failure): the Codex manager is authoritative, so unioning only the
|
|
448
|
+
* accounts that resolved would cache a partial catalog and hide the failed
|
|
449
|
+
* account's models for the cache TTL. Aborting keeps the previous/bundled
|
|
450
|
+
* catalog instead.
|
|
451
|
+
*/
|
|
452
|
+
async function resolveCodexDiscoveryAccounts(
|
|
453
|
+
authStorage: AuthStorage,
|
|
454
|
+
resolvedAccessToken: string,
|
|
455
|
+
): Promise<OpenAICodexAccount[] | null> {
|
|
456
|
+
const accesses = await authStorage.getOAuthAccesses("openai-codex");
|
|
457
|
+
const accounts: OpenAICodexAccount[] = [];
|
|
458
|
+
for (const access of accesses) {
|
|
459
|
+
if (!access.ok) return null;
|
|
460
|
+
accounts.push({ accessToken: access.accessToken, accountId: access.accountId });
|
|
461
|
+
}
|
|
462
|
+
if (!accounts.some(account => account.accessToken === resolvedAccessToken)) {
|
|
463
|
+
const matchingCredential = getOAuthCredentialsForProvider(authStorage, "openai-codex").find(
|
|
464
|
+
credential => credential.access === resolvedAccessToken,
|
|
465
|
+
);
|
|
466
|
+
accounts.push({ accessToken: resolvedAccessToken, accountId: matchingCredential?.accountId });
|
|
467
|
+
}
|
|
468
|
+
return accounts;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function mergeCompat<TBase extends object, TOverride extends object>(
|
|
472
|
+
baseCompat: TBase | null | undefined,
|
|
473
|
+
overrideCompat: TOverride | null | undefined,
|
|
474
|
+
): (TBase & TOverride) | TBase | TOverride | undefined {
|
|
475
|
+
if (!baseCompat) return overrideCompat ?? undefined;
|
|
476
|
+
if (!overrideCompat) return baseCompat;
|
|
477
|
+
|
|
478
|
+
const merged: Record<string, unknown> = { ...(baseCompat as Record<string, unknown>) };
|
|
479
|
+
for (const [key, overrideValue] of Object.entries(overrideCompat)) {
|
|
480
|
+
const baseValue = (baseCompat as Record<string, unknown>)[key];
|
|
481
|
+
merged[key] =
|
|
482
|
+
isRecord(baseValue) && isRecord(overrideValue) ? mergeCompat(baseValue, overrideValue) : overrideValue;
|
|
483
|
+
}
|
|
484
|
+
return merged as TBase & TOverride;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function mergeRemoteCompactionConfig(
|
|
488
|
+
baseConfig: RemoteCompactionConfig<Api> | undefined,
|
|
489
|
+
overrideConfig: RemoteCompactionConfig<Api> | undefined,
|
|
490
|
+
): RemoteCompactionConfig<Api> | undefined {
|
|
491
|
+
if (!baseConfig) return overrideConfig;
|
|
492
|
+
if (!overrideConfig) return baseConfig;
|
|
493
|
+
return { ...baseConfig, ...overrideConfig };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function mergeProviderRemoteCompactionConfig(
|
|
497
|
+
modelConfig: RemoteCompactionConfig<Api> | undefined,
|
|
498
|
+
providerConfig: RemoteCompactionConfig<Api> | undefined,
|
|
499
|
+
): RemoteCompactionConfig<Api> | undefined {
|
|
500
|
+
return mergeRemoteCompactionConfig(providerConfig, modelConfig);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Project a built model back to spec shape for the model-manager/cache
|
|
505
|
+
* boundary: sparse compat comes from `compatConfig`, never from the resolved
|
|
506
|
+
* record.
|
|
507
|
+
*/
|
|
508
|
+
function toModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi> {
|
|
509
|
+
return { ...model, compat: model.compatConfig } as ModelSpec<TApi>;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* The patchable subset of `Model` fields shared by `modelOverrides` entries,
|
|
514
|
+
* custom model definitions, and parsed custom-model overlays. `undefined`
|
|
515
|
+
* always means "leave the base value alone".
|
|
516
|
+
*/
|
|
517
|
+
interface ModelPatch {
|
|
518
|
+
name?: string;
|
|
519
|
+
reasoning?: boolean;
|
|
520
|
+
thinking?: ThinkingConfig;
|
|
521
|
+
input?: ("text" | "image")[];
|
|
522
|
+
supportsTools?: boolean;
|
|
523
|
+
cost?: Partial<Model<Api>["cost"]>;
|
|
524
|
+
contextWindow?: number;
|
|
525
|
+
maxTokens?: number;
|
|
526
|
+
omitMaxOutputTokens?: boolean;
|
|
527
|
+
headers?: Record<string, string>;
|
|
528
|
+
compat?: ModelSpec<Api>["compat"];
|
|
529
|
+
contextPromotionTarget?: string;
|
|
530
|
+
compactionModel?: string;
|
|
531
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
532
|
+
premiumMultiplier?: number;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* How a patch treats the base model's transport metadata (headers/compat):
|
|
537
|
+
* - `merge`: fold the patch into the base's (modelOverrides semantics).
|
|
538
|
+
* - `replace`: the patch owns transport wholesale — same-id custom definitions
|
|
539
|
+
* already folded provider-level headers/compat in during parsing, so bundled
|
|
540
|
+
* transport metadata must not be re-merged (see `#mergeCustomModels`).
|
|
541
|
+
*/
|
|
542
|
+
type ModelTransportPolicy = "merge" | "replace";
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Credential-aware model projection supplied by an extension provider. Receives
|
|
546
|
+
* the fully composed catalog and returns the list the host should serve.
|
|
547
|
+
*/
|
|
548
|
+
type ModifyModelsHook = (models: Model<Api>[], credentials: OAuthCredentials) => Model<Api>[];
|
|
549
|
+
|
|
550
|
+
function applyModelPatch(base: Model<Api>, patch: ModelPatch, transport: ModelTransportPolicy): Model<Api> {
|
|
551
|
+
const result = { ...base };
|
|
552
|
+
if (patch.name !== undefined) result.name = patch.name;
|
|
553
|
+
if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
|
|
554
|
+
if (patch.thinking !== undefined) result.thinking = patch.thinking;
|
|
555
|
+
if (patch.input !== undefined) result.input = patch.input;
|
|
556
|
+
if (patch.supportsTools !== undefined) result.supportsTools = patch.supportsTools;
|
|
557
|
+
if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
|
|
558
|
+
if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
|
|
559
|
+
if (patch.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = patch.omitMaxOutputTokens;
|
|
560
|
+
if (patch.contextPromotionTarget !== undefined) result.contextPromotionTarget = patch.contextPromotionTarget;
|
|
561
|
+
if (patch.compactionModel !== undefined) result.compactionModel = patch.compactionModel;
|
|
562
|
+
if (patch.remoteCompaction !== undefined) {
|
|
563
|
+
result.remoteCompaction = mergeRemoteCompactionConfig(base.remoteCompaction, patch.remoteCompaction);
|
|
564
|
+
}
|
|
565
|
+
if (patch.premiumMultiplier !== undefined) result.premiumMultiplier = patch.premiumMultiplier;
|
|
566
|
+
if (patch.cost) {
|
|
567
|
+
result.cost = {
|
|
568
|
+
input: patch.cost.input ?? base.cost.input,
|
|
569
|
+
output: patch.cost.output ?? base.cost.output,
|
|
570
|
+
cacheRead: patch.cost.cacheRead ?? base.cost.cacheRead,
|
|
571
|
+
cacheWrite: patch.cost.cacheWrite ?? base.cost.cacheWrite,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
let compat: ModelSpec<Api>["compat"];
|
|
575
|
+
if (transport === "merge") {
|
|
576
|
+
if (patch.headers) {
|
|
577
|
+
result.headers = { ...base.headers, ...patch.headers };
|
|
578
|
+
}
|
|
579
|
+
compat = mergeCompat(base.compatConfig, patch.compat);
|
|
580
|
+
} else {
|
|
581
|
+
result.headers = patch.headers;
|
|
582
|
+
compat = patch.compat;
|
|
583
|
+
}
|
|
584
|
+
const built = buildModel({ ...result, compat } as ModelSpec<Api>);
|
|
585
|
+
if (patch.thinking !== undefined && built.thinking !== undefined) {
|
|
586
|
+
// Config-authored capability metadata owns the explicit surface; build
|
|
587
|
+
// first so non-reasoning and wire-disabled models still suppress it.
|
|
588
|
+
built.thinking = patch.thinking;
|
|
589
|
+
}
|
|
590
|
+
return built;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {
|
|
594
|
+
return applyModelPatch(model, override as ModelPatch, "merge");
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
interface CustomModelDefinitionLike extends ModelPatch {
|
|
598
|
+
id: string;
|
|
599
|
+
api?: Api;
|
|
600
|
+
baseUrl?: string;
|
|
601
|
+
cost?: Model<Api>["cost"];
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
interface CustomModelBuildOptions {
|
|
605
|
+
useDefaults: boolean;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
interface CustomModelOverlay extends ModelPatch {
|
|
609
|
+
id: string;
|
|
610
|
+
provider: string;
|
|
611
|
+
api: Api;
|
|
612
|
+
baseUrl: string;
|
|
613
|
+
cost?: Model<Api>["cost"];
|
|
614
|
+
isOAuth?: boolean;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function mergeCustomModelHeaders(
|
|
618
|
+
providerHeaders: Record<string, string> | undefined,
|
|
619
|
+
modelHeaders: Record<string, string> | undefined,
|
|
620
|
+
authHeader: boolean | undefined,
|
|
621
|
+
apiKeyConfig: string | undefined,
|
|
622
|
+
): Record<string, string> | undefined {
|
|
623
|
+
return createLiveConfigHeaders([providerHeaders, modelHeaders], { authHeader, apiKeyConfig });
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function mergeAuthHeaderSources(
|
|
627
|
+
sources: readonly HeaderSource[],
|
|
628
|
+
authHeader: boolean | undefined,
|
|
629
|
+
apiKeyConfig: string | undefined,
|
|
630
|
+
): Record<string, string> | undefined {
|
|
631
|
+
return createLiveConfigHeaders(sources, { authHeader, apiKeyConfig });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Decide whether a custom-yaml model should force OAuth-style request shaping.
|
|
636
|
+
* - Explicit `auth: oauth` → force on.
|
|
637
|
+
* endpoints are typically Claude-Code-style proxies (e.g. CLIProxyAPI) that expect
|
|
638
|
+
* the cloaked request shape regardless of how the proxy itself is authenticated.
|
|
639
|
+
* - Otherwise → unset.
|
|
640
|
+
*/
|
|
641
|
+
function resolveCustomModelIsOAuth(api: Api, providerAuth: ProviderAuthMode | undefined): boolean | undefined {
|
|
642
|
+
if (providerAuth === "oauth") return true;
|
|
643
|
+
if (providerAuth !== undefined) return undefined;
|
|
644
|
+
if (api === "anthropic-messages") return true;
|
|
645
|
+
return undefined;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function buildCustomModelOverlay(
|
|
649
|
+
providerName: string,
|
|
650
|
+
providerBaseUrl: string,
|
|
651
|
+
providerApi: Api | undefined,
|
|
652
|
+
providerHeaders: Record<string, string> | undefined,
|
|
653
|
+
providerApiKey: string | undefined,
|
|
654
|
+
authHeader: boolean | undefined,
|
|
655
|
+
providerCompat: ModelSpec<Api>["compat"] | undefined,
|
|
656
|
+
providerAuth: ProviderAuthMode | undefined,
|
|
657
|
+
providerRemoteCompaction: RemoteCompactionConfig<Api> | undefined,
|
|
658
|
+
modelDef: CustomModelDefinitionLike,
|
|
659
|
+
): CustomModelOverlay | undefined {
|
|
660
|
+
const api = modelDef.api ?? providerApi;
|
|
661
|
+
if (!api) return undefined;
|
|
662
|
+
return {
|
|
663
|
+
id: modelDef.id,
|
|
664
|
+
provider: providerName,
|
|
665
|
+
api,
|
|
666
|
+
baseUrl: modelDef.baseUrl ?? providerBaseUrl,
|
|
667
|
+
name: modelDef.name,
|
|
668
|
+
reasoning: modelDef.reasoning,
|
|
669
|
+
thinking: modelDef.thinking,
|
|
670
|
+
input: modelDef.input,
|
|
671
|
+
supportsTools: modelDef.supportsTools,
|
|
672
|
+
cost: modelDef.cost,
|
|
673
|
+
contextWindow: modelDef.contextWindow,
|
|
674
|
+
maxTokens: modelDef.maxTokens,
|
|
675
|
+
omitMaxOutputTokens: modelDef.omitMaxOutputTokens,
|
|
676
|
+
headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
|
|
677
|
+
compat: mergeCompat(providerCompat, modelDef.compat),
|
|
678
|
+
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
679
|
+
compactionModel: modelDef.compactionModel,
|
|
680
|
+
remoteCompaction: mergeRemoteCompactionConfig(providerRemoteCompaction, modelDef.remoteCompaction),
|
|
681
|
+
premiumMultiplier: modelDef.premiumMultiplier,
|
|
682
|
+
isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomModelOverlay {
|
|
687
|
+
if (model.id !== "gpt-5.4" || model.provider === "github-copilot" || model.contextWindow !== undefined) {
|
|
688
|
+
return model;
|
|
689
|
+
}
|
|
690
|
+
return { ...model, contextWindow: 1_000_000 };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuildOptions): Model<Api> {
|
|
694
|
+
const resolvedModel = options.useDefaults ? applyStandaloneCustomModelPolicies(model) : model;
|
|
695
|
+
const reference = options.useDefaults
|
|
696
|
+
? resolveModelReference(resolvedModel.id, getBundledModelReferenceIndex())
|
|
697
|
+
: undefined;
|
|
698
|
+
const cost =
|
|
699
|
+
resolvedModel.cost ??
|
|
700
|
+
reference?.cost ??
|
|
701
|
+
(options.useDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
|
|
702
|
+
const input = resolvedModel.input ?? reference?.input ?? (options.useDefaults ? ["text"] : undefined);
|
|
703
|
+
const supportsTools = resolvedModel.supportsTools ?? reference?.supportsTools;
|
|
704
|
+
return buildModel({
|
|
705
|
+
id: resolvedModel.id,
|
|
706
|
+
name: resolvedModel.name ?? (options.useDefaults ? resolvedModel.id : undefined),
|
|
707
|
+
api: resolvedModel.api,
|
|
708
|
+
provider: resolvedModel.provider,
|
|
709
|
+
baseUrl: resolvedModel.baseUrl,
|
|
710
|
+
reasoning: resolvedModel.reasoning ?? reference?.reasoning ?? (options.useDefaults ? false : undefined),
|
|
711
|
+
thinking: inheritReferenceThinking(resolvedModel.thinking, reference, resolvedModel.provider),
|
|
712
|
+
input: input as ("text" | "image")[],
|
|
713
|
+
...(supportsTools !== undefined ? { supportsTools } : {}),
|
|
714
|
+
cost,
|
|
715
|
+
contextWindow: resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : null),
|
|
716
|
+
maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : null),
|
|
717
|
+
headers: resolvedModel.headers,
|
|
718
|
+
omitMaxOutputTokens: resolvedModel.omitMaxOutputTokens ?? reference?.omitMaxOutputTokens,
|
|
719
|
+
compat: mergeCompat(reference?.compatConfig, resolvedModel.compat),
|
|
720
|
+
contextPromotionTarget: resolvedModel.contextPromotionTarget,
|
|
721
|
+
compactionModel: resolvedModel.compactionModel,
|
|
722
|
+
remoteCompaction: resolvedModel.remoteCompaction,
|
|
723
|
+
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
724
|
+
isOAuth: resolvedModel.isOAuth,
|
|
725
|
+
} as ModelSpec<Api>);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function normalizeSuppressedSelector(
|
|
729
|
+
selector: string,
|
|
730
|
+
hasLiveModel?: (provider: string, id: string) => boolean,
|
|
731
|
+
): string {
|
|
732
|
+
const trimmed = selector.trim();
|
|
733
|
+
if (!trimmed) return trimmed;
|
|
734
|
+
const parsed = parseModelString(trimmed, {
|
|
735
|
+
allowMaxSuffix: true,
|
|
736
|
+
allowAutoAlias: true,
|
|
737
|
+
isLiteralModelId: (provider, id) => hasLiveModel?.(provider, id) === true,
|
|
738
|
+
});
|
|
739
|
+
if (!parsed) return trimmed;
|
|
740
|
+
// Retired effort-tier variant ids normalize to their collapsed logical id
|
|
741
|
+
// so persisted suppressions keyed by raw member ids still bind.
|
|
742
|
+
const aliasId = resolveVariantAlias(parsed.provider, parsed.id);
|
|
743
|
+
return `${parsed.provider}/${aliasId ?? parsed.id}`;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Look up a model's override, falling back to entries keyed by retired
|
|
748
|
+
* effort-tier variant ids (models.yml authored before collapsing). A raw key
|
|
749
|
+
* only re-binds when no live model holds that id.
|
|
750
|
+
*/
|
|
751
|
+
function resolveModelOverrideWithAliases(
|
|
752
|
+
overrides: Map<string, ModelOverride>,
|
|
753
|
+
model: Model<Api>,
|
|
754
|
+
hasLiveModel: (provider: string, id: string) => boolean,
|
|
755
|
+
): ModelOverride | undefined {
|
|
756
|
+
const direct = overrides.get(model.id);
|
|
757
|
+
if (direct) return direct;
|
|
758
|
+
for (const rawId of getVariantAliasSources(model.provider, model.id)) {
|
|
759
|
+
if (hasLiveModel(model.provider, rawId)) continue;
|
|
760
|
+
const remapped = overrides.get(rawId);
|
|
761
|
+
if (remapped) {
|
|
762
|
+
logger.debug("model override re-keyed through variant alias", {
|
|
763
|
+
provider: model.provider,
|
|
764
|
+
from: rawId,
|
|
765
|
+
to: model.id,
|
|
766
|
+
});
|
|
767
|
+
return remapped;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return undefined;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function getDisabledProviderIdsFromSettings(): Set<string> {
|
|
774
|
+
try {
|
|
775
|
+
return new Set(settings.get("disabledProviders"));
|
|
776
|
+
} catch {
|
|
777
|
+
return new Set();
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/** Authentication material returned to legacy extensions for one model request. */
|
|
782
|
+
export type ResolvedRequestAuth =
|
|
783
|
+
| {
|
|
784
|
+
ok: true;
|
|
785
|
+
apiKey?: string;
|
|
786
|
+
headers?: Record<string, string>;
|
|
787
|
+
env?: Record<string, string>;
|
|
788
|
+
}
|
|
789
|
+
| { ok: false; error: string };
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
|
793
|
+
*/
|
|
794
|
+
export class ModelRegistry {
|
|
795
|
+
#models: Model<Api>[] = [];
|
|
796
|
+
#unprojectedModels: Model<Api>[] = [];
|
|
797
|
+
#hasFullSnapshot = false;
|
|
798
|
+
#cachedStandardModels: Model<Api>[] = [];
|
|
799
|
+
#cachedDiscoverableModels: Model<Api>[] = [];
|
|
800
|
+
#cachedAuthoritativeProviders: Set<string> = new Set();
|
|
801
|
+
#internedStaticModels: Map<string, Model<Api>> = new Map();
|
|
802
|
+
#providerLookupSnapshots: Map<string, Model<Api>[]> = new Map();
|
|
803
|
+
#customProviderApiKeys: Map<string, string> = new Map();
|
|
804
|
+
#keylessProviders: Set<string> = new Set();
|
|
805
|
+
#discoverableProviders: DiscoveryProviderConfig[] = [];
|
|
806
|
+
#customModelOverlays: CustomModelOverlay[] = [];
|
|
807
|
+
#providerOverrides: Map<string, ProviderOverride> = new Map();
|
|
808
|
+
#modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
|
|
809
|
+
#configError: ConfigError | undefined = undefined;
|
|
810
|
+
#modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
811
|
+
#lastStaticLoadMtime: number | null = null;
|
|
812
|
+
#registeredProviderSources: Set<string> = new Set();
|
|
813
|
+
#providerDiscoveryStates: Map<string, ProviderDiscoveryState> = new Map();
|
|
814
|
+
#cacheDbPath?: string;
|
|
815
|
+
#suppressedSelectors: Map<string, number> = new Map();
|
|
816
|
+
#backgroundRefresh?: Promise<void>;
|
|
817
|
+
#lastDiscoveryWarnings: Map<string, string> = new Map();
|
|
818
|
+
// Runtime extension model overlays — persist across refresh() cycles so that
|
|
819
|
+
// models registered by extensions survive the model selector's offline reload.
|
|
820
|
+
#runtimeModelOverlays: CustomModelOverlay[] = [];
|
|
821
|
+
#runtimeProviderApiKeys: Map<string, string> = new Map();
|
|
822
|
+
#runtimeProviderOverrides: Map<string, ProviderOverride> = new Map();
|
|
823
|
+
// Credential-aware model projections registered via
|
|
824
|
+
// `registerProvider({ oauth: { modifyModels } })`. Persisted for the same
|
|
825
|
+
// reason as #runtimeModelOverlays: the overlays hold the *pre-projection*
|
|
826
|
+
// definitions, so without re-applying the projection every static reload
|
|
827
|
+
// would silently revert the provider to its unprojected catalog.
|
|
828
|
+
#runtimeModelModifiers: Map<string, ModifyModelsHook> = new Map();
|
|
829
|
+
#lastModelModifierWarnings: Map<string, string> = new Map();
|
|
830
|
+
#runtimeProvidersBySource: Map<string, Set<string>> = new Map();
|
|
831
|
+
#runtimeProviderSourceByName: Map<string, string> = new Map();
|
|
832
|
+
// Runtime model managers registered by extensions via fetchDynamicModels.
|
|
833
|
+
// Keyed by provider name; use the same SQLite cache path as builtins.
|
|
834
|
+
#runtimeModelManagers: Map<string, { options: ModelManagerOptions<Api>; sourceId: string }> = new Map();
|
|
835
|
+
#ignoreLocalModelConfig: boolean;
|
|
836
|
+
#fetch: FetchImpl;
|
|
837
|
+
|
|
838
|
+
#resolveCommandBackedApiKey(provider: string): CommandApiKeyResolution {
|
|
839
|
+
const keyConfig = this.#customProviderApiKeys.get(provider);
|
|
840
|
+
if (!isCommandConfigValue(keyConfig)) return { configured: false };
|
|
841
|
+
const value = resolveConfigValue(keyConfig);
|
|
842
|
+
if (value) {
|
|
843
|
+
this.authStorage.setConfigApiKey(provider, value);
|
|
844
|
+
return { configured: true, value };
|
|
845
|
+
}
|
|
846
|
+
this.authStorage.removeConfigApiKey(provider);
|
|
847
|
+
return { configured: true };
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
#installProviderApiKey(provider: string, keyConfig: string): void {
|
|
851
|
+
this.#customProviderApiKeys.set(provider, keyConfig);
|
|
852
|
+
const resolved = resolveConfigValue(keyConfig);
|
|
853
|
+
if (resolved) {
|
|
854
|
+
this.authStorage.setConfigApiKey(provider, resolved);
|
|
855
|
+
} else if (isCommandConfigValue(keyConfig)) {
|
|
856
|
+
this.authStorage.removeConfigApiKey(provider);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* @param authStorage - Auth storage for API key resolution
|
|
862
|
+
*
|
|
863
|
+
* Sync constructor — eagerly loads config (including migrations), cache
|
|
864
|
+
* metadata, and custom models. Bundled providers are enriched selectively
|
|
865
|
+
* when synchronous callers query them. Production boot paths SHOULD prefer
|
|
866
|
+
* {@link ModelRegistry.create} so the YAML/JSONC migration step lands off the
|
|
867
|
+
* event loop's hot path before the first `tryLoad()` runs.
|
|
868
|
+
*/
|
|
869
|
+
constructor(
|
|
870
|
+
readonly authStorage: AuthStorage,
|
|
871
|
+
modelsPath?: string,
|
|
872
|
+
options?: {
|
|
873
|
+
/**
|
|
874
|
+
* Gateway mode: ignore local `models.yml` entirely (provider overrides,
|
|
875
|
+
* config API keys, custom models, custom discovery). A broker-backed
|
|
876
|
+
* gateway serves only bundled + broker-discovered catalog metadata and
|
|
877
|
+
* must never apply client-side credential or routing overrides.
|
|
878
|
+
*/
|
|
879
|
+
ignoreLocalModelConfig?: boolean;
|
|
880
|
+
fetch?: FetchImpl;
|
|
881
|
+
},
|
|
882
|
+
) {
|
|
883
|
+
this.#ignoreLocalModelConfig = options?.ignoreLocalModelConfig ?? false;
|
|
884
|
+
this.#fetch =
|
|
885
|
+
options?.fetch ??
|
|
886
|
+
(isBunTestRuntime()
|
|
887
|
+
? () => Promise.reject(new Error("network disabled in model-registry runtime test"))
|
|
888
|
+
: wrapFetchForExtraCa(fetch));
|
|
889
|
+
this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
|
|
890
|
+
this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
|
|
891
|
+
// Set up fallback resolver for custom provider API keys
|
|
892
|
+
this.authStorage.setFallbackResolver(provider => {
|
|
893
|
+
const keyConfig = this.#customProviderApiKeys.get(provider);
|
|
894
|
+
if (!keyConfig) return undefined;
|
|
895
|
+
return resolveConfigValue(keyConfig);
|
|
896
|
+
});
|
|
897
|
+
// Load config and cache-backed layers synchronously in the constructor.
|
|
898
|
+
this.#loadModels();
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Reload models from disk (built-in + custom config).
|
|
903
|
+
*/
|
|
904
|
+
async refresh(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
|
|
905
|
+
this.#reloadStaticModels();
|
|
906
|
+
this.#suppressedSelectors.clear();
|
|
907
|
+
await this.#refreshRuntimeDiscoveries(strategy);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
refreshInBackground(strategy: ModelRefreshStrategy = "online-if-uncached"): void {
|
|
911
|
+
if (this.#backgroundRefresh) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const refreshPromise = this.refresh(strategy)
|
|
915
|
+
.catch(error => {
|
|
916
|
+
logger.warn("background model refresh failed", {
|
|
917
|
+
error: error instanceof Error ? error.message : String(error),
|
|
918
|
+
});
|
|
919
|
+
})
|
|
920
|
+
.finally(() => {
|
|
921
|
+
if (this.#backgroundRefresh === refreshPromise) {
|
|
922
|
+
this.#backgroundRefresh = undefined;
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
this.#backgroundRefresh = refreshPromise;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Wait for any in-flight background model discovery to settle.
|
|
930
|
+
*
|
|
931
|
+
* Background discovery started by {@link refreshInBackground} is
|
|
932
|
+
* fire-and-forget; RPC consumers (e.g. `get_available_models`,
|
|
933
|
+
* `set_model`) and deferred `--model` resolution that read the registry
|
|
934
|
+
* immediately after session creation can otherwise observe a partial
|
|
935
|
+
* catalog before discovery-backed providers have populated `#models`.
|
|
936
|
+
* Awaiting the tracked promise ensures the response reflects every
|
|
937
|
+
* configured provider once the initial background refresh resolves.
|
|
938
|
+
*
|
|
939
|
+
* No-op when no refresh is in flight (`#backgroundRefresh` cleared in the
|
|
940
|
+
* `finally` of `refreshInBackground` on completion). Resolves immediately
|
|
941
|
+
* in that case so already-warm sessions are unaffected. Discovery errors
|
|
942
|
+
* remain swallowed by `refreshInBackground`'s existing `.catch`.
|
|
943
|
+
*/
|
|
944
|
+
async awaitBackgroundRefresh(): Promise<void> {
|
|
945
|
+
if (this.#backgroundRefresh) {
|
|
946
|
+
await this.#backgroundRefresh;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
|
|
951
|
+
this.#reloadStaticModels();
|
|
952
|
+
for (const selector of this.#suppressedSelectors.keys()) {
|
|
953
|
+
if (selector.startsWith(`${providerId}/`)) {
|
|
954
|
+
this.#suppressedSelectors.delete(selector);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]));
|
|
958
|
+
// #reloadStaticModels above may have rebuilt #models from static sources,
|
|
959
|
+
// dropping models previously discovered by OTHER runtime providers (their
|
|
960
|
+
// fetchDynamicModels results live only in #models + the SQLite cache, not
|
|
961
|
+
// in #loadModels' static inputs). Restore them from cache with the default
|
|
962
|
+
// online-if-uncached strategy: no network while their cached row is
|
|
963
|
+
// fresh, so the scoped refresh above stays the only forced fetch.
|
|
964
|
+
const otherRuntimeProviderIds = new Set(
|
|
965
|
+
[...this.#runtimeModelManagers.keys()].filter(runtimeId => runtimeId !== providerId),
|
|
966
|
+
);
|
|
967
|
+
if (otherRuntimeProviderIds.size > 0) {
|
|
968
|
+
await this.#refreshRuntimeDiscoveries("online-if-uncached", otherRuntimeProviderIds);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Refresh dynamic metadata that can appear only after a local model loads.
|
|
974
|
+
*/
|
|
975
|
+
async refreshSelectedModelMetadata(model: Model<Api>): Promise<Model<Api>> {
|
|
976
|
+
const llamaCppDiscoveryConfig = this.#discoverableProviders.find(
|
|
977
|
+
providerConfig => providerConfig.provider === model.provider && providerConfig.discovery.type === "llama.cpp",
|
|
978
|
+
);
|
|
979
|
+
if (!llamaCppDiscoveryConfig) {
|
|
980
|
+
return model;
|
|
981
|
+
}
|
|
982
|
+
this.#ensureFullSnapshot();
|
|
983
|
+
const runtimeMetadata = await discoverLlamaCppModelRuntimeMetadata(
|
|
984
|
+
model,
|
|
985
|
+
this.#nonResolvingDiscoveryContext(),
|
|
986
|
+
llamaCppDiscoveryConfig.discovery.timeoutMs,
|
|
987
|
+
);
|
|
988
|
+
if (runtimeMetadata === undefined) {
|
|
989
|
+
return this.find(model.provider, model.id) ?? model;
|
|
990
|
+
}
|
|
991
|
+
const { contextWindow, maxTokens, input } = runtimeMetadata;
|
|
992
|
+
const current = this.find(model.provider, model.id) ?? model;
|
|
993
|
+
const override = this.#resolveLiveModelOverride(current);
|
|
994
|
+
const customModel = this.#resolveLiveCustomModelOverlay(current);
|
|
995
|
+
const patch: ModelPatch = {};
|
|
996
|
+
if (
|
|
997
|
+
contextWindow !== undefined &&
|
|
998
|
+
override?.contextWindow === undefined &&
|
|
999
|
+
customModel?.contextWindow === undefined &&
|
|
1000
|
+
current.contextWindow !== contextWindow
|
|
1001
|
+
) {
|
|
1002
|
+
patch.contextWindow = contextWindow;
|
|
1003
|
+
}
|
|
1004
|
+
const effectiveContextWindow =
|
|
1005
|
+
override?.contextWindow ??
|
|
1006
|
+
customModel?.contextWindow ??
|
|
1007
|
+
patch.contextWindow ??
|
|
1008
|
+
current.contextWindow ??
|
|
1009
|
+
contextWindow;
|
|
1010
|
+
if (maxTokens !== undefined && effectiveContextWindow !== undefined) {
|
|
1011
|
+
const effectiveMaxTokens = Math.min(maxTokens, effectiveContextWindow);
|
|
1012
|
+
if (
|
|
1013
|
+
override?.maxTokens === undefined &&
|
|
1014
|
+
customModel?.maxTokens === undefined &&
|
|
1015
|
+
current.maxTokens !== effectiveMaxTokens
|
|
1016
|
+
) {
|
|
1017
|
+
patch.maxTokens = effectiveMaxTokens;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (
|
|
1021
|
+
input !== undefined &&
|
|
1022
|
+
override?.input === undefined &&
|
|
1023
|
+
customModel?.input === undefined &&
|
|
1024
|
+
(current.input.length !== input.length || current.input.some((value, index) => value !== input[index]))
|
|
1025
|
+
) {
|
|
1026
|
+
patch.input = input;
|
|
1027
|
+
}
|
|
1028
|
+
if (patch.contextWindow === undefined && patch.maxTokens === undefined && patch.input === undefined) {
|
|
1029
|
+
return current;
|
|
1030
|
+
}
|
|
1031
|
+
const unprojected = resolveProviderModelReference(current.provider, current.id, this.#unprojectedModels);
|
|
1032
|
+
if (unprojected) {
|
|
1033
|
+
const patchedBase = applyModelPatch(unprojected, patch, "merge");
|
|
1034
|
+
this.#unprojectedModels = this.#unprojectedModels.map(candidate =>
|
|
1035
|
+
candidate.provider === unprojected.provider && candidate.id === unprojected.id ? patchedBase : candidate,
|
|
1036
|
+
);
|
|
1037
|
+
this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
|
|
1038
|
+
return resolveProviderModelReference(current.provider, current.id, this.#models) ?? patchedBase;
|
|
1039
|
+
}
|
|
1040
|
+
const patched = applyModelPatch(current, patch, "merge");
|
|
1041
|
+
this.#models = this.#models.map(candidate =>
|
|
1042
|
+
candidate.provider === current.provider && candidate.id === current.id ? patched : candidate,
|
|
1043
|
+
);
|
|
1044
|
+
return patched;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Discover models for providers registered at runtime via `fetchDynamicModels`
|
|
1049
|
+
* (extension providers). Merges the discovered catalog into the existing model
|
|
1050
|
+
* set without reloading static models, so dynamically-discovered models from
|
|
1051
|
+
* other providers are preserved. No-op when no runtime providers are registered.
|
|
1052
|
+
*
|
|
1053
|
+
* Drives the same SQLite model cache as built-in providers, so the default
|
|
1054
|
+
* `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
|
|
1055
|
+
*/
|
|
1056
|
+
async refreshRuntimeProviders(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
|
|
1057
|
+
if (this.#runtimeModelManagers.size === 0) {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
await this.#refreshRuntimeDiscoveries(strategy, new Set(this.#runtimeModelManagers.keys()));
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
#reloadStaticModels(): void {
|
|
1064
|
+
const currentMtime = this.#modelsConfigFile.getMtimeMs();
|
|
1065
|
+
if (currentMtime !== null && currentMtime === this.#lastStaticLoadMtime) {
|
|
1066
|
+
// Models config unchanged since last load; reloading would be redundant.
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
this.#modelsConfigFile.invalidate();
|
|
1070
|
+
this.#customProviderApiKeys.clear();
|
|
1071
|
+
this.#keylessProviders.clear();
|
|
1072
|
+
this.#discoverableProviders = [];
|
|
1073
|
+
// Drop config-sourced apiKeys from AuthStorage before reload; entries
|
|
1074
|
+
// removed from models.yml must actually disappear from the resolver, not
|
|
1075
|
+
// linger from the previous parse. The post-load setters below repopulate.
|
|
1076
|
+
this.authStorage.clearConfigApiKeys();
|
|
1077
|
+
// Restore runtime API keys before #loadModels — survives because
|
|
1078
|
+
// #loadModels only calls .set() on #customProviderApiKeys, never reassigns it.
|
|
1079
|
+
for (const [k, v] of this.#runtimeProviderApiKeys) {
|
|
1080
|
+
this.#installProviderApiKey(k, v);
|
|
1081
|
+
}
|
|
1082
|
+
this.#providerOverrides.clear();
|
|
1083
|
+
this.#modelOverrides.clear();
|
|
1084
|
+
this.#configError = undefined;
|
|
1085
|
+
this.#providerDiscoveryStates.clear();
|
|
1086
|
+
this.#loadModels();
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Get any error from loading custom models config (undefined if no error).
|
|
1091
|
+
*/
|
|
1092
|
+
getError(): ConfigError | undefined {
|
|
1093
|
+
return this.#configError;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
#loadModels() {
|
|
1097
|
+
this.#resetStaticComposition();
|
|
1098
|
+
// Load custom config first (to know which providers to override).
|
|
1099
|
+
const {
|
|
1100
|
+
models: customModels = [],
|
|
1101
|
+
overrides = new Map(),
|
|
1102
|
+
modelOverrides = new Map(),
|
|
1103
|
+
keylessProviders = new Set(),
|
|
1104
|
+
discoverableProviders = [],
|
|
1105
|
+
configuredProviders = new Set(),
|
|
1106
|
+
error: configError,
|
|
1107
|
+
} = this.#loadCustomModels();
|
|
1108
|
+
this.#configError = configError;
|
|
1109
|
+
this.#keylessProviders = keylessProviders;
|
|
1110
|
+
this.#discoverableProviders = discoverableProviders;
|
|
1111
|
+
this.#customModelOverlays = customModels;
|
|
1112
|
+
this.#providerOverrides = overrides;
|
|
1113
|
+
this.#modelOverrides = modelOverrides;
|
|
1114
|
+
|
|
1115
|
+
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
1116
|
+
const cachedStandardResult = this.#loadCachedStandardProviderModels();
|
|
1117
|
+
this.#cachedStandardModels = this.#applyHardcodedModelPolicies(cachedStandardResult.models);
|
|
1118
|
+
this.#cachedDiscoverableModels = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
|
|
1119
|
+
// Only drop bundled fallback models when the cached project-catalog row is
|
|
1120
|
+
// itself fresh AND authoritative. A stale or non-authoritative snapshot
|
|
1121
|
+
// (e.g. after ADC discovery failure rewrote the row with authoritative=0)
|
|
1122
|
+
// must not strip bundled Vertex Gemini entries — that would leave only the
|
|
1123
|
+
// stale project-scoped rows in API-key-only environments.
|
|
1124
|
+
this.#cachedAuthoritativeProviders = new Set<string>();
|
|
1125
|
+
for (const provider of providersWithAuthoritativeProjectCatalog(this.#cachedStandardModels)) {
|
|
1126
|
+
if (cachedStandardResult.authoritativeFreshProviders.has(provider)) {
|
|
1127
|
+
this.#cachedAuthoritativeProviders.add(provider);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
for (const provider of cachedStandardResult.authoritativeFreshProviders) {
|
|
1131
|
+
if (AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS.has(provider)) {
|
|
1132
|
+
this.#cachedAuthoritativeProviders.add(provider);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
this.#lastStaticLoadMtime = this.#modelsConfigFile.getMtimeMs();
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
#resetStaticComposition(): void {
|
|
1139
|
+
this.#models = [];
|
|
1140
|
+
this.#unprojectedModels = [];
|
|
1141
|
+
this.#hasFullSnapshot = false;
|
|
1142
|
+
this.#internedStaticModels.clear();
|
|
1143
|
+
this.#providerLookupSnapshots.clear();
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
#knownStaticProviders(): string[] {
|
|
1147
|
+
const providers = new Set<string>(getBundledProviders());
|
|
1148
|
+
for (const model of this.#cachedStandardModels) providers.add(model.provider);
|
|
1149
|
+
for (const model of this.#cachedDiscoverableModels) providers.add(model.provider);
|
|
1150
|
+
for (const model of this.#customModelOverlays) providers.add(model.provider);
|
|
1151
|
+
for (const model of this.#runtimeModelOverlays) providers.add(model.provider);
|
|
1152
|
+
return [...providers];
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
#internStaticModels(models: Model<Api>[]): Model<Api>[] {
|
|
1156
|
+
return models.map(model => {
|
|
1157
|
+
const key = `${model.provider}\u0000${model.id}`;
|
|
1158
|
+
const interned = this.#internedStaticModels.get(key);
|
|
1159
|
+
if (interned) return interned;
|
|
1160
|
+
this.#internedStaticModels.set(key, model);
|
|
1161
|
+
return model;
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Re-apply the credential-aware projections registered by extension providers.
|
|
1167
|
+
*
|
|
1168
|
+
* Runtime overlays hold the pre-projection definitions, so the registry keeps
|
|
1169
|
+
* those definitions separate from `#models` and reruns the ordered hooks after
|
|
1170
|
+
* every catalog rebuild. Otherwise an offline refresh silently restores the
|
|
1171
|
+
* provider's placeholder catalog.
|
|
1172
|
+
*
|
|
1173
|
+
* A throwing hook falls back to the catalog produced by earlier hooks instead
|
|
1174
|
+
* of failing the whole composition; one bad extension must not empty the
|
|
1175
|
+
* registry. The failure is logged (deduped per provider) so it is not silent.
|
|
1176
|
+
* Each hook receives a deep clone because the public contract permits
|
|
1177
|
+
* mutation of both the array and its model records before returning.
|
|
1178
|
+
*/
|
|
1179
|
+
#applyRuntimeModelModifiers(models: Model<Api>[]): Model<Api>[] {
|
|
1180
|
+
if (this.#runtimeModelModifiers.size === 0) return models;
|
|
1181
|
+
let projected = models;
|
|
1182
|
+
for (const [providerName, modifyModels] of this.#runtimeModelModifiers) {
|
|
1183
|
+
const credential = this.authStorage.getOAuthCredential(providerName);
|
|
1184
|
+
if (!credential) continue;
|
|
1185
|
+
try {
|
|
1186
|
+
projected = modifyModels(structuredClone(projected), credential);
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
this.#warnModelModifierFailure(providerName, error instanceof Error ? error.message : String(error));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return projected;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Dedup key is separate from `#lastDiscoveryWarnings` so a repeated modifier
|
|
1196
|
+
* failure cannot mask a subsequent discovery failure for the same provider.
|
|
1197
|
+
*/
|
|
1198
|
+
#warnModelModifierFailure(provider: string, error: string): void {
|
|
1199
|
+
if (this.#lastModelModifierWarnings.get(provider) === error) return;
|
|
1200
|
+
this.#lastModelModifierWarnings.set(provider, error);
|
|
1201
|
+
logger.warn("extension model projection failed; serving unprojected catalog", { provider, error });
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
#composeUnprojectedStaticModels(providerFilter?: ReadonlySet<string>): Model<Api>[] {
|
|
1205
|
+
const select = <T extends { provider: string }>(models: readonly T[]): T[] =>
|
|
1206
|
+
providerFilter ? models.filter(model => providerFilter.has(model.provider)) : [...models];
|
|
1207
|
+
let builtInModels = this.#applyHardcodedModelPolicies(
|
|
1208
|
+
this.#loadBuiltInModels(this.#providerOverrides, providerFilter),
|
|
1209
|
+
);
|
|
1210
|
+
if (this.#cachedAuthoritativeProviders.size > 0) {
|
|
1211
|
+
builtInModels = dropProviderModels(builtInModels, this.#cachedAuthoritativeProviders);
|
|
1212
|
+
}
|
|
1213
|
+
const resolvedDefaults = this.#mergeResolvedModels(
|
|
1214
|
+
this.#mergeResolvedModels(builtInModels, select(this.#cachedStandardModels)),
|
|
1215
|
+
select(this.#cachedDiscoverableModels),
|
|
1216
|
+
);
|
|
1217
|
+
const withConfigModels = this.#mergeCustomModels(resolvedDefaults, select(this.#customModelOverlays));
|
|
1218
|
+
const combined = this.#mergeCustomModels(withConfigModels, select(this.#runtimeModelOverlays));
|
|
1219
|
+
const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
|
|
1220
|
+
return this.#applyLlamaCppQwenThinkingToModels(this.#applyRuntimeProviderOverrides(withModelOverrides));
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
#composeStaticModels(providerFilter?: ReadonlySet<string>): Model<Api>[] {
|
|
1224
|
+
// A modifier is a whole-catalog transform. Build and project the full catalog
|
|
1225
|
+
// before narrowing a lazy lookup, matching getAll() followed by filtering.
|
|
1226
|
+
const projectFullCatalog = providerFilter !== undefined && this.#runtimeModelModifiers.size > 0;
|
|
1227
|
+
const unprojected = this.#composeUnprojectedStaticModels(projectFullCatalog ? undefined : providerFilter);
|
|
1228
|
+
const projected = this.#applyRuntimeModelModifiers(unprojected);
|
|
1229
|
+
const selected = projectFullCatalog ? projected.filter(model => providerFilter.has(model.provider)) : projected;
|
|
1230
|
+
return this.#internStaticModels(selected);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
#ensureFullSnapshot(): Model<Api>[] {
|
|
1234
|
+
if (!this.#hasFullSnapshot) {
|
|
1235
|
+
this.#unprojectedModels = this.#composeUnprojectedStaticModels();
|
|
1236
|
+
this.#models = this.#internStaticModels(this.#applyRuntimeModelModifiers(this.#unprojectedModels));
|
|
1237
|
+
this.#hasFullSnapshot = true;
|
|
1238
|
+
this.#providerLookupSnapshots.clear();
|
|
1239
|
+
}
|
|
1240
|
+
return this.#models;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/** Load built-in models, applying provider-level overrides only.
|
|
1244
|
+
* Per-model overrides are applied later by #applyModelOverrides. */
|
|
1245
|
+
#loadBuiltInModels(overrides: Map<string, ProviderOverride>, providerFilter?: ReadonlySet<string>): Model<Api>[] {
|
|
1246
|
+
return getBundledProviders().flatMap(provider => {
|
|
1247
|
+
if (providerFilter && !providerFilter.has(provider)) return [];
|
|
1248
|
+
const models = getBundledModels(provider as Parameters<typeof getBundledModels>[0]) as Model<Api>[];
|
|
1249
|
+
const providerOverride = overrides.get(provider);
|
|
1250
|
+
|
|
1251
|
+
return models.map(m => {
|
|
1252
|
+
if (!providerOverride) return m;
|
|
1253
|
+
const withTransportOverride = this.#applyProviderTransportOverride(m, providerOverride);
|
|
1254
|
+
return buildModel({
|
|
1255
|
+
...withTransportOverride,
|
|
1256
|
+
compat: mergeCompat(m.compatConfig, providerOverride.compat),
|
|
1257
|
+
} as ModelSpec<Api>);
|
|
1258
|
+
});
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
#mergeResolvedModels(baseModels: Model<Api>[], replacementModels: Model<Api>[]): Model<Api>[] {
|
|
1263
|
+
return mergeByModelKey(baseModels, replacementModels, (existing, replacementModel) => {
|
|
1264
|
+
if (!existing) return replacementModel;
|
|
1265
|
+
const supportsTools = replacementModel.supportsTools ?? existing.supportsTools;
|
|
1266
|
+
return {
|
|
1267
|
+
...replacementModel,
|
|
1268
|
+
contextWindow: replacementModel.contextWindow ?? existing.contextWindow,
|
|
1269
|
+
maxTokens: replacementModel.maxTokens ?? existing.maxTokens,
|
|
1270
|
+
omitMaxOutputTokens: replacementModel.omitMaxOutputTokens ?? existing.omitMaxOutputTokens,
|
|
1271
|
+
...(supportsTools !== undefined ? { supportsTools } : {}),
|
|
1272
|
+
};
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/** Merge custom models with built-in, replacing by provider+id match */
|
|
1277
|
+
#mergeCustomModels(builtInModels: Model<Api>[], customModels: CustomModelOverlay[]): Model<Api>[] {
|
|
1278
|
+
return mergeByModelKey(builtInModels, customModels, (existingModel, customModel) => {
|
|
1279
|
+
if (!existingModel) return finalizeCustomModel(customModel, { useDefaults: true });
|
|
1280
|
+
// Same-id custom definitions replace bundled transport behavior, so the
|
|
1281
|
+
// patch is applied with the `replace` transport policy.
|
|
1282
|
+
return applyModelPatch(
|
|
1283
|
+
{
|
|
1284
|
+
...existingModel,
|
|
1285
|
+
id: customModel.id,
|
|
1286
|
+
provider: customModel.provider,
|
|
1287
|
+
api: customModel.api,
|
|
1288
|
+
baseUrl: customModel.baseUrl,
|
|
1289
|
+
},
|
|
1290
|
+
customModel,
|
|
1291
|
+
"replace",
|
|
1292
|
+
);
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
#descriptorBaseUrl(providerId: string): string | undefined {
|
|
1297
|
+
return (
|
|
1298
|
+
this.#runtimeProviderOverrides.get(providerId)?.baseUrl ??
|
|
1299
|
+
this.#providerOverrides.get(providerId)?.baseUrl ??
|
|
1300
|
+
(this.#hasFullSnapshot ? this.getProviderBaseUrl(providerId) : undefined)
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
#resolveStartupModelCacheProviderId(providerId: string): string {
|
|
1305
|
+
const baseUrl =
|
|
1306
|
+
this.#runtimeProviderOverrides.get(providerId)?.baseUrl ??
|
|
1307
|
+
this.#providerOverrides.get(providerId)?.baseUrl ??
|
|
1308
|
+
(this.#hasFullSnapshot ? this.getProviderBaseUrl(providerId) : undefined);
|
|
1309
|
+
return resolveModelCacheProviderId(providerId, { baseUrl });
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
#loadCachedStandardProviderModels(): { models: Model<Api>[]; authoritativeFreshProviders: Set<string> } {
|
|
1313
|
+
const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(provider => provider.provider));
|
|
1314
|
+
const cachedModels: Model<Api>[] = [];
|
|
1315
|
+
const authoritativeFreshProviders = new Set<string>();
|
|
1316
|
+
for (const providerId of STARTUP_MODEL_CACHE_PROVIDER_IDS) {
|
|
1317
|
+
if (configuredDiscoveryProviders.has(providerId)) {
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
const cacheProviderId = this.#resolveStartupModelCacheProviderId(providerId);
|
|
1321
|
+
const cache = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1322
|
+
if (!cache) {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (cache.fresh && cache.authoritative) {
|
|
1326
|
+
authoritativeFreshProviders.add(providerId);
|
|
1327
|
+
}
|
|
1328
|
+
// The v10 model cache never persists request headers (#5780): restore
|
|
1329
|
+
// them from the bundled static catalog, and drop cached rows whose
|
|
1330
|
+
// headers cannot be rebuilt so the bundled fallback (which still
|
|
1331
|
+
// carries its headers) wins the startup merge instead of a cached
|
|
1332
|
+
// model with required transport headers missing.
|
|
1333
|
+
const omittedHeaderIds = new Set(cache.headerOmittedModelIds);
|
|
1334
|
+
const unrestorableHeaderIds = new Set(cache.unrestorableHeaderModelIds);
|
|
1335
|
+
const bundledById =
|
|
1336
|
+
omittedHeaderIds.size > 0
|
|
1337
|
+
? new Map(
|
|
1338
|
+
(getBundledModels(providerId as Parameters<typeof getBundledModels>[0]) as Model<Api>[]).map(
|
|
1339
|
+
bundledModel => [bundledModel.id, bundledModel],
|
|
1340
|
+
),
|
|
1341
|
+
)
|
|
1342
|
+
: undefined;
|
|
1343
|
+
const models: ModelSpec<Api>[] = [];
|
|
1344
|
+
for (const cachedModel of cache.models) {
|
|
1345
|
+
const spec = cachedModel.provider === providerId ? cachedModel : { ...cachedModel, provider: providerId };
|
|
1346
|
+
if (!omittedHeaderIds.has(spec.id)) {
|
|
1347
|
+
models.push(spec);
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
// Current unrestorable markers prove that neither same-id nor
|
|
1351
|
+
// request-model bundled headers matched the live model. Only markers
|
|
1352
|
+
// from the old id-only writer may recover through `requestModelId`.
|
|
1353
|
+
const unrestorable = unrestorableHeaderIds.has(spec.id);
|
|
1354
|
+
const bundledHeaders = (
|
|
1355
|
+
unrestorable
|
|
1356
|
+
? cache.legacyHeaderRestoreMarkers && spec.requestModelId
|
|
1357
|
+
? bundledById?.get(spec.requestModelId)
|
|
1358
|
+
: undefined
|
|
1359
|
+
: (bundledById?.get(spec.id) ??
|
|
1360
|
+
(spec.requestModelId ? bundledById?.get(spec.requestModelId) : undefined))
|
|
1361
|
+
)?.headers;
|
|
1362
|
+
if (!bundledHeaders) continue;
|
|
1363
|
+
models.push({ ...spec, headers: bundledHeaders });
|
|
1364
|
+
}
|
|
1365
|
+
const providerOverride = this.#providerOverrides.get(providerId);
|
|
1366
|
+
const withTransport = providerOverride
|
|
1367
|
+
? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
|
|
1368
|
+
: models;
|
|
1369
|
+
const withCompat = providerOverride?.compat
|
|
1370
|
+
? withTransport.map(model =>
|
|
1371
|
+
buildModel({
|
|
1372
|
+
...model,
|
|
1373
|
+
compat: mergeCompat(model.compat, providerOverride.compat),
|
|
1374
|
+
} as ModelSpec<Api>),
|
|
1375
|
+
)
|
|
1376
|
+
: withTransport.map(model => buildModel(model));
|
|
1377
|
+
cachedModels.push(...this.#applyProviderModelOverrides(providerId, withCompat));
|
|
1378
|
+
}
|
|
1379
|
+
return { models: cachedModels, authoritativeFreshProviders };
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
#loadCachedDiscoverableModels(): Model<Api>[] {
|
|
1383
|
+
const cachedModels: Model<Api>[] = [];
|
|
1384
|
+
for (const providerConfig of this.#discoverableProviders) {
|
|
1385
|
+
const cache = readModelCache<Api>(
|
|
1386
|
+
this.#configuredDiscoveryCacheProviderId(providerConfig),
|
|
1387
|
+
24 * 60 * 60 * 1000,
|
|
1388
|
+
Date.now,
|
|
1389
|
+
this.#cacheDbPath,
|
|
1390
|
+
);
|
|
1391
|
+
if (!cache) {
|
|
1392
|
+
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
1393
|
+
provider: providerConfig.provider,
|
|
1394
|
+
status: "idle",
|
|
1395
|
+
optional: providerConfig.optional ?? false,
|
|
1396
|
+
stale: false,
|
|
1397
|
+
models: [],
|
|
1398
|
+
});
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
const configStale = this.#isDiscoveryCacheOlderThanModelsConfig(cache.updatedAt);
|
|
1402
|
+
// Cached rows never persist headers (#5780); models that had live
|
|
1403
|
+
// headers cannot be rebuilt here, so exclude them and mark the
|
|
1404
|
+
// discovery stale to force a refetch instead of returning models
|
|
1405
|
+
// missing required transport headers.
|
|
1406
|
+
const omittedHeaderIds = new Set(cache.headerOmittedModelIds);
|
|
1407
|
+
const usableCacheModels =
|
|
1408
|
+
omittedHeaderIds.size > 0 ? cache.models.filter(model => !omittedHeaderIds.has(model.id)) : cache.models;
|
|
1409
|
+
const models = this.#applyProviderModelOverrides(
|
|
1410
|
+
providerConfig.provider,
|
|
1411
|
+
this.#normalizeDiscoverableModels(
|
|
1412
|
+
providerConfig,
|
|
1413
|
+
this.#applyProviderCompat(
|
|
1414
|
+
providerConfig.compat,
|
|
1415
|
+
usableCacheModels.map(model => buildModel(model)),
|
|
1416
|
+
),
|
|
1417
|
+
),
|
|
1418
|
+
);
|
|
1419
|
+
cachedModels.push(...models);
|
|
1420
|
+
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
1421
|
+
provider: providerConfig.provider,
|
|
1422
|
+
status: "cached",
|
|
1423
|
+
optional: providerConfig.optional ?? false,
|
|
1424
|
+
stale:
|
|
1425
|
+
providerConfig.discovery.type === "llama.cpp" ||
|
|
1426
|
+
!cache.fresh ||
|
|
1427
|
+
!cache.authoritative ||
|
|
1428
|
+
configStale ||
|
|
1429
|
+
omittedHeaderIds.size > 0,
|
|
1430
|
+
fetchedAt: cache.updatedAt,
|
|
1431
|
+
models: models.map(model => model.id),
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
return cachedModels;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
#applyProviderCompat(compat: ModelSpec<Api>["compat"] | undefined, models: Model<Api>[]): Model<Api>[] {
|
|
1438
|
+
if (!compat) return models;
|
|
1439
|
+
return models.map(model =>
|
|
1440
|
+
buildModel({ ...model, compat: mergeCompat(model.compatConfig, compat) } as ModelSpec<Api>),
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
#normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
|
|
1445
|
+
const withDecoderMetadata =
|
|
1446
|
+
providerConfig.discovery.type === "ollama" ||
|
|
1447
|
+
providerConfig.discovery.type === "llama.cpp" ||
|
|
1448
|
+
providerConfig.discovery.type === "lm-studio"
|
|
1449
|
+
? models.map(model =>
|
|
1450
|
+
buildModel({ ...model, imageInputDecoder: "stb", compat: model.compatConfig } as ModelSpec<Api>),
|
|
1451
|
+
)
|
|
1452
|
+
: models;
|
|
1453
|
+
|
|
1454
|
+
const withRemoteCompaction = providerConfig.remoteCompaction
|
|
1455
|
+
? withDecoderMetadata.map(model =>
|
|
1456
|
+
buildModel({
|
|
1457
|
+
...model,
|
|
1458
|
+
remoteCompaction: mergeProviderRemoteCompactionConfig(
|
|
1459
|
+
model.remoteCompaction,
|
|
1460
|
+
providerConfig.remoteCompaction,
|
|
1461
|
+
),
|
|
1462
|
+
compat: model.compatConfig,
|
|
1463
|
+
} as ModelSpec<Api>),
|
|
1464
|
+
)
|
|
1465
|
+
: withDecoderMetadata;
|
|
1466
|
+
|
|
1467
|
+
if (providerConfig.provider !== "ollama" || providerConfig.api !== "openai-responses") {
|
|
1468
|
+
return withRemoteCompaction;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
const contextLengthOverride = getOllamaContextLengthOverride();
|
|
1472
|
+
return withRemoteCompaction.map(model => {
|
|
1473
|
+
const normalized =
|
|
1474
|
+
model.api === "openai-completions"
|
|
1475
|
+
? buildModel({
|
|
1476
|
+
...model,
|
|
1477
|
+
api: "openai-responses" as const,
|
|
1478
|
+
compat: model.compatConfig,
|
|
1479
|
+
} as ModelSpec<Api>)
|
|
1480
|
+
: model;
|
|
1481
|
+
if (contextLengthOverride === undefined) {
|
|
1482
|
+
return normalized;
|
|
1483
|
+
}
|
|
1484
|
+
return {
|
|
1485
|
+
...normalized,
|
|
1486
|
+
contextWindow: contextLengthOverride,
|
|
1487
|
+
maxTokens: Math.min(contextLengthOverride, DISCOVERY_DEFAULT_MAX_TOKENS),
|
|
1488
|
+
};
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
#addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
|
|
1493
|
+
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
1494
|
+
if (!configuredProviders.has("ollama") && !disabledProviders.has("ollama")) {
|
|
1495
|
+
this.#discoverableProviders.push({
|
|
1496
|
+
provider: "ollama",
|
|
1497
|
+
api: "openai-responses",
|
|
1498
|
+
baseUrl: getImplicitOllamaBaseUrl(),
|
|
1499
|
+
discovery: { type: "ollama" },
|
|
1500
|
+
optional: true,
|
|
1501
|
+
});
|
|
1502
|
+
this.#keylessProviders.add("ollama");
|
|
1503
|
+
}
|
|
1504
|
+
if (!configuredProviders.has("llama.cpp") && !disabledProviders.has("llama.cpp")) {
|
|
1505
|
+
this.#discoverableProviders.push({
|
|
1506
|
+
provider: "llama.cpp",
|
|
1507
|
+
api: "openai-responses",
|
|
1508
|
+
baseUrl: Bun.env.LLAMA_CPP_BASE_URL || "http://127.0.0.1:8080",
|
|
1509
|
+
discovery: { type: "llama.cpp" },
|
|
1510
|
+
optional: true,
|
|
1511
|
+
});
|
|
1512
|
+
// Only mark as keyless if no API key is configured
|
|
1513
|
+
if (!this.authStorage.hasAuth("llama.cpp")) {
|
|
1514
|
+
this.#keylessProviders.add("llama.cpp");
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (!configuredProviders.has("lm-studio") && !disabledProviders.has("lm-studio")) {
|
|
1518
|
+
this.#discoverableProviders.push({
|
|
1519
|
+
provider: "lm-studio",
|
|
1520
|
+
api: "openai-completions",
|
|
1521
|
+
baseUrl: Bun.env.LM_STUDIO_BASE_URL || "http://127.0.0.1:1234/v1",
|
|
1522
|
+
discovery: { type: "lm-studio" },
|
|
1523
|
+
optional: true,
|
|
1524
|
+
});
|
|
1525
|
+
this.#keylessProviders.add("lm-studio");
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
#loadCustomModels(): CustomModelsResult {
|
|
1530
|
+
// Gateway mode: serve bundled + broker-discovered catalog metadata only.
|
|
1531
|
+
// Local models.yml provider overrides (baseUrl/apiKey/headers/transport),
|
|
1532
|
+
// custom models, custom discovery, and config API keys are all client-side
|
|
1533
|
+
// routing that MUST NOT reach a broker-backed gateway — applying them would
|
|
1534
|
+
// send broker bearers to a configured endpoint, install config keys that
|
|
1535
|
+
// shadow broker credentials (bypassing account pooling/refresh/accounting),
|
|
1536
|
+
// or route a pi-native gateway back into itself.
|
|
1537
|
+
if (this.#ignoreLocalModelConfig) {
|
|
1538
|
+
return {
|
|
1539
|
+
models: [],
|
|
1540
|
+
overrides: new Map(),
|
|
1541
|
+
modelOverrides: new Map(),
|
|
1542
|
+
keylessProviders: new Set(),
|
|
1543
|
+
discoverableProviders: [],
|
|
1544
|
+
configuredProviders: new Set(),
|
|
1545
|
+
found: false,
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
const { value, error, status } = this.#modelsConfigFile.tryLoad();
|
|
1549
|
+
|
|
1550
|
+
if (status === "error") {
|
|
1551
|
+
return {
|
|
1552
|
+
models: [],
|
|
1553
|
+
overrides: new Map(),
|
|
1554
|
+
modelOverrides: new Map(),
|
|
1555
|
+
keylessProviders: new Set(),
|
|
1556
|
+
discoverableProviders: [],
|
|
1557
|
+
configuredProviders: new Set(),
|
|
1558
|
+
error,
|
|
1559
|
+
found: true,
|
|
1560
|
+
};
|
|
1561
|
+
} else if (status === "not-found") {
|
|
1562
|
+
return {
|
|
1563
|
+
models: [],
|
|
1564
|
+
overrides: new Map(),
|
|
1565
|
+
modelOverrides: new Map(),
|
|
1566
|
+
keylessProviders: new Set(),
|
|
1567
|
+
discoverableProviders: [],
|
|
1568
|
+
configuredProviders: new Set(),
|
|
1569
|
+
found: false,
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
const overrides = new Map<string, ProviderOverride>();
|
|
1574
|
+
const allModelOverrides = new Map<string, Map<string, ModelOverride>>();
|
|
1575
|
+
const keylessProviders = new Set<string>();
|
|
1576
|
+
const discoverableProviders: DiscoveryProviderConfig[] = [];
|
|
1577
|
+
const providerEntries = Object.entries(value.providers ?? {});
|
|
1578
|
+
const configuredProviders = new Set(Object.keys(value.providers ?? {}));
|
|
1579
|
+
for (const [providerName, providerConfig] of providerEntries) {
|
|
1580
|
+
const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
|
|
1581
|
+
// Always set overrides when baseUrl/headers/apiKey/authHeader/compat/disableStrictTools/transport are present
|
|
1582
|
+
if (
|
|
1583
|
+
providerConfig.baseUrl ||
|
|
1584
|
+
resolvedProviderHeaders ||
|
|
1585
|
+
providerConfig.apiKey ||
|
|
1586
|
+
providerConfig.authHeader !== undefined ||
|
|
1587
|
+
providerConfig.compat ||
|
|
1588
|
+
providerConfig.disableStrictTools ||
|
|
1589
|
+
providerConfig.remoteCompaction ||
|
|
1590
|
+
providerConfig.transport
|
|
1591
|
+
) {
|
|
1592
|
+
const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
|
|
1593
|
+
overrides.set(providerName, {
|
|
1594
|
+
baseUrl:
|
|
1595
|
+
providerConfig.discovery?.type === "litellm"
|
|
1596
|
+
? normalizeLiteLLMDiscoveryBaseUrl(providerConfig.baseUrl)
|
|
1597
|
+
: providerConfig.baseUrl,
|
|
1598
|
+
headers: resolvedProviderHeaders,
|
|
1599
|
+
apiKey: providerConfig.apiKey,
|
|
1600
|
+
authHeader: providerConfig.authHeader,
|
|
1601
|
+
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
1602
|
+
remoteCompaction: providerConfig.remoteCompaction,
|
|
1603
|
+
transport: providerConfig.transport,
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
const authMode = (providerConfig.auth ?? "apiKey") as ProviderAuthMode;
|
|
1608
|
+
if (authMode === "none") {
|
|
1609
|
+
keylessProviders.add(providerName);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
if (providerConfig.discovery && (providerConfig.api || providerConfig.discovery.type === "proxy")) {
|
|
1613
|
+
const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
|
|
1614
|
+
discoverableProviders.push({
|
|
1615
|
+
provider: providerName,
|
|
1616
|
+
// Proxy discovery derives per-model api from /v1/models's
|
|
1617
|
+
// supported_endpoint_types; the provider-level api is only a
|
|
1618
|
+
// fallback for entries that don't advertise one.
|
|
1619
|
+
api: (providerConfig.api ?? "openai-completions") as Api,
|
|
1620
|
+
baseUrl: providerConfig.baseUrl,
|
|
1621
|
+
headers: resolvedProviderHeaders,
|
|
1622
|
+
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
1623
|
+
remoteCompaction: providerConfig.remoteCompaction,
|
|
1624
|
+
discovery: providerConfig.discovery,
|
|
1625
|
+
optional: false,
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// Store API key for fallback resolver AND register as config override
|
|
1630
|
+
// so it wins over OAuth tokens from the broker — when the user pins a
|
|
1631
|
+
// bearer in models.yml (e.g. for an auth-gateway baseUrl), that bearer
|
|
1632
|
+
// must authenticate the outbound request.
|
|
1633
|
+
if (providerConfig.apiKey) {
|
|
1634
|
+
this.#installProviderApiKey(providerName, providerConfig.apiKey);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// Parse per-model overrides
|
|
1638
|
+
if (providerConfig.modelOverrides) {
|
|
1639
|
+
const perModel = new Map<string, ModelOverride>();
|
|
1640
|
+
for (const [modelId, override] of Object.entries(providerConfig.modelOverrides)) {
|
|
1641
|
+
perModel.set(
|
|
1642
|
+
modelId,
|
|
1643
|
+
override.headers ? { ...override, headers: resolveConfigHeaders(override.headers) } : override,
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
allModelOverrides.set(providerName, perModel);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
return {
|
|
1651
|
+
models: this.#parseModels(value),
|
|
1652
|
+
overrides,
|
|
1653
|
+
modelOverrides: allModelOverrides,
|
|
1654
|
+
keylessProviders,
|
|
1655
|
+
discoverableProviders,
|
|
1656
|
+
configuredProviders,
|
|
1657
|
+
found: true,
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
async #refreshRuntimeDiscoveries(
|
|
1662
|
+
strategy: ModelRefreshStrategy,
|
|
1663
|
+
providerFilter?: ReadonlySet<string>,
|
|
1664
|
+
): Promise<void> {
|
|
1665
|
+
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
1666
|
+
const selectedDiscoverableProviders = (
|
|
1667
|
+
providerFilter
|
|
1668
|
+
? this.#discoverableProviders.filter(provider => providerFilter.has(provider.provider))
|
|
1669
|
+
: this.#discoverableProviders
|
|
1670
|
+
).filter(provider => !disabledProviders.has(provider.provider));
|
|
1671
|
+
const configuredDiscoveriesPromise =
|
|
1672
|
+
selectedDiscoverableProviders.length === 0
|
|
1673
|
+
? Promise.resolve<Model<Api>[]>([])
|
|
1674
|
+
: Promise.all(
|
|
1675
|
+
selectedDiscoverableProviders.map(provider => this.#discoverProviderModels(provider, strategy)),
|
|
1676
|
+
).then(results => results.flat());
|
|
1677
|
+
const [configuredDiscovered, builtInDiscovery] = await Promise.all([
|
|
1678
|
+
configuredDiscoveriesPromise,
|
|
1679
|
+
this.#discoverBuiltInProviderModels(strategy, providerFilter),
|
|
1680
|
+
]);
|
|
1681
|
+
const discovered = [...configuredDiscovered, ...builtInDiscovery.models];
|
|
1682
|
+
if (discovered.length === 0 && builtInDiscovery.authoritativeProviders.size === 0) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
this.#ensureFullSnapshot();
|
|
1686
|
+
const discoveredModels = this.#applyHardcodedModelPolicies(
|
|
1687
|
+
discovered.map(model =>
|
|
1688
|
+
mergeDiscoveredModel(
|
|
1689
|
+
model,
|
|
1690
|
+
resolveProviderModelReference(model.provider, model.id, this.#unprojectedModels),
|
|
1691
|
+
this.#providerOverrides.get(model.provider),
|
|
1692
|
+
),
|
|
1693
|
+
),
|
|
1694
|
+
);
|
|
1695
|
+
const authoritativeProviders = providersWithAuthoritativeProjectCatalog(discoveredModels);
|
|
1696
|
+
for (const provider of builtInDiscovery.authoritativeProviders) {
|
|
1697
|
+
authoritativeProviders.add(provider);
|
|
1698
|
+
}
|
|
1699
|
+
const baseModels =
|
|
1700
|
+
authoritativeProviders.size > 0
|
|
1701
|
+
? dropProviderModels(this.#unprojectedModels, authoritativeProviders)
|
|
1702
|
+
: this.#unprojectedModels;
|
|
1703
|
+
const resolved = this.#mergeResolvedModels(baseModels, discoveredModels);
|
|
1704
|
+
const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
|
|
1705
|
+
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
1706
|
+
const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
|
|
1707
|
+
this.#unprojectedModels = this.#applyLlamaCppQwenThinkingToModels(
|
|
1708
|
+
this.#applyRuntimeProviderOverrides(withModelOverrides),
|
|
1709
|
+
);
|
|
1710
|
+
this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
#configuredDiscoveryCacheProviderId(providerConfig: DiscoveryProviderConfig): string {
|
|
1714
|
+
if (providerConfig.discovery.type === "ollama") {
|
|
1715
|
+
return resolveOllamaModelCacheProviderId(providerConfig.provider, providerConfig.baseUrl);
|
|
1716
|
+
}
|
|
1717
|
+
if (providerConfig.discovery.type === "openai-models-list") {
|
|
1718
|
+
// context-v3 invalidates rows cached before server-advertised input
|
|
1719
|
+
// modalities were parsed from `/v1/models`; warm v2 rows pinned
|
|
1720
|
+
// vision-capable ids at `input: ["text"]` until a forced refresh.
|
|
1721
|
+
return `${providerConfig.provider}:openai-models-list-context-v3`;
|
|
1722
|
+
}
|
|
1723
|
+
if (providerConfig.discovery.type === "litellm") {
|
|
1724
|
+
// rich-v2 invalidates rows cached before reseller usage-suffix stripping
|
|
1725
|
+
// (stale display names like `MiniMax-M3 (3x usage)`); keep in lockstep
|
|
1726
|
+
// with the catalog package's `litellm:rich-vN` namespace.
|
|
1727
|
+
return `${providerConfig.provider}:litellm-rich-v2`;
|
|
1728
|
+
}
|
|
1729
|
+
return providerConfig.provider;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
#isDiscoveryCacheOlderThanModelsConfig(cacheUpdatedAt: number): boolean {
|
|
1733
|
+
const configMtime = this.#modelsConfigFile.getMtimeMs();
|
|
1734
|
+
return configMtime !== null && cacheUpdatedAt < Math.floor(configMtime);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
async #discoverProviderModels(
|
|
1738
|
+
providerConfig: DiscoveryProviderConfig,
|
|
1739
|
+
strategy: ModelRefreshStrategy,
|
|
1740
|
+
): Promise<Model<Api>[]> {
|
|
1741
|
+
const cacheProviderId = this.#configuredDiscoveryCacheProviderId(providerConfig);
|
|
1742
|
+
const cached = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1743
|
+
const cacheOlderThanConfig = cached !== null && this.#isDiscoveryCacheOlderThanModelsConfig(cached.updatedAt);
|
|
1744
|
+
const bypassFreshCache = providerConfig.discovery.type === "llama.cpp" && strategy === "online-if-uncached";
|
|
1745
|
+
const effectiveStrategy =
|
|
1746
|
+
strategy === "online-if-uncached" && (cacheOlderThanConfig || bypassFreshCache) ? "online" : strategy;
|
|
1747
|
+
const requiresAuth = !this.#keylessProviders.has(providerConfig.provider);
|
|
1748
|
+
if (requiresAuth) {
|
|
1749
|
+
const apiKey = await this.#peekApiKeyForProvider(providerConfig.provider);
|
|
1750
|
+
if (!isAuthenticated(apiKey)) {
|
|
1751
|
+
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
1752
|
+
provider: providerConfig.provider,
|
|
1753
|
+
status: "unauthenticated",
|
|
1754
|
+
optional: providerConfig.optional ?? false,
|
|
1755
|
+
stale: cached !== null,
|
|
1756
|
+
fetchedAt: cached?.updatedAt,
|
|
1757
|
+
models: cached?.models.map(model => model.id) ?? [],
|
|
1758
|
+
});
|
|
1759
|
+
this.#lastDiscoveryWarnings.delete(providerConfig.provider);
|
|
1760
|
+
return cached
|
|
1761
|
+
? this.#normalizeDiscoverableModels(
|
|
1762
|
+
providerConfig,
|
|
1763
|
+
cached.models.map(model => buildModel(model)),
|
|
1764
|
+
)
|
|
1765
|
+
: [];
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
const providerId = providerConfig.provider;
|
|
1770
|
+
let discoveryError: string | undefined;
|
|
1771
|
+
const fetchDynamicModels = async (): Promise<readonly ModelSpec<Api>[] | null> => {
|
|
1772
|
+
try {
|
|
1773
|
+
const models = this.#applyProviderModelOverrides(
|
|
1774
|
+
providerId,
|
|
1775
|
+
await discoverModelsByProviderType(providerConfig, this.#discoveryContext()),
|
|
1776
|
+
);
|
|
1777
|
+
this.#lastDiscoveryWarnings.delete(providerId);
|
|
1778
|
+
return models.map(toModelSpec);
|
|
1779
|
+
} catch (error) {
|
|
1780
|
+
discoveryError = error instanceof Error ? error.message : String(error);
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
|
|
1785
|
+
const manager = createModelManager<Api>({
|
|
1786
|
+
providerId,
|
|
1787
|
+
staticModels: [],
|
|
1788
|
+
cacheDbPath: this.#cacheDbPath,
|
|
1789
|
+
cacheProviderId,
|
|
1790
|
+
cacheTtlMs: 24 * 60 * 60 * 1000,
|
|
1791
|
+
fetchDynamicModels,
|
|
1792
|
+
});
|
|
1793
|
+
const result = await manager.refresh(effectiveStrategy);
|
|
1794
|
+
const status = discoveryError
|
|
1795
|
+
? result.models.length > 0
|
|
1796
|
+
? "cached"
|
|
1797
|
+
: "unavailable"
|
|
1798
|
+
: effectiveStrategy === "offline"
|
|
1799
|
+
? cached
|
|
1800
|
+
? "cached"
|
|
1801
|
+
: "idle"
|
|
1802
|
+
: result.models.length > 0
|
|
1803
|
+
? "ok"
|
|
1804
|
+
: "empty";
|
|
1805
|
+
this.#providerDiscoveryStates.set(providerId, {
|
|
1806
|
+
provider: providerId,
|
|
1807
|
+
status,
|
|
1808
|
+
optional: providerConfig.optional ?? false,
|
|
1809
|
+
stale: result.stale || status === "cached" || ((cacheOlderThanConfig || bypassFreshCache) && status !== "ok"),
|
|
1810
|
+
fetchedAt: discoveryError ? cached?.updatedAt : Date.now(),
|
|
1811
|
+
models: result.models.map(model => model.id),
|
|
1812
|
+
error: discoveryError,
|
|
1813
|
+
});
|
|
1814
|
+
if (discoveryError) {
|
|
1815
|
+
this.#warnProviderDiscoveryFailure(providerConfig, discoveryError);
|
|
1816
|
+
}
|
|
1817
|
+
return this.#applyProviderModelOverrides(
|
|
1818
|
+
providerId,
|
|
1819
|
+
this.#normalizeDiscoverableModels(
|
|
1820
|
+
providerConfig,
|
|
1821
|
+
this.#applyProviderCompat(providerConfig.compat, result.models),
|
|
1822
|
+
),
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
#discoveryContext(): DiscoveryContext {
|
|
1827
|
+
return {
|
|
1828
|
+
fetch: this.#fetch,
|
|
1829
|
+
getBearerApiKeyResolver: async provider => {
|
|
1830
|
+
const apiKey = await this.getApiKeyForProvider(provider);
|
|
1831
|
+
if (!isDiscoveryBearerApiKey(apiKey)) {
|
|
1832
|
+
return undefined;
|
|
1833
|
+
}
|
|
1834
|
+
return this.resolver(provider);
|
|
1835
|
+
},
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
#nonResolvingDiscoveryContext(): DiscoveryContext {
|
|
1840
|
+
return {
|
|
1841
|
+
fetch: this.#fetch,
|
|
1842
|
+
getBearerApiKeyResolver: async () => undefined,
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
#warnProviderDiscoveryFailure(providerConfig: DiscoveryProviderConfig, error: string): void {
|
|
1847
|
+
const previous = this.#lastDiscoveryWarnings.get(providerConfig.provider);
|
|
1848
|
+
if (previous === error) {
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
this.#lastDiscoveryWarnings.set(providerConfig.provider, error);
|
|
1852
|
+
logger.warn("model discovery failed for provider", {
|
|
1853
|
+
provider: providerConfig.provider,
|
|
1854
|
+
url: providerConfig.baseUrl,
|
|
1855
|
+
error,
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
async #discoverBuiltInProviderModels(
|
|
1860
|
+
strategy: ModelRefreshStrategy,
|
|
1861
|
+
providerFilter?: ReadonlySet<string>,
|
|
1862
|
+
): Promise<BuiltInDiscoveryResult> {
|
|
1863
|
+
// Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
|
|
1864
|
+
const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(p => p.provider));
|
|
1865
|
+
const managerOptions = await this.#collectBuiltInModelManagerOptions(
|
|
1866
|
+
strategy,
|
|
1867
|
+
providerFilter,
|
|
1868
|
+
configuredDiscoveryProviders,
|
|
1869
|
+
);
|
|
1870
|
+
if (managerOptions.length === 0) {
|
|
1871
|
+
return { models: [], authoritativeProviders: new Set() };
|
|
1872
|
+
}
|
|
1873
|
+
const discoveries = await Promise.all(
|
|
1874
|
+
managerOptions.map(options => this.#discoverWithModelManager(options, strategy)),
|
|
1875
|
+
);
|
|
1876
|
+
const authoritativeProviders = new Set<string>();
|
|
1877
|
+
const models: Model<Api>[] = [];
|
|
1878
|
+
for (const discovery of discoveries) {
|
|
1879
|
+
models.push(...discovery.models);
|
|
1880
|
+
for (const provider of discovery.authoritativeProviders) {
|
|
1881
|
+
authoritativeProviders.add(provider);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
return { models, authoritativeProviders };
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
async #resolveBuiltInDiscoveryApiKey(
|
|
1888
|
+
providerId: string,
|
|
1889
|
+
strategy: ModelRefreshStrategy,
|
|
1890
|
+
cacheProviderId: string,
|
|
1891
|
+
authoritative: boolean,
|
|
1892
|
+
): Promise<string | undefined> {
|
|
1893
|
+
const peekedKey = await this.#peekApiKeyForProvider(providerId);
|
|
1894
|
+
if (isAuthenticated(peekedKey) || strategy === "offline") {
|
|
1895
|
+
return peekedKey;
|
|
1896
|
+
}
|
|
1897
|
+
const oauthCredentials = getOAuthCredentialsForProvider(this.authStorage, providerId);
|
|
1898
|
+
if (oauthCredentials.length === 0) {
|
|
1899
|
+
return peekedKey;
|
|
1900
|
+
}
|
|
1901
|
+
// Authoritative providers prune bundled models only when their manager is
|
|
1902
|
+
// actually constructed, which needs an authenticated key. A fresh cache does
|
|
1903
|
+
// not let us skip the refresh here: with an expired OAuth token peekedKey is
|
|
1904
|
+
// undefined, the manager is never added, and stale bundled models survive the
|
|
1905
|
+
// full cache TTL. So only take the no-refresh shortcut for non-authoritative
|
|
1906
|
+
// providers, whose bundled models stay visible regardless.
|
|
1907
|
+
if (strategy === "online-if-uncached" && !authoritative) {
|
|
1908
|
+
// Mirror shouldFetchRemoteSources: built-in managers use the catalog's
|
|
1909
|
+
// default TTL, so only refresh when the manager will actually fetch.
|
|
1910
|
+
const cache = readModelCache<Api>(
|
|
1911
|
+
cacheProviderId,
|
|
1912
|
+
BUILT_IN_DISCOVERY_CACHE_TTL_MS,
|
|
1913
|
+
Date.now,
|
|
1914
|
+
this.#cacheDbPath,
|
|
1915
|
+
);
|
|
1916
|
+
const cacheAgeMs = cache ? Date.now() - cache.updatedAt : Number.POSITIVE_INFINITY;
|
|
1917
|
+
if (cache?.fresh && (cache.authoritative || cacheAgeMs < BUILT_IN_DISCOVERY_NON_AUTHORITATIVE_RETRY_MS)) {
|
|
1918
|
+
return peekedKey;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
try {
|
|
1922
|
+
return await this.getApiKeyForProvider(providerId);
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
logger.debug("OAuth refresh failed during model discovery preflight", {
|
|
1925
|
+
provider: providerId,
|
|
1926
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1927
|
+
});
|
|
1928
|
+
return peekedKey;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
async #collectBuiltInModelManagerOptions(
|
|
1933
|
+
strategy: ModelRefreshStrategy,
|
|
1934
|
+
providerFilter: ReadonlySet<string> | undefined,
|
|
1935
|
+
configuredDiscoveryProviders: ReadonlySet<string>,
|
|
1936
|
+
): Promise<ModelManagerOptions<Api>[]> {
|
|
1937
|
+
const specialProviderDescriptors: Array<{
|
|
1938
|
+
providerId: string;
|
|
1939
|
+
authoritative: boolean;
|
|
1940
|
+
resolveKey: (value: string | undefined) => string | undefined;
|
|
1941
|
+
createOptions: (key: string) => ModelManagerOptions<Api>;
|
|
1942
|
+
}> = [
|
|
1943
|
+
{
|
|
1944
|
+
providerId: "google-antigravity",
|
|
1945
|
+
authoritative: false,
|
|
1946
|
+
resolveKey: extractGoogleOAuthToken,
|
|
1947
|
+
createOptions: oauthToken =>
|
|
1948
|
+
googleAntigravityModelManagerOptions({
|
|
1949
|
+
oauthToken,
|
|
1950
|
+
endpoint: this.#descriptorBaseUrl("google-antigravity"),
|
|
1951
|
+
fetch: this.#fetch,
|
|
1952
|
+
}),
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
providerId: "google-gemini-cli",
|
|
1956
|
+
authoritative: false,
|
|
1957
|
+
resolveKey: extractGoogleOAuthToken,
|
|
1958
|
+
createOptions: oauthToken =>
|
|
1959
|
+
googleGeminiCliModelManagerOptions({
|
|
1960
|
+
oauthToken,
|
|
1961
|
+
endpoint: this.#descriptorBaseUrl("google-gemini-cli"),
|
|
1962
|
+
fetch: this.#fetch,
|
|
1963
|
+
}),
|
|
1964
|
+
},
|
|
1965
|
+
{
|
|
1966
|
+
providerId: "openai-codex",
|
|
1967
|
+
authoritative: true,
|
|
1968
|
+
resolveKey: value => value,
|
|
1969
|
+
createOptions: accessToken =>
|
|
1970
|
+
openaiCodexModelManagerOptions({
|
|
1971
|
+
resolveAccounts: () => resolveCodexDiscoveryAccounts(this.authStorage, accessToken),
|
|
1972
|
+
fetch: this.#fetch,
|
|
1973
|
+
}),
|
|
1974
|
+
},
|
|
1975
|
+
];
|
|
1976
|
+
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
1977
|
+
const standardProviderDescriptors = PROVIDER_DESCRIPTORS.filter(descriptor => {
|
|
1978
|
+
if (disabledProviders.has(descriptor.providerId)) return false;
|
|
1979
|
+
if (configuredDiscoveryProviders.has(descriptor.providerId)) return false;
|
|
1980
|
+
return providerFilter ? providerFilter.has(descriptor.providerId) : true;
|
|
1981
|
+
});
|
|
1982
|
+
const enabledSpecialProviderDescriptors = specialProviderDescriptors.filter(descriptor => {
|
|
1983
|
+
if (disabledProviders.has(descriptor.providerId)) return false;
|
|
1984
|
+
if (configuredDiscoveryProviders.has(descriptor.providerId)) return false;
|
|
1985
|
+
return providerFilter ? providerFilter.has(descriptor.providerId) : true;
|
|
1986
|
+
});
|
|
1987
|
+
const standardProviderKeys = await Promise.all(
|
|
1988
|
+
standardProviderDescriptors.map(descriptor => {
|
|
1989
|
+
const cacheProviderId = this.#resolveStartupModelCacheProviderId(descriptor.providerId);
|
|
1990
|
+
return this.#resolveBuiltInDiscoveryApiKey(
|
|
1991
|
+
descriptor.providerId,
|
|
1992
|
+
strategy,
|
|
1993
|
+
cacheProviderId,
|
|
1994
|
+
descriptor.dynamicModelsAuthoritative ?? false,
|
|
1995
|
+
);
|
|
1996
|
+
}),
|
|
1997
|
+
);
|
|
1998
|
+
const specialKeys = await Promise.all(
|
|
1999
|
+
enabledSpecialProviderDescriptors.map(descriptor =>
|
|
2000
|
+
this.#resolveBuiltInDiscoveryApiKey(
|
|
2001
|
+
descriptor.providerId,
|
|
2002
|
+
strategy,
|
|
2003
|
+
descriptor.providerId,
|
|
2004
|
+
descriptor.authoritative,
|
|
2005
|
+
),
|
|
2006
|
+
),
|
|
2007
|
+
);
|
|
2008
|
+
const options: ModelManagerOptions<Api>[] = [];
|
|
2009
|
+
for (let i = 0; i < standardProviderDescriptors.length; i++) {
|
|
2010
|
+
const descriptor = standardProviderDescriptors[i];
|
|
2011
|
+
const apiKey = standardProviderKeys[i];
|
|
2012
|
+
const hasExplicitVllmConfig =
|
|
2013
|
+
descriptor.providerId === "vllm" &&
|
|
2014
|
+
(this.#runtimeProviderOverrides.has(descriptor.providerId) ||
|
|
2015
|
+
this.#providerOverrides.has(descriptor.providerId) ||
|
|
2016
|
+
this.#keylessProviders.has(descriptor.providerId));
|
|
2017
|
+
if (isAuthenticated(apiKey) || descriptor.allowUnauthenticated || hasExplicitVllmConfig) {
|
|
2018
|
+
const discoveryConfig = {
|
|
2019
|
+
apiKey: isDiscoveryBearerApiKey(apiKey) ? apiKey : undefined,
|
|
2020
|
+
baseUrl: this.#descriptorBaseUrl(descriptor.providerId),
|
|
2021
|
+
fetch: this.#fetch,
|
|
2022
|
+
};
|
|
2023
|
+
const preparedConfig =
|
|
2024
|
+
getProviderDefinition(descriptor.providerId)?.prepareModelDiscovery?.(discoveryConfig) ??
|
|
2025
|
+
discoveryConfig;
|
|
2026
|
+
options.push(descriptor.createModelManagerOptions(preparedConfig));
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
for (let i = 0; i < enabledSpecialProviderDescriptors.length; i++) {
|
|
2031
|
+
const descriptor = enabledSpecialProviderDescriptors[i];
|
|
2032
|
+
const key = descriptor.resolveKey(specialKeys[i]);
|
|
2033
|
+
if (!isAuthenticated(key)) {
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
options.push(descriptor.createOptions(key));
|
|
2037
|
+
}
|
|
2038
|
+
// Append runtime model managers registered by extensions via fetchDynamicModels.
|
|
2039
|
+
for (const { options: managerOpts } of this.#runtimeModelManagers.values()) {
|
|
2040
|
+
if (
|
|
2041
|
+
!configuredDiscoveryProviders.has(managerOpts.providerId) &&
|
|
2042
|
+
(!providerFilter || providerFilter.has(managerOpts.providerId))
|
|
2043
|
+
) {
|
|
2044
|
+
options.push(managerOpts);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
return options;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
async #discoverWithModelManager(
|
|
2051
|
+
options: ModelManagerOptions<Api>,
|
|
2052
|
+
strategy: ModelRefreshStrategy,
|
|
2053
|
+
): Promise<BuiltInDiscoveryResult> {
|
|
2054
|
+
try {
|
|
2055
|
+
const manager = createModelManager({ ...options, cacheDbPath: this.#cacheDbPath });
|
|
2056
|
+
const result = await manager.refresh(strategy);
|
|
2057
|
+
const models = result.models.map(model =>
|
|
2058
|
+
model.provider === options.providerId ? model : { ...model, provider: options.providerId },
|
|
2059
|
+
);
|
|
2060
|
+
const authoritativeProviders = new Set<string>();
|
|
2061
|
+
if (options.dynamicModelsAuthoritative && !result.stale) {
|
|
2062
|
+
authoritativeProviders.add(options.providerId);
|
|
2063
|
+
}
|
|
2064
|
+
return { models, authoritativeProviders };
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
logger.warn("model discovery failed for provider", {
|
|
2067
|
+
provider: options.providerId,
|
|
2068
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2069
|
+
});
|
|
2070
|
+
return { models: [], authoritativeProviders: new Set() };
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
#applyProviderModelOverrides(provider: string, models: Model<Api>[]): Model<Api>[] {
|
|
2075
|
+
const overrides = this.#modelOverrides.get(provider);
|
|
2076
|
+
if (!overrides || overrides.size === 0) return models;
|
|
2077
|
+
let liveIds: Set<string> | null = null;
|
|
2078
|
+
const hasLiveModel = (_provider: string, id: string) => {
|
|
2079
|
+
liveIds ??= new Set(models.map(m => m.id));
|
|
2080
|
+
return liveIds.has(id);
|
|
2081
|
+
};
|
|
2082
|
+
return models.map(model => {
|
|
2083
|
+
const override = resolveModelOverrideWithAliases(overrides, model, hasLiveModel);
|
|
2084
|
+
if (!override) return model;
|
|
2085
|
+
return applyModelOverride(model, override);
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
// #applyLlamaCppQwenThinkingToModels re-runs applyLlamaCppQwenThinking as the
|
|
2090
|
+
// outermost transform for llama.cpp-provider models, after discovery merges,
|
|
2091
|
+
// cache fallbacks, and provider/transport overrides have run. It is
|
|
2092
|
+
// idempotent, so it restores the routed Qwen model's chat-completions api,
|
|
2093
|
+
// `/v1` runtime base URL, and disable dialect even when a configured `baseUrl`
|
|
2094
|
+
// override (which wins in mergeDiscoveredModel) or a fallback to a pre-fix
|
|
2095
|
+
// cached row would otherwise leave the old spec in place.
|
|
2096
|
+
#applyLlamaCppQwenThinkingToModels(models: Model<Api>[]): Model<Api>[] {
|
|
2097
|
+
const llamaCppProviders = new Set<string>();
|
|
2098
|
+
for (const provider of this.#discoverableProviders) {
|
|
2099
|
+
if (provider.discovery.type === "llama.cpp") llamaCppProviders.add(provider.provider);
|
|
2100
|
+
}
|
|
2101
|
+
if (llamaCppProviders.size === 0) return models;
|
|
2102
|
+
return models.map(model => (llamaCppProviders.has(model.provider) ? applyLlamaCppQwenThinking(model) : model));
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
#mergeProviderOverride(baseOverride: ProviderOverride | undefined, override: ProviderOverride): ProviderOverride {
|
|
2106
|
+
return {
|
|
2107
|
+
baseUrl: override.baseUrl ?? baseOverride?.baseUrl,
|
|
2108
|
+
apiKey: override.apiKey ?? baseOverride?.apiKey,
|
|
2109
|
+
authHeader: override.authHeader ?? baseOverride?.authHeader,
|
|
2110
|
+
headers: override.headers
|
|
2111
|
+
? createLiveConfigHeaders([baseOverride?.headers, override.headers])
|
|
2112
|
+
: baseOverride?.headers,
|
|
2113
|
+
compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
|
|
2114
|
+
remoteCompaction: mergeRemoteCompactionConfig(baseOverride?.remoteCompaction, override.remoteCompaction),
|
|
2115
|
+
transport: override.transport ?? baseOverride?.transport,
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
#applyProviderTransportOverride<
|
|
2119
|
+
T extends { baseUrl?: string; headers?: Record<string, string>; remoteCompaction?: RemoteCompactionConfig<Api> },
|
|
2120
|
+
>(
|
|
2121
|
+
entry: T,
|
|
2122
|
+
override: Pick<
|
|
2123
|
+
ProviderOverride,
|
|
2124
|
+
"baseUrl" | "headers" | "authHeader" | "apiKey" | "remoteCompaction" | "transport"
|
|
2125
|
+
>,
|
|
2126
|
+
): T {
|
|
2127
|
+
const headers = mergeAuthHeaderSources(
|
|
2128
|
+
override.headers ? [entry.headers, override.headers] : [entry.headers],
|
|
2129
|
+
override.authHeader,
|
|
2130
|
+
override.apiKey,
|
|
2131
|
+
);
|
|
2132
|
+
return {
|
|
2133
|
+
...entry,
|
|
2134
|
+
baseUrl: override.baseUrl ?? entry.baseUrl,
|
|
2135
|
+
headers,
|
|
2136
|
+
// Preserve the model's existing transport when the override omits one;
|
|
2137
|
+
// providers without a `transport` field keep the default per-API dispatch.
|
|
2138
|
+
...(override.transport !== undefined ? { transport: override.transport } : {}),
|
|
2139
|
+
remoteCompaction: mergeProviderRemoteCompactionConfig(entry.remoteCompaction, override.remoteCompaction),
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
#applyProviderTransportOverrideToModel(
|
|
2143
|
+
model: Model<Api>,
|
|
2144
|
+
override: Pick<
|
|
2145
|
+
ProviderOverride,
|
|
2146
|
+
"baseUrl" | "headers" | "authHeader" | "apiKey" | "remoteCompaction" | "transport"
|
|
2147
|
+
>,
|
|
2148
|
+
): Model<Api> {
|
|
2149
|
+
return buildModel(this.#applyProviderTransportOverride(toModelSpec(model), override));
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
#applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
|
|
2153
|
+
if (this.#runtimeProviderOverrides.size === 0) return models;
|
|
2154
|
+
return models.map(model => {
|
|
2155
|
+
const override = this.#runtimeProviderOverrides.get(model.provider);
|
|
2156
|
+
if (!override) return model;
|
|
2157
|
+
return this.#applyProviderTransportOverrideToModel(model, override);
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
#resolveLiveModelOverride(model: Model<Api>): ModelOverride | undefined {
|
|
2161
|
+
const providerOverrides = this.#modelOverrides.get(model.provider);
|
|
2162
|
+
if (!providerOverrides) return undefined;
|
|
2163
|
+
return resolveModelOverrideWithAliases(
|
|
2164
|
+
providerOverrides,
|
|
2165
|
+
model,
|
|
2166
|
+
(provider, id) => this.find(provider, id) !== undefined,
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
#resolveLiveCustomModelOverlay(model: Model<Api>): CustomModelOverlay | undefined {
|
|
2171
|
+
return (
|
|
2172
|
+
this.#customModelOverlays.find(overlay => overlay.provider === model.provider && overlay.id === model.id) ??
|
|
2173
|
+
this.#runtimeModelOverlays.find(overlay => overlay.provider === model.provider && overlay.id === model.id)
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
#applyModelOverrides(models: Model<Api>[], overrides: Map<string, Map<string, ModelOverride>>): Model<Api>[] {
|
|
2178
|
+
if (overrides.size === 0) return models;
|
|
2179
|
+
let liveKeys: Set<string> | null = null;
|
|
2180
|
+
const hasLiveModel = (provider: string, id: string) => {
|
|
2181
|
+
liveKeys ??= new Set(models.map(m => `${m.provider}\u0000${m.id}`));
|
|
2182
|
+
return liveKeys.has(`${provider}\u0000${id}`);
|
|
2183
|
+
};
|
|
2184
|
+
return models.map(model => {
|
|
2185
|
+
const providerOverrides = overrides.get(model.provider);
|
|
2186
|
+
if (!providerOverrides) return model;
|
|
2187
|
+
const override = resolveModelOverrideWithAliases(providerOverrides, model, hasLiveModel);
|
|
2188
|
+
if (!override) return model;
|
|
2189
|
+
return applyModelOverride(model, override);
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
#applyHardcodedModelPolicies(models: Model<Api>[]): Model<Api>[] {
|
|
2193
|
+
return models.map(model => {
|
|
2194
|
+
if (model.provider === "ollama-cloud" && model.omitMaxOutputTokens !== true) {
|
|
2195
|
+
model = applyModelOverride(model, { omitMaxOutputTokens: true });
|
|
2196
|
+
}
|
|
2197
|
+
if (model.id !== "gpt-5.4" || model.provider === "github-copilot") {
|
|
2198
|
+
return model;
|
|
2199
|
+
}
|
|
2200
|
+
const overrides = this.#modelOverrides.get(model.provider)?.get(model.id);
|
|
2201
|
+
if (!overrides) {
|
|
2202
|
+
return applyModelOverride(model, { contextWindow: 1_000_000 });
|
|
2203
|
+
}
|
|
2204
|
+
return applyModelOverride(model, {
|
|
2205
|
+
contextWindow: overrides.contextWindow ?? 1_000_000,
|
|
2206
|
+
...overrides,
|
|
2207
|
+
});
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
#parseModels(config: ModelsConfig): CustomModelOverlay[] {
|
|
2212
|
+
const models: CustomModelOverlay[] = [];
|
|
2213
|
+
for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
|
|
2214
|
+
const modelDefs = providerConfig.models ?? [];
|
|
2215
|
+
if (modelDefs.length === 0) continue; // Override-only, no custom models
|
|
2216
|
+
const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
|
|
2217
|
+
if (providerConfig.apiKey) {
|
|
2218
|
+
this.#installProviderApiKey(providerName, providerConfig.apiKey);
|
|
2219
|
+
}
|
|
2220
|
+
for (const modelDef of modelDefs) {
|
|
2221
|
+
const providerCompat = providerConfig.disableStrictTools
|
|
2222
|
+
? mergeCompat(providerConfig.compat, { disableStrictTools: true })
|
|
2223
|
+
: providerConfig.compat;
|
|
2224
|
+
const model = buildCustomModelOverlay(
|
|
2225
|
+
providerName,
|
|
2226
|
+
providerConfig.baseUrl!,
|
|
2227
|
+
providerConfig.api as Api | undefined,
|
|
2228
|
+
resolvedProviderHeaders,
|
|
2229
|
+
providerConfig.apiKey,
|
|
2230
|
+
providerConfig.authHeader,
|
|
2231
|
+
providerCompat,
|
|
2232
|
+
(providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
|
|
2233
|
+
providerConfig.remoteCompaction,
|
|
2234
|
+
modelDef as CustomModelDefinitionLike,
|
|
2235
|
+
);
|
|
2236
|
+
if (!model) continue;
|
|
2237
|
+
models.push(model);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
return models;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
#modelsForProviderLookup(provider: string): Model<Api>[] {
|
|
2244
|
+
if (this.#hasFullSnapshot) return this.#models;
|
|
2245
|
+
const normalizedProvider = provider.trim().toLowerCase();
|
|
2246
|
+
if (!normalizedProvider) return [];
|
|
2247
|
+
const cached = this.#providerLookupSnapshots.get(normalizedProvider);
|
|
2248
|
+
if (cached) return cached;
|
|
2249
|
+
const matchingProviders = new Set(
|
|
2250
|
+
this.#knownStaticProviders().filter(candidate => candidate.toLowerCase() === normalizedProvider),
|
|
2251
|
+
);
|
|
2252
|
+
const models = this.#composeStaticModels(matchingProviders);
|
|
2253
|
+
this.#providerLookupSnapshots.set(normalizedProvider, models);
|
|
2254
|
+
return models;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
/**
|
|
2258
|
+
* Get all models (built-in + custom).
|
|
2259
|
+
* If custom config had errors, returns only built-in models.
|
|
2260
|
+
*/
|
|
2261
|
+
getAll(): Model<Api>[] {
|
|
2262
|
+
return this.#ensureFullSnapshot();
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
/**
|
|
2266
|
+
* Availability predicate with per-provider memoization. Auth lookups
|
|
2267
|
+
* (`authStorage.hasAuth`) and the disabled-provider set are resolved once
|
|
2268
|
+
* per provider instead of once per model, which matters when filtering the
|
|
2269
|
+
* full bundled catalog (thousands of models, ~50 providers).
|
|
2270
|
+
*/
|
|
2271
|
+
#createProviderAvailabilityCheck(): (provider: string) => boolean {
|
|
2272
|
+
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
2273
|
+
const byProvider = new Map<string, boolean>();
|
|
2274
|
+
return provider => {
|
|
2275
|
+
let available = byProvider.get(provider);
|
|
2276
|
+
if (available === undefined) {
|
|
2277
|
+
available =
|
|
2278
|
+
!disabledProviders.has(provider) &&
|
|
2279
|
+
(this.#keylessProviders.has(provider) || this.authStorage.hasAuth(provider));
|
|
2280
|
+
byProvider.set(provider, available);
|
|
2281
|
+
}
|
|
2282
|
+
return available;
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
/**
|
|
2287
|
+
* Get only models that have auth configured.
|
|
2288
|
+
* This is a fast check that doesn't refresh OAuth tokens.
|
|
2289
|
+
*/
|
|
2290
|
+
getAvailable(): Model<Api>[] {
|
|
2291
|
+
const isProviderAvailable = this.#createProviderAvailabilityCheck();
|
|
2292
|
+
if (this.#hasFullSnapshot) {
|
|
2293
|
+
return this.#models.filter(model => isProviderAvailable(model.provider));
|
|
2294
|
+
}
|
|
2295
|
+
const availableProviders = new Set(this.#knownStaticProviders().filter(isProviderAvailable));
|
|
2296
|
+
return this.#composeStaticModels(availableProviders);
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
/**
|
|
2300
|
+
* Check whether auth is configured for a model's provider.
|
|
2301
|
+
*
|
|
2302
|
+
* Mirrors the upstream `@mariozechner/pi-coding-agent` API surface so that
|
|
2303
|
+
* external plugins/extensions and downstream wrappers (e.g. subagent launch
|
|
2304
|
+
* paths that pre-flight auth before model resolution) can probe a model
|
|
2305
|
+
* without resolving an API key. Returns true for keyless providers as well
|
|
2306
|
+
* as providers with stored credentials. See issue #993.
|
|
2307
|
+
*
|
|
2308
|
+
* Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
|
|
2309
|
+
* configured by its presence alone — the program is NOT executed — and OAuth
|
|
2310
|
+
* tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the
|
|
2311
|
+
* model-switch pre-flight off the event loop's hot path; the real key
|
|
2312
|
+
* (command execution + OAuth refresh) is resolved lazily per request via
|
|
2313
|
+
* {@link ModelRegistry.resolver}.
|
|
2314
|
+
*/
|
|
2315
|
+
hasConfiguredAuth(model: Model<Api>): boolean {
|
|
2316
|
+
const keyConfig = this.#customProviderApiKeys.get(model.provider);
|
|
2317
|
+
return (
|
|
2318
|
+
isCommandConfigValue(keyConfig) ||
|
|
2319
|
+
this.#keylessProviders.has(model.provider) ||
|
|
2320
|
+
this.authStorage.hasAuth(model.provider)
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
/**
|
|
2325
|
+
* Whether the provider's configured API key is resolved from a command.
|
|
2326
|
+
*
|
|
2327
|
+
* Callers use this to distinguish the registry's command-first resolver
|
|
2328
|
+
* path from lower-priority credentials in {@link authStorage}.
|
|
2329
|
+
*/
|
|
2330
|
+
hasCommandBackedApiKey(provider: string): boolean {
|
|
2331
|
+
const keyConfig = this.#customProviderApiKeys.get(provider);
|
|
2332
|
+
return isCommandConfigValue(keyConfig);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
getDiscoverableProviders(): string[] {
|
|
2336
|
+
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
2337
|
+
return this.#discoverableProviders
|
|
2338
|
+
.filter(provider => !disabledProviders.has(provider.provider))
|
|
2339
|
+
.map(provider => provider.provider);
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
/**
|
|
2343
|
+
* Whether `providerId` is known to the registry: it has at least one live
|
|
2344
|
+
* model, or it is configured for dynamic discovery (models.yml `discovery:`
|
|
2345
|
+
* or a runtime extension provider) and is not disabled. Discovery-only
|
|
2346
|
+
* providers can hold zero models at startup — cached rows never persist
|
|
2347
|
+
* live auth headers (#5780), so a provider whose discovered models all
|
|
2348
|
+
* carry config headers (`authHeader: true`) only materializes models after
|
|
2349
|
+
* the online refresh completes.
|
|
2350
|
+
*/
|
|
2351
|
+
hasProvider(providerId: string): boolean {
|
|
2352
|
+
const providerModels = this.#hasFullSnapshot ? this.#models : this.#composeStaticModels(new Set([providerId]));
|
|
2353
|
+
if (providerModels.some(model => model.provider === providerId)) return true;
|
|
2354
|
+
if (getDisabledProviderIdsFromSettings().has(providerId)) return false;
|
|
2355
|
+
return (
|
|
2356
|
+
this.#discoverableProviders.some(provider => provider.provider === providerId) ||
|
|
2357
|
+
this.#runtimeModelManagers.has(providerId)
|
|
2358
|
+
);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
|
|
2362
|
+
return this.#providerDiscoveryStates.get(provider);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
/**
|
|
2366
|
+
* Find a model by provider and ID.
|
|
2367
|
+
*/
|
|
2368
|
+
find(provider: string, modelId: string): Model<Api> | undefined {
|
|
2369
|
+
return resolveProviderModelReference(provider, modelId, this.#modelsForProviderLookup(provider));
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
/**
|
|
2373
|
+
* Get the base URL associated with a provider, if any model defines one.
|
|
2374
|
+
*/
|
|
2375
|
+
getProviderBaseUrl(provider: string): string | undefined {
|
|
2376
|
+
return this.#modelsForProviderLookup(provider).find(m => m.provider === provider && m.baseUrl)?.baseUrl;
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Get provider-level headers without including per-model overrides.
|
|
2380
|
+
*/
|
|
2381
|
+
getProviderHeaders(provider: string): Record<string, string> | undefined {
|
|
2382
|
+
return createLiveConfigHeaders([
|
|
2383
|
+
this.#providerOverrides.get(provider)?.headers,
|
|
2384
|
+
this.#runtimeProviderOverrides.get(provider)?.headers,
|
|
2385
|
+
]);
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
/**
|
|
2389
|
+
* Get API key for a model.
|
|
2390
|
+
*/
|
|
2391
|
+
async getApiKey(
|
|
2392
|
+
model: Model<Api>,
|
|
2393
|
+
sessionId?: string,
|
|
2394
|
+
options?: { signal?: AbortSignal },
|
|
2395
|
+
): Promise<string | undefined> {
|
|
2396
|
+
const commandKey = this.#resolveCommandBackedApiKey(model.provider);
|
|
2397
|
+
if (commandKey.configured) return commandKey.value;
|
|
2398
|
+
if (this.#keylessProviders.has(model.provider) && !this.authStorage.hasAuth(model.provider)) {
|
|
2399
|
+
return kNoAuth;
|
|
2400
|
+
}
|
|
2401
|
+
return this.authStorage.getApiKey(model.provider, sessionId, {
|
|
2402
|
+
baseUrl: model.baseUrl,
|
|
2403
|
+
modelId: model.id,
|
|
2404
|
+
signal: options?.signal,
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
/** Resolve request authentication through the historical Pi extension facade. */
|
|
2409
|
+
async getApiKeyAndHeaders(model: Model<Api>): Promise<ResolvedRequestAuth> {
|
|
2410
|
+
try {
|
|
2411
|
+
const apiKey = await this.getApiKey(model);
|
|
2412
|
+
if (apiKey === undefined) {
|
|
2413
|
+
return { ok: false, error: `No API key found for "${model.provider}"` };
|
|
2414
|
+
}
|
|
2415
|
+
const headers = this.getProviderHeaders(model.provider);
|
|
2416
|
+
return { ok: true, apiKey, headers };
|
|
2417
|
+
} catch (error) {
|
|
2418
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/**
|
|
2423
|
+
* Get API key for a provider (e.g., "openai").
|
|
2424
|
+
*
|
|
2425
|
+
* `options.forceRefresh` powers step (b) of the auth-retry policy — it
|
|
2426
|
+
* re-mints the session-sticky OAuth token even when the cached copy still
|
|
2427
|
+
* looks valid. `options.signal` is threaded into any broker-bound refresh.
|
|
2428
|
+
*/
|
|
2429
|
+
async getApiKeyForProvider(
|
|
2430
|
+
provider: string,
|
|
2431
|
+
sessionId?: string,
|
|
2432
|
+
options?: { baseUrl?: string; modelId?: string; forceRefresh?: boolean; signal?: AbortSignal },
|
|
2433
|
+
): Promise<string | undefined> {
|
|
2434
|
+
const commandKey = this.#resolveCommandBackedApiKey(provider);
|
|
2435
|
+
if (commandKey.configured) return commandKey.value;
|
|
2436
|
+
if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
|
|
2437
|
+
return kNoAuth;
|
|
2438
|
+
}
|
|
2439
|
+
return this.authStorage.getApiKey(provider, sessionId, {
|
|
2440
|
+
baseUrl: options?.baseUrl,
|
|
2441
|
+
modelId: options?.modelId,
|
|
2442
|
+
forceRefresh: options?.forceRefresh,
|
|
2443
|
+
signal: options?.signal,
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/**
|
|
2448
|
+
* Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
|
|
2449
|
+
* policy. Accepts a provider id with options, or a model with an optional
|
|
2450
|
+
* session id (`resolver(model, sessionId)`) which derives `baseUrl`/`modelId`
|
|
2451
|
+
* from the model. Callers that need the initial key for a guard can call
|
|
2452
|
+
* `resolveApiKeyOnce(resolver)`.
|
|
2453
|
+
*/
|
|
2454
|
+
resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
|
|
2455
|
+
resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
|
|
2456
|
+
resolver(target: string | ApiKeyResolverModel, optionsOrSessionId?: ApiKeyResolverOptions | string): ApiKeyResolver {
|
|
2457
|
+
const options = typeof optionsOrSessionId === "string" ? { sessionId: optionsOrSessionId } : optionsOrSessionId;
|
|
2458
|
+
if (typeof target === "string") {
|
|
2459
|
+
return createApiKeyResolver(this, target, options);
|
|
2460
|
+
}
|
|
2461
|
+
return createApiKeyResolver(this, target.provider, {
|
|
2462
|
+
...options,
|
|
2463
|
+
baseUrl: target.baseUrl,
|
|
2464
|
+
modelId: target.id,
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
|
|
2469
|
+
const commandKey = this.#resolveCommandBackedApiKey(provider);
|
|
2470
|
+
if (commandKey.configured) return commandKey.value;
|
|
2471
|
+
if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
|
|
2472
|
+
return kNoAuth;
|
|
2473
|
+
}
|
|
2474
|
+
return this.authStorage.peekApiKey(provider);
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
/**
|
|
2478
|
+
* Check if a model is using OAuth credentials (subscription).
|
|
2479
|
+
*/
|
|
2480
|
+
isUsingOAuth(model: Model<Api>): boolean {
|
|
2481
|
+
return this.authStorage.hasOAuth(model.provider);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
#clearRuntimeProviderState(providerName: string): void {
|
|
2485
|
+
this.#runtimeProviderApiKeys.delete(providerName);
|
|
2486
|
+
this.#runtimeProviderOverrides.delete(providerName);
|
|
2487
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(overlay => overlay.provider !== providerName);
|
|
2488
|
+
this.#runtimeModelManagers.delete(providerName);
|
|
2489
|
+
this.#runtimeModelModifiers.delete(providerName);
|
|
2490
|
+
this.#lastModelModifierWarnings.delete(providerName);
|
|
2491
|
+
this.authStorage.removeConfigApiKey(providerName);
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
/**
|
|
2495
|
+
* Remove custom API/OAuth registrations for a specific extension source.
|
|
2496
|
+
*/
|
|
2497
|
+
clearSourceRegistrations(sourceId: string): void {
|
|
2498
|
+
unregisterCustomApis(sourceId);
|
|
2499
|
+
unregisterOAuthProviders(sourceId);
|
|
2500
|
+
const sourceProviders = this.#runtimeProvidersBySource.get(sourceId);
|
|
2501
|
+
if (!sourceProviders || sourceProviders.size === 0) {
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
this.#ensureFullSnapshot();
|
|
2505
|
+
this.#runtimeProvidersBySource.delete(sourceId);
|
|
2506
|
+
for (const providerName of sourceProviders) {
|
|
2507
|
+
if (this.#runtimeProviderSourceByName.get(providerName) !== sourceId) {
|
|
2508
|
+
continue;
|
|
2509
|
+
}
|
|
2510
|
+
this.#runtimeProviderSourceByName.delete(providerName);
|
|
2511
|
+
this.#clearRuntimeProviderState(providerName);
|
|
2512
|
+
}
|
|
2513
|
+
this.#lastStaticLoadMtime = null;
|
|
2514
|
+
this.#reloadStaticModels();
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
/**
|
|
2518
|
+
* Remove registrations for extension sources that are no longer active.
|
|
2519
|
+
*/
|
|
2520
|
+
syncExtensionSources(activeSourceIds: string[]): void {
|
|
2521
|
+
const activeSources = new Set(activeSourceIds);
|
|
2522
|
+
for (const sourceId of this.#registeredProviderSources) {
|
|
2523
|
+
if (activeSources.has(sourceId)) {
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
this.clearSourceRegistrations(sourceId);
|
|
2527
|
+
this.#registeredProviderSources.delete(sourceId);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
/**
|
|
2532
|
+
* Register a provider dynamically (from extensions).
|
|
2533
|
+
*
|
|
2534
|
+
* If provider has models: replaces all existing models for this provider.
|
|
2535
|
+
* If provider has only baseUrl/headers: overrides existing models' URLs.
|
|
2536
|
+
* If provider has streamSimple: registers a custom API streaming function.
|
|
2537
|
+
* If provider has oauth: registers OAuth provider for /login support.
|
|
2538
|
+
*/
|
|
2539
|
+
registerProvider(providerName: string, config: ProviderConfigInput, sourceId?: string): void {
|
|
2540
|
+
if (config.streamSimple && !config.api) {
|
|
2541
|
+
throw new Error(`Provider ${providerName}: "api" is required when registering streamSimple.`);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
validateProviderConfiguration(
|
|
2545
|
+
providerName,
|
|
2546
|
+
{
|
|
2547
|
+
baseUrl: config.baseUrl,
|
|
2548
|
+
headers: config.headers,
|
|
2549
|
+
apiKey: config.apiKey,
|
|
2550
|
+
api: config.api,
|
|
2551
|
+
oauthConfigured: Boolean(config.oauth),
|
|
2552
|
+
models: (config.models ?? []) as ProviderValidationModel[],
|
|
2553
|
+
},
|
|
2554
|
+
"runtime-register",
|
|
2555
|
+
);
|
|
2556
|
+
|
|
2557
|
+
if (config.streamSimple && config.api) {
|
|
2558
|
+
const streamSimple = config.streamSimple;
|
|
2559
|
+
registerCustomApi(config.api, streamSimple, sourceId, (model, context, options) =>
|
|
2560
|
+
streamSimple(model, context, options as SimpleStreamOptions),
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
if (config.oauth) {
|
|
2565
|
+
registerOAuthProvider({
|
|
2566
|
+
...config.oauth,
|
|
2567
|
+
id: providerName,
|
|
2568
|
+
sourceId,
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
let sourceHandoff = false;
|
|
2573
|
+
if (sourceId) {
|
|
2574
|
+
this.#registeredProviderSources.add(sourceId);
|
|
2575
|
+
const previousSourceId = this.#runtimeProviderSourceByName.get(providerName);
|
|
2576
|
+
if (previousSourceId && previousSourceId !== sourceId) {
|
|
2577
|
+
const previousProviders = this.#runtimeProvidersBySource.get(previousSourceId);
|
|
2578
|
+
previousProviders?.delete(providerName);
|
|
2579
|
+
if (previousProviders && previousProviders.size === 0) {
|
|
2580
|
+
this.#runtimeProvidersBySource.delete(previousSourceId);
|
|
2581
|
+
}
|
|
2582
|
+
this.#clearRuntimeProviderState(providerName);
|
|
2583
|
+
sourceHandoff = true;
|
|
2584
|
+
}
|
|
2585
|
+
const sourceProviders = this.#runtimeProvidersBySource.get(sourceId) ?? new Set<string>();
|
|
2586
|
+
sourceProviders.add(providerName);
|
|
2587
|
+
this.#runtimeProvidersBySource.set(sourceId, sourceProviders);
|
|
2588
|
+
this.#runtimeProviderSourceByName.set(providerName, sourceId);
|
|
2589
|
+
}
|
|
2590
|
+
if (sourceHandoff) {
|
|
2591
|
+
this.#lastStaticLoadMtime = null;
|
|
2592
|
+
this.#reloadStaticModels();
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
this.#ensureFullSnapshot();
|
|
2596
|
+
if (config.apiKey) {
|
|
2597
|
+
this.#installProviderApiKey(providerName, config.apiKey);
|
|
2598
|
+
// Persist runtime API keys so they survive #reloadStaticModels() cycles
|
|
2599
|
+
this.#runtimeProviderApiKeys.set(providerName, config.apiKey);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
if (config.models && config.models.length > 0) {
|
|
2603
|
+
// Build model overlays that persist across refresh() cycles
|
|
2604
|
+
const newOverlays: CustomModelOverlay[] = [];
|
|
2605
|
+
for (const modelDef of config.models) {
|
|
2606
|
+
const overlay = buildCustomModelOverlay(
|
|
2607
|
+
providerName,
|
|
2608
|
+
config.baseUrl!,
|
|
2609
|
+
config.api,
|
|
2610
|
+
config.headers,
|
|
2611
|
+
config.apiKey,
|
|
2612
|
+
config.authHeader,
|
|
2613
|
+
config.compat,
|
|
2614
|
+
undefined,
|
|
2615
|
+
config.remoteCompaction,
|
|
2616
|
+
modelDef as CustomModelDefinitionLike,
|
|
2617
|
+
);
|
|
2618
|
+
if (!overlay) {
|
|
2619
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
|
|
2620
|
+
}
|
|
2621
|
+
newOverlays.push(overlay);
|
|
2622
|
+
}
|
|
2623
|
+
// Store as runtime overlays so they survive #reloadStaticModels()
|
|
2624
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(m => m.provider !== providerName);
|
|
2625
|
+
this.#runtimeModelOverlays.push(...newOverlays);
|
|
2626
|
+
|
|
2627
|
+
// Update the unprojected snapshot, then rerun every whole-catalog
|
|
2628
|
+
// projection exactly once. Incremental projection is not safe because one
|
|
2629
|
+
// provider's hook may inspect or suppress another provider's models.
|
|
2630
|
+
const nextModels = this.#unprojectedModels.filter(model => model.provider !== providerName);
|
|
2631
|
+
for (const overlay of newOverlays) {
|
|
2632
|
+
nextModels.push(finalizeCustomModel(overlay, { useDefaults: true }));
|
|
2633
|
+
}
|
|
2634
|
+
const runtimeTransportOverride = this.#runtimeProviderOverrides.get(providerName);
|
|
2635
|
+
this.#unprojectedModels = runtimeTransportOverride
|
|
2636
|
+
? nextModels.map(model => {
|
|
2637
|
+
if (model.provider !== providerName) return model;
|
|
2638
|
+
return this.#applyProviderTransportOverrideToModel(model, runtimeTransportOverride);
|
|
2639
|
+
})
|
|
2640
|
+
: nextModels;
|
|
2641
|
+
|
|
2642
|
+
if (config.oauth?.modifyModels) {
|
|
2643
|
+
this.#runtimeModelModifiers.set(providerName, config.oauth.modifyModels);
|
|
2644
|
+
} else {
|
|
2645
|
+
this.#runtimeModelModifiers.delete(providerName);
|
|
2646
|
+
}
|
|
2647
|
+
this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
|
|
2648
|
+
this.#providerLookupSnapshots.clear();
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
if (config.fetchDynamicModels) {
|
|
2653
|
+
const fetcher = config.fetchDynamicModels;
|
|
2654
|
+
const providerBaseUrl = config.baseUrl ?? "";
|
|
2655
|
+
const providerApi = config.api;
|
|
2656
|
+
const providerHeaders = config.headers;
|
|
2657
|
+
const providerApiKey = config.apiKey;
|
|
2658
|
+
const providerAuthHeader = config.authHeader;
|
|
2659
|
+
const providerCompat = config.compat;
|
|
2660
|
+
const managerOptions: ModelManagerOptions<Api> = {
|
|
2661
|
+
providerId: providerName as Parameters<typeof createModelManager>[0]["providerId"],
|
|
2662
|
+
staticModels: [],
|
|
2663
|
+
cacheDbPath: this.#cacheDbPath,
|
|
2664
|
+
cacheTtlMs: 24 * 60 * 60 * 1000,
|
|
2665
|
+
dynamicModelsAuthoritative: true,
|
|
2666
|
+
fetchDynamicModels: async () => {
|
|
2667
|
+
const apiKey = await this.#peekApiKeyForProvider(providerName);
|
|
2668
|
+
const resolvedKey = isAuthenticated(apiKey) ? apiKey : undefined;
|
|
2669
|
+
const modelDefs = await withRuntimeDynamicModelsTimeout(RUNTIME_DYNAMIC_MODEL_FETCH_TIMEOUT_MS, () =>
|
|
2670
|
+
fetcher(resolvedKey),
|
|
2671
|
+
);
|
|
2672
|
+
const results: Model<Api>[] = [];
|
|
2673
|
+
for (const modelDef of modelDefs) {
|
|
2674
|
+
const overlay = buildCustomModelOverlay(
|
|
2675
|
+
providerName,
|
|
2676
|
+
modelDef.baseUrl ?? providerBaseUrl,
|
|
2677
|
+
modelDef.api ?? providerApi,
|
|
2678
|
+
providerHeaders,
|
|
2679
|
+
providerApiKey,
|
|
2680
|
+
providerAuthHeader,
|
|
2681
|
+
providerCompat,
|
|
2682
|
+
undefined,
|
|
2683
|
+
config.remoteCompaction,
|
|
2684
|
+
modelDef as CustomModelDefinitionLike,
|
|
2685
|
+
);
|
|
2686
|
+
if (overlay) results.push(finalizeCustomModel(overlay, { useDefaults: true }));
|
|
2687
|
+
}
|
|
2688
|
+
return results.map(toModelSpec);
|
|
2689
|
+
},
|
|
2690
|
+
};
|
|
2691
|
+
this.#runtimeModelManagers.set(providerName, { options: managerOptions, sourceId: sourceId ?? "" });
|
|
2692
|
+
// Discovery is driven by refreshRuntimeProviders() after the drain — not
|
|
2693
|
+
// here, so registration has no network side effect and callers can await.
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
if (
|
|
2697
|
+
config.baseUrl ||
|
|
2698
|
+
config.headers ||
|
|
2699
|
+
config.apiKey ||
|
|
2700
|
+
config.authHeader !== undefined ||
|
|
2701
|
+
config.remoteCompaction !== undefined ||
|
|
2702
|
+
config.transport !== undefined
|
|
2703
|
+
) {
|
|
2704
|
+
const transportOverride = {
|
|
2705
|
+
baseUrl: config.baseUrl,
|
|
2706
|
+
headers: config.headers,
|
|
2707
|
+
apiKey: config.apiKey,
|
|
2708
|
+
authHeader: config.authHeader,
|
|
2709
|
+
remoteCompaction: config.remoteCompaction,
|
|
2710
|
+
transport: config.transport,
|
|
2711
|
+
};
|
|
2712
|
+
const nextRuntimeOverride = this.#mergeProviderOverride(
|
|
2713
|
+
this.#runtimeProviderOverrides.get(providerName),
|
|
2714
|
+
transportOverride,
|
|
2715
|
+
);
|
|
2716
|
+
this.#runtimeProviderOverrides.set(providerName, nextRuntimeOverride);
|
|
2717
|
+
this.#unprojectedModels = this.#applyLlamaCppQwenThinkingToModels(
|
|
2718
|
+
this.#unprojectedModels.map(model => {
|
|
2719
|
+
if (model.provider !== providerName) return model;
|
|
2720
|
+
return this.#applyProviderTransportOverrideToModel(model, transportOverride);
|
|
2721
|
+
}),
|
|
2722
|
+
);
|
|
2723
|
+
this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
|
|
2724
|
+
this.#providerLookupSnapshots.clear();
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
/**
|
|
2729
|
+
* Suppress a specific model selector (e.g., "provider/id") until a specific timestamp.
|
|
2730
|
+
*/
|
|
2731
|
+
suppressSelector(selector: string, untilMs: number): void {
|
|
2732
|
+
this.#suppressedSelectors.set(
|
|
2733
|
+
normalizeSuppressedSelector(selector, (provider, id) => this.find(provider, id) !== undefined),
|
|
2734
|
+
untilMs,
|
|
2735
|
+
);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
/**
|
|
2739
|
+
* Check if a model selector is currently suppressed due to rate limits.
|
|
2740
|
+
*/
|
|
2741
|
+
isSelectorSuppressed(selector: string): boolean {
|
|
2742
|
+
const normalizedSelector = normalizeSuppressedSelector(
|
|
2743
|
+
selector,
|
|
2744
|
+
(provider, id) => this.find(provider, id) !== undefined,
|
|
2745
|
+
);
|
|
2746
|
+
const suppressedUntil = this.#suppressedSelectors.get(normalizedSelector);
|
|
2747
|
+
if (!suppressedUntil) return false;
|
|
2748
|
+
if (suppressedUntil <= Date.now()) {
|
|
2749
|
+
this.#suppressedSelectors.delete(normalizedSelector);
|
|
2750
|
+
return false;
|
|
2751
|
+
}
|
|
2752
|
+
return true;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
/**
|
|
2756
|
+
* Clear the cooldown suppression for one selector after an explicit user selection.
|
|
2757
|
+
*/
|
|
2758
|
+
clearSuppressedSelector(selector: string): void {
|
|
2759
|
+
this.#suppressedSelectors.delete(
|
|
2760
|
+
normalizeSuppressedSelector(selector, (provider, id) => this.find(provider, id) !== undefined),
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
/**
|
|
2765
|
+
* Clear all cooldown suppressions recorded via {@link suppressSelector}.
|
|
2766
|
+
* Used to reset retry-fallback cooldown state without a full {@link refresh}.
|
|
2767
|
+
*/
|
|
2768
|
+
clearSuppressedSelectors(): void {
|
|
2769
|
+
this.#suppressedSelectors.clear();
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
/**
|
|
2774
|
+
* Input type for registerProvider API (from extensions).
|
|
2775
|
+
*/
|
|
2776
|
+
export interface ProviderConfigInput {
|
|
2777
|
+
baseUrl?: string;
|
|
2778
|
+
apiKey?: string;
|
|
2779
|
+
api?: Api;
|
|
2780
|
+
streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
2781
|
+
headers?: Record<string, string>;
|
|
2782
|
+
compat?: ModelSpec<Api>["compat"];
|
|
2783
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
2784
|
+
authHeader?: boolean;
|
|
2785
|
+
/** Streaming transport override — see {@link Model.transport}. */
|
|
2786
|
+
transport?: Model<Api>["transport"];
|
|
2787
|
+
oauth?: {
|
|
2788
|
+
name: string;
|
|
2789
|
+
login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials | string>;
|
|
2790
|
+
refreshToken?(credentials: OAuthCredentials): Promise<OAuthCredentials>;
|
|
2791
|
+
getApiKey?(credentials: OAuthCredentials): string;
|
|
2792
|
+
modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
|
|
2793
|
+
};
|
|
2794
|
+
/**
|
|
2795
|
+
* Async factory that fetches the live model list from the provider endpoint.
|
|
2796
|
+
* When present, the result is run through the same SQLite model-cache as
|
|
2797
|
+
* built-in providers (keyed by provider name, default 24 h TTL).
|
|
2798
|
+
* The factory receives the resolved API key (undefined when unauthenticated).
|
|
2799
|
+
*/
|
|
2800
|
+
fetchDynamicModels?: (
|
|
2801
|
+
apiKey: string | undefined,
|
|
2802
|
+
) => Promise<readonly NonNullable<ProviderConfigInput["models"]>[number][]>;
|
|
2803
|
+
models?: Array<{
|
|
2804
|
+
id: string;
|
|
2805
|
+
name: string;
|
|
2806
|
+
api?: Api;
|
|
2807
|
+
baseUrl?: string;
|
|
2808
|
+
reasoning: boolean;
|
|
2809
|
+
thinking?: ThinkingConfig;
|
|
2810
|
+
input: ("text" | "image")[];
|
|
2811
|
+
supportsTools?: boolean;
|
|
2812
|
+
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
2813
|
+
contextWindow: number;
|
|
2814
|
+
maxTokens: number;
|
|
2815
|
+
headers?: Record<string, string>;
|
|
2816
|
+
compat?: ModelSpec<Api>["compat"];
|
|
2817
|
+
contextPromotionTarget?: string;
|
|
2818
|
+
compactionModel?: string;
|
|
2819
|
+
remoteCompaction?: RemoteCompactionConfig<Api>;
|
|
2820
|
+
premiumMultiplier?: number;
|
|
2821
|
+
}>;
|
|
2822
|
+
}
|