talos-code 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +111 -3
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/archive/zip.js +73 -0
- package/dist/args.js +97 -0
- package/dist/automations/history-store.js +34 -0
- package/dist/automations/policy-store.js +43 -0
- package/dist/automations/runner.js +107 -0
- package/dist/automations/schedule.js +85 -0
- package/dist/automations/windows-task.js +53 -0
- package/dist/commands/advanced-cli.js +233 -0
- package/dist/commands/checkpoint-cli.js +45 -0
- package/dist/commands/config-cli.js +107 -0
- package/dist/commands/context.js +17 -0
- package/dist/commands/extensions-cli.js +207 -0
- package/dist/commands/project-cli.js +74 -0
- package/dist/commands/project-commands.js +143 -0
- package/dist/commands/provider-cli.js +185 -0
- package/dist/commands/services-cli.js +248 -0
- package/dist/commands/session-cli.js +171 -0
- package/dist/commands/system-cli.js +405 -0
- package/dist/config/commands.js +60 -0
- package/dist/config/load.js +382 -0
- package/dist/config/migrations.js +48 -0
- package/dist/config/types.js +11 -0
- package/dist/diagnostics/development-log.js +154 -0
- package/dist/diagnostics/doctor.js +104 -0
- package/dist/diagnostics/redact.js +80 -0
- package/dist/diagnostics/zip.js +52 -0
- package/dist/errors.js +156 -0
- package/dist/events/bridge.js +130 -0
- package/dist/extensions/installer.js +178 -0
- package/dist/extensions/package-schema.js +24 -0
- package/dist/headless/result.js +84 -0
- package/dist/headless/run.js +230 -0
- package/dist/i18n/en/approval.js +43 -0
- package/dist/i18n/en/common.js +9 -0
- package/dist/i18n/en/credentials.js +82 -0
- package/dist/i18n/en/errors.js +246 -0
- package/dist/i18n/en/firstrun.js +89 -0
- package/dist/i18n/en/providers.js +37 -0
- package/dist/i18n/en/screen.js +736 -0
- package/dist/i18n/en/tools.js +127 -0
- package/dist/i18n/en.js +14 -0
- package/dist/i18n/error-view.js +307 -0
- package/dist/i18n/index.js +19 -0
- package/dist/i18n/kernel-map.js +139 -0
- package/dist/io.js +45 -0
- package/dist/main.js +330 -0
- package/dist/paths.js +41 -0
- package/dist/protocol/v2/codec.js +9 -0
- package/dist/protocol/v2/events.js +555 -0
- package/dist/protocol/v2/index.js +4 -0
- package/dist/protocol/v2/replay.js +28 -0
- package/dist/protocol/v2/types.js +1 -0
- package/dist/provider/control-plane.js +135 -0
- package/dist/provider/environment-keys.js +275 -0
- package/dist/provider/health.js +110 -0
- package/dist/provider/missing-key.js +48 -0
- package/dist/provider/model-catalog.js +168 -0
- package/dist/provider/provider-text.js +13 -0
- package/dist/provider/store.js +95 -0
- package/dist/provider/system-keyring.js +214 -0
- package/dist/runtime/active-run.js +63 -0
- package/dist/runtime/agent-tree.js +74 -0
- package/dist/runtime/attachments.js +84 -0
- package/dist/runtime/brokered-executor.js +395 -0
- package/dist/runtime/context-status.js +76 -0
- package/dist/runtime/create-runtime.js +111 -0
- package/dist/runtime/model-profile.js +361 -0
- package/dist/runtime/output-store.js +137 -0
- package/dist/runtime/provider-attempts.js +185 -0
- package/dist/runtime/replay-buffer.js +153 -0
- package/dist/runtime/repo.js +95 -0
- package/dist/runtime/session-facade.js +135 -0
- package/dist/runtime/session-summary.js +125 -0
- package/dist/runtime/supervisor.js +262 -0
- package/dist/runtime/talos-composition.js +1006 -0
- package/dist/runtime/types.js +5 -0
- package/dist/runtime/usage-snapshot.js +82 -0
- package/dist/security/auto-classifier.js +38 -0
- package/dist/security/credential-free-environment.js +54 -0
- package/dist/security/evaluate.js +243 -0
- package/dist/security/execution-backends.js +236 -0
- package/dist/security/execution-broker.js +102 -0
- package/dist/security/forge-scan.js +74 -0
- package/dist/security/from-approval.js +39 -0
- package/dist/security/mxc-execution-backend.js +281 -0
- package/dist/security/permission-engine.js +138 -0
- package/dist/security/permission-explanation.js +54 -0
- package/dist/security/persist.js +179 -0
- package/dist/security/plugin-guard.js +230 -0
- package/dist/security/process-tree-evidence-store.js +74 -0
- package/dist/security/process-tree-probe.js +554 -0
- package/dist/security/project-resource-inventory.js +186 -0
- package/dist/security/project-trust-gate.js +38 -0
- package/dist/security/project-trust.js +367 -0
- package/dist/security/rule-parser.js +201 -0
- package/dist/security/shell-segmentation.js +68 -0
- package/dist/security/trust-authority.js +324 -0
- package/dist/security/types.js +1 -0
- package/dist/security/workspace-identity.js +102 -0
- package/dist/services/automation-facade.js +59 -0
- package/dist/services/forge-facade.js +77 -0
- package/dist/services/hook-facade.js +240 -0
- package/dist/services/index.js +16 -0
- package/dist/services/library-facade.js +174 -0
- package/dist/services/mcp-facade.js +348 -0
- package/dist/services/memory-facade.js +126 -0
- package/dist/services/notes-facade.js +157 -0
- package/dist/services/plugin-facade.js +308 -0
- package/dist/services/research-facade.js +110 -0
- package/dist/services/task-board-facade.js +236 -0
- package/dist/services/workflow-catalog.js +246 -0
- package/dist/sessions/export-format.js +98 -0
- package/dist/sessions/metadata-store.js +160 -0
- package/dist/sessions/share.js +122 -0
- package/dist/sessions/transfer.js +16 -0
- package/dist/subcommands.js +62 -0
- package/dist/tui/agent-roster.js +36 -0
- package/dist/tui/app.js +3761 -0
- package/dist/tui/approval.js +36 -0
- package/dist/tui/boot/boot-sequence.js +76 -0
- package/dist/tui/boot/cinematic.js +243 -0
- package/dist/tui/boot/desktop-logo.js +82 -0
- package/dist/tui/boot.js +3 -0
- package/dist/tui/busy-input.js +58 -0
- package/dist/tui/catalog-service.js +559 -0
- package/dist/tui/components/assistant-stream.js +1 -0
- package/dist/tui/components/command-menu.js +68 -0
- package/dist/tui/components/composer.js +219 -0
- package/dist/tui/components/diff.js +35 -0
- package/dist/tui/components/footer.js +48 -0
- package/dist/tui/components/header.js +6 -0
- package/dist/tui/components/markdown.js +305 -0
- package/dist/tui/components/status-indicator.js +32 -0
- package/dist/tui/components/terminal-shell.js +205 -0
- package/dist/tui/components/thinking-row.js +19 -0
- package/dist/tui/components/tool-row.js +97 -0
- package/dist/tui/components/transcript-virtualizer.js +165 -0
- package/dist/tui/components/transcript.js +43 -0
- package/dist/tui/diff-model.js +77 -0
- package/dist/tui/editor-history.js +21 -0
- package/dist/tui/editor.js +210 -0
- package/dist/tui/event-adapter.js +436 -0
- package/dist/tui/exit-output.js +58 -0
- package/dist/tui/external-editor.js +53 -0
- package/dist/tui/file-completion.js +89 -0
- package/dist/tui/focus-manager.js +5 -0
- package/dist/tui/highlight.js +30 -0
- package/dist/tui/input-router.js +18 -0
- package/dist/tui/interrupt.js +99 -0
- package/dist/tui/keybindings.js +194 -0
- package/dist/tui/keymap-resolver.js +91 -0
- package/dist/tui/launch-state.js +139 -0
- package/dist/tui/line-diff.js +57 -0
- package/dist/tui/live-activity.js +45 -0
- package/dist/tui/metrics.js +123 -0
- package/dist/tui/onboarding.js +36 -0
- package/dist/tui/overlays/agent-tree.js +39 -0
- package/dist/tui/overlays/approval-dialog.js +640 -0
- package/dist/tui/overlays/automation-center.js +51 -0
- package/dist/tui/overlays/checkpoint-picker.js +47 -0
- package/dist/tui/overlays/context-inspector.js +36 -0
- package/dist/tui/overlays/effort-line.js +110 -0
- package/dist/tui/overlays/forge-center.js +46 -0
- package/dist/tui/overlays/help-dialog.js +16 -0
- package/dist/tui/overlays/history-picker.js +37 -0
- package/dist/tui/overlays/hook-center.js +70 -0
- package/dist/tui/overlays/library-center.js +55 -0
- package/dist/tui/overlays/mcp-center.js +60 -0
- package/dist/tui/overlays/memory-center.js +63 -0
- package/dist/tui/overlays/model-picker.js +72 -0
- package/dist/tui/overlays/notes-center.js +53 -0
- package/dist/tui/overlays/overlay-host.js +8 -0
- package/dist/tui/overlays/plugin-center.js +76 -0
- package/dist/tui/overlays/provider-picker.js +145 -0
- package/dist/tui/overlays/queue-editor.js +32 -0
- package/dist/tui/overlays/research-center.js +59 -0
- package/dist/tui/overlays/scroll-window.js +234 -0
- package/dist/tui/overlays/session-picker.js +115 -0
- package/dist/tui/overlays/tasks-center.js +83 -0
- package/dist/tui/overlays/theme-picker.js +21 -0
- package/dist/tui/overlays/transcript-search.js +58 -0
- package/dist/tui/overlays/trust-center.js +29 -0
- package/dist/tui/overlays/workflow-center.js +46 -0
- package/dist/tui/project-file-index.js +214 -0
- package/dist/tui/project-references.js +85 -0
- package/dist/tui/project-trust-prompt.js +287 -0
- package/dist/tui/prompt-history-store.js +116 -0
- package/dist/tui/queue-store.js +110 -0
- package/dist/tui/regions/budget.js +59 -0
- package/dist/tui/regions/views.js +96 -0
- package/dist/tui/render-coordinator.js +148 -0
- package/dist/tui/render-scheduler.js +4 -0
- package/dist/tui/run.js +69 -0
- package/dist/tui/selection-list.js +29 -0
- package/dist/tui/session-controller.js +778 -0
- package/dist/tui/session-export.js +54 -0
- package/dist/tui/shell-input.js +35 -0
- package/dist/tui/shell-layout.js +48 -0
- package/dist/tui/shell-model.js +98 -0
- package/dist/tui/slash-commands.js +80 -0
- package/dist/tui/state.js +151 -0
- package/dist/tui/status-bar.js +125 -0
- package/dist/tui/status-view.js +40 -0
- package/dist/tui/terminal-capabilities.js +9 -0
- package/dist/tui/terminal-session.js +11 -0
- package/dist/tui/text-width.js +66 -0
- package/dist/tui/theme-catalog.js +25 -0
- package/dist/tui/theme-store.js +23 -0
- package/dist/tui/theme.js +23 -0
- package/dist/tui/tool-display.js +135 -0
- package/dist/tui/tool-facts.js +1 -0
- package/dist/tui/tools/bash-renderer.js +32 -0
- package/dist/tui/tools/change.js +61 -0
- package/dist/tui/tools/edit-renderer.js +17 -0
- package/dist/tui/tools/generic-renderer.js +13 -0
- package/dist/tui/tools/list-renderer.js +14 -0
- package/dist/tui/tools/read-renderer.js +13 -0
- package/dist/tui/tools/registry.js +20 -0
- package/dist/tui/tools/row-format.js +187 -0
- package/dist/tui/tools/search-renderer.js +38 -0
- package/dist/tui/tools/shared.js +97 -0
- package/dist/tui/tools/write-renderer.js +15 -0
- package/dist/tui/transcript-model.js +441 -0
- package/dist/tui/ui-preferences.js +79 -0
- package/dist/tui/usage-view.js +77 -0
- package/dist/tui/vim-mode.js +99 -0
- package/dist/update/check.js +15 -0
- package/dist/update/npm.js +51 -0
- package/dist/update/run.js +129 -0
- package/dist/version.js +2 -0
- package/dist/workspace/checkpoint-store.js +210 -0
- package/dist/workspace/checkpoint.js +413 -0
- package/dist/workspace/restore.js +232 -0
- package/package.json +63 -5
- package/vendor/context-engine/package.json +11 -0
- package/vendor/context-engine/src/compaction-planner.mjs +57 -0
- package/vendor/context-engine/src/contracts.mjs +48 -0
- package/vendor/context-engine/src/engine.mjs +445 -0
- package/vendor/context-engine/src/node/context-export.mjs +164 -0
- package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
- package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
- package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
- package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
- package/vendor/context-engine/src/profiles.mjs +10 -0
- package/vendor/context-engine/src/retrieval.mjs +104 -0
- package/vendor/context-engine/src/summary.mjs +97 -0
- package/vendor/context-engine/src/usage.mjs +34 -0
- package/vendor/harness-ui/package.json +38 -0
- package/vendor/harness-ui/src/acp-agent.mjs +350 -0
- package/vendor/harness-ui/src/agent-service.mjs +2188 -0
- package/vendor/harness-ui/src/agui-events.mjs +452 -0
- package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
- package/vendor/harness-ui/src/artifact-store.mjs +39 -0
- package/vendor/harness-ui/src/assistenza.mjs +123 -0
- package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
- package/vendor/harness-ui/src/automation-store.mjs +145 -0
- package/vendor/harness-ui/src/browser-annota.mjs +639 -0
- package/vendor/harness-ui/src/browser-frame.mjs +211 -0
- package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
- package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
- package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
- package/vendor/harness-ui/src/browser-stream.mjs +445 -0
- package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
- package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
- package/vendor/harness-ui/src/config.mjs +697 -0
- package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
- package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
- package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
- package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
- package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
- package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
- package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
- package/vendor/harness-ui/src/context-runtime.mjs +118 -0
- package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
- package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
- package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
- package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
- package/vendor/harness-ui/src/custom-task.mjs +171 -0
- package/vendor/harness-ui/src/doctor.mjs +142 -0
- package/vendor/harness-ui/src/document-filename.mjs +97 -0
- package/vendor/harness-ui/src/document-generator.mjs +493 -0
- package/vendor/harness-ui/src/document-report.mjs +331 -0
- package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
- package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
- package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
- package/vendor/harness-ui/src/forge-contract.mjs +221 -0
- package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
- package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
- package/vendor/harness-ui/src/generation-idle.mjs +332 -0
- package/vendor/harness-ui/src/gguf-header.mjs +207 -0
- package/vendor/harness-ui/src/git-service.mjs +626 -0
- package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
- package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
- package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
- package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
- package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
- package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
- package/vendor/harness-ui/src/hook-registry.mjs +186 -0
- package/vendor/harness-ui/src/http-app.mjs +6259 -0
- package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
- package/vendor/harness-ui/src/id-archivio.mjs +27 -0
- package/vendor/harness-ui/src/image-generator.mjs +143 -0
- package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
- package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
- package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
- package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
- package/vendor/harness-ui/src/library-store.mjs +652 -0
- package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
- package/vendor/harness-ui/src/local-model-store.mjs +339 -0
- package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
- package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
- package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
- package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
- package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
- package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
- package/vendor/harness-ui/src/mcp-client.mjs +98 -0
- package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
- package/vendor/harness-ui/src/mcp-session.mjs +177 -0
- package/vendor/harness-ui/src/memory-store.mjs +227 -0
- package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
- package/vendor/harness-ui/src/model-catalog.mjs +129 -0
- package/vendor/harness-ui/src/model-destination.mjs +189 -0
- package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
- package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
- package/vendor/harness-ui/src/notes-store.mjs +250 -0
- package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
- package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
- package/vendor/harness-ui/src/path-policy.mjs +442 -0
- package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
- package/vendor/harness-ui/src/plugin-session.mjs +180 -0
- package/vendor/harness-ui/src/process-policy.mjs +345 -0
- package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
- package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
- package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
- package/vendor/harness-ui/src/provider-probe.mjs +582 -0
- package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
- package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
- package/vendor/harness-ui/src/public-problem.mjs +109 -0
- package/vendor/harness-ui/src/research/card.mjs +235 -0
- package/vendor/harness-ui/src/research/citations.mjs +142 -0
- package/vendor/harness-ui/src/research/collector.mjs +275 -0
- package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
- package/vendor/harness-ui/src/research/dossier.mjs +114 -0
- package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
- package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
- package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
- package/vendor/harness-ui/src/research/independence.mjs +159 -0
- package/vendor/harness-ui/src/research/ledger.mjs +166 -0
- package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
- package/vendor/harness-ui/src/research/narration.mjs +181 -0
- package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
- package/vendor/harness-ui/src/research/opposing.mjs +305 -0
- package/vendor/harness-ui/src/research/outline.mjs +111 -0
- package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
- package/vendor/harness-ui/src/research/pdf.mjs +291 -0
- package/vendor/harness-ui/src/research/plan.mjs +301 -0
- package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
- package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
- package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
- package/vendor/harness-ui/src/research/recheck.mjs +194 -0
- package/vendor/harness-ui/src/research/report.mjs +203 -0
- package/vendor/harness-ui/src/research/run.mjs +527 -0
- package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
- package/vendor/harness-ui/src/research/verification.mjs +572 -0
- package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
- package/vendor/harness-ui/src/research-store.mjs +1133 -0
- package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
- package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
- package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
- package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
- package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
- package/vendor/harness-ui/src/search-source-store.mjs +172 -0
- package/vendor/harness-ui/src/session-registry.mjs +6095 -0
- package/vendor/harness-ui/src/session-store.mjs +220 -0
- package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
- package/vendor/harness-ui/src/setup-stato.mjs +31 -0
- package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
- package/vendor/harness-ui/src/skill-registry.mjs +120 -0
- package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
- package/vendor/harness-ui/src/static-files.mjs +96 -0
- package/vendor/harness-ui/src/stream-partition.mjs +123 -0
- package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
- package/vendor/harness-ui/src/task-catalog.mjs +65 -0
- package/vendor/harness-ui/src/tasks-store.mjs +220 -0
- package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
- package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
- package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
- package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
- package/vendor/harness-ui/src/usage-cache.mjs +315 -0
- package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
- package/vendor/harness-ui/src/workspace-context.mjs +124 -0
- package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
- package/vendor/harness-ui/src/workspace-files.mjs +589 -0
- package/vendor/harness-ui/src/workspace-info.mjs +189 -0
- package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
- package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
- package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
- package/vendor/manifest.json +170 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { ID_MOTORI_LOCALI_OPENAI, REGISTRO_FORNITORI } from './provider-registry.mjs';
|
|
2
|
+
import { createStreamPartitioner } from './stream-partition.mjs';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* ⛔ 12/09 — P-A: l'ottavo dei tredici elenchi. Era l'UNICO posto del repo che conoscesse
|
|
6
|
+
* `lmstudio`, e proprio per questo LM Studio era scoperto, sondato, caricabile e scaricabile —
|
|
7
|
+
* e non sceglibile in chat. Adesso i due motori locali su wire OpenAI li nomina il registro.
|
|
8
|
+
*/
|
|
9
|
+
const PROVIDERS = Object.freeze(Object.fromEntries(ID_MOTORI_LOCALI_OPENAI
|
|
10
|
+
.map((id) => [id, Object.freeze({ baseUrl: REGISTRO_FORNITORI[id].baseUrl, listPath: REGISTRO_FORNITORI[id].catalogo.percorso })])));
|
|
11
|
+
|
|
12
|
+
export class OpenAiCompatibleRuntimeError extends Error {
|
|
13
|
+
constructor(message, code = 'RUNTIME_FAILED') {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'OpenAiCompatibleRuntimeError';
|
|
16
|
+
this.code = code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fail(message, code = 'RUNTIME_INVALID') {
|
|
21
|
+
throw new OpenAiCompatibleRuntimeError(message, code);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* P-D, 12/09/2026: adatta il corpo HTTP secondo il profilo del registro.
|
|
26
|
+
* `extra_body` appartiene agli SDK Python: sul wire i campi sono al primo livello.
|
|
27
|
+
* Gli avvisi sono dati locali da rendere al chiamante, mai campi inviati al modello.
|
|
28
|
+
* Il chiamante di produzione richiede l'aggancio in runtime-owner-adapter.mjs:
|
|
29
|
+
* diff non applicato nel rapporto P-D, perché fuori dal perimetro assegnato.
|
|
30
|
+
*/
|
|
31
|
+
export function preparaRichiestaCompatibile(provider, corpo) {
|
|
32
|
+
const record = REGISTRO_FORNITORI[provider];
|
|
33
|
+
// P-K — il corpo HTTP usa reasoning_effort, non l'involucro del router.
|
|
34
|
+
if (record?.cloud) return preparaRichiestaCloud(record, corpo);
|
|
35
|
+
// P-K — fine
|
|
36
|
+
if (record?.richiestaCompatibile) return preparaProfiloCompatibile(record, corpo);
|
|
37
|
+
if (record?.ragionamento?.formato !== 'thinking') return { corpo, avvisi: [] };
|
|
38
|
+
const oggetto = v => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
39
|
+
if (!oggetto(corpo) || typeof corpo.model !== 'string') fail(`Richiesta ${record.etichetta} non valida.`);
|
|
40
|
+
if (corpo.extra_body !== undefined && !oggetto(corpo.extra_body)) fail(`Opzioni ${record.etichetta} non valide.`);
|
|
41
|
+
const unito = { ...corpo, ...(corpo.extra_body ?? {}) };
|
|
42
|
+
const { extra_body, reasoning, reasoning_effort, thinking, ...resto } = unito;
|
|
43
|
+
if (thinking !== undefined && (!oggetto(thinking) || !['enabled', 'disabled'].includes(thinking.type))) fail(`Controllo del ragionamento ${record.etichetta} non valido.`);
|
|
44
|
+
const id = corpo.model.startsWith(`${provider}:`) ? corpo.model.slice(provider.length + 1) : corpo.model;
|
|
45
|
+
const modello = record.modelliNoti.find(m => m.id === id);
|
|
46
|
+
const opzioni = modello?.ragionamento;
|
|
47
|
+
const avvisi = [];
|
|
48
|
+
const effort = reasoning_effort ?? reasoning?.effort;
|
|
49
|
+
const richiesto = typeof effort === 'string' ? effort.trim().toLowerCase() : effort;
|
|
50
|
+
const preferenza = thinking?.type ?? (reasoning?.enabled === false || richiesto === 'none' ? 'disabled' : reasoning?.enabled === true || richiesto != null ? 'enabled' : undefined);
|
|
51
|
+
// L'involucro extra non può cambiare la destinazione o il contenuto dell'utente.
|
|
52
|
+
const risultato = { ...resto, model: corpo.model, ...(corpo.messages !== undefined ? { messages: corpo.messages } : {}) };
|
|
53
|
+
if (preferenza !== undefined && opzioni?.thinking?.length) {
|
|
54
|
+
let tipo = preferenza;
|
|
55
|
+
if (!opzioni.thinking.includes(tipo)) {
|
|
56
|
+
tipo = 'enabled';
|
|
57
|
+
avvisi.push(`${record.etichetta} · ${modello.nome}: il modello non consente di disattivare il ragionamento; resta attivo.`);
|
|
58
|
+
}
|
|
59
|
+
risultato.thinking = { type: tipo, ...(typeof thinking?.clear_thinking === 'boolean' ? { clear_thinking: thinking.clear_thinking } : {}) };
|
|
60
|
+
} else if (preferenza !== undefined && richiesto == null) {
|
|
61
|
+
avvisi.push(`${record.etichetta}: controllo del ragionamento non documentato per questo modello; non inviato.`);
|
|
62
|
+
}
|
|
63
|
+
if (richiesto != null) {
|
|
64
|
+
if (!opzioni?.livelli.includes(richiesto)) {
|
|
65
|
+
avvisi.push(`${record.etichetta}: livello di ragionamento richiesto non previsto dal profilo P-D per questo modello; non inviato.`);
|
|
66
|
+
} else if (risultato.thinking?.type !== 'disabled') {
|
|
67
|
+
risultato.reasoning_effort = richiesto;
|
|
68
|
+
} else {
|
|
69
|
+
avvisi.push(`${record.etichetta}: livello di ragionamento non inviato perché il ragionamento è disattivato.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { corpo: risultato, avvisi };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// P-K — OpenAI v1 ufficiale: nessuna deduzione di famiglia dal nome della distribuzione Azure.
|
|
76
|
+
function preparaRichiestaCloud(record, corpo) {
|
|
77
|
+
const oggetto = v => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
78
|
+
if (!oggetto(corpo) || typeof corpo.model !== 'string' || !corpo.model.trim()) fail(`Richiesta ${record.etichetta} non valida.`);
|
|
79
|
+
const risultato = { ...corpo }, avvisi = [];
|
|
80
|
+
if (corpo.reasoning !== undefined) {
|
|
81
|
+
if (!oggetto(corpo.reasoning)) fail(`Opzioni di ragionamento ${record.etichetta} non valide.`);
|
|
82
|
+
delete risultato.reasoning;
|
|
83
|
+
if (corpo.reasoning.effort !== undefined) {
|
|
84
|
+
if (corpo.reasoning_effort !== undefined && corpo.reasoning_effort !== corpo.reasoning.effort) fail(`Opzioni di ragionamento ${record.etichetta} in conflitto.`);
|
|
85
|
+
risultato.reasoning_effort = corpo.reasoning.effort;
|
|
86
|
+
}
|
|
87
|
+
if (Object.keys(corpo.reasoning).some(k => k !== 'effort')) avvisi.push(`${record.etichetta}: queste opzioni di ragionamento non sono previste dal collegamento.`);
|
|
88
|
+
}
|
|
89
|
+
// I limiti di generazione restano quelli chiesti; compatibilità finale dipendente dal modello.
|
|
90
|
+
return { corpo: risultato, avvisi };
|
|
91
|
+
}
|
|
92
|
+
// P-K — fine
|
|
93
|
+
|
|
94
|
+
/** P-G, 12/09/2026: sole differenze documentate nel record, senza confronti sui fornitori. */
|
|
95
|
+
function preparaProfiloCompatibile(record, corpo) {
|
|
96
|
+
const oggetto = v => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
97
|
+
if (!oggetto(corpo) || typeof corpo.model !== 'string' || !corpo.model.trim()) fail(`Richiesta ${record.etichetta} non valida.`);
|
|
98
|
+
const profilo = record.richiestaCompatibile;
|
|
99
|
+
const id = corpo.model.startsWith(`${record.id}:`) ? corpo.model.slice(record.id.length + 1) : corpo.model;
|
|
100
|
+
const modello = Object.hasOwn(profilo.modelli, id) ? profilo.modelli[id] : null;
|
|
101
|
+
if (['thinking', 'enable_thinking'].includes(profilo.ragionamento)) {
|
|
102
|
+
return modello ? preparaControlloThinking(record, modello, corpo) : { corpo, avvisi: [] };
|
|
103
|
+
}
|
|
104
|
+
const risultato = { ...corpo };
|
|
105
|
+
const avvisi = [];
|
|
106
|
+
|
|
107
|
+
if (modello?.strumentiConFormato === false && corpo.tools != null && corpo.response_format != null) {
|
|
108
|
+
fail(`${record.etichetta}: questo modello non consente strumenti e formato di risposta vincolato nella stessa richiesta.`);
|
|
109
|
+
}
|
|
110
|
+
if (profilo.limiteUscita === 'max_completion_tokens' && Object.hasOwn(corpo, 'max_tokens')) {
|
|
111
|
+
if (corpo.max_tokens != null && corpo.max_completion_tokens != null && corpo.max_completion_tokens !== corpo.max_tokens) {
|
|
112
|
+
fail(`${record.etichetta}: sono stati indicati due limiti di uscita diversi.`);
|
|
113
|
+
}
|
|
114
|
+
risultato.max_completion_tokens = corpo.max_completion_tokens ?? corpo.max_tokens;
|
|
115
|
+
delete risultato.max_tokens;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (profilo.ragionamento === 'effort' && corpo.reasoning != null) {
|
|
119
|
+
if (!oggetto(corpo.reasoning)) fail(`Opzioni di ragionamento ${record.etichetta} non valide.`);
|
|
120
|
+
const { effort, enabled, ...altre } = corpo.reasoning;
|
|
121
|
+
if (effort != null && typeof effort !== 'string') fail(`Livello di ragionamento ${record.etichetta} non valido.`);
|
|
122
|
+
if (enabled !== undefined && typeof enabled !== 'boolean') fail(`Controllo del ragionamento ${record.etichetta} non valido.`);
|
|
123
|
+
const richiesto = enabled === false ? 'none' : effort;
|
|
124
|
+
if ((enabled === false && effort != null && effort !== 'none')
|
|
125
|
+
|| (richiesto != null && corpo.reasoning_effort != null && corpo.reasoning_effort !== richiesto)) {
|
|
126
|
+
fail(`${record.etichetta}: sono state indicate preferenze di ragionamento discordanti.`);
|
|
127
|
+
}
|
|
128
|
+
delete risultato.reasoning;
|
|
129
|
+
if (richiesto != null) risultato.reasoning_effort = richiesto;
|
|
130
|
+
if (Object.keys(altre).length || (enabled === true && richiesto == null && corpo.reasoning_effort == null)) {
|
|
131
|
+
avvisi.push(`${record.etichetta}: alcune opzioni di ragionamento non hanno una traduzione documentata; non inviate.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Un modello futuro o non documentato conserva i parametri: nessuna incompatibilità dedotta.
|
|
135
|
+
if (risultato.reasoning_effort != null && modello?.livelliRagionamento
|
|
136
|
+
&& !modello.livelliRagionamento.includes(risultato.reasoning_effort)) {
|
|
137
|
+
delete risultato.reasoning_effort;
|
|
138
|
+
avvisi.push(`${record.etichetta}: il livello di ragionamento richiesto non è documentato per questo modello; non inviato.`);
|
|
139
|
+
}
|
|
140
|
+
return { corpo: risultato, avvisi };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** P-I: controllo binario solo per modelli documentati; non inventa livelli di profondità. */
|
|
144
|
+
function preparaControlloThinking(record, modello, corpo) {
|
|
145
|
+
const oggetto = v => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
146
|
+
const invalida = () => fail(`${record.etichetta}: opzioni di ragionamento non valide o discordanti.`);
|
|
147
|
+
if (corpo.extra_body !== undefined && !oggetto(corpo.extra_body)) invalida();
|
|
148
|
+
const risultato = { ...corpo };
|
|
149
|
+
for (const [k, v] of Object.entries(corpo.extra_body ?? {})) {
|
|
150
|
+
// L'involucro SDK non può riscrivere destinazione, contenuto o prototipo.
|
|
151
|
+
if (['model', 'messages', 'extra_body', '__proto__', 'constructor', 'prototype'].includes(k)) invalida();
|
|
152
|
+
if (Object.hasOwn(corpo, k) && JSON.stringify(corpo[k]) !== JSON.stringify(v)) invalida();
|
|
153
|
+
risultato[k] = v;
|
|
154
|
+
}
|
|
155
|
+
delete risultato.extra_body;
|
|
156
|
+
const { reasoning, reasoning_effort, thinking, enable_thinking } = risultato;
|
|
157
|
+
const qwen = record.richiestaCompatibile.ragionamento === 'enable_thinking';
|
|
158
|
+
if (reasoning !== undefined && !oggetto(reasoning)) invalida();
|
|
159
|
+
if (reasoning?.enabled !== undefined && typeof reasoning.enabled !== 'boolean') invalida();
|
|
160
|
+
const livelli = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'default'];
|
|
161
|
+
for (const effort of [reasoning?.effort, reasoning_effort]) if (effort !== undefined && !livelli.includes(effort)) invalida();
|
|
162
|
+
if (reasoning?.effort !== undefined && reasoning_effort !== undefined && reasoning.effort !== reasoning_effort) invalida();
|
|
163
|
+
if (enable_thinking !== undefined && (!qwen || typeof enable_thinking !== 'boolean')) invalida();
|
|
164
|
+
if (thinking !== undefined) {
|
|
165
|
+
const tipi = qwen ? ['enabled', 'disabled'] : [modello.thinking.attivo ?? 'adaptive', 'disabled'];
|
|
166
|
+
if (!oggetto(thinking) || !tipi.includes(thinking.type)
|
|
167
|
+
|| Object.keys(thinking).some(k => !['type', 'keep'].includes(k))
|
|
168
|
+
|| (thinking.keep !== undefined && (qwen || modello.thinking.attivo !== 'enabled' || ![null, 'all'].includes(thinking.keep)))
|
|
169
|
+
|| (modello.thinking.conserva && thinking.keep !== undefined && thinking.keep !== modello.thinking.conserva)) invalida();
|
|
170
|
+
}
|
|
171
|
+
const effort = reasoning_effort ?? reasoning?.effort;
|
|
172
|
+
const preferenze = [reasoning?.enabled, effort === undefined ? undefined : effort !== 'none',
|
|
173
|
+
thinking === undefined ? undefined : thinking.type !== 'disabled', enable_thinking].filter(v => v !== undefined);
|
|
174
|
+
if (new Set(preferenze).size > 1) invalida();
|
|
175
|
+
const avvisi = [];
|
|
176
|
+
let attivo = preferenze[0];
|
|
177
|
+
delete risultato.reasoning;
|
|
178
|
+
delete risultato.reasoning_effort;
|
|
179
|
+
delete risultato.thinking;
|
|
180
|
+
delete risultato.enable_thinking;
|
|
181
|
+
if ((effort !== undefined && effort !== 'none') || Object.keys(reasoning ?? {}).some(k => !['enabled', 'effort'].includes(k))) {
|
|
182
|
+
avvisi.push(`${record.etichetta}: questo modello espone solo l'attivazione del ragionamento; il livello richiesto non viene inviato.`);
|
|
183
|
+
}
|
|
184
|
+
if (attivo === false && !modello.thinking.disattivabile) {
|
|
185
|
+
attivo = true;
|
|
186
|
+
avvisi.push(`${record.etichetta}: questo modello non consente di disattivare il ragionamento; resta attivo.`);
|
|
187
|
+
}
|
|
188
|
+
if (attivo !== undefined) {
|
|
189
|
+
if (qwen) risultato.enable_thinking = attivo;
|
|
190
|
+
else if (modello.thinking.attivo !== null) risultato.thinking = { ...(thinking ?? {}),
|
|
191
|
+
type: attivo ? modello.thinking.attivo : 'disabled',
|
|
192
|
+
...(attivo && modello.thinking.conserva ? { keep: modello.thinking.conserva } : {}) };
|
|
193
|
+
}
|
|
194
|
+
const thinkingEffettivo = attivo ?? modello.thinking.predefinito;
|
|
195
|
+
if (modello.thinking.soloStreaming && thinkingEffettivo && risultato.stream !== true) {
|
|
196
|
+
fail(`${record.etichetta}: questo modello richiede lo streaming quando il ragionamento è attivo.`);
|
|
197
|
+
}
|
|
198
|
+
if ((modello.sceltaObbligata === false && risultato.tool_choice === 'required')
|
|
199
|
+
|| (modello.sceltaForzataConThinking === false && thinkingEffettivo && oggetto(risultato.tool_choice))) {
|
|
200
|
+
fail(`${record.etichetta}: la scelta forzata dello strumento non è compatibile con questo modello e la modalità richiesta.`);
|
|
201
|
+
}
|
|
202
|
+
if (modello.temperaturaServer && Object.hasOwn(risultato, 'temperature')) {
|
|
203
|
+
delete risultato.temperature;
|
|
204
|
+
avvisi.push(`${record.etichetta}: la temperatura è gestita dal modello; il valore richiesto non viene inviato.`);
|
|
205
|
+
}
|
|
206
|
+
return { corpo: risultato, avvisi };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function capability(value) {
|
|
210
|
+
return typeof value === 'boolean' ? { state: 'observed', value } : { state: 'unknown', value: null };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function context(value) {
|
|
214
|
+
return Number.isSafeInteger(value) && value > 0
|
|
215
|
+
? { state: 'observed', value }
|
|
216
|
+
: { state: 'unknown', value: null };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function readJson(response) {
|
|
220
|
+
if (!response?.ok) throw new OpenAiCompatibleRuntimeError(`runtime returned HTTP ${response?.status ?? 0}`, 'RUNTIME_HTTP_ERROR');
|
|
221
|
+
try { return await response.json(); } catch { throw new OpenAiCompatibleRuntimeError('runtime returned invalid JSON', 'RUNTIME_RESPONSE_INVALID'); }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function* readStreamLines(response) {
|
|
225
|
+
if (!response?.ok) throw new OpenAiCompatibleRuntimeError(`runtime returned HTTP ${response?.status ?? 0}`, 'RUNTIME_HTTP_ERROR');
|
|
226
|
+
if (!response.body || typeof response.body.getReader !== 'function') fail('runtime stream body is unavailable', 'RUNTIME_RESPONSE_INVALID');
|
|
227
|
+
const reader = response.body.getReader();
|
|
228
|
+
const decoder = new TextDecoder();
|
|
229
|
+
let buffer = '';
|
|
230
|
+
try {
|
|
231
|
+
while (true) {
|
|
232
|
+
const { value, done } = await reader.read();
|
|
233
|
+
buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done });
|
|
234
|
+
const lines = buffer.split(/\r?\n/u);
|
|
235
|
+
buffer = lines.pop() ?? '';
|
|
236
|
+
yield* lines;
|
|
237
|
+
if (done) break;
|
|
238
|
+
}
|
|
239
|
+
if (buffer.trim() !== '') yield buffer;
|
|
240
|
+
} finally { await reader.cancel().catch(() => {}); }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function normalizeOllamaModel(raw, observedAt) {
|
|
244
|
+
const id = typeof raw?.name === 'string' && raw.name.trim() ? raw.name : '';
|
|
245
|
+
if (!id) return null;
|
|
246
|
+
const details = raw.details ?? {};
|
|
247
|
+
return {
|
|
248
|
+
id,
|
|
249
|
+
name: id,
|
|
250
|
+
source: 'ollama',
|
|
251
|
+
context: context(details.context_length),
|
|
252
|
+
verifiedContext: Number.isSafeInteger(details.context_length) && details.context_length > 0,
|
|
253
|
+
capabilities: {
|
|
254
|
+
vision: capability(Array.isArray(details.families) && details.families.some((family) => /(?:clip|vision|llava)/iu.test(family))),
|
|
255
|
+
toolUse: { state: 'unknown', value: null },
|
|
256
|
+
reasoning: { state: 'unknown', value: null },
|
|
257
|
+
},
|
|
258
|
+
sizeBytes: Number.isSafeInteger(raw.size) ? raw.size : null,
|
|
259
|
+
quantization: typeof details.quantization_level === 'string' ? details.quantization_level : null,
|
|
260
|
+
observedAt,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function normalizeLmModel(raw, observedAt) {
|
|
265
|
+
if (raw?.type !== 'llm' || typeof raw.key !== 'string' || raw.key.trim() === '') return null;
|
|
266
|
+
const reasoning = raw.capabilities?.reasoning;
|
|
267
|
+
const allowedReasoning = Array.isArray(reasoning?.allowed_options) ? reasoning.allowed_options : [];
|
|
268
|
+
return {
|
|
269
|
+
id: raw.key,
|
|
270
|
+
name: typeof raw.display_name === 'string' && raw.display_name ? raw.display_name : raw.key,
|
|
271
|
+
source: 'lmstudio',
|
|
272
|
+
context: context(raw.max_context_length),
|
|
273
|
+
verifiedContext: Number.isSafeInteger(raw.max_context_length) && raw.max_context_length > 0,
|
|
274
|
+
capabilities: {
|
|
275
|
+
vision: capability(raw.capabilities?.vision),
|
|
276
|
+
toolUse: capability(raw.capabilities?.trained_for_tool_use),
|
|
277
|
+
reasoning: allowedReasoning.length > 0 ? { state: 'observed', value: true } : { state: 'observed', value: false },
|
|
278
|
+
},
|
|
279
|
+
sizeBytes: Number.isSafeInteger(raw.size_bytes) ? raw.size_bytes : null,
|
|
280
|
+
quantization: typeof raw.quantization?.name === 'string' ? raw.quantization.name : null,
|
|
281
|
+
observedAt,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function createOpenAiCompatibleRuntime({
|
|
286
|
+
fetchImpl = fetch,
|
|
287
|
+
now = () => new Date(),
|
|
288
|
+
endpoints = {},
|
|
289
|
+
} = {}) {
|
|
290
|
+
const activeRequests = new Map();
|
|
291
|
+
/*
|
|
292
|
+
* ⛔ 12/09 — L'INDIRIZZO SI RILEGGE A OGNI CHIAMATA, e non si fotografa all'avvio.
|
|
293
|
+
*
|
|
294
|
+
* `endpoints[id]` può essere un oggetto (com'era) oppure una FUNZIONE che lo torna adesso. La
|
|
295
|
+
* differenza conta: chi cambia l'indirizzo di LM Studio nel pannello Provider a server acceso
|
|
296
|
+
* lo cambia per la chat (`model-destination.mjs` legge il portachiavi a ogni richiesta) — se
|
|
297
|
+
* il catalogo restasse sull'indirizzo di partenza, la scheda elencherebbe i modelli di un
|
|
298
|
+
* motore e la chat ne chiamerebbe un altro, senza un errore da nessuna parte.
|
|
299
|
+
* ⛔ Un override che lancia non spegne il motore: si torna al valore del registro.
|
|
300
|
+
*/
|
|
301
|
+
function config(provider) {
|
|
302
|
+
if (!Object.hasOwn(PROVIDERS, provider)) fail(`unknown local provider: ${provider}`);
|
|
303
|
+
const override = endpoints[provider];
|
|
304
|
+
let scelto = override;
|
|
305
|
+
if (typeof override === 'function') { try { scelto = override(); } catch { scelto = null; } }
|
|
306
|
+
return { ...PROVIDERS[provider], ...(scelto && typeof scelto === 'object' ? scelto : {}) };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function request(provider, path, options = {}) {
|
|
310
|
+
const target = `${config(provider).baseUrl}${path}`;
|
|
311
|
+
try { return await fetchImpl(target, { ...options, headers: { Accept: 'application/json', ...(options.headers ?? {}) } }); }
|
|
312
|
+
catch (error) {
|
|
313
|
+
if (error?.name === 'AbortError') throw error;
|
|
314
|
+
throw new OpenAiCompatibleRuntimeError(`runtime ${provider} unreachable: ${error.message}`, 'RUNTIME_UNREACHABLE');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function detect(provider) {
|
|
319
|
+
const observedAt = now().toISOString();
|
|
320
|
+
try {
|
|
321
|
+
const body = await readJson(await request(provider, config(provider).listPath));
|
|
322
|
+
const valid = provider === 'ollama' ? Array.isArray(body?.models) : Array.isArray(body?.models);
|
|
323
|
+
if (!valid) return { provider, state: 'unknown', baseUrl: config(provider).baseUrl, observedAt, failureReason: 'INVALID_RESPONSE' };
|
|
324
|
+
return { provider, state: 'observed', baseUrl: config(provider).baseUrl, observedAt };
|
|
325
|
+
} catch (error) {
|
|
326
|
+
return { provider, state: 'unknown', baseUrl: config(provider).baseUrl, observedAt, failureReason: error.code || 'RUNTIME_FAILED' };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function listModels(provider) {
|
|
331
|
+
const body = await readJson(await request(provider, config(provider).listPath));
|
|
332
|
+
if (!Array.isArray(body?.models)) fail('runtime model list is invalid', 'RUNTIME_RESPONSE_INVALID');
|
|
333
|
+
const observedAt = now().toISOString();
|
|
334
|
+
const normalize = provider === 'ollama' ? normalizeOllamaModel : normalizeLmModel;
|
|
335
|
+
return body.models.map((model) => normalize(model, observedAt)).filter(Boolean);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function inspect(provider, modelId) {
|
|
339
|
+
const model = (await listModels(provider)).find(({ id }) => id === modelId);
|
|
340
|
+
if (!model) fail(`model ${modelId} not found in ${provider}`, 'MODEL_NOT_FOUND');
|
|
341
|
+
return model;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function load(provider, modelId, { contextLength } = {}) {
|
|
345
|
+
if (provider !== 'lmstudio') fail('Ollama manages loading through its own lifecycle', 'RUNTIME_OPERATION_UNSUPPORTED');
|
|
346
|
+
const body = await readJson(await request(provider, '/api/v1/models/load', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: modelId, ...(contextLength ? { context_length: contextLength } : {}) }) }));
|
|
347
|
+
if (!['loaded', 'already_loaded'].includes(body?.status)) fail('LM Studio did not confirm model load', 'MODEL_LOAD_UNCONFIRMED');
|
|
348
|
+
return { state: 'loaded', provider, modelId, observedAt: now().toISOString() };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function unload(provider, modelId) {
|
|
352
|
+
if (provider !== 'lmstudio') fail('Ollama manages unloading through its own lifecycle', 'RUNTIME_OPERATION_UNSUPPORTED');
|
|
353
|
+
const body = await readJson(await request(provider, '/api/v1/models/unload', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: modelId }) }));
|
|
354
|
+
if (!['unloaded', 'already_unloaded'].includes(body?.status)) fail('LM Studio did not confirm model unload', 'MODEL_UNLOAD_UNCONFIRMED');
|
|
355
|
+
return { state: 'unloaded', provider, modelId, observedAt: now().toISOString() };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function* generateStream({ provider, modelId, messages, tools, signal, requestId } = {}) {
|
|
359
|
+
if (!Array.isArray(messages) || typeof modelId !== 'string' || modelId.trim() === '') fail('stream request is invalid');
|
|
360
|
+
const controller = new AbortController();
|
|
361
|
+
if (signal?.aborted) controller.abort(signal.reason);
|
|
362
|
+
const combinedSignal = signal && typeof AbortSignal.any === 'function' ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
363
|
+
if (signal && typeof AbortSignal.any !== 'function') signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
|
|
364
|
+
if (requestId) activeRequests.set(requestId, controller);
|
|
365
|
+
const isOllama = provider === 'ollama';
|
|
366
|
+
const body = isOllama
|
|
367
|
+
? { model: modelId, messages, stream: true, ...(tools ? { tools } : {}) }
|
|
368
|
+
: { model: modelId, messages, stream: true, ...(tools ? { tools } : {}) };
|
|
369
|
+
let taggedEvents = [];
|
|
370
|
+
const taggedContent = createStreamPartitioner({
|
|
371
|
+
onText: (value) => taggedEvents.push({ type: 'text', value }),
|
|
372
|
+
onReasoning: (value) => taggedEvents.push({ type: 'reasoning', value }),
|
|
373
|
+
onToolCall: (value) => taggedEvents.push({ type: 'tool_call', ...value }),
|
|
374
|
+
});
|
|
375
|
+
const flushTaggedEvents = function* () {
|
|
376
|
+
const events = taggedEvents;
|
|
377
|
+
taggedEvents = [];
|
|
378
|
+
for (const event of events) yield event;
|
|
379
|
+
};
|
|
380
|
+
try {
|
|
381
|
+
const response = await request(provider, isOllama ? '/api/chat' : '/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: isOllama ? 'application/x-ndjson' : 'text/event-stream' }, body: JSON.stringify(body), signal: combinedSignal });
|
|
382
|
+
for await (const line of readStreamLines(response)) {
|
|
383
|
+
const trimmed = line.trim();
|
|
384
|
+
if (!trimmed || (!isOllama && !trimmed.startsWith('data:'))) continue;
|
|
385
|
+
const payload = isOllama ? trimmed : trimmed.slice(5).trim();
|
|
386
|
+
if (payload === '[DONE]') { taggedContent.finish(); yield* flushTaggedEvents(); yield { type: 'done' }; return; }
|
|
387
|
+
let chunk;
|
|
388
|
+
try { chunk = JSON.parse(payload); } catch { yield { type: 'error', code: 'RUNTIME_RESPONSE_INVALID', message: 'runtime emitted malformed stream JSON' }; continue; }
|
|
389
|
+
if (isOllama) {
|
|
390
|
+
if (typeof chunk.message?.thinking === 'string' && chunk.message.thinking) yield { type: 'reasoning', value: chunk.message.thinking };
|
|
391
|
+
if (typeof chunk.message?.content === 'string' && chunk.message.content) {
|
|
392
|
+
taggedContent.push(chunk.message.content);
|
|
393
|
+
yield* flushTaggedEvents();
|
|
394
|
+
}
|
|
395
|
+
for (const call of chunk.message?.tool_calls ?? []) yield { type: 'tool_call', name: call.function?.name ?? '', arguments: call.function?.arguments ?? {} };
|
|
396
|
+
if (chunk.done === true) yield { type: 'done' };
|
|
397
|
+
} else {
|
|
398
|
+
const delta = chunk.choices?.[0]?.delta ?? {};
|
|
399
|
+
const reasoning = delta.reasoning_content ?? delta.reasoning;
|
|
400
|
+
if (typeof reasoning === 'string' && reasoning) yield { type: 'reasoning', value: reasoning };
|
|
401
|
+
if (typeof delta.content === 'string' && delta.content) {
|
|
402
|
+
taggedContent.push(delta.content);
|
|
403
|
+
yield* flushTaggedEvents();
|
|
404
|
+
}
|
|
405
|
+
for (const call of delta.tool_calls ?? []) yield { type: 'tool_call', id: call.id, name: call.function?.name ?? '', arguments: call.function?.arguments ?? '' };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
taggedContent.finish();
|
|
409
|
+
yield* flushTaggedEvents();
|
|
410
|
+
} finally {
|
|
411
|
+
if (requestId) activeRequests.delete(requestId);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function cancel(requestId) {
|
|
416
|
+
const controller = activeRequests.get(requestId);
|
|
417
|
+
if (!controller) return false;
|
|
418
|
+
controller.abort();
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function health(provider) {
|
|
423
|
+
const response = await request(provider, provider === 'ollama' ? '/' : '/api/v1/models');
|
|
424
|
+
return { provider, ok: Boolean(response.ok), observedAt: now().toISOString() };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return Object.freeze({ detect, listModels, inspect, load, unload, generateStream, cancel, health });
|
|
428
|
+
}
|