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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P-E, 12/09/2026: API models.dev dietro un contratto AVM.
|
|
3
|
+
* Ricerca, pin, licenza e collegamenti non applicati nel rapporto P-E in harness-ui/.claude/.
|
|
4
|
+
* I prezzi sono dichiarazioni per la stima; questo modulo non calcola la spesa di una sessione.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
7
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { fornitore, catalogoDiRiservaPer } from './provider-registry.mjs';
|
|
11
|
+
|
|
12
|
+
export const MODELS_DEV_SCHEMA_REVISION = 'e7a74d6dc7ce56d1a9dda94c18fcf185248d77b4';
|
|
13
|
+
const URL_PREDEFINITO = 'https://models.dev/api.json';
|
|
14
|
+
// Politica applicativa ripresa da Hermes; non è una promessa di aggiornamento della fonte.
|
|
15
|
+
const TTL_MS = 4 * 60 * 60 * 1000;
|
|
16
|
+
const RINVIO_MS = 5 * 60 * 1000;
|
|
17
|
+
const LICENZA_DATI = `MIT License
|
|
18
|
+
|
|
19
|
+
Copyright (c) 2025 models.dev
|
|
20
|
+
|
|
21
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
22
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
23
|
+
in the Software without restriction, including without limitation the rights
|
|
24
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
25
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
26
|
+
furnished to do so, subject to the following conditions:
|
|
27
|
+
|
|
28
|
+
The above copyright notice and this permission notice shall be included in all
|
|
29
|
+
copies or substantial portions of the Software.
|
|
30
|
+
|
|
31
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
37
|
+
SOFTWARE.`;
|
|
38
|
+
|
|
39
|
+
export class ModelsDevCatalogError extends Error {
|
|
40
|
+
constructor(message, code = 'CATALOG_UNAVAILABLE') {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'ModelsDevCatalogError';
|
|
43
|
+
this.code = code;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Nessuna coercizione: false, stringa vuota e null non sono il numero zero.
|
|
48
|
+
const Numero = z.number().finite().nonnegative();
|
|
49
|
+
const Prezzi = z.object({
|
|
50
|
+
input: Numero.nullish(), output: Numero.nullish(),
|
|
51
|
+
cache_read: Numero.nullish(), cache_write: Numero.nullish(),
|
|
52
|
+
reasoning: Numero.nullish(), input_audio: Numero.nullish(), output_audio: Numero.nullish(),
|
|
53
|
+
});
|
|
54
|
+
const Fascia = Prezzi.extend({ tier: z.object({ type: z.literal('context'), size: Numero.int() }) });
|
|
55
|
+
const Costi = Prezzi.extend({ tiers: z.array(Fascia).optional(), context_over_200k: Prezzi.optional() });
|
|
56
|
+
const Identificatore = z.string().min(1).max(1024);
|
|
57
|
+
const Modello = z.object({
|
|
58
|
+
id: Identificatore, name: z.string().min(1).optional(),
|
|
59
|
+
limit: z.object({ context: Numero.nullish(), input: Numero.nullish(), output: Numero.nullish() }).nullish(),
|
|
60
|
+
cost: Costi.nullish(), tool_call: z.boolean().nullish(), reasoning: z.boolean().nullish(),
|
|
61
|
+
modalities: z.object({ input: z.array(z.string()).optional(), output: z.array(z.string()).optional() }).nullish(),
|
|
62
|
+
release_date: z.string().optional(), last_updated: z.string().optional(),
|
|
63
|
+
});
|
|
64
|
+
const Catalogo = z.record(Identificatore, z.object({
|
|
65
|
+
id: Identificatore, name: z.string().min(1), models: z.record(Identificatore, Modello),
|
|
66
|
+
})).superRefine((dati, ctx) => {
|
|
67
|
+
if (!Object.keys(dati).length) ctx.addIssue({ code: 'custom', message: 'Catalogo vuoto' });
|
|
68
|
+
for (const [id, p] of Object.entries(dati)) {
|
|
69
|
+
if (p.id !== id) ctx.addIssue({ code: 'custom', message: 'Identità fornitore incoerente' });
|
|
70
|
+
for (const [modelloId, m] of Object.entries(p.models)) {
|
|
71
|
+
if (m.id !== modelloId) ctx.addIssue({ code: 'custom', message: 'Identità modello incoerente' });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const Etag = z.string().regex(/^(W\/)?"[^"\r\n]*"$/u).max(1024).nullable();
|
|
76
|
+
const Busta = z.object({
|
|
77
|
+
versione: z.literal(1), schemaRevision: z.literal(MODELS_DEV_SCHEMA_REVISION),
|
|
78
|
+
url: z.string(), etag: Etag,
|
|
79
|
+
acquisitoAlle: z.number().int().nonnegative(), verificatoAlle: z.number().int().nonnegative(),
|
|
80
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/u), dati: Catalogo,
|
|
81
|
+
});
|
|
82
|
+
const impronta = testo => createHash('sha256').update(testo).digest('hex');
|
|
83
|
+
const numeroOppureNull = valore => typeof valore === 'number' && Number.isFinite(valore) && valore >= 0 ? valore : null;
|
|
84
|
+
const perToken = valore => valore == null ? null : valore / 1_000_000;
|
|
85
|
+
const eta = (ora, acquisitoAlle) => ora >= acquisitoAlle ? ora - acquisitoAlle : null;
|
|
86
|
+
|
|
87
|
+
function prezziPerMilione(costi) {
|
|
88
|
+
return {
|
|
89
|
+
valuta: 'USD', unita: 'milione-token',
|
|
90
|
+
...Object.fromEntries(['input', 'output', 'cache_read', 'cache_write', 'reasoning', 'input_audio', 'output_audio'].map(k => [k, numeroOppureNull(costi?.[k])])),
|
|
91
|
+
tiers: structuredClone(costi?.tiers ?? []),
|
|
92
|
+
context_over_200k: structuredClone(costi?.context_over_200k ?? null),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizza(m, record, metadati) {
|
|
97
|
+
const prezzi = prezziPerMilione(m.cost);
|
|
98
|
+
return {
|
|
99
|
+
// Solo OpenRouter usa gli id senza prefisso di destinazione; gli id upstream restano intatti.
|
|
100
|
+
id: record.id === 'openrouter' ? m.id : `${record.id}:${m.id}`,
|
|
101
|
+
modelId: m.id, provider: record.id, nome: m.name || 'Nome non disponibile', alias: false,
|
|
102
|
+
contextLength: m.limit?.context > 0 ? m.limit.context : null,
|
|
103
|
+
maxOutputTokens: m.limit?.output > 0 ? m.limit.output : null,
|
|
104
|
+
maxInputTokens: m.limit?.input > 0 ? m.limit.input : null,
|
|
105
|
+
contestoVerificato: false,
|
|
106
|
+
prezzoPrompt: perToken(prezzi.input), prezzoCompletion: perToken(prezzi.output),
|
|
107
|
+
prezzoCacheRead: perToken(prezzi.cache_read), prezzoCacheWrite: perToken(prezzi.cache_write),
|
|
108
|
+
prezziPerMilione: prezzi,
|
|
109
|
+
capacita: { toolCall: m.tool_call ?? null, reasoning: m.reasoning ?? null },
|
|
110
|
+
inputModalities: m.modalities?.input ? [...m.modalities.input] : null,
|
|
111
|
+
outputModalities: m.modalities?.output ? [...m.modalities.output] : null,
|
|
112
|
+
// Il selettore storico legge qui livelli/configurazione, non un booleano di supporto.
|
|
113
|
+
reasoning: null,
|
|
114
|
+
dataRilascio: m.release_date ?? null, ultimoAggiornamentoFonte: m.last_updated ?? null,
|
|
115
|
+
catalogo: structuredClone(metadati),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Cache unica per URL, sotto la cartella dati ricevuta da loadConfig().
|
|
121
|
+
* fetchFn, clock e fsImpl sono iniettabili: nessuna rete necessaria nei test.
|
|
122
|
+
* ottieni(id, opzioni) espone soltanto i modelli del record richiesto, non i campi di trasporto upstream.
|
|
123
|
+
*/
|
|
124
|
+
export function createModelsDevCatalog({
|
|
125
|
+
cartellaStore, url = URL_PREDEFINITO, fetchFn = globalThis.fetch, clock = () => new Date(),
|
|
126
|
+
ttlMs = TTL_MS, retryMs = RINVIO_MS, timeoutMs = 15_000, fsImpl = {},
|
|
127
|
+
} = {}) {
|
|
128
|
+
let indirizzo;
|
|
129
|
+
try { indirizzo = new URL(url); } catch { throw new ModelsDevCatalogError('Indirizzo del catalogo non valido.', 'CONFIG_INVALID'); }
|
|
130
|
+
if (!['http:', 'https:'].includes(indirizzo.protocol) || indirizzo.username || indirizzo.password || indirizzo.hash || !cartellaStore) {
|
|
131
|
+
throw new ModelsDevCatalogError('Configurazione del catalogo non valida.', 'CONFIG_INVALID');
|
|
132
|
+
}
|
|
133
|
+
url = indirizzo.href;
|
|
134
|
+
const fs = { mkdir, readFile, rename, rm, writeFile, ...fsImpl };
|
|
135
|
+
const cartellaCache = join(cartellaStore, 'cache');
|
|
136
|
+
const percorsoCache = join(cartellaCache, `models-dev-${impronta(url).slice(0, 16)}.json`);
|
|
137
|
+
let copia = null, discoLetto = false, inVolo = null, riprovaAlle = 0, fallbackRete = false;
|
|
138
|
+
const avvisi = new Map();
|
|
139
|
+
const avvisa = (codice, messaggio) => avvisi.set(codice, { codice, messaggio });
|
|
140
|
+
|
|
141
|
+
function erroreSenzaCopia() {
|
|
142
|
+
return avvisi.has('CATALOG_CACHE_CORRUPT')
|
|
143
|
+
? new ModelsDevCatalogError('Copia del catalogo danneggiata: rifiutata. La fonte non è disponibile per recuperarla.', 'CATALOG_CACHE_CORRUPT')
|
|
144
|
+
: new ModelsDevCatalogError('Catalogo non disponibile: impossibile leggere la fonte e nessuna copia salvata utilizzabile.');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function leggiDisco() {
|
|
148
|
+
if (discoLetto) return;
|
|
149
|
+
discoLetto = true;
|
|
150
|
+
let testo;
|
|
151
|
+
try { testo = await fs.readFile(percorsoCache, 'utf8'); }
|
|
152
|
+
catch (e) {
|
|
153
|
+
if (e.code !== 'ENOENT') avvisa('CATALOG_CACHE_READ_FAILED', 'La copia salvata non è leggibile.');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const busta = JSON.parse(testo);
|
|
158
|
+
if (!Busta.safeParse(busta).success || busta.url !== url || busta.verificatoAlle < busta.acquisitoAlle || impronta(JSON.stringify(busta.dati)) !== busta.sha256) throw new Error('Copia non valida');
|
|
159
|
+
copia = busta;
|
|
160
|
+
} catch {
|
|
161
|
+
// Nessun ETag viene caricato separatamente: non può sopravvivere ai dati che garantisce.
|
|
162
|
+
avvisa('CATALOG_CACHE_CORRUPT', 'Copia del catalogo danneggiata: rifiutata.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function salvaDisco() {
|
|
167
|
+
const temporaneo = `${percorsoCache}.${randomUUID()}.tmp`;
|
|
168
|
+
try {
|
|
169
|
+
await fs.mkdir(cartellaCache, { recursive: true });
|
|
170
|
+
await fs.writeFile(temporaneo, JSON.stringify(copia), { encoding: 'utf8', flag: 'wx' });
|
|
171
|
+
await fs.rename(temporaneo, percorsoCache);
|
|
172
|
+
avvisi.delete('CATALOG_CACHE_WRITE_FAILED');
|
|
173
|
+
} catch {
|
|
174
|
+
avvisa('CATALOG_CACHE_WRITE_FAILED', 'Catalogo letto, ma non è stato possibile salvare la copia.');
|
|
175
|
+
} finally {
|
|
176
|
+
await fs.rm(temporaneo, { force: true }).catch(() => {});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function aggiorna(forza) {
|
|
181
|
+
await leggiDisco();
|
|
182
|
+
const ora = clock().getTime();
|
|
183
|
+
const etaVerifica = copia ? eta(ora, copia.verificatoAlle) : null;
|
|
184
|
+
if (!forza && !copia && ora < riprovaAlle) throw erroreSenzaCopia();
|
|
185
|
+
if (!forza && copia && ((etaVerifica !== null && etaVerifica < ttlMs) || ora < riprovaAlle)) return true;
|
|
186
|
+
try {
|
|
187
|
+
const headers = { Accept: 'application/json' };
|
|
188
|
+
if (copia?.etag) headers['If-None-Match'] = copia.etag;
|
|
189
|
+
const risposta = await fetchFn(url, { method: 'GET', headers, credentials: 'omit', signal: AbortSignal.timeout(timeoutMs) });
|
|
190
|
+
const ricevutoEtag = risposta.headers?.get('etag') ?? null;
|
|
191
|
+
const nuovoEtag = Etag.safeParse(ricevutoEtag).success ? ricevutoEtag : null;
|
|
192
|
+
const invariato = risposta.status === 304;
|
|
193
|
+
if (invariato) {
|
|
194
|
+
if (!copia) throw new ModelsDevCatalogError('Il catalogo non ha restituito dati utilizzabili.');
|
|
195
|
+
copia = { ...copia, verificatoAlle: clock().getTime(), etag: nuovoEtag ?? copia.etag };
|
|
196
|
+
} else {
|
|
197
|
+
if (!risposta.ok) {
|
|
198
|
+
await risposta.body?.cancel?.();
|
|
199
|
+
throw new ModelsDevCatalogError('La fonte del catalogo non è disponibile.');
|
|
200
|
+
}
|
|
201
|
+
const dati = await risposta.json();
|
|
202
|
+
if (!Catalogo.safeParse(dati).success) throw new ModelsDevCatalogError('La fonte ha restituito un catalogo non valido.');
|
|
203
|
+
const acquisitoAlle = clock().getTime();
|
|
204
|
+
copia = { versione: 1, schemaRevision: MODELS_DEV_SCHEMA_REVISION, url,
|
|
205
|
+
etag: nuovoEtag, acquisitoAlle, verificatoAlle: acquisitoAlle,
|
|
206
|
+
sha256: impronta(JSON.stringify(dati)), dati, licenza: LICENZA_DATI };
|
|
207
|
+
}
|
|
208
|
+
fallbackRete = false;
|
|
209
|
+
riprovaAlle = 0;
|
|
210
|
+
avvisi.delete('CATALOG_REFRESH_FAILED');
|
|
211
|
+
await salvaDisco();
|
|
212
|
+
return invariato;
|
|
213
|
+
} catch {
|
|
214
|
+
riprovaAlle = clock().getTime() + retryMs;
|
|
215
|
+
if (!copia) {
|
|
216
|
+
throw erroreSenzaCopia();
|
|
217
|
+
}
|
|
218
|
+
fallbackRete = true;
|
|
219
|
+
avvisa('CATALOG_REFRESH_FAILED', 'Aggiornamento non disponibile: viene usata la copia salvata.');
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function ottieni(id, { forzaAggiornamento = false } = {}) {
|
|
225
|
+
const record = fornitore(id);
|
|
226
|
+
const modelsDevId = record?.modelsDevId ?? null;
|
|
227
|
+
const indisponibile = { provider: id, modelsDevId, disponibile: false, modelli: [],
|
|
228
|
+
motivo: `Catalogo${record ? ` ${record.etichetta}` : ''} non disponibile.`, fonte: 'models.dev',
|
|
229
|
+
aggiornatoAlle: null, verificatoAlle: null, etaCacheMs: null, daCache: false, fallbackRete: false, avvisi: [] };
|
|
230
|
+
if (!modelsDevId) return indisponibile;
|
|
231
|
+
// Una promessa condivisa comprende lettura disco, download e scrittura atomica.
|
|
232
|
+
if (!inVolo) inVolo = aggiorna(forzaAggiornamento).finally(() => { inVolo = null; });
|
|
233
|
+
const daCache = await inVolo;
|
|
234
|
+
const metadati = { fonte: 'models.dev', aggiornatoAlle: new Date(copia.acquisitoAlle).toISOString(),
|
|
235
|
+
verificatoAlle: new Date(copia.verificatoAlle).toISOString(), etaCacheMs: eta(clock().getTime(), copia.acquisitoAlle),
|
|
236
|
+
daCache, fallbackRete, avvisi: structuredClone([...avvisi.values()]) };
|
|
237
|
+
if (!Object.hasOwn(copia.dati, modelsDevId)) return { ...indisponibile, ...metadati };
|
|
238
|
+
const modelli = Object.values(copia.dati[modelsDevId].models)
|
|
239
|
+
.map(m => normalizza(m, record, metadati))
|
|
240
|
+
.sort((a, b) => a.nome.localeCompare(b.nome, 'it') || a.id.localeCompare(b.id));
|
|
241
|
+
return { provider: id, modelsDevId, disponibile: true, modelli, motivo: null, ...metadati };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return Object.freeze({ ottieni, percorsoCache });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Porta per GET /api/v1/providers/:id/models. Nessuna chiave transita nel catalogo. */
|
|
248
|
+
export function createProviderModelCatalog({ catalogo, chiaveConfigurata } = {}) {
|
|
249
|
+
async function ottieni(id, opzioni) {
|
|
250
|
+
const record = fornitore(id);
|
|
251
|
+
if (!record?.destinazioneChat || record.catalogo.fonte === 'runtime-locale' || id === 'openrouter') {
|
|
252
|
+
throw new ModelsDevCatalogError('Catalogo non disponibile per questa destinazione.', 'REPORT_UNAVAILABLE');
|
|
253
|
+
}
|
|
254
|
+
if (record.chiaveObbligatoria && (typeof chiaveConfigurata !== 'function' || await chiaveConfigurata(id) !== true)) {
|
|
255
|
+
throw new ModelsDevCatalogError(`Collega la chiave ${record.etichetta} dal pannello Provider.`, 'PROVIDER_KEY_REQUIRED');
|
|
256
|
+
}
|
|
257
|
+
let dati;
|
|
258
|
+
try {
|
|
259
|
+
dati = await catalogo.ottieni(id, opzioni);
|
|
260
|
+
} catch (errore) {
|
|
261
|
+
// Il catalogo stesso preferisce già la copia valida, anche vecchia. Qui non ne ha una.
|
|
262
|
+
const riserva = catalogoDiRiservaPer(id);
|
|
263
|
+
if (!riserva) throw new ModelsDevCatalogError('Catalogo non disponibile.', 'CATALOG_UPSTREAM_ERROR');
|
|
264
|
+
if (errore instanceof ModelsDevCatalogError && errore.code === 'CATALOG_CACHE_CORRUPT') {
|
|
265
|
+
const avviso = { codice: 'CATALOG_CACHE_CORRUPT', messaggio: 'Copia danneggiata rifiutata: viene usato l’elenco di riserva.' };
|
|
266
|
+
riserva.avvisi.push(avviso);
|
|
267
|
+
for (const m of riserva.modelli) m.catalogo.avvisi.push({ ...avviso });
|
|
268
|
+
}
|
|
269
|
+
return { ...riserva, credenzialeVerificata: false };
|
|
270
|
+
}
|
|
271
|
+
// Un catalogo raggiunto che non pubblica quel fornitore non è un guasto di rete.
|
|
272
|
+
if (!dati.disponibile) throw new ModelsDevCatalogError(dati.motivo, 'CATALOG_UPSTREAM_ERROR');
|
|
273
|
+
return { ...dati, credenzialeVerificata: false };
|
|
274
|
+
}
|
|
275
|
+
return Object.freeze({ ottieni });
|
|
276
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* model-catalog.mjs — il catalogo VERO di OpenRouter, non 7 scorciatoie
|
|
3
|
+
* scritte a mano. Owner, 27/8: "introdurre nella modale un picker per il
|
|
4
|
+
* modello, dropdown stilizzato (l'abbiamo già fatto nel mobile)" — il
|
|
5
|
+
* componente mobile (TalosMobileComposerModelPicker.vue) raggruppa i
|
|
6
|
+
* modelli per provider e dice onestamente quando la scoperta È FALLITA
|
|
7
|
+
* (non solo "zero risultati"). Stesso principio qui.
|
|
8
|
+
*
|
|
9
|
+
* ⛔ `GET https://openrouter.ai/api/v1/models` è pubblico — verificato
|
|
10
|
+
* dal vivo il 27/8: 417 modelli, nessuna chiave richiesta per leggerlo
|
|
11
|
+
* (l'autenticazione serve solo per CHIAMARE un modello, non per elencarli).
|
|
12
|
+
* Formato risposta confermato: `{data: [{id, name, context_length,
|
|
13
|
+
* pricing:{prompt, completion}, ...}]}` — `id` è già nel formato
|
|
14
|
+
* `vendor/nome-modello` che talosHarness.mjs instrada.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { catalogoDiRiservaPer } from './provider-registry.mjs';
|
|
18
|
+
|
|
19
|
+
const OPENROUTER_MODELS_URL = 'https://openrouter.ai/api/v1/models';
|
|
20
|
+
const TTL_PREDEFINITO_MS = 10 * 60 * 1000; // 10 minuti — non richiamare OpenRouter a ogni apertura del foglio.
|
|
21
|
+
|
|
22
|
+
export class ModelCatalogError extends Error {
|
|
23
|
+
constructor(message, code = 'CATALOG_UNAVAILABLE') {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'ModelCatalogError';
|
|
26
|
+
this.code = code;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizza(modelloGrezzo) {
|
|
31
|
+
const id = typeof modelloGrezzo?.id === 'string' ? modelloGrezzo.id : '';
|
|
32
|
+
// ⛔ 27/8, trovato dalla pipeline QA visiva: 12 dei 417 id reali hanno il
|
|
33
|
+
// prefisso `~` (alias "sempre l'ultima versione", es.
|
|
34
|
+
// `~deepseek/deepseek-v4-flash-latest`). Il PROVIDER va calcolato dopo
|
|
35
|
+
// averlo tolto, altrimenti "deepseek" e "~deepseek" diventano due gruppi
|
|
36
|
+
// diversi nel picker per lo stesso vendor. `id` resta INTATTO (con la
|
|
37
|
+
// tilde) — è quello che viaggia nella chiamata vera.
|
|
38
|
+
const alias = id.startsWith('~');
|
|
39
|
+
const idSenzaAlias = alias ? id.slice(1) : id;
|
|
40
|
+
const provider = idSenzaAlias.includes('/') ? idSenzaAlias.split('/')[0] : 'altro';
|
|
41
|
+
const listaStringhe = (value) => Array.isArray(value)
|
|
42
|
+
? value.filter((item) => typeof item === 'string' && item.length <= 128).slice(0, 32)
|
|
43
|
+
: [];
|
|
44
|
+
const architecture = modelloGrezzo?.architecture && typeof modelloGrezzo.architecture === 'object'
|
|
45
|
+
? modelloGrezzo.architecture : {};
|
|
46
|
+
const reasoningGrezzo = modelloGrezzo?.reasoning && typeof modelloGrezzo.reasoning === 'object'
|
|
47
|
+
? modelloGrezzo.reasoning : null;
|
|
48
|
+
const reasoning = reasoningGrezzo ? {
|
|
49
|
+
supportedEfforts: listaStringhe(reasoningGrezzo.supported_efforts),
|
|
50
|
+
defaultEffort: typeof reasoningGrezzo.default_effort === 'string' && reasoningGrezzo.default_effort.length <= 128
|
|
51
|
+
? reasoningGrezzo.default_effort : null,
|
|
52
|
+
defaultEnabled: typeof reasoningGrezzo.default_enabled === 'boolean' ? reasoningGrezzo.default_enabled : null,
|
|
53
|
+
mandatory: reasoningGrezzo.mandatory === true,
|
|
54
|
+
} : null;
|
|
55
|
+
return {
|
|
56
|
+
id,
|
|
57
|
+
provider,
|
|
58
|
+
alias,
|
|
59
|
+
nome: typeof modelloGrezzo?.name === 'string' && modelloGrezzo.name ? modelloGrezzo.name : id,
|
|
60
|
+
contextLength: Number.isFinite(modelloGrezzo?.context_length) ? modelloGrezzo.context_length : null,
|
|
61
|
+
prezzoPrompt: modelloGrezzo?.pricing?.prompt ?? null,
|
|
62
|
+
prezzoCompletion: modelloGrezzo?.pricing?.completion ?? null,
|
|
63
|
+
inputModalities: listaStringhe(architecture.input_modalities),
|
|
64
|
+
outputModalities: listaStringhe(architecture.output_modalities),
|
|
65
|
+
supportedParameters: listaStringhe(modelloGrezzo?.supported_parameters),
|
|
66
|
+
reasoning,
|
|
67
|
+
description: typeof modelloGrezzo?.description === 'string' ? modelloGrezzo.description.slice(0, 2000) : '',
|
|
68
|
+
createdAt: Number.isFinite(modelloGrezzo?.created) ? modelloGrezzo.created : null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {{fetchFn?: typeof fetch, clock?: () => Date, ttlMs?: number, url?: string}} [opts]
|
|
74
|
+
*/
|
|
75
|
+
export function createModelCatalog({
|
|
76
|
+
fetchFn = fetch, clock = () => new Date(), ttlMs = TTL_PREDEFINITO_MS, url = OPENROUTER_MODELS_URL,
|
|
77
|
+
} = {}) {
|
|
78
|
+
let cache = null; // { creatoAlle: number, modelli: Array }
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {{forzaAggiornamento?: boolean}} [opts]
|
|
82
|
+
* @returns {Promise<{modelli: Array, daCache: boolean, aggiornatoAlle: string|null}>}
|
|
83
|
+
*/
|
|
84
|
+
async function ottieni({ forzaAggiornamento = false } = {}) {
|
|
85
|
+
const ora = clock().getTime();
|
|
86
|
+
if (!forzaAggiornamento && cache && (ora - cache.creatoAlle) < ttlMs) {
|
|
87
|
+
return { modelli: cache.modelli, daCache: true, aggiornatoAlle: new Date(cache.creatoAlle).toISOString() };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
let risposta;
|
|
92
|
+
try {
|
|
93
|
+
risposta = await fetchFn(url, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(15_000) });
|
|
94
|
+
} catch {
|
|
95
|
+
// ⛔ un errore di rete non è "zero modelli": la UI deve poterli distinguere (stesso principio di discoveryProblems nel mobile).
|
|
96
|
+
throw new ModelCatalogError('Catalogo OpenRouter non raggiungibile.', 'CATALOG_UNREACHABLE');
|
|
97
|
+
}
|
|
98
|
+
if (!risposta.ok) {
|
|
99
|
+
throw new ModelCatalogError(`Catalogo OpenRouter ha risposto ${risposta.status}`, 'CATALOG_UPSTREAM_ERROR');
|
|
100
|
+
}
|
|
101
|
+
let corpo;
|
|
102
|
+
try {
|
|
103
|
+
corpo = await risposta.json();
|
|
104
|
+
} catch {
|
|
105
|
+
throw new ModelCatalogError('Catalogo OpenRouter: risposta non valida', 'CATALOG_UPSTREAM_ERROR');
|
|
106
|
+
}
|
|
107
|
+
if (!Array.isArray(corpo?.data)) {
|
|
108
|
+
throw new ModelCatalogError('Catalogo OpenRouter: formato inatteso', 'CATALOG_UPSTREAM_ERROR');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const modelli = corpo.data
|
|
112
|
+
.map(normalizza)
|
|
113
|
+
.filter((modello) => modello.id !== '')
|
|
114
|
+
.sort((a, b) => a.provider.localeCompare(b.provider) || a.nome.localeCompare(b.nome));
|
|
115
|
+
|
|
116
|
+
cache = { creatoAlle: ora, modelli };
|
|
117
|
+
return { modelli, daCache: false, aggiornatoAlle: new Date(ora).toISOString() };
|
|
118
|
+
} catch {
|
|
119
|
+
// P-F: la copia reale ha precedenza; la riserva non entra mai nella cache del catalogo.
|
|
120
|
+
if (cache) return { modelli: structuredClone(cache.modelli), daCache: true, fonte: 'openrouter',
|
|
121
|
+
aggiornatoAlle: new Date(cache.creatoAlle).toISOString(),
|
|
122
|
+
etaCacheMs: Math.max(0, clock().getTime() - cache.creatoAlle), fallbackRete: true,
|
|
123
|
+
motivo: 'Catalogo non raggiungibile: viene usata la copia salvata.' };
|
|
124
|
+
return catalogoDiRiservaPer('openrouter');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return Object.freeze({ ottieni });
|
|
129
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dove vive un modello, e come gli si parla.
|
|
3
|
+
*
|
|
4
|
+
* ## Perché esiste
|
|
5
|
+
*
|
|
6
|
+
* Owner, 03/9: «se non riesco ad aggiungere più provider oltre a OpenRouter e
|
|
7
|
+
* soprattutto usare i modelli locali, l'applicazione è spacciata». E prima:
|
|
8
|
+
* «non è un limite quello che mi hai detto tu, è un finto limite».
|
|
9
|
+
*
|
|
10
|
+
* Aveva ragione, e la misura lo dimostra: nel kernel c'era UNA sola chiamata
|
|
11
|
+
* cablata (riga 406 della copia caricata dal desktop, 392 di quella mobile) —
|
|
12
|
+
* una stringa, non un'architettura. E sulla macchina dell'owner erano già
|
|
13
|
+
* salvate CINQUE credenziali funzionanti, misurate il 03/9 contro i servizi
|
|
14
|
+
* veri: OpenRouter 424 modelli, OpenAI 124, Gemini 50, Anthropic 11,
|
|
15
|
+
* DeepSeek 3. Cinque provider pagati e fermi, perché nessuno li chiamava.
|
|
16
|
+
*
|
|
17
|
+
* ## Il fatto che rende la cura piccola
|
|
18
|
+
*
|
|
19
|
+
* Quasi tutti parlano GIÀ lo stesso protocollo: `POST /v1/chat/completions`
|
|
20
|
+
* con `Authorization: Bearer`, stesso corpo. Vale per OpenAI, DeepSeek,
|
|
21
|
+
* OpenRouter, Ollama e — verificato in `local-runtime-llama-server.mjs`, che
|
|
22
|
+
* chiama esattamente quell'endpoint — anche per llama.cpp in locale.
|
|
23
|
+
* ⇒ Per i modelli LOCALI e altri quattro provider basta cambiare indirizzo e
|
|
24
|
+
* intestazioni. Nessuna traduzione del corpo, nessun adattatore nuovo.
|
|
25
|
+
*
|
|
26
|
+
* Anthropic e Gemini usano native-provider-adapter con SDK fissati nel lock:
|
|
27
|
+
* /v1/messages e :generateContent, immagini e firme di ragionamento native.
|
|
28
|
+
*
|
|
29
|
+
* ## La convenzione sul nome, e perché i DUE PUNTI
|
|
30
|
+
*
|
|
31
|
+
* Gli id OpenRouter sono già `autore/modello` (`deepseek/deepseek-v4-flash`),
|
|
32
|
+
* quindi lo slash è occupato e non può separare la fonte. Si usa il carattere
|
|
33
|
+
* `:` come prefisso — `local:qwen3-0.6b-…`, `ollama:llama3.2` — e
|
|
34
|
+
* **nessun prefisso significa OpenRouter**, così tutto ciò che esiste oggi
|
|
35
|
+
* continua a funzionare identico, senza migrare una sola sessione salvata.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { ID_DESTINAZIONE_CHAT, ID_NATIVI_SDK, REGISTRO_FORNITORI, idPerWire } from './provider-registry.mjs';
|
|
39
|
+
// P-K
|
|
40
|
+
import { destinazioneCloud } from './provider-auth-cloud.mjs';
|
|
41
|
+
// P-K — fine
|
|
42
|
+
|
|
43
|
+
export class ModelDestinationError extends Error {
|
|
44
|
+
constructor(message, code = 'MODEL_DESTINATION_INVALID') {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = 'ModelDestinationError';
|
|
47
|
+
this.code = code;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Le fonti riconosciute nel prefisso. ⛔ Tutto il resto è un id OpenRouter.
|
|
53
|
+
*
|
|
54
|
+
* ⛔ 12/09 — P-A: era un array scritto a mano, e `config.mjs:186` ne teneva una **copia dentro una
|
|
55
|
+
* stringa di regex**. Adesso tutte e due si derivano dal registro: aggiungere `lmstudio` è stata
|
|
56
|
+
* una riga di dato, non sette modifiche sparse (P-C).
|
|
57
|
+
*/
|
|
58
|
+
export const FONTI_MODELLO = ID_DESTINAZIONE_CHAT;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Spacca `fonte:modello`. ⛔ Solo sul PRIMO due punti, e solo se ciò che sta
|
|
62
|
+
* davanti è una fonte conosciuta: un id che contenesse un `:` per altri motivi
|
|
63
|
+
* non deve essere dirottato su un provider inesistente.
|
|
64
|
+
*/
|
|
65
|
+
export function separaFonteModello(modello) {
|
|
66
|
+
if (typeof modello !== 'string' || modello.trim() === '') {
|
|
67
|
+
throw new ModelDestinationError('model id is missing', 'MODEL_DESTINATION_INVALID');
|
|
68
|
+
}
|
|
69
|
+
const taglio = modello.indexOf(':');
|
|
70
|
+
if (taglio > 0) {
|
|
71
|
+
const fonte = modello.slice(0, taglio);
|
|
72
|
+
if (FONTI_MODELLO.includes(fonte)) return { fonte, modelloRemoto: modello.slice(taglio + 1) };
|
|
73
|
+
}
|
|
74
|
+
return { fonte: 'openrouter', modelloRemoto: modello };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Lista di sessione: solo identificatori, mai indirizzi, chiavi o capacità dichiarate dal client. */
|
|
78
|
+
export function validaFallbackProviders(lista = [], { usaAttrezzi = false } = {}) {
|
|
79
|
+
const invalida = () => { throw new ModelDestinationError('Controlla i fornitori e i modelli scelti per continuare la sessione.', 'PROVIDER_FALLBACK_INVALID'); };
|
|
80
|
+
if (!Array.isArray(lista) || lista.length > 8) invalida();
|
|
81
|
+
const viste = new Set();
|
|
82
|
+
return lista.map(voce => {
|
|
83
|
+
if (!voce || typeof voce !== 'object' || Array.isArray(voce) || Object.keys(voce).some(k => !['provider', 'model'].includes(k))) invalida();
|
|
84
|
+
const { provider, model } = voce;
|
|
85
|
+
const record = Object.hasOwn(REGISTRO_FORNITORI, provider) ? REGISTRO_FORNITORI[provider] : null;
|
|
86
|
+
if (!record?.destinazioneChat || !record.credenziale || typeof model !== 'string' || model.length > 200 || !/^[a-zA-Z0-9][a-zA-Z0-9._/:@-]*$/u.test(model) || model.includes('://') || FONTI_MODELLO.some(p => model.startsWith(`${p}:`))) invalida();
|
|
87
|
+
const id = `${provider}:${model}`;
|
|
88
|
+
if (viste.has(id)) invalida();
|
|
89
|
+
viste.add(id);
|
|
90
|
+
if (usaAttrezzi && record.modelliDiRiserva?.find(m => m.id === model)?.toolCalling !== true) {
|
|
91
|
+
throw new ModelDestinationError('Il modello di riserva non dichiara il supporto agli attrezzi della sessione.', 'PROVIDER_FALLBACK_TOOLS_UNSUPPORTED');
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze({ provider, model });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Chi parla `POST {base}{percorso}` con Bearer: il corpo non si tocca.
|
|
99
|
+
* ⛔ `wire: 'locale'` sta qui dentro perché il supervisore espone lo stesso protocollo — ma esce
|
|
100
|
+
* prima, sulla sua strada: la sua chiave non deve passare da questo file.
|
|
101
|
+
*/
|
|
102
|
+
const COMPATIBILI_OPENAI = Object.freeze([...idPerWire('openai-chat'), ...idPerWire('locale')]);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Chi NON lo parla, e cosa gli servirebbe. ⛔ Il messaggio dice la cosa vera —
|
|
106
|
+
* la credenziale può essere ottima, manca la traduzione dalla nostra parte —
|
|
107
|
+
* perché un errore che sembra colpa della chiave manda a rigenerarne una buona.
|
|
108
|
+
*
|
|
109
|
+
* ⛔ Sono i tre wire serviti dagli SDK fissati nel lock (`native-provider-adapter.mjs`): il
|
|
110
|
+
* registro li nomina uno per uno, così «nativo» smette di essere una lista da ricordare.
|
|
111
|
+
*/
|
|
112
|
+
const NATIVI = ID_NATIVI_SDK;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} modello id, con o senza prefisso di fonte
|
|
116
|
+
* @param {object} deps
|
|
117
|
+
* @param {(fonte: string) => string|null} deps.leggiChiave dal portachiavi
|
|
118
|
+
* @param {(fonte: string) => {endpoint?: string|null}} deps.leggiRuntime indirizzo configurato
|
|
119
|
+
* @param {() => boolean} [deps.localePronto] il motore locale è acceso adesso?
|
|
120
|
+
* @returns {{fonte: string, url: string, headers: Record<string,string>, modelloRemoto: string}}
|
|
121
|
+
*/
|
|
122
|
+
export function risolviDestinazioneModello(modello, { leggiChiave, leggiRuntime, localePronto = () => false } = {}) {
|
|
123
|
+
if (typeof leggiChiave !== 'function' || typeof leggiRuntime !== 'function') {
|
|
124
|
+
throw new ModelDestinationError('destination dependencies are invalid', 'MODEL_DESTINATION_MISCONFIGURED');
|
|
125
|
+
}
|
|
126
|
+
const { fonte, modelloRemoto } = separaFonteModello(modello);
|
|
127
|
+
|
|
128
|
+
// P-L · nessuna chiave, nessun endpoint scelto dal browser e nessun ripiego remoto.
|
|
129
|
+
if (REGISTRO_FORNITORI[fonte]?.wire === 'acp') {
|
|
130
|
+
let runtime = null;
|
|
131
|
+
try { runtime = leggiRuntime(fonte); }
|
|
132
|
+
catch (e) { if (e?.code !== 'PROVIDER_INVALID') throw e; }
|
|
133
|
+
return { fonte, modelloRemoto, esterno: true, runtime };
|
|
134
|
+
}
|
|
135
|
+
// P-L · fine destinazione agente esterno.
|
|
136
|
+
|
|
137
|
+
if (!COMPATIBILI_OPENAI.includes(fonte) && !NATIVI.includes(fonte)) throw new ModelDestinationError(`Fonte del modello non riconosciuta: ${fonte}`, 'MODEL_DESTINATION_INVALID');
|
|
138
|
+
|
|
139
|
+
if (fonte === 'local') {
|
|
140
|
+
/*
|
|
141
|
+
* ⛔⛔ IL MOTORE LOCALE VUOLE UNA CHIAVE — misurato, non dedotto.
|
|
142
|
+
*
|
|
143
|
+
* La prima stesura costruiva l'URL a mano e non mandava credenziali,
|
|
144
|
+
* ragionando che «127.0.0.1 non ha autenticazione». Provato dal vivo
|
|
145
|
+
* contro il runtime acceso: **HTTP 401, "Invalid API Key" in 4 ms**.
|
|
146
|
+
* `llama-server-supervisor.mjs` genera `randomBytes(32)` a ogni avvio e
|
|
147
|
+
* lo passa come `--api-key`; la chiave vive solo lì dentro, e `status()`
|
|
148
|
+
* NON la espone — giustamente, perché quella risposta finisce nel
|
|
149
|
+
* browser.
|
|
150
|
+
*
|
|
151
|
+
* ⇒ Non si copia il segreto qui: si passa dal `request()` del
|
|
152
|
+
* supervisore, che è il punto in cui la chiave già sta. Il risolutore
|
|
153
|
+
* dice SOLO che la destinazione è locale e che il motore è pronto; a
|
|
154
|
+
* spedire ci pensa chi possiede la credenziale.
|
|
155
|
+
*/
|
|
156
|
+
if (typeof localePronto !== 'function' || !localePronto()) {
|
|
157
|
+
throw new ModelDestinationError('Il motore locale non è acceso: caricalo dal Laboratorio modelli prima di usarlo in chat.', 'LOCAL_RUNTIME_NOT_READY');
|
|
158
|
+
}
|
|
159
|
+
return { fonte, modelloRemoto, locale: true, percorso: REGISTRO_FORNITORI[fonte].endpoint.chat };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const runtime = leggiRuntime(fonte) || {};
|
|
163
|
+
const record = REGISTRO_FORNITORI[fonte];
|
|
164
|
+
const base = typeof runtime.endpoint === 'string' && runtime.endpoint.trim() !== '' ? runtime.endpoint.replace(/\/+$/u, '') : null;
|
|
165
|
+
if (!base) throw new ModelDestinationError(`Manca l'indirizzo di ${record.etichetta}.`, 'PROVIDER_RUNTIME_INVALID');
|
|
166
|
+
|
|
167
|
+
const chiave = leggiChiave(fonte);
|
|
168
|
+
/*
|
|
169
|
+
* ⛔ Ollama e LM Studio girano in casa e non hanno account: pretendere una
|
|
170
|
+
* chiave li escluderebbe per una regola che non li riguarda. Gli altri sì, e
|
|
171
|
+
* senza si dice CHE COSA manca invece di partire e prendersi un 401.
|
|
172
|
+
* ⛔ 12/09 — la domanda non è più «è ollama?»: è `chiaveObbligatoria` sul record. Erano due
|
|
173
|
+
* verità diverse (`requiresKey` nel portachiavi diceva già `false` per Ollama, e qui c'era un
|
|
174
|
+
* confronto per nome), e ora è una sola.
|
|
175
|
+
*/
|
|
176
|
+
if (record.chiaveObbligatoria === true && (typeof chiave !== 'string' || chiave.trim() === '')) {
|
|
177
|
+
throw new ModelDestinationError(`Manca la chiave per ${record.etichetta}: inseriscila in Laboratorio modelli → Provider.`, 'PROVIDER_KEY_MISSING');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// P-K — lo stesso contratto di autenticazione per chat e sonda.
|
|
181
|
+
if (record.cloud) return destinazioneCloud(fonte, runtime, chiave, modelloRemoto);
|
|
182
|
+
// P-K — fine
|
|
183
|
+
if (NATIVI.includes(fonte)) return { fonte, modelloRemoto, native: true, baseURL: base, apiKey: chiave };
|
|
184
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
185
|
+
if (chiave) headers.Authorization = `Bearer ${chiave}`;
|
|
186
|
+
/* ⛔ Ollama e LM Studio espongono il protocollo OpenAI sotto /v1, il loro indirizzo base no:
|
|
187
|
+
è il record a dire dove bussare (`endpoint.chat`), non un `if` sul nome. */
|
|
188
|
+
return { fonte, modelloRemoto, url: `${base}${record.endpoint.chat}`, headers };
|
|
189
|
+
}
|