@gajae-code/coding-agent 0.17.2 → 0.17.5
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 +267 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/github-release.d.ts +17 -0
- package/dist/types/cli/public-command-entry.d.ts +56 -0
- package/dist/types/cli/public-command-errors.d.ts +126 -0
- package/dist/types/cli/public-command-evidence.d.ts +99 -0
- package/dist/types/cli/public-command-help.d.ts +65 -0
- package/dist/types/cli/public-command-registry.d.ts +69 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/skills-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +4 -50
- package/dist/types/commands/sdk.d.ts +20 -37
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/commands/skills.d.ts +9 -0
- package/dist/types/config/autorouting-tier-map.d.ts +1049 -3
- package/dist/types/config/file-lock.d.ts +13 -3
- package/dist/types/config/model-profile-activation.d.ts +45 -1
- package/dist/types/config/model-registry.d.ts +14 -3
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +12 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +11 -2
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
- package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
- package/dist/types/harness-control-plane/finalize.d.ts +14 -3
- package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/parse.d.ts +9 -1
- package/dist/types/main.d.ts +23 -1
- package/dist/types/modes/acp/acp-agent.d.ts +55 -1
- package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
- package/dist/types/modes/components/session-selector.d.ts +5 -2
- package/dist/types/modes/components/user-message-selector.d.ts +5 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/runtime/process-lifecycle.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +69 -6
- package/dist/types/runtime-mcp/pool.d.ts +17 -0
- package/dist/types/runtime-mcp/redaction.d.ts +20 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
- package/dist/types/sdk/broker/broker.d.ts +17 -4
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/identity.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
- package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
- package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
- package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/session-index.d.ts +22 -3
- package/dist/types/sdk/broker/spawn-authority.d.ts +12 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
- package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
- package/dist/types/sdk/bus/index.d.ts +19 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
- package/dist/types/sdk/cli/master-cli.d.ts +6 -10
- package/dist/types/sdk/cli/rows.d.ts +19 -1
- package/dist/types/sdk/cli/session-cli.d.ts +22 -5
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/guides/cli.d.ts +3 -4
- package/dist/types/sdk/guides/verify.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +9 -0
- package/dist/types/sdk/host/session-runtime.d.ts +31 -0
- package/dist/types/sdk/host/types.d.ts +10 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +85 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +3 -3
- package/dist/types/sdk/startup-capability.d.ts +2 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +175 -23
- package/dist/types/session/fallback-chain-controller.d.ts +6 -6
- package/dist/types/session/fold-coordinator.d.ts +17 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
- package/dist/types/session/session-manager.d.ts +64 -1
- package/dist/types/session/session-work-lease.d.ts +14 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
- package/dist/types/setup/provider-onboarding.d.ts +81 -1
- package/dist/types/task/types.d.ts +2 -0
- package/dist/types/tools/bash-interactive.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/fetch.d.ts +10 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/tools/subagent.d.ts +8 -1
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf.d.ts +4 -0
- package/dist/types/utils/session-title-generation.d.ts +6 -0
- package/dist/types/utils/title-generator.d.ts +11 -2
- package/dist/types/web/scrapers/types.d.ts +3 -0
- package/package.json +18 -10
- package/scripts/build-binary.ts +9 -3
- package/scripts/compile-args.ts +0 -9
- package/scripts/embed-mupdf.ts +52 -0
- package/scripts/generate-sdk-operation-inventory.ts +31 -1
- package/scripts/vendor-markit.ts +196 -0
- package/src/async/job-manager.ts +22 -1
- package/src/cli/daemon-cli.ts +53 -18
- package/src/cli/github-release.ts +87 -5
- package/src/cli/mcp-cli.ts +38 -4
- package/src/cli/public-command-entry.ts +450 -0
- package/src/cli/public-command-errors.ts +581 -0
- package/src/cli/public-command-evidence.ts +717 -0
- package/src/cli/public-command-help.ts +360 -0
- package/src/cli/public-command-registry.ts +541 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +10 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli/stats-cli.ts +11 -1
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +175 -117
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +543 -3
- package/src/config/file-lock.ts +206 -65
- package/src/config/model-discovery-manager.ts +4 -4
- package/src/config/model-profile-activation.ts +417 -66
- package/src/config/model-registry.ts +254 -36
- package/src/config/model-resolver.ts +6 -1
- package/src/config/settings-schema.ts +26 -2
- package/src/coordinator-mcp/policy.ts +10 -5
- package/src/coordinator-mcp/question-gate-codec.ts +17 -0
- package/src/coordinator-mcp/question-state.ts +179 -9
- package/src/coordinator-mcp/server.ts +521 -87
- package/src/coordinator-mcp/session-reaper.ts +6 -4
- package/src/crash/record-loader.ts +6 -2
- package/src/daemon/operator-contract.ts +26 -12
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/context-manager.ts +8 -8
- package/src/eval/js/worker-core.ts +9 -4
- package/src/eval/js/worker-protocol.ts +2 -0
- package/src/exec/bash-executor.ts +3 -0
- package/src/extensibility/extensions/runner.ts +52 -4
- package/src/extensibility/extensions/types.ts +10 -0
- package/src/extensibility/gjc-plugins/registry.ts +196 -37
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
- package/src/extensibility/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/state-writer.ts +317 -6
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/harness-control-plane/control-endpoint.ts +24 -6
- package/src/harness-control-plane/finalize.ts +312 -25
- package/src/harness-control-plane/owner.ts +323 -89
- package/src/internal-urls/docs-index.generated.ts +23 -20
- package/src/internal-urls/embedded-protocol.ts +83 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/parse.ts +21 -1
- package/src/internal-urls/router.ts +4 -2
- package/src/main.ts +192 -74
- package/src/modes/acp/acp-agent.ts +973 -56
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/gjc-bundle-settings.ts +21 -1
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/tool-status-header.ts +72 -6
- package/src/modes/components/user-message-selector.ts +57 -15
- package/src/modes/controllers/command-controller.ts +58 -32
- package/src/modes/controllers/extension-ui-controller.ts +33 -7
- package/src/modes/controllers/input-controller.ts +4 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +161 -15
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
- package/src/modes/types.ts +2 -2
- package/src/prompts/agent-fragments/browser-aside.md +1 -0
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/read.md +2 -0
- package/src/runtime/network-prewarm-service.test.ts +36 -1
- package/src/runtime/network-prewarm-service.ts +28 -3
- package/src/runtime/process-lifecycle.ts +22 -11
- package/src/runtime-mcp/config.ts +16 -1
- package/src/runtime-mcp/loader.ts +6 -3
- package/src/runtime-mcp/manager.ts +845 -109
- package/src/runtime-mcp/pool.test.ts +31 -0
- package/src/runtime-mcp/pool.ts +304 -28
- package/src/runtime-mcp/redaction.ts +42 -0
- package/src/runtime-mcp/startup-policy.ts +7 -0
- package/src/runtime-mcp/tool-bridge.ts +71 -0
- package/src/runtime-mcp/transports/http.ts +108 -25
- package/src/sdk/acp/adapter.ts +27 -8
- package/src/sdk/broker/broker.ts +1431 -198
- package/src/sdk/broker/ensure.ts +21 -3
- package/src/sdk/broker/identity.ts +22 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/managed-task-dag.ts +1927 -0
- package/src/sdk/broker/managed-task-verification.ts +642 -0
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +175 -14
- package/src/sdk/broker/startup-failure.ts +80 -1
- package/src/sdk/broker/stderr-drainer.ts +59 -0
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/index.ts +707 -81
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -3
- package/src/sdk/cli/master-cli.ts +47 -36
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +705 -184
- package/src/sdk/client/client.ts +18 -0
- package/src/sdk/guides/catalog.ts +2 -2
- package/src/sdk/guides/cli.ts +20 -62
- package/src/sdk/guides/verify.ts +14 -0
- package/src/sdk/host/host.ts +49 -5
- package/src/sdk/host/session-runtime.test.ts +184 -2
- package/src/sdk/host/session-runtime.ts +322 -101
- package/src/sdk/host/types.ts +12 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
- package/src/sdk/host/websocket-transport.ts +40 -6
- package/src/sdk/index.ts +11 -0
- package/src/sdk/lifecycle/service.ts +2 -0
- package/src/sdk/prompt-deadline-flush.ts +356 -0
- package/src/sdk/prompt-deadline-manager.ts +133 -1
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/session-router.ts +9 -1
- package/src/sdk/session.ts +763 -116
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +397 -42
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2472 -407
- package/src/session/fallback-chain-controller.ts +6 -7
- package/src/session/fold-coordinator.ts +24 -5
- package/src/session/internal/managed-session-scope.ts +68 -18
- package/src/session/internal/managed-session-storage.ts +334 -12
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +259 -15
- package/src/task/executor.ts +46 -17
- package/src/task/index.ts +14 -2
- package/src/task/types.ts +2 -0
- package/src/tools/bash-interactive.ts +2 -0
- package/src/tools/bash.ts +118 -78
- package/src/tools/fetch.ts +140 -25
- package/src/tools/job.ts +151 -25
- package/src/tools/output-meta.ts +15 -2
- package/src/tools/path-utils.ts +27 -3
- package/src/tools/read.ts +3 -3
- package/src/tools/steer-fold.ts +107 -0
- package/src/tools/subagent-render.ts +24 -1
- package/src/tools/subagent.ts +27 -1
- package/src/tools/tool-catalog.generated.ts +29 -2
- package/src/tools/tool-errors.ts +4 -0
- package/src/utils/git.ts +53 -3
- package/src/utils/markit.ts +55 -41
- package/src/utils/mupdf-assets.d.ts +4 -0
- package/src/utils/mupdf-embedded.ts +4 -0
- package/src/utils/mupdf.ts +136 -0
- package/src/utils/session-title-generation.ts +29 -0
- package/src/utils/title-generator.ts +46 -2
- package/src/web/scrapers/types.ts +24 -3
- package/vendor/markit-ai/LICENSE +21 -0
- package/vendor/markit-ai/MANIFEST.json +412 -0
- package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/config.js +139 -0
- package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
- package/vendor/markit-ai/dist/commands/convert.js +113 -0
- package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/formats.js +92 -0
- package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/init.js +29 -0
- package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/onboard.js +61 -0
- package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/plugin.js +58 -0
- package/vendor/markit-ai/dist/config.d.ts +26 -0
- package/vendor/markit-ai/dist/config.js +42 -0
- package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/audio.js +104 -0
- package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/csv.js +83 -0
- package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/docx.js +57 -0
- package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/epub.js +128 -0
- package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
- package/vendor/markit-ai/dist/converters/github.js +148 -0
- package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/html.js +30 -0
- package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/image.js +124 -0
- package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
- package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
- package/vendor/markit-ai/dist/converters/iwork.js +391 -0
- package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/json.js +21 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
- package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
- package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
- package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
- package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
- package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
- package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
- package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/pptx.js +289 -0
- package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
- package/vendor/markit-ai/dist/converters/rss.js +138 -0
- package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
- package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
- package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/xml.js +18 -0
- package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/yaml.js +17 -0
- package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/zip.js +57 -0
- package/vendor/markit-ai/dist/index.d.ts +30 -0
- package/vendor/markit-ai/dist/index.js +26 -0
- package/vendor/markit-ai/dist/main.d.ts +2 -0
- package/vendor/markit-ai/dist/main.js +191 -0
- package/vendor/markit-ai/dist/markit.d.ts +45 -0
- package/vendor/markit-ai/dist/markit.js +267 -0
- package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
- package/vendor/markit-ai/dist/plugins/api.js +42 -0
- package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
- package/vendor/markit-ai/dist/plugins/index.js +3 -0
- package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/installer.js +176 -0
- package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
- package/vendor/markit-ai/dist/plugins/loader.js +61 -0
- package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/types.js +1 -0
- package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
- package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
- package/vendor/markit-ai/dist/providers/index.js +56 -0
- package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/openai.js +76 -0
- package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
- package/vendor/markit-ai/dist/providers/types.js +1 -0
- package/vendor/markit-ai/dist/types.d.ts +36 -0
- package/vendor/markit-ai/dist/types.js +1 -0
- package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
- package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
- package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
- package/vendor/markit-ai/dist/utils/output.js +31 -0
- package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
- package/vendor/markit-ai/dist/utils/turndown.js +70 -0
- package/vendor/markit-ai/package.json +74 -0
- package/vendor/markit-ai.patch +49 -0
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { type AuthStorage, resolveOAuthStorageProvider } from "@gajae-code/ai/core";
|
|
5
|
+
import {
|
|
6
|
+
isSafeCatalogModelId,
|
|
7
|
+
MODELS_LIST_REQUEST_TIMEOUT_MS,
|
|
8
|
+
readBoundedModelsJson,
|
|
9
|
+
} from "@gajae-code/ai/utils/discovery/openai-compatible";
|
|
10
|
+
import { getAgentDbPath, getAgentDir, logger } from "@gajae-code/utils";
|
|
11
|
+
import { $rotatingCredentialEnv } from "@gajae-code/utils/env";
|
|
5
12
|
import { YAML } from "bun";
|
|
13
|
+
import { withFileLock } from "../config/file-lock";
|
|
6
14
|
import { type ModelsConfig, ModelsConfigSchema, type ProviderDiscovery } from "../config/models-config-schema";
|
|
7
15
|
import { compareRankedProviders, famousProviderIndex } from "../config/provider-ranking";
|
|
8
|
-
import { AuthStorage } from "../session/auth-storage";
|
|
16
|
+
import { AuthStorage as AuthStorageImpl } from "../session/auth-storage";
|
|
9
17
|
import providerPresets from "./provider-presets.json";
|
|
10
18
|
|
|
11
19
|
export type ProviderCompatibility = "openai" | "anthropic";
|
|
@@ -21,6 +29,37 @@ export interface ProviderSetupInput {
|
|
|
21
29
|
models?: string[];
|
|
22
30
|
modelsPath?: string;
|
|
23
31
|
force?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Opt into live OpenAI `/v1/models` discovery for an OpenAI-compatible
|
|
34
|
+
* custom provider. Manual `--model` ids may be omitted when set; the
|
|
35
|
+
* runtime auto-discovers the catalog on refresh and merges it without
|
|
36
|
+
* duplicates.
|
|
37
|
+
*
|
|
38
|
+
* When no manual models are given, setup probes the endpoint before
|
|
39
|
+
* writing config so an unreachable endpoint, rejected credential, or
|
|
40
|
+
* empty catalog fails the setup loudly instead of writing a provider
|
|
41
|
+
* that can never serve a model. There is deliberately no skip flag: the
|
|
42
|
+
* pre-write probe is the enforcement point, and the wizard's
|
|
43
|
+
* interactive probe is a preview only (inputs may have changed since).
|
|
44
|
+
*/
|
|
45
|
+
discover?: boolean;
|
|
46
|
+
/** Probe override for tests; defaults to the live endpoint probe. */
|
|
47
|
+
probeDiscovery?: (input: ProviderDiscoveryProbeInput) => Promise<ProviderDiscoveryProbeResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Cancel discovery and persistence until the atomic config rename is
|
|
50
|
+
* invoked. Later cancellation does not undo a committed provider.
|
|
51
|
+
* Combined with the shared deadline inside the probe; callers that omit
|
|
52
|
+
* it get deadline-only probe behavior.
|
|
53
|
+
*/
|
|
54
|
+
discoverySignal?: AbortSignal;
|
|
55
|
+
/**
|
|
56
|
+
* Live credential authority for literal-key persistence. Interactive
|
|
57
|
+
* callers pass the active session registry's AuthStorage so the
|
|
58
|
+
* subsequent targeted online refresh authenticates from the same
|
|
59
|
+
* authority that stored the key. When omitted, onboarding falls back
|
|
60
|
+
* to a short-lived store at the agent DB path (headless/CLI behavior).
|
|
61
|
+
*/
|
|
62
|
+
authStorage?: ProviderSetupCredentialStore;
|
|
24
63
|
}
|
|
25
64
|
|
|
26
65
|
export interface ProviderSetupResult {
|
|
@@ -32,12 +71,19 @@ export interface ProviderSetupResult {
|
|
|
32
71
|
modelsPath: string;
|
|
33
72
|
redactedApiKey: string;
|
|
34
73
|
credentialSource: "literal" | "env";
|
|
74
|
+
/** True when the provider persists a `discovery:` block (live catalog). */
|
|
75
|
+
discoveryEnabled: boolean;
|
|
76
|
+
/** The persisted discovery type, when any (e.g. `openai-models-list`). */
|
|
77
|
+
discoveryType?: string;
|
|
35
78
|
preset?: string;
|
|
36
79
|
presetName?: string;
|
|
37
80
|
}
|
|
38
81
|
|
|
39
82
|
type ProviderConfig = NonNullable<NonNullable<ModelsConfig["providers"]>[string]>;
|
|
40
83
|
type ProviderCompatConfig = NonNullable<ProviderConfig["compat"]>;
|
|
84
|
+
type ProviderSetupCredentialStore = Pick<AuthStorage, "set" | "remove" | "exportSnapshot"> & {
|
|
85
|
+
peekApiKey?: (provider: string) => Promise<string | undefined>;
|
|
86
|
+
};
|
|
41
87
|
|
|
42
88
|
interface ProviderPreset {
|
|
43
89
|
id: string;
|
|
@@ -62,8 +108,6 @@ interface ProviderPreset {
|
|
|
62
108
|
}
|
|
63
109
|
|
|
64
110
|
const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
65
|
-
const REDACT_PREFIX = 4;
|
|
66
|
-
const REDACT_SUFFIX = 4;
|
|
67
111
|
export const PROVIDER_PRESETS: readonly ProviderPreset[] = providerPresets as ProviderPreset[];
|
|
68
112
|
|
|
69
113
|
export function getDefaultModelsPath(): string {
|
|
@@ -118,10 +162,35 @@ export function parseModelList(values: readonly string[]): string[] {
|
|
|
118
162
|
return [...new Set(models)];
|
|
119
163
|
}
|
|
120
164
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
165
|
+
async function resolveStoredApiKeyForProbe(
|
|
166
|
+
providerId: string,
|
|
167
|
+
inputStore: ProviderSetupCredentialStore | undefined,
|
|
168
|
+
): Promise<string | undefined> {
|
|
169
|
+
let store = inputStore;
|
|
170
|
+
let ownedStore: AuthStorageImpl | undefined;
|
|
171
|
+
if (!store) {
|
|
172
|
+
ownedStore = await AuthStorageImpl.create(getAgentDbPath());
|
|
173
|
+
store = ownedStore;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const storageProvider = resolveOAuthStorageProvider(providerId);
|
|
177
|
+
const snapshot = store.exportSnapshot();
|
|
178
|
+
const hasStoredApiKey = snapshot.credentials.some(
|
|
179
|
+
entry => entry.provider === storageProvider && entry.credential.type === "api_key",
|
|
180
|
+
);
|
|
181
|
+
if (!hasStoredApiKey) return undefined;
|
|
182
|
+
if (store.peekApiKey) return (await store.peekApiKey(providerId))?.trim() || undefined;
|
|
183
|
+
const entry = snapshot.credentials.find(
|
|
184
|
+
candidate => candidate.provider === storageProvider && candidate.credential.type === "api_key",
|
|
185
|
+
);
|
|
186
|
+
return entry?.credential.type === "api_key" ? entry.credential.key.trim() || undefined : undefined;
|
|
187
|
+
} finally {
|
|
188
|
+
ownedStore?.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function redactSecret(_secret: string): string {
|
|
193
|
+
return "***";
|
|
125
194
|
}
|
|
126
195
|
|
|
127
196
|
function apiForCompatibility(compatibility: ProviderCompatibility): ProviderSetupApi {
|
|
@@ -140,6 +209,7 @@ function resolvePresetInput(input: ProviderSetupInput): {
|
|
|
140
209
|
api: ProviderSetupApi;
|
|
141
210
|
compat?: ProviderCompatConfig;
|
|
142
211
|
discovery?: ProviderDiscovery;
|
|
212
|
+
requestedDiscovery: boolean;
|
|
143
213
|
} {
|
|
144
214
|
const preset = input.preset ? findProviderPreset(input.preset) : undefined;
|
|
145
215
|
if (input.preset && !preset) {
|
|
@@ -181,6 +251,14 @@ function resolvePresetInput(input: ProviderSetupInput): {
|
|
|
181
251
|
if (!compatibility) {
|
|
182
252
|
throw new Error("Provider compatibility is required unless --preset is used.");
|
|
183
253
|
}
|
|
254
|
+
if (input.discover && preset) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Provider preset '${preset.id}' manages its own model catalog; omit --discover or use --compat openai for a custom provider.`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
if (input.discover && compatibility !== "openai") {
|
|
260
|
+
throw new Error("Model discovery (--discover) requires an OpenAI-compatible provider; use --compat openai.");
|
|
261
|
+
}
|
|
184
262
|
return {
|
|
185
263
|
compatibility,
|
|
186
264
|
preset,
|
|
@@ -193,6 +271,7 @@ function resolvePresetInput(input: ProviderSetupInput): {
|
|
|
193
271
|
api: preset?.api ?? apiForCompatibility(compatibility),
|
|
194
272
|
compat: preset?.compat,
|
|
195
273
|
discovery: preset?.discovery,
|
|
274
|
+
requestedDiscovery: input.discover === true,
|
|
196
275
|
};
|
|
197
276
|
}
|
|
198
277
|
|
|
@@ -261,8 +340,13 @@ function validateSetupInput(input: ProviderSetupInput): {
|
|
|
261
340
|
if (!apiKey) throw new Error("API key is required.");
|
|
262
341
|
|
|
263
342
|
const models = parseModelList(resolved.models);
|
|
264
|
-
|
|
265
|
-
|
|
343
|
+
// An explicit `--discover` on an OpenAI-compatible custom provider persists
|
|
344
|
+
// `discovery: { type: openai-models-list }`, which the runtime also
|
|
345
|
+
// auto-enables — so manual models become optional rather than required.
|
|
346
|
+
const discovery: ProviderDiscovery | undefined =
|
|
347
|
+
resolved.discovery ?? (resolved.requestedDiscovery ? { type: "openai-models-list" } : undefined);
|
|
348
|
+
if (models.length === 0 && !discovery)
|
|
349
|
+
throw new Error("At least one model id or model discovery is required. Use --model <id> or --discover.");
|
|
266
350
|
validateModelApi(resolved.modelApi, models, resolved.preset?.id ?? resolved.providerId);
|
|
267
351
|
|
|
268
352
|
return {
|
|
@@ -275,7 +359,7 @@ function validateSetupInput(input: ProviderSetupInput): {
|
|
|
275
359
|
api: resolved.api,
|
|
276
360
|
modelApi: resolved.modelApi,
|
|
277
361
|
compat: resolved.compat,
|
|
278
|
-
discovery
|
|
362
|
+
discovery,
|
|
279
363
|
preset: resolved.preset,
|
|
280
364
|
};
|
|
281
365
|
}
|
|
@@ -295,7 +379,7 @@ async function readModelsConfig(modelsPath: string): Promise<ModelsConfig> {
|
|
|
295
379
|
return checked.data;
|
|
296
380
|
}
|
|
297
381
|
|
|
298
|
-
async function writeModelsConfig(modelsPath: string, config: ModelsConfig): Promise<void> {
|
|
382
|
+
async function writeModelsConfig(modelsPath: string, config: ModelsConfig, signal?: AbortSignal): Promise<void> {
|
|
299
383
|
const checked = ModelsConfigSchema.safeParse(config);
|
|
300
384
|
if (!checked.success) {
|
|
301
385
|
const first = checked.error.issues[0];
|
|
@@ -313,6 +397,8 @@ async function writeModelsConfig(modelsPath: string, config: ModelsConfig): Prom
|
|
|
313
397
|
} finally {
|
|
314
398
|
await tempHandle.close();
|
|
315
399
|
}
|
|
400
|
+
// Rename is the commit boundary. Cancellation after it cannot undo the saved provider.
|
|
401
|
+
if (signal?.aborted) throw new Error("Provider setup was cancelled; setup did not write any config.");
|
|
316
402
|
await fs.rename(tempPath, modelsPath);
|
|
317
403
|
try {
|
|
318
404
|
const directoryHandle = await fs.open(directory, "r");
|
|
@@ -336,6 +422,45 @@ export async function addApiCompatibleProvider(input: ProviderSetupInput): Promi
|
|
|
336
422
|
if (existing.providers?.[validated.providerId] && !input.force) {
|
|
337
423
|
throw new Error(`Provider '${validated.providerId}' already exists. Use --force to replace it.`);
|
|
338
424
|
}
|
|
425
|
+
// P2: a discovery-only add with no manual models must prove the endpoint
|
|
426
|
+
// serves a catalog before config is written — otherwise an unreachable
|
|
427
|
+
// endpoint or rejected credential would be recorded as configured while
|
|
428
|
+
// the provider can never serve a model. Manual-model adds skip the probe
|
|
429
|
+
// (the static list degrades gracefully). The check runs after the
|
|
430
|
+
// duplicate check above so a duplicate is rejected locally before any
|
|
431
|
+
// credential crosses the network.
|
|
432
|
+
// Only explicitly requested custom `--discover` setups are probe-gated.
|
|
433
|
+
// Preset-managed discovery (goat, litellm, openai-compatible-proxy, ...)
|
|
434
|
+
// keeps its documented contract — presets write env-var references and
|
|
435
|
+
// never validate live credentials — so preset adds skip the probe and
|
|
436
|
+
// resolve their catalog lazily at runtime as before.
|
|
437
|
+
if (validated.discovery?.type === "openai-models-list" && validated.models.length === 0 && !validated.preset) {
|
|
438
|
+
const probe = input.probeDiscovery ?? probeOpenAIModelsList;
|
|
439
|
+
const credentialSource = validated.credentialSource;
|
|
440
|
+
const storedApiKey =
|
|
441
|
+
credentialSource === "env" && input.force
|
|
442
|
+
? await resolveStoredApiKeyForProbe(validated.providerId, input.authStorage)
|
|
443
|
+
: undefined;
|
|
444
|
+
const probed = await probe({
|
|
445
|
+
baseUrl: validated.baseUrl,
|
|
446
|
+
apiKeyEnv: credentialSource === "env" && !storedApiKey ? validated.apiKey : undefined,
|
|
447
|
+
apiKey: credentialSource === "literal" ? validated.apiKey : storedApiKey,
|
|
448
|
+
signal: input.discoverySignal,
|
|
449
|
+
});
|
|
450
|
+
// The caller (wizard revision/cancel) may have aborted while the probe
|
|
451
|
+
// was resolving: recheck before any credential or config write so a
|
|
452
|
+
// stale revision cannot be persisted after dismissal.
|
|
453
|
+
if (input.discoverySignal?.aborted) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`Model discovery for '${validated.providerId}' was cancelled; setup did not write any config.`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (probed.models.length === 0) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
`Model discovery for '${validated.providerId}' returned no models; add --model <id> or fix the endpoint catalog.`,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
339
464
|
const provider: ProviderConfig = {
|
|
340
465
|
baseUrl: validated.baseUrl,
|
|
341
466
|
api: validated.api,
|
|
@@ -353,22 +478,97 @@ export async function addApiCompatibleProvider(input: ProviderSetupInput): Promi
|
|
|
353
478
|
if (validated.discovery) provider.discovery = validated.discovery;
|
|
354
479
|
if (validated.credentialSource === "env") {
|
|
355
480
|
provider.apiKeyEnv = validated.apiKey;
|
|
356
|
-
}
|
|
357
|
-
|
|
481
|
+
}
|
|
482
|
+
// Serialize config merging under the file lock. Credential storage is a
|
|
483
|
+
// separate authority, not an atomic transaction with models.yml. Publish the
|
|
484
|
+
// new config first and only then update the credential authority so a live
|
|
485
|
+
// session can never send a replacement key to the old base URL.
|
|
486
|
+
type CredentialStore = ProviderSetupCredentialStore;
|
|
487
|
+
const cancelledError = (): Error =>
|
|
488
|
+
new Error(`Model discovery for '${validated.providerId}' was cancelled; setup did not write any config.`);
|
|
489
|
+
// A cancellable replacement must not overwrite OAuth rows that cannot be
|
|
490
|
+
// restored from the redacted snapshot. The check is read-only and happens
|
|
491
|
+
// before the config commit; credentials are never rolled back after a
|
|
492
|
+
// concurrent writer can have changed them.
|
|
493
|
+
const hasUnrestorableCredentials = (store: CredentialStore): boolean => {
|
|
494
|
+
const storageProvider = resolveOAuthStorageProvider(validated.providerId);
|
|
495
|
+
const priorEntries = store.exportSnapshot().credentials.filter(entry => entry.provider === storageProvider);
|
|
496
|
+
return priorEntries.some(entry => entry.credential.type !== "api_key");
|
|
497
|
+
};
|
|
498
|
+
const withCredentialStore = async (fn: (store: CredentialStore) => Promise<void>): Promise<void> => {
|
|
499
|
+
if (input.authStorage) {
|
|
500
|
+
await fn(input.authStorage);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const authStorage = await AuthStorageImpl.create(getAgentDbPath());
|
|
358
504
|
try {
|
|
359
|
-
await authStorage
|
|
505
|
+
await fn(authStorage);
|
|
360
506
|
} finally {
|
|
361
507
|
authStorage.close();
|
|
362
508
|
}
|
|
363
|
-
}
|
|
364
|
-
const next: ModelsConfig = {
|
|
365
|
-
...existing,
|
|
366
|
-
providers: {
|
|
367
|
-
...(existing.providers ?? {}),
|
|
368
|
-
[validated.providerId]: provider,
|
|
369
|
-
},
|
|
370
509
|
};
|
|
371
|
-
await
|
|
510
|
+
await withFileLock(modelsPath, async () => {
|
|
511
|
+
if (input.discoverySignal?.aborted) {
|
|
512
|
+
throw cancelledError();
|
|
513
|
+
}
|
|
514
|
+
const current = await readModelsConfig(modelsPath);
|
|
515
|
+
if (input.discoverySignal?.aborted) throw cancelledError();
|
|
516
|
+
if (current.providers?.[validated.providerId] && !input.force) {
|
|
517
|
+
throw new Error(`Provider '${validated.providerId}' already exists. Use --force to replace it.`);
|
|
518
|
+
}
|
|
519
|
+
if (validated.credentialSource !== "env" && input.discoverySignal) {
|
|
520
|
+
await withCredentialStore(async store => {
|
|
521
|
+
if (hasUnrestorableCredentials(store)) {
|
|
522
|
+
throw new Error(
|
|
523
|
+
`Provider '${validated.providerId}' holds non-API-key credentials that cannot be restored if setup is cancelled; remove them first or omit --force.`,
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
if (input.discoverySignal?.aborted) throw cancelledError();
|
|
529
|
+
const configExistedBeforeCommit = await Bun.file(modelsPath).exists();
|
|
530
|
+
let configCommitted = false;
|
|
531
|
+
try {
|
|
532
|
+
await writeModelsConfig(
|
|
533
|
+
modelsPath,
|
|
534
|
+
{
|
|
535
|
+
...current,
|
|
536
|
+
providers: {
|
|
537
|
+
...(current.providers ?? {}),
|
|
538
|
+
[validated.providerId]: provider,
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
input.discoverySignal,
|
|
542
|
+
);
|
|
543
|
+
configCommitted = true;
|
|
544
|
+
// Cancellation after the atomic config rename cannot undo the saved
|
|
545
|
+
// provider. Finish publishing the matching literal credential even if
|
|
546
|
+
// the caller dismissed the wizard during the rename.
|
|
547
|
+
if (validated.credentialSource !== "env") {
|
|
548
|
+
await withCredentialStore(store =>
|
|
549
|
+
store.set(validated.providerId, { type: "api_key", key: validated.apiKey }),
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
} catch (error) {
|
|
553
|
+
if (configCommitted) {
|
|
554
|
+
try {
|
|
555
|
+
if (configExistedBeforeCommit) await writeModelsConfig(modelsPath, current);
|
|
556
|
+
else await fs.rm(modelsPath, { force: true });
|
|
557
|
+
} catch {
|
|
558
|
+
// Store/config errors can contain credentials; never log or
|
|
559
|
+
// interpolate them. Log independently of the wizard, which may
|
|
560
|
+
// already be dismissed.
|
|
561
|
+
const message = `Provider '${validated.providerId}' setup failed while publishing credentials and could not restore the previous config; credential recovery is required.`;
|
|
562
|
+
logger.error(message);
|
|
563
|
+
throw new Error(message, { cause: error });
|
|
564
|
+
}
|
|
565
|
+
throw new Error(
|
|
566
|
+
`Provider '${validated.providerId}' setup could not publish credentials; the previous config was restored.`,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
571
|
+
});
|
|
372
572
|
return {
|
|
373
573
|
providerId: validated.providerId,
|
|
374
574
|
compatibility: validated.compatibility,
|
|
@@ -378,6 +578,8 @@ export async function addApiCompatibleProvider(input: ProviderSetupInput): Promi
|
|
|
378
578
|
modelsPath,
|
|
379
579
|
redactedApiKey: redactSecret(validated.apiKey),
|
|
380
580
|
credentialSource: validated.credentialSource,
|
|
581
|
+
discoveryEnabled: validated.discovery !== undefined,
|
|
582
|
+
discoveryType: validated.discovery?.type,
|
|
381
583
|
preset: validated.preset?.id,
|
|
382
584
|
presetName: validated.preset?.name,
|
|
383
585
|
};
|
|
@@ -394,11 +596,182 @@ function isLocalHttpHost(hostname: string): boolean {
|
|
|
394
596
|
);
|
|
395
597
|
}
|
|
396
598
|
|
|
599
|
+
export interface ProviderDiscoveryProbeInput {
|
|
600
|
+
baseUrl: string;
|
|
601
|
+
apiKeyEnv?: string;
|
|
602
|
+
apiKey?: string;
|
|
603
|
+
timeoutMs?: number;
|
|
604
|
+
signal?: AbortSignal;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export interface ProviderDiscoveryProbeResult {
|
|
608
|
+
models: string[];
|
|
609
|
+
endpoint: string;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function redactProbeUrl(value: URL): string {
|
|
613
|
+
return `${value.origin}${value.pathname}`;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function normalizeProbeBaseUrl(rawBaseUrl: string): URL {
|
|
617
|
+
const parsed = new URL(rawBaseUrl.trim());
|
|
618
|
+
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
619
|
+
parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath || "/v1" : `${trimmedPath}/v1`;
|
|
620
|
+
parsed.hash = "";
|
|
621
|
+
return parsed;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Probe an OpenAI-compatible `/v1/models` endpoint and return the sorted
|
|
626
|
+
* model ids. Used by the Add-custom-provider wizard so gateway catalogs
|
|
627
|
+
* (LiteLLM and friends) do not need manual transcription.
|
|
628
|
+
*
|
|
629
|
+
* Credential handling: an `apiKeyEnv` name resolves through the trusted
|
|
630
|
+
* credential env only (never `cwd/.env`); a literal `apiKey` is used as-is.
|
|
631
|
+
* Errors name the redacted endpoint only — the key material never appears
|
|
632
|
+
* in messages, and callers must not log the request headers.
|
|
633
|
+
*/
|
|
634
|
+
export async function probeOpenAIModelsList(input: ProviderDiscoveryProbeInput): Promise<ProviderDiscoveryProbeResult> {
|
|
635
|
+
let baseUrl: URL;
|
|
636
|
+
try {
|
|
637
|
+
baseUrl = normalizeProbeBaseUrl(input.baseUrl);
|
|
638
|
+
} catch {
|
|
639
|
+
throw new Error("Model discovery needs a valid absolute base URL before probing.");
|
|
640
|
+
}
|
|
641
|
+
if (baseUrl.protocol !== "https:" && baseUrl.protocol !== "http:") {
|
|
642
|
+
throw new Error("Model discovery needs an http or https base URL.");
|
|
643
|
+
}
|
|
644
|
+
// P1: never transmit the bearer before the URL passes the same
|
|
645
|
+
// HTTPS-unless-loopback validation that setup itself enforces — a remote
|
|
646
|
+
// http:// URL must be rejected before any secret crosses the network.
|
|
647
|
+
if (baseUrl.protocol === "http:" && !isLocalHttpHost(baseUrl.hostname)) {
|
|
648
|
+
throw new Error("Model discovery needs https unless the endpoint targets localhost or a loopback address.");
|
|
649
|
+
}
|
|
650
|
+
const modelsUrl = new URL(baseUrl);
|
|
651
|
+
modelsUrl.pathname = `${modelsUrl.pathname.replace(/\/+$/g, "")}/models`;
|
|
652
|
+
const endpoint = redactProbeUrl(modelsUrl);
|
|
653
|
+
if (input.signal?.aborted) {
|
|
654
|
+
throw new Error(`Model discovery for ${endpoint} was cancelled before probing.`);
|
|
655
|
+
}
|
|
656
|
+
// Rotating reader (same as the model registry): an agent-.env credential
|
|
657
|
+
// rotated or removed after startup must be honored live, not served from
|
|
658
|
+
// the import-time snapshot.
|
|
659
|
+
const apiKey = input.apiKeyEnv ? $rotatingCredentialEnv(input.apiKeyEnv)?.trim() : input.apiKey?.trim();
|
|
660
|
+
if (!apiKey) {
|
|
661
|
+
throw new Error(
|
|
662
|
+
input.apiKeyEnv
|
|
663
|
+
? `Discovery needs ${input.apiKeyEnv} set in the trusted environment before ${endpoint} can be probed.`
|
|
664
|
+
: `Discovery needs an API key before ${endpoint} can be probed.`,
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
const headers: Record<string, string> = {};
|
|
668
|
+
let response: Response;
|
|
669
|
+
try {
|
|
670
|
+
response = await fetch(modelsUrl, {
|
|
671
|
+
headers: { ...headers, Authorization: `Bearer ${apiKey}` },
|
|
672
|
+
// Combine the caller signal with the shared deadline: a caller
|
|
673
|
+
// signal alone must never leave the request without a timeout.
|
|
674
|
+
signal: AbortSignal.any([
|
|
675
|
+
...(input.signal ? [input.signal] : []),
|
|
676
|
+
AbortSignal.timeout(input.timeoutMs ?? MODELS_LIST_REQUEST_TIMEOUT_MS),
|
|
677
|
+
]),
|
|
678
|
+
});
|
|
679
|
+
} catch (error) {
|
|
680
|
+
// Transport layers may echo request details (including the bearer)
|
|
681
|
+
// in failure text: scrub resolved credential material before the
|
|
682
|
+
// reason reaches any error surface.
|
|
683
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
684
|
+
const scrubbed = apiKey ? reason.split(apiKey).join("[redacted]") : reason;
|
|
685
|
+
throw new Error(`Model discovery failed for ${endpoint}: ${scrubbed}`);
|
|
686
|
+
}
|
|
687
|
+
if (!response.ok) {
|
|
688
|
+
if (response.status === 401 || response.status === 403) {
|
|
689
|
+
throw new Error(
|
|
690
|
+
`Model discovery rejected by ${endpoint} (HTTP ${response.status}): the credential was rejected; check the API key.`,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
throw new Error(`Model discovery failed for ${endpoint}: HTTP ${response.status}.`);
|
|
694
|
+
}
|
|
695
|
+
// Shared bounded reader (1MB cap): an unbounded response.json() would let
|
|
696
|
+
// a malicious endpoint exhaust process memory before ID filtering runs.
|
|
697
|
+
let payload: unknown;
|
|
698
|
+
try {
|
|
699
|
+
payload = await readBoundedModelsJson(response);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
702
|
+
if (reason.includes("exceeds the size limit")) {
|
|
703
|
+
throw new Error(`Model discovery failed for ${endpoint}: the response exceeds the size limit.`);
|
|
704
|
+
}
|
|
705
|
+
throw new Error(`Model discovery failed for ${endpoint}: the response was not valid JSON.`);
|
|
706
|
+
}
|
|
707
|
+
if (typeof payload !== "object" || payload === null || !Array.isArray((payload as { data?: unknown }).data)) {
|
|
708
|
+
throw new Error(`Model discovery failed for ${endpoint}: the response was not an OpenAI models list.`);
|
|
709
|
+
}
|
|
710
|
+
const ids = new Set<string>();
|
|
711
|
+
for (const item of (payload as { data: unknown[] }).data) {
|
|
712
|
+
// P1: the wire catalog is attacker-controlled — drop IDs that fail the
|
|
713
|
+
// shared catalog safety check (control chars, OSC/ANSI, oversize)
|
|
714
|
+
// before they can reach the setup-screen renderer.
|
|
715
|
+
const rawId = typeof item === "object" && item !== null ? (item as { id?: unknown }).id : undefined;
|
|
716
|
+
if (!isSafeCatalogModelId(rawId)) continue;
|
|
717
|
+
ids.add(rawId.trim());
|
|
718
|
+
}
|
|
719
|
+
return { models: [...ids].sort((a, b) => a.localeCompare(b)), endpoint };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
export interface DiscoveryCatalogRefresher {
|
|
723
|
+
refresh(mode: "offline" | "online" | "online-if-uncached", credentialSessionId?: string): Promise<void>;
|
|
724
|
+
refreshProvider(
|
|
725
|
+
providerId: string,
|
|
726
|
+
strategy?: "offline" | "online" | "online-if-uncached",
|
|
727
|
+
credentialSessionId?: string,
|
|
728
|
+
): Promise<void>;
|
|
729
|
+
getProviderDiscoveryState(providerId: string): { status: string; error?: string } | undefined;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Reload static config offline, then refresh only the newly added discovery
|
|
734
|
+
* provider online so freshly requested live catalogs are selectable
|
|
735
|
+
* immediately (including alongside manual models). Preset-managed
|
|
736
|
+
* discovery is excluded by callers: presets resolve lazily at runtime.
|
|
737
|
+
* Returns a recovery hint instead of reporting unconditional success when
|
|
738
|
+
* the live catalog stays unavailable: the config is saved and valid, but
|
|
739
|
+
* the user needs to know the catalog did not populate and how to recover
|
|
740
|
+
* (wait for the next refresh, or re-add with explicit `--model` ids).
|
|
741
|
+
*/
|
|
742
|
+
export async function reloadAndRefreshDiscoveryCatalog(
|
|
743
|
+
registry: DiscoveryCatalogRefresher,
|
|
744
|
+
providerId: string,
|
|
745
|
+
credentialSessionId?: string,
|
|
746
|
+
): Promise<string | null> {
|
|
747
|
+
await registry.refresh("offline", credentialSessionId);
|
|
748
|
+
try {
|
|
749
|
+
await registry.refreshProvider(providerId, "online", credentialSessionId);
|
|
750
|
+
} catch {
|
|
751
|
+
// Fall through to the status read: discovery failures surface as
|
|
752
|
+
// state, not rejections.
|
|
753
|
+
}
|
|
754
|
+
const state = registry.getProviderDiscoveryState(providerId);
|
|
755
|
+
if (state?.status === "ok") return null;
|
|
756
|
+
const detail = state?.error ? ` (${state.error})` : "";
|
|
757
|
+
return (
|
|
758
|
+
`Live catalog unavailable${detail}; the provider is saved and will populate on the next online refresh. ` +
|
|
759
|
+
`To pin models now, re-add with --force --model <id> (the provider already exists).`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
397
763
|
export function formatProviderSetupResult(result: ProviderSetupResult): string {
|
|
398
764
|
return [
|
|
399
765
|
`Provider '${result.providerId}' configured as ${result.compatibility}-compatible.`,
|
|
400
766
|
...(result.presetName ? [`Preset: ${result.presetName}`] : []),
|
|
401
|
-
`Models: ${result.modelIds.length > 0 ? result.modelIds.join(", ") : "discovered automatically"}`,
|
|
767
|
+
`Models: ${result.modelIds.length > 0 ? result.modelIds.join(", ") : result.discoveryEnabled ? "discovered automatically" : "(none)"}`,
|
|
768
|
+
...(result.discoveryEnabled
|
|
769
|
+
? [
|
|
770
|
+
result.discoveryType === "openai-models-list"
|
|
771
|
+
? "Discovery: live OpenAI /v1/models catalog"
|
|
772
|
+
: `Discovery: live ${result.discoveryType ?? "provider"} catalog`,
|
|
773
|
+
]
|
|
774
|
+
: []),
|
|
402
775
|
`Base URL: ${result.baseUrl}`,
|
|
403
776
|
`API key: ${result.credentialSource === "env" ? `${result.redactedApiKey} (environment variable)` : result.redactedApiKey}`,
|
|
404
777
|
`Config: ${result.modelsPath}`,
|
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
{"id":"cline-pass","aliases":["clinepass","cline"],"name":"ClinePass","description":"ClinePass subscription models discovered from Cline's upstream models.dev catalog","compatibility":"openai","api":"openai-completions","providerId":"cline-pass","baseUrl":"https://api.cline.bot/api/v1","apiKeyEnv":"CLINE_API_KEY","discovery":{"type":"models-dev","modelsDevProvider":"cline-pass"},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning"}},
|
|
7
7
|
{"id":"commandcode-goat","aliases":["commandcode","command-code","goat"],"name":"Command Code GOAT","description":"Command Code GOAT coding-plan models discovered from its OpenAI-compatible Provider API","compatibility":"openai","api":"openai-completions","providerId":"commandcode-goat","baseUrl":"https://api.commandcode.ai/provider/v1","apiKeyEnv":"CMD_API_KEY","discovery":{"type":"openai-models-list","apiByModelPrefix":{"claude":"anthropic-messages","anthropic/claude":"anthropic-messages"}},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","reasoningContentField":"reasoning_content"}},
|
|
8
8
|
{"id":"litellm","aliases":["litellm-proxy"],"name":"LiteLLM Proxy","description":"OpenAI-compatible LiteLLM proxy endpoint (user-supplied base URL) with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"litellm","parameterized":true,"apiKeyEnv":"LITELLM_API_KEY","discovery":{"type":"openai-models-list"}},
|
|
9
|
-
{"id":"openai-compatible-proxy","aliases":["openai-proxy","compatible-proxy","custom-proxy"],"name":"OpenAI-Compatible Proxy","description":"Generic OpenAI-compatible gateway (OpenRouter, OpenCode Go, custom) with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"openai-compatible-proxy","parameterized":true,"apiKeyEnv":"OPENAI_COMPATIBLE_API_KEY","discovery":{"type":"openai-models-list"}}
|
|
9
|
+
{"id":"openai-compatible-proxy","aliases":["openai-proxy","compatible-proxy","custom-proxy"],"name":"OpenAI-Compatible Proxy","description":"Generic OpenAI-compatible gateway (OpenRouter, OpenCode Go, custom) with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"openai-compatible-proxy","parameterized":true,"apiKeyEnv":"OPENAI_COMPATIBLE_API_KEY","discovery":{"type":"openai-models-list"}},
|
|
10
|
+
{"id":"ionet","aliases":["io-net","io-intelligence"],"name":"IO Intelligence (io.net)","description":"OpenAI-compatible IO Intelligence endpoint by io.net with live model discovery","compatibility":"openai","api":"openai-completions","providerId":"ionet","baseUrl":"https://api.intelligence.io.solutions/api/v1","apiKeyEnv":"IONET_API_KEY","discovery":{"type":"openai-models-list"},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"supportsUsageInStreaming":false,"maxTokensField":"max_tokens","reasoningContentField":"reasoning_content","extraBody":{"tool_choice":"auto"}}}
|
|
10
11
|
]
|