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,95 @@
|
|
|
1
|
+
// P-K — adattatore REST cloud, contratti ufficiali fissati al 12/09/2026.
|
|
2
|
+
// Fonti e alternative: .claude/RAPPORTO-PK-AZURE-BEDROCK-VERTEX-2026-09-12.md.
|
|
3
|
+
import { REGISTRO_FORNITORI } from './provider-registry.mjs';
|
|
4
|
+
|
|
5
|
+
export class ProviderCloudError extends Error {
|
|
6
|
+
constructor(message, code = 'PROVIDER_RUNTIME_INVALID') {
|
|
7
|
+
super(message); this.name = 'ProviderCloudError'; this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Il formato su disco resta un indirizzo ufficiale, senza query private o segrete. */
|
|
12
|
+
export function normalizzaRuntimeCloud(provider, { endpoint } = {}) {
|
|
13
|
+
const record = REGISTRO_FORNITORI[provider];
|
|
14
|
+
if (!record?.cloud) return null;
|
|
15
|
+
const invalido = () => { throw new ProviderCloudError(`Controlla l'indirizzo e la configurazione di ${record.etichetta}.`); };
|
|
16
|
+
let url;
|
|
17
|
+
try { url = new URL(endpoint); } catch { invalido(); }
|
|
18
|
+
const locale = ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname);
|
|
19
|
+
if (url.protocol !== 'https:' && !(locale && url.protocol === 'http:')) invalido();
|
|
20
|
+
if (url.username || url.password || url.hash || [...url.searchParams.keys()].some(k => provider !== 'azure' || k !== 'api-version')) invalido();
|
|
21
|
+
let path = url.pathname.replace(/\/+$/u, '');
|
|
22
|
+
let regione = null, progetto = null, versioneApi = 'v1', endpointRisorsa = null;
|
|
23
|
+
if (provider === 'azure') {
|
|
24
|
+
const versioni = url.searchParams.getAll('api-version');
|
|
25
|
+
if (versioni.length > 1 || versioni.some(v => !['v1', '2024-10-21'].includes(v))) invalido();
|
|
26
|
+
versioneApi = versioni[0] ?? 'v1';
|
|
27
|
+
if (!['', '/openai', '/openai/v1'].includes(path)) invalido();
|
|
28
|
+
if (versioneApi === '2024-10-21' && path === '/openai/v1') invalido();
|
|
29
|
+
path = versioneApi === 'v1' ? '/openai/v1' : '/openai';
|
|
30
|
+
endpointRisorsa = url.origin;
|
|
31
|
+
if (versioneApi === 'v1') url.search = '';
|
|
32
|
+
} else if (provider === 'bedrock') {
|
|
33
|
+
const runtime = /^bedrock-runtime\.([a-z0-9-]+)\.amazonaws\.com$/u.exec(url.hostname);
|
|
34
|
+
const mantle = /^bedrock-mantle\.([a-z0-9-]+)\.api\.aws$/u.exec(url.hostname);
|
|
35
|
+
regione = runtime?.[1] ?? mantle?.[1] ?? null;
|
|
36
|
+
if (!path) path = mantle ? '/v1' : '/openai/v1';
|
|
37
|
+
if (!['/v1', '/openai/v1'].includes(path)) invalido();
|
|
38
|
+
// Una regione esplicita nel nome resta la regione della destinazione.
|
|
39
|
+
} else if (provider === 'vertex') {
|
|
40
|
+
const parti = /^\/v1\/projects\/([a-zA-Z0-9][a-zA-Z0-9-]{0,62})\/locations\/([a-z0-9-]+)\/endpoints\/openapi$/u.exec(path);
|
|
41
|
+
if (!parti) invalido();
|
|
42
|
+
[, progetto, regione] = parti;
|
|
43
|
+
if (url.hostname.endsWith('aiplatform.googleapis.com')) {
|
|
44
|
+
const host = regione === 'global' ? 'aiplatform.googleapis.com' : `${regione}-aiplatform.googleapis.com`;
|
|
45
|
+
if (url.hostname !== host) invalido();
|
|
46
|
+
}
|
|
47
|
+
} else invalido();
|
|
48
|
+
url.pathname = path;
|
|
49
|
+
return { endpoint: url.toString(), regione, progetto, versioneApi, endpointRisorsa };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Stringa legacy oppure involucro v1, custodito interamente come UNA chiave del pool. */
|
|
53
|
+
export function intestazioniCloud(provider, chiave, { ora = Date.now() } = {}) {
|
|
54
|
+
const record = REGISTRO_FORNITORI[provider];
|
|
55
|
+
const invalida = () => { throw new ProviderCloudError(`Controlla la credenziale di ${record.etichetta}.`, 'PROVIDER_CLOUD_CREDENTIAL_INVALID'); };
|
|
56
|
+
if (typeof chiave !== 'string' || !chiave.trim()) throw new ProviderCloudError(`Manca la chiave per ${record.etichetta}.`, 'PROVIDER_KEY_MISSING');
|
|
57
|
+
let valore = chiave.trim(), tipo = record.auth.tipo;
|
|
58
|
+
if (valore.startsWith('{')) {
|
|
59
|
+
let dati;
|
|
60
|
+
try { dati = JSON.parse(valore); } catch { invalida(); }
|
|
61
|
+
if (!dati || dati.versione !== 1 || !['bearer', ...(provider === 'azure' ? ['header'] : [])].includes(dati.tipo)
|
|
62
|
+
|| Object.keys(dati).some(k => !['versione', 'tipo', 'valore', 'scadeAlle'].includes(k))) invalida();
|
|
63
|
+
valore = dati.valore; tipo = dati.tipo;
|
|
64
|
+
if (dati.scadeAlle !== undefined) {
|
|
65
|
+
if (typeof dati.scadeAlle !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{3})?Z$/u.test(dati.scadeAlle) || !Number.isFinite(Date.parse(dati.scadeAlle))) invalida();
|
|
66
|
+
if (Date.parse(dati.scadeAlle) <= ora) throw new ProviderCloudError(`L'accesso a ${record.etichetta} è scaduto: inserisci una credenziale aggiornata.`, 'PROVIDER_CLOUD_TOKEN_EXPIRED');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (typeof valore !== 'string' || !/^[\x21-\x7e]+$/u.test(valore) || valore.length > 4096) invalida();
|
|
70
|
+
return tipo === 'header' ? { 'api-key': valore } : { Authorization: `Bearer ${valore}` };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function destinazioneCloud(provider, runtime, chiave, modelloRemoto, { catalogo = false } = {}) {
|
|
74
|
+
const config = normalizzaRuntimeCloud(provider, runtime);
|
|
75
|
+
const headers = { 'Content-Type': 'application/json', ...intestazioniCloud(provider, chiave) };
|
|
76
|
+
const url = new URL(config.endpoint);
|
|
77
|
+
if (catalogo) {
|
|
78
|
+
// Il catalogo Azure è authoring v1 anche quando l'inferenza usa la versione precedente.
|
|
79
|
+
if (provider === 'azure') { url.pathname = '/openai/v1/models'; url.search = ''; }
|
|
80
|
+
else url.pathname += '/models';
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Il deployment Azure è scelto dall'owner, mai dedotto dal nome del modello base.
|
|
84
|
+
if (provider === 'azure') {
|
|
85
|
+
if (typeof modelloRemoto !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,199}$/u.test(modelloRemoto) || modelloRemoto.includes('..')) {
|
|
86
|
+
throw new ProviderCloudError('Controlla il nome della distribuzione di Azure AI Foundry.', 'MODEL_DESTINATION_INVALID');
|
|
87
|
+
}
|
|
88
|
+
if (config.versioneApi !== 'v1') url.pathname += `/deployments/${encodeURIComponent(modelloRemoto)}`;
|
|
89
|
+
}
|
|
90
|
+
url.pathname += '/chat/completions';
|
|
91
|
+
}
|
|
92
|
+
// Il segreto è disponibile al trasporto, ma non alla serializzazione diagnostica.
|
|
93
|
+
return Object.defineProperty({ fonte: provider, modelloRemoto, url: url.toString(), cloud: true }, 'headers', { value: headers });
|
|
94
|
+
}
|
|
95
|
+
// P-K — fine
|
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credenziali provider del server locale.
|
|
3
|
+
*
|
|
4
|
+
* Il browser può inviare una chiave solo a questo boundary: il valore vive nel
|
|
5
|
+
* portachiavi del sistema operativo (o arriva da ambiente all'avvio) e non entra
|
|
6
|
+
* mai in una risposta, in una sessione o in una preferenza web. Le preferenze
|
|
7
|
+
* non segrete (indirizzo e timeout) possono essere ripristinate da un file
|
|
8
|
+
* locale dedicato; le chiavi non entrano mai in quel file.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
import { ID_CON_CREDENZIALE, REGISTRO_FORNITORI } from './provider-registry.mjs';
|
|
15
|
+
// P-K
|
|
16
|
+
import { normalizzaRuntimeCloud } from './provider-auth-cloud.mjs';
|
|
17
|
+
// P-K-bis/P-L-bis: preferenze pubbliche, separate dall'universo del portachiavi.
|
|
18
|
+
import { validaRuntimeAgenteEsterno } from './acp-agent.mjs';
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* ⛔⛔ 12/09 — P-A: QUESTE DUE COSTANTI ERANO IL PRIMO DEI TREDICI ELENCHI PARALLELI.
|
|
22
|
+
*
|
|
23
|
+
* Erano scritte a mano, e dicevano cose che altri file smentivano: `deepseek` con
|
|
24
|
+
* `execution: 'in preparazione'` mentre `model-destination.mjs` lo instradava da sempre, e
|
|
25
|
+
* `lmstudio` assente benché fosse già sondato e caricabile. Adesso sono una PROIEZIONE del
|
|
26
|
+
* registro: aggiungere un fornitore qui non è più possibile, e non deve esserlo.
|
|
27
|
+
*
|
|
28
|
+
* ⛔ I nomi dei campi restano quelli di prima (`label`, `keyEnv`, `supportsEndpoint`…): questa è
|
|
29
|
+
* la forma che la rotta `/api/v1/providers` e il componente `provider-card.js` già leggono, e
|
|
30
|
+
* il registro non è una scusa per rompere un contratto verso il browser. La traduzione avviene
|
|
31
|
+
* QUI, in un posto solo, e il test di parità la presidia.
|
|
32
|
+
*/
|
|
33
|
+
export const PROVIDER_IDS = ID_CON_CREDENZIALE;
|
|
34
|
+
|
|
35
|
+
export const PROVIDER_DEFINITIONS = Object.freeze(Object.fromEntries(PROVIDER_IDS.map((id) => {
|
|
36
|
+
const record = REGISTRO_FORNITORI[id];
|
|
37
|
+
return [id, Object.freeze({
|
|
38
|
+
id,
|
|
39
|
+
label: record.etichetta,
|
|
40
|
+
keyEnv: record.auth.nomeVariabile,
|
|
41
|
+
defaultEndpoint: record.baseUrl,
|
|
42
|
+
...(record.envIndirizzo.length ? { endpointEnv: record.envIndirizzo } : {}),
|
|
43
|
+
supportsEndpoint: record.indirizzoModificabile === true,
|
|
44
|
+
supportsTimeout: record.limiti.tempoMassimoModificabile === true,
|
|
45
|
+
requiresKey: record.chiaveObbligatoria === true,
|
|
46
|
+
execution: record.esecuzione,
|
|
47
|
+
/* ⛔ La guardia della UI: il pulsante «Accedi con …» esiste solo dove il record dichiara un
|
|
48
|
+
flusso. Prima era un `provider === 'openrouter'` scritto dentro `listPublic()`. */
|
|
49
|
+
supportsOAuth: Boolean(record.oauth),
|
|
50
|
+
})];
|
|
51
|
+
})));
|
|
52
|
+
|
|
53
|
+
const KEYRING_SERVICE = 'talos-harness-provider';
|
|
54
|
+
/*
|
|
55
|
+
* ⛔⛔ P0 · punto 7 (16/09/2026) — COSA MISURA `timeoutSeconds`, DA OGGI.
|
|
56
|
+
*
|
|
57
|
+
* Fino a ieri era una DEADLINE TOTALE sulla chiamata al fornitore: partiva con la richiesta e non
|
|
58
|
+
* si fermava quando la risposta cominciava ad arrivare. Misurato con un fornitore finto che emette
|
|
59
|
+
* un token ogni 2 s per 90 s, col default di 60: **tagliato a 60.002 ms, ultimo token a 58.023 ms**
|
|
60
|
+
* — cioè uccideva una generazione viva, e diceva «il fornitore ha superato il tempo massimo».
|
|
61
|
+
*
|
|
62
|
+
* ⇒ Da adesso è il **tempo massimo alla PRIMA RISPOSTA** (fino alle intestazioni HTTP). Dopo quel
|
|
63
|
+
* momento non conta più niente: la generazione è sorvegliata dal solo failsafe di inattività
|
|
64
|
+
* (`src/generation-idle.mjs`, `TALOS_GENERATION_IDLE_MS`).
|
|
65
|
+
*
|
|
66
|
+
* ⛔ **I valori salvati NON si migrano, e non devono.** Un 60 scritto ieri voleva dire «se il
|
|
67
|
+
* fornitore non dà segno di vita entro un minuto, lascia perdere»: con la nuova semantica dice
|
|
68
|
+
* esattamente quello, e per di più smette di fare il danno che nessuno aveva chiesto. Riscrivere
|
|
69
|
+
* le preferenze di chi non ha chiesto niente sarebbe cambiargli la configurazione di nascosto.
|
|
70
|
+
* ⛔ Per questo i tre numeri qui sotto restano identici: 5 s è ancora un minimo sensato per «non
|
|
71
|
+
* risponde proprio», e 300 s un massimo generoso. È la SEMANTICA a essere cambiata, non la scala
|
|
72
|
+
* — e cambiare anche la scala renderebbe impossibile capire quale delle due ha causato cosa.
|
|
73
|
+
*/
|
|
74
|
+
const DEFAULT_TIMEOUT_SECONDS = 60;
|
|
75
|
+
const MIN_TIMEOUT_SECONDS = 5;
|
|
76
|
+
const MAX_TIMEOUT_SECONDS = 300;
|
|
77
|
+
const MAX_KEY_LENGTH = 4096;
|
|
78
|
+
const POOL_SERVICE = 'talos-harness-provider-pool';
|
|
79
|
+
const POOL_INDEX_SERVICE = 'talos-harness-provider-pool-index';
|
|
80
|
+
const MAX_POOL_KEYS = 8;
|
|
81
|
+
export const PANCHINE_PROVIDER_MS = Object.freeze({
|
|
82
|
+
traffico: 3_600_000, credenziale: 300_000, credito: 3_600_000,
|
|
83
|
+
rete: 60_000, 'timeout-fornitore': 60_000, 'guasto-fornitore': 60_000, 'flusso-interrotto': 60_000,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function normalizzaChiave(value) {
|
|
87
|
+
if (typeof value !== 'string' || !value.trim()) throw new ProviderCredentialError('PROVIDER_KEY_REQUIRED');
|
|
88
|
+
const normalized = value.trim();
|
|
89
|
+
if (normalized.length > MAX_KEY_LENGTH || /[\r\n\0]/u.test(normalized)) throw new ProviderCredentialError('PROVIDER_KEY_INVALID');
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
92
|
+
function voceChiave(value, priorita = 0, origine = 'custodia') {
|
|
93
|
+
if (!Number.isSafeInteger(priorita) || Math.abs(priorita) > 1000) throw new ProviderCredentialError('PROVIDER_KEY_INVALID');
|
|
94
|
+
const chiave = normalizzaChiave(value);
|
|
95
|
+
return { chiave, impronta: createHash('sha256').update(chiave).digest('hex'), priorita, origine, inPanchinaFino: null, causa: null };
|
|
96
|
+
}
|
|
97
|
+
function istanteAssoluto(value) {
|
|
98
|
+
if (typeof value === 'number' || (typeof value === 'string' && /^\d+(?:\.\d+)?$/u.test(value))) {
|
|
99
|
+
const n = Number(value); return n > 0 ? (n < 1e12 ? n * 1000 : n) : null;
|
|
100
|
+
}
|
|
101
|
+
return typeof value === 'string' && /^\d{4}-\d\d-\d\dT/u.test(value) ? Date.parse(value) : null;
|
|
102
|
+
}
|
|
103
|
+
function durataReset(value) {
|
|
104
|
+
if (typeof value !== 'string' || !/^(?:\d+(?:\.\d+)?(?:ms|s|m|h))+$/u.test(value)) return null;
|
|
105
|
+
return [...value.matchAll(/(\d+(?:\.\d+)?)(ms|s|m|h)/gu)].reduce((s, m) => s + Number(m[1]) * ({ms:1,s:1000,m:60000,h:3600000}[m[2]]), 0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** RFC 9110; OpenAI durate, Anthropic RFC3339. Nessun reset dedotto dal testo libero. */
|
|
109
|
+
export function leggiScadenzaFornitore(provider, { headers, resetAt } = {}, ora = Date.now()) {
|
|
110
|
+
const h = headers instanceof Headers ? headers : new Headers(headers || {});
|
|
111
|
+
const validi = [];
|
|
112
|
+
const aggiungi = n => { if (Number.isFinite(n) && n >= ora && n <= 8.64e15) validi.push(n); };
|
|
113
|
+
if (resetAt != null) aggiungi(istanteAssoluto(resetAt));
|
|
114
|
+
const retry = h.get('retry-after');
|
|
115
|
+
if (retry != null) aggiungi(/^\d+$/u.test(retry) ? ora + Number(retry) * 1000 : /^[A-Za-z]{3}, /u.test(retry) ? Date.parse(retry) : null);
|
|
116
|
+
// P-J — le scadenze seguono il wire anche per Z.AI e MiniMax, nello stesso pool P-H.
|
|
117
|
+
const prefisso = provider === 'openai' ? 'x-ratelimit-' : REGISTRO_FORNITORI[provider]?.wire === 'anthropic-messages' ? 'anthropic-ratelimit-' : null;
|
|
118
|
+
if (prefisso) for (const dimensione of ['requests', 'tokens', 'project-tokens', 'input-tokens', 'output-tokens']) {
|
|
119
|
+
const reset = h.get(provider === 'openai' ? `${prefisso}reset-${dimensione}` : `${prefisso}${dimensione}-reset`);
|
|
120
|
+
const remaining = h.get(provider === 'openai' ? `${prefisso}remaining-${dimensione}` : `${prefisso}${dimensione}-remaining`);
|
|
121
|
+
if (reset == null || (remaining != null && Number(remaining) > 0)) continue;
|
|
122
|
+
const valore = provider === 'openai' ? durataReset(reset) : istanteAssoluto(reset);
|
|
123
|
+
if (valore != null) aggiungi(provider === 'openai' ? ora + valore : valore);
|
|
124
|
+
}
|
|
125
|
+
return validi.length ? Math.max(...validi) : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const MESSAGE_BY_CODE = Object.freeze({
|
|
129
|
+
PROVIDER_INVALID: 'Provider non riconosciuto',
|
|
130
|
+
PROVIDER_KEY_REQUIRED: 'Inserisci una chiave prima di salvarla',
|
|
131
|
+
PROVIDER_KEY_INVALID: 'La chiave inserita non è valida',
|
|
132
|
+
PROVIDER_STORE_UNAVAILABLE: 'Il portachiavi del computer non è disponibile: controlla Doctor',
|
|
133
|
+
PROVIDER_RUNTIME_INVALID: 'Controlla i campi del collegamento: indirizzo, modelli, comando e tempo massimo',
|
|
134
|
+
PROVIDER_RUNTIME_UNAVAILABLE: 'Non è stato possibile salvare le preferenze del provider: controlla Doctor',
|
|
135
|
+
PROVIDER_POOL_FULL: 'Sono già presenti otto chiavi per questo fornitore',
|
|
136
|
+
PROVIDER_KEY_NOT_FOUND: 'La chiave non è più presente: aggiorna il pannello',
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export class ProviderCredentialError extends Error {
|
|
140
|
+
constructor(code, details = '') {
|
|
141
|
+
super(MESSAGE_BY_CODE[code] || 'Configurazione provider non disponibile');
|
|
142
|
+
this.name = 'ProviderCredentialError';
|
|
143
|
+
this.code = code;
|
|
144
|
+
this.details = details;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function definitionFor(provider) {
|
|
149
|
+
const definition = PROVIDER_DEFINITIONS[provider];
|
|
150
|
+
if (!definition) throw new ProviderCredentialError('PROVIDER_INVALID');
|
|
151
|
+
return definition;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function readEnvironment(env, names) {
|
|
155
|
+
for (const name of names || []) {
|
|
156
|
+
const value = env?.[name];
|
|
157
|
+
if (typeof value === 'string' && value.trim() !== '') return value.trim();
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function normalizeProviderEndpoint(provider, rawEndpoint) {
|
|
163
|
+
const definition = definitionFor(provider);
|
|
164
|
+
if (!definition.supportsEndpoint) throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID');
|
|
165
|
+
if (typeof rawEndpoint !== 'string' || rawEndpoint.trim() === '') throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID');
|
|
166
|
+
// P-K — il contratto HTTP resta endpoint + timeout, nessuna migrazione.
|
|
167
|
+
if (REGISTRO_FORNITORI[provider].cloud) {
|
|
168
|
+
try { return normalizzaRuntimeCloud(provider, { endpoint: rawEndpoint }).endpoint; }
|
|
169
|
+
catch { throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID'); }
|
|
170
|
+
}
|
|
171
|
+
// P-K — fine
|
|
172
|
+
let parsed;
|
|
173
|
+
try { parsed = new URL(rawEndpoint.trim()); } catch { throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID'); }
|
|
174
|
+
if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password || parsed.hash || !parsed.hostname) {
|
|
175
|
+
throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID');
|
|
176
|
+
}
|
|
177
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/u, '') || '';
|
|
178
|
+
return parsed.toString().replace(/\/$/u, '');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function normalizeTimeout(value) {
|
|
182
|
+
if (!Number.isInteger(value) || value < MIN_TIMEOUT_SECONDS || value > MAX_TIMEOUT_SECONDS) {
|
|
183
|
+
throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID');
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function noSecretLogger(logger, message) {
|
|
189
|
+
try { if (typeof logger === 'function') logger(message); } catch { /* diagnosi non deve interrompere il server */ }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isRecord(value) {
|
|
193
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// P-K-bis: soltanto identità, mai dichiarazioni di capacità o valori di credenziali.
|
|
197
|
+
function normalizzaModelli(provider, value, segreti = []) {
|
|
198
|
+
const invalido = () => { throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID'); };
|
|
199
|
+
if (!REGISTRO_FORNITORI[provider]?.cloud || !Array.isArray(value) || value.length > 50) invalido();
|
|
200
|
+
const visti = new Set();
|
|
201
|
+
return value.map(m => {
|
|
202
|
+
if (!isRecord(m) || Object.keys(m).some(k => !['id', 'nome'].includes(k))
|
|
203
|
+
|| typeof m.id !== 'string' || m.id.length > 200 || !/^[a-zA-Z0-9][a-zA-Z0-9._/:@-]*$/u.test(m.id)
|
|
204
|
+
|| m.id.includes('://') || m.id.split('/').some(p => !p || p === '.' || p === '..')
|
|
205
|
+
|| (provider === 'azure' && !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/u.test(m.id))
|
|
206
|
+
|| PROVIDER_IDS.some(p => m.id.startsWith(`${p}:`)) || visti.has(m.id)
|
|
207
|
+
|| (Object.hasOwn(m, 'nome') && (typeof m.nome !== 'string' || !m.nome.trim() || m.nome.length > 120 || /[\p{Cc}\p{Cf}<>]/u.test(m.nome)))
|
|
208
|
+
|| segreti.some(s => s && [m.id, m.nome ?? ''].some(v => v.includes(s)))) invalido();
|
|
209
|
+
visti.add(m.id);
|
|
210
|
+
return { id: m.id, ...(m.nome === undefined ? {} : { nome: m.nome.trim() }) };
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function normalizzaAgente(value, env, segreti = []) {
|
|
214
|
+
try {
|
|
215
|
+
const agente = validaRuntimeAgenteEsterno(value, { env });
|
|
216
|
+
if (segreti.some(s => s && [agente.comando, agente.cwd, ...agente.argomenti].some(v => v.includes(s)))) throw new Error('credenziale');
|
|
217
|
+
return agente;
|
|
218
|
+
}
|
|
219
|
+
catch { throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID'); }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function readRuntimePreferences(runtimeFile, runtimes, logger, env, segreti) {
|
|
223
|
+
if (typeof runtimeFile !== 'string' || runtimeFile.trim() === '' || !existsSync(runtimeFile)) return;
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = JSON.parse(readFileSync(runtimeFile, 'utf8'));
|
|
227
|
+
if (parsed?.version !== 1 || !isRecord(parsed.providers)) throw new Error('schema');
|
|
228
|
+
} catch {
|
|
229
|
+
noSecretLogger(logger, 'Preferenze provider ignorate: file non leggibile');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
for (const provider of [...PROVIDER_IDS, 'esterno']) {
|
|
233
|
+
if (provider === 'esterno') {
|
|
234
|
+
const row = parsed.providers.esterno;
|
|
235
|
+
if (row === undefined) continue;
|
|
236
|
+
try {
|
|
237
|
+
if (!isRecord(row) || Object.keys(row).some(k => k !== 'agente')) throw new Error('schema');
|
|
238
|
+
runtimes.set(provider, { agente: normalizzaAgente(row.agente, env, segreti) });
|
|
239
|
+
} catch { noSecretLogger(logger, 'Preferenza agente esterno ignorata: valore non valido'); }
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const definition = PROVIDER_DEFINITIONS[provider];
|
|
243
|
+
if (!definition.supportsTimeout) continue;
|
|
244
|
+
const row = parsed.providers[provider];
|
|
245
|
+
if (!isRecord(row)) continue;
|
|
246
|
+
try {
|
|
247
|
+
const timeout = normalizeTimeout(row.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS);
|
|
248
|
+
const extra = Object.hasOwn(row, 'modelli') ? { modelli: normalizzaModelli(provider, row.modelli, segreti) } : {};
|
|
249
|
+
if (!definition.supportsEndpoint || row.endpointConfigured === false) {
|
|
250
|
+
runtimes.set(provider, { endpoint: null, endpointConfigured: false, timeoutSeconds: timeout, ...extra });
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const endpoint = normalizeProviderEndpoint(provider, row.endpoint);
|
|
254
|
+
runtimes.set(provider, { endpoint, endpointConfigured: true, timeoutSeconds: timeout, ...extra });
|
|
255
|
+
} catch {
|
|
256
|
+
noSecretLogger(logger, `Preferenza provider ${provider} ignorata: valore non valido`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function writeRuntimePreferences(runtimeFile, runtimes) {
|
|
262
|
+
if (typeof runtimeFile !== 'string' || runtimeFile.trim() === '') return;
|
|
263
|
+
const providers = {};
|
|
264
|
+
for (const [provider, value] of runtimes.entries()) {
|
|
265
|
+
if (provider === 'esterno') { providers.esterno = { agente: value.agente }; continue; }
|
|
266
|
+
const definition = PROVIDER_DEFINITIONS[provider];
|
|
267
|
+
if (!definition?.supportsTimeout) continue;
|
|
268
|
+
providers[provider] = {
|
|
269
|
+
endpoint: definition.supportsEndpoint && value.endpointConfigured ? value.endpoint : null,
|
|
270
|
+
endpointConfigured: definition.supportsEndpoint && value.endpointConfigured === true,
|
|
271
|
+
timeoutSeconds: value.timeoutSeconds,
|
|
272
|
+
...(value.modelli ? { modelli: value.modelli } : {}),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const temporary = `${runtimeFile}.tmp`;
|
|
276
|
+
try {
|
|
277
|
+
writeFileSync(temporary, `${JSON.stringify({ version: 1, providers }, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
278
|
+
renameSync(temporary, runtimeFile);
|
|
279
|
+
} catch {
|
|
280
|
+
try { if (existsSync(temporary)) unlinkSync(temporary); } catch { /* best effort */ }
|
|
281
|
+
throw new ProviderCredentialError('PROVIDER_RUNTIME_UNAVAILABLE');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @param {{env?:Record<string,string|undefined>, keyring?:{get:Function,set:Function,remove:Function}|null, runtimeFile?:string|null, logger?:(message:string)=>void}} options
|
|
287
|
+
*/
|
|
288
|
+
export function createProviderCredentialStore({ env = process.env, keyring = null, runtimeFile = null, logger = () => {}, ora = Date.now } = {}) {
|
|
289
|
+
const pools = new Map();
|
|
290
|
+
const conIndice = new Set();
|
|
291
|
+
/* ⛔ PO-01 — i provider la cui chiave viene dal PORTACHIAVI. Senza questo insieme l'origine si
|
|
292
|
+
potrebbe solo indovinare, perché `keys` non ricorda da dove è arrivato ogni valore. */
|
|
293
|
+
const daPortachiavi = new Set();
|
|
294
|
+
const runtimes = new Map();
|
|
295
|
+
for (const provider of PROVIDER_IDS) {
|
|
296
|
+
const definition = PROVIDER_DEFINITIONS[provider];
|
|
297
|
+
const key = readEnvironment(env, definition.keyEnv);
|
|
298
|
+
try {
|
|
299
|
+
const rawPool = readEnvironment(env, [`${definition.keyEnv[0]}_POOL`]);
|
|
300
|
+
const elenco = rawPool ? JSON.parse(rawPool) : [];
|
|
301
|
+
if (!Array.isArray(elenco) || elenco.length > MAX_POOL_KEYS) throw new Error('schema');
|
|
302
|
+
const righe = [...(key ? [voceChiave(key, 0, 'ambiente')] : []), ...elenco.map((v, i) => voceChiave(typeof v === 'string' ? v : v?.key, typeof v === 'string' ? i + 1 : v?.priorita ?? i + 1, 'ambiente'))];
|
|
303
|
+
const viste = new Set();
|
|
304
|
+
const uniche = righe.filter(v => { if (viste.has(v.impronta)) return false; viste.add(v.impronta); return true; });
|
|
305
|
+
if (uniche.length > MAX_POOL_KEYS) throw new Error('schema');
|
|
306
|
+
pools.set(provider, uniche);
|
|
307
|
+
} catch {
|
|
308
|
+
noSecretLogger(logger, `Elenco chiavi ${provider} ignorato: formato non valido`);
|
|
309
|
+
try { if (key) pools.set(provider, [voceChiave(key, 0, 'ambiente')]); } catch { /* nessun segreto in diagnosi */ }
|
|
310
|
+
}
|
|
311
|
+
if (definition.supportsEndpoint) {
|
|
312
|
+
const endpoint = readEnvironment(env, definition.endpointEnv);
|
|
313
|
+
if (endpoint) {
|
|
314
|
+
try { runtimes.set(provider, { endpoint: normalizeProviderEndpoint(provider, endpoint), endpointConfigured: true, timeoutSeconds: DEFAULT_TIMEOUT_SECONDS }); }
|
|
315
|
+
catch { noSecretLogger(logger, `Endpoint ${provider} ignorato: formato non valido`); }
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const segretiRuntime = () => [...pools.values()].flat().map(v => v.chiave);
|
|
320
|
+
readRuntimePreferences(runtimeFile, runtimes, logger, env, segretiRuntime());
|
|
321
|
+
|
|
322
|
+
function requireProvider(provider) { return definitionFor(provider); }
|
|
323
|
+
function righe(provider) { requireProvider(provider); return [...(pools.get(provider) || [])].sort((a,b) => a.priorita - b.priorita); }
|
|
324
|
+
function pubblica(v, i = 0) {
|
|
325
|
+
const inPanchina = v.inPanchinaFino > ora();
|
|
326
|
+
return { impronta: v.impronta, nome: `Chiave ${i + 1}`, priorita: v.priorita, origine: v.origine,
|
|
327
|
+
stato: inPanchina ? 'in-panchina' : 'disponibile', inPanchinaFino: inPanchina ? v.inPanchinaFino : null,
|
|
328
|
+
causa: inPanchina ? v.causa : null };
|
|
329
|
+
}
|
|
330
|
+
function elencaPool(provider) { return righe(provider).map(pubblica); }
|
|
331
|
+
function scegliChiave(provider, { escluse = [] } = {}) {
|
|
332
|
+
const v = righe(provider).find(v => !(v.inPanchinaFino > ora()) && !escluse.includes(v.impronta));
|
|
333
|
+
// Il segreto serve solo al trasporto; JSON/log dell'oggetto non lo enumerano.
|
|
334
|
+
return v ? Object.defineProperty(pubblica(v), 'chiave', { value: v.chiave }) : null;
|
|
335
|
+
}
|
|
336
|
+
function getKey(provider) { return scegliChiave(provider)?.chiave ?? null; }
|
|
337
|
+
function hasKey(provider) { return righe(provider).length > 0; }
|
|
338
|
+
function keyringOperation(operation, provider, value, service = KEYRING_SERVICE) {
|
|
339
|
+
if (!keyring || typeof keyring[operation] !== 'function') throw new ProviderCredentialError('PROVIDER_STORE_UNAVAILABLE');
|
|
340
|
+
try { return operation === 'set' ? keyring.set(service, provider, value) : keyring.remove(service, provider); }
|
|
341
|
+
catch { throw new ProviderCredentialError('PROVIDER_STORE_UNAVAILABLE'); }
|
|
342
|
+
}
|
|
343
|
+
function salvaPool(provider, nuove) {
|
|
344
|
+
const precedenti = righe(provider), aggiunte = [];
|
|
345
|
+
try {
|
|
346
|
+
for (const v of nuove) {
|
|
347
|
+
if (conIndice.has(provider) && precedenti.some(p => p.impronta === v.impronta)) continue;
|
|
348
|
+
keyringOperation('set', `${provider}:${v.impronta}`, v.chiave, POOL_SERVICE);
|
|
349
|
+
aggiunte.push(v.impronta);
|
|
350
|
+
}
|
|
351
|
+
// L'indice si pubblica per ultimo. Un indice vuoto impedisce di resuscitare ambiente/legacy.
|
|
352
|
+
keyringOperation('set', `${provider}:indice`, JSON.stringify({ version: 1, chiavi: nuove.map(v => [v.impronta, v.priorita, v.inPanchinaFino, v.causa]) }), POOL_INDEX_SERVICE);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
for (const id of aggiunte) { try { keyringOperation('remove', `${provider}:${id}`, null, POOL_SERVICE); } catch { noSecretLogger(logger, 'Pulizia del portachiavi incompleta: controlla Doctor'); } }
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
pools.set(provider, nuove.map(v => ({ ...v, origine: 'custodia' })));
|
|
358
|
+
conIndice.add(provider); daPortachiavi.add(provider);
|
|
359
|
+
for (const v of precedenti) if (!nuove.some(n => n.impronta === v.impronta)) {
|
|
360
|
+
keyringOperation('remove', `${provider}:${v.impronta}`, null, POOL_SERVICE);
|
|
361
|
+
}
|
|
362
|
+
// L'account singolo resta leggibile dai consumatori legacy finché la sua
|
|
363
|
+
// credenziale appartiene al pool. Una rimozione esplicita cancella entrambe le copie.
|
|
364
|
+
let legacy;
|
|
365
|
+
try { legacy = keyring.get?.(KEYRING_SERVICE, provider); } catch { throw new ProviderCredentialError('PROVIDER_STORE_UNAVAILABLE'); }
|
|
366
|
+
if (!nuove.some(v => v.chiave === legacy?.trim())) keyringOperation('remove', provider);
|
|
367
|
+
}
|
|
368
|
+
function aggiungiChiave(provider, value, { priorita } = {}) {
|
|
369
|
+
const attuali = righe(provider), nuova = voceChiave(value, priorita ?? Math.min(1000, Math.max(-1, ...attuali.map(v => v.priorita)) + 1));
|
|
370
|
+
const esistente = attuali.find(v => v.impronta === nuova.impronta);
|
|
371
|
+
if (esistente) return pubblica(esistente, attuali.indexOf(esistente));
|
|
372
|
+
if (attuali.length >= MAX_POOL_KEYS) throw new ProviderCredentialError('PROVIDER_POOL_FULL');
|
|
373
|
+
salvaPool(provider, [...attuali, nuova]);
|
|
374
|
+
return elencaPool(provider).find(v => v.impronta === nuova.impronta);
|
|
375
|
+
}
|
|
376
|
+
function rimuoviChiave(provider, impronta) {
|
|
377
|
+
const attuali = righe(provider);
|
|
378
|
+
if (!attuali.some(v => v.impronta === impronta)) throw new ProviderCredentialError('PROVIDER_KEY_NOT_FOUND');
|
|
379
|
+
salvaPool(provider, attuali.filter(v => v.impronta !== impronta));
|
|
380
|
+
return { provider, keyConfigured: hasKey(provider), pool: elencaPool(provider) };
|
|
381
|
+
}
|
|
382
|
+
function mettiInPanchina(provider, impronta, { classe, headers, resetAt } = {}) {
|
|
383
|
+
const attuali = righe(provider), v = attuali.find(v => v.impronta === impronta);
|
|
384
|
+
if (!v) return null; // rimossa durante una richiesta in volo
|
|
385
|
+
if (!Object.hasOwn(PANCHINE_PROVIDER_MS, classe)) return pubblica(v);
|
|
386
|
+
const stima = classe === 'traffico' && attuali.length === 1 ? 60_000 : PANCHINE_PROVIDER_MS[classe];
|
|
387
|
+
const nuova = { ...v, inPanchinaFino: leggiScadenzaFornitore(provider, { headers, resetAt }, ora()) ?? ora() + stima, causa: classe };
|
|
388
|
+
const nuove = attuali.map(r => r === v ? nuova : r);
|
|
389
|
+
// Le panchine ambientali restano in memoria: nessuna scrittura segreta non richiesta.
|
|
390
|
+
if (daPortachiavi.has(provider)) {
|
|
391
|
+
try { salvaPool(provider, nuove); }
|
|
392
|
+
catch { pools.set(provider, nuove); noSecretLogger(logger, `Panchina ${provider} attiva in memoria: salvataggio non disponibile`); }
|
|
393
|
+
} else pools.set(provider, nuove);
|
|
394
|
+
if (classe === 'credenziale') noSecretLogger(logger, `Credenziale rifiutata da ${PROVIDER_DEFINITIONS[provider].label}: controlla gli accessi`);
|
|
395
|
+
return pubblica(nuova);
|
|
396
|
+
}
|
|
397
|
+
function setKey(provider, value) {
|
|
398
|
+
requireProvider(provider);
|
|
399
|
+
const normalized = normalizzaChiave(value);
|
|
400
|
+
if (conIndice.has(provider)) {
|
|
401
|
+
const attuali = righe(provider), nuova = voceChiave(normalized, attuali[0]?.priorita ?? 0);
|
|
402
|
+
salvaPool(provider, [nuova, ...attuali.slice(1).filter(v => v.impronta !== nuova.impronta)]);
|
|
403
|
+
} else {
|
|
404
|
+
keyringOperation('set', provider, normalized);
|
|
405
|
+
pools.set(provider, [voceChiave(normalized)]);
|
|
406
|
+
}
|
|
407
|
+
daPortachiavi.add(provider); // appena salvata lì: l'accesso e il campo «incolla» finiscono nello stesso posto
|
|
408
|
+
return { provider, keyConfigured: true };
|
|
409
|
+
}
|
|
410
|
+
function clearKey(provider) {
|
|
411
|
+
requireProvider(provider);
|
|
412
|
+
salvaPool(provider, []);
|
|
413
|
+
daPortachiavi.delete(provider);
|
|
414
|
+
return { provider, keyConfigured: false };
|
|
415
|
+
}
|
|
416
|
+
function loadFromKeyring() {
|
|
417
|
+
if (!keyring || typeof keyring.get !== 'function') return { loaded: 0, available: false };
|
|
418
|
+
let loaded = 0;
|
|
419
|
+
for (const provider of PROVIDER_IDS) {
|
|
420
|
+
try {
|
|
421
|
+
const indice = keyring.get(POOL_INDEX_SERVICE, `${provider}:indice`);
|
|
422
|
+
if (indice != null) {
|
|
423
|
+
// Un indice danneggiato NON autorizza la vecchia credenziale o l'ambiente.
|
|
424
|
+
pools.set(provider, []); conIndice.add(provider); daPortachiavi.add(provider);
|
|
425
|
+
const dati = JSON.parse(indice);
|
|
426
|
+
if (dati?.version !== 1 || !Array.isArray(dati.chiavi) || dati.chiavi.length > MAX_POOL_KEYS) throw new Error('schema');
|
|
427
|
+
const viste = new Set();
|
|
428
|
+
const nuove = dati.chiavi.map(r => {
|
|
429
|
+
if (!Array.isArray(r) || r.length !== 4 || !/^[a-f0-9]{64}$/u.test(r[0]) || viste.has(r[0])) throw new Error('schema');
|
|
430
|
+
viste.add(r[0]);
|
|
431
|
+
const v = voceChiave(keyring.get(POOL_SERVICE, `${provider}:${r[0]}`), r[1]);
|
|
432
|
+
if (v.impronta !== r[0] || (r[2] !== null && (!Number.isSafeInteger(r[2]) || r[2] < 0 || r[2] > 8.64e15)) || (r[3] !== null && !Object.hasOwn(PANCHINE_PROVIDER_MS, r[3]))) throw new Error('schema');
|
|
433
|
+
return { ...v, inPanchinaFino: r[2], causa: r[3] };
|
|
434
|
+
});
|
|
435
|
+
pools.set(provider, nuove); if (nuove.length) loaded += 1;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const value = keyring.get(KEYRING_SERVICE, provider);
|
|
439
|
+
if (typeof value === 'string' && value.trim() !== '' && value.length <= MAX_KEY_LENGTH) { pools.set(provider, [voceChiave(value)]); daPortachiavi.add(provider); loaded += 1; }
|
|
440
|
+
} catch { noSecretLogger(logger, `Chiave ${provider} non leggibile dal portachiavi`); }
|
|
441
|
+
}
|
|
442
|
+
return { loaded, available: true };
|
|
443
|
+
}
|
|
444
|
+
function getRuntime(provider) {
|
|
445
|
+
if (provider === 'esterno') return { provider, agente: structuredClone(runtimes.get(provider)?.agente ?? null) };
|
|
446
|
+
const definition = requireProvider(provider);
|
|
447
|
+
const saved = runtimes.get(provider);
|
|
448
|
+
const endpoint = saved?.endpoint ?? definition.defaultEndpoint;
|
|
449
|
+
// P-K — campi non segreti derivati dalla stessa preferenza anche dopo riavvio.
|
|
450
|
+
const cloud = REGISTRO_FORNITORI[provider].cloud && endpoint ? normalizzaRuntimeCloud(provider, { endpoint }) : null;
|
|
451
|
+
return { provider, endpoint, endpointConfigured: saved?.endpointConfigured === true, timeoutSeconds: saved?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS, ...(cloud ?? {}),
|
|
452
|
+
...(REGISTRO_FORNITORI[provider].cloud ? { modelli: structuredClone(saved?.modelli ?? []) } : {}) };
|
|
453
|
+
// P-K — fine
|
|
454
|
+
}
|
|
455
|
+
function setRuntime(provider, value = {}) {
|
|
456
|
+
// P-K-bis: la whitelist vale anche fuori da HTTP; validare tutto prima di scrivere.
|
|
457
|
+
if (!isRecord(value) || Object.keys(value).some(k => !(provider === 'esterno' ? ['agente'] : ['endpoint', 'timeoutSeconds', 'modelli']).includes(k))) {
|
|
458
|
+
throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID');
|
|
459
|
+
}
|
|
460
|
+
if (provider === 'esterno') {
|
|
461
|
+
const previous = runtimes.get(provider);
|
|
462
|
+
const agente = normalizzaAgente(value.agente, env, segretiRuntime());
|
|
463
|
+
runtimes.set(provider, { agente });
|
|
464
|
+
try { writeRuntimePreferences(runtimeFile, runtimes); }
|
|
465
|
+
catch (error) { if (previous) runtimes.set(provider, previous); else runtimes.delete(provider); throw error; }
|
|
466
|
+
return getRuntime(provider);
|
|
467
|
+
}
|
|
468
|
+
const { endpoint, timeoutSeconds = (REGISTRO_FORNITORI[provider]?.cloud ? runtimes.get(provider)?.timeoutSeconds : null) ?? DEFAULT_TIMEOUT_SECONDS } = value;
|
|
469
|
+
const definition = requireProvider(provider);
|
|
470
|
+
const timeout = normalizeTimeout(timeoutSeconds);
|
|
471
|
+
const previous = runtimes.get(provider);
|
|
472
|
+
const extra = Object.hasOwn(value, 'modelli') ? { modelli: normalizzaModelli(provider, value.modelli, segretiRuntime()) }
|
|
473
|
+
: previous?.modelli ? { modelli: previous.modelli } : {};
|
|
474
|
+
if (!definition.supportsEndpoint) {
|
|
475
|
+
if (!definition.supportsTimeout) throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID');
|
|
476
|
+
runtimes.set(provider, { endpoint: null, endpointConfigured: false, timeoutSeconds: timeout });
|
|
477
|
+
try { writeRuntimePreferences(runtimeFile, runtimes); }
|
|
478
|
+
catch (error) { if (previous) runtimes.set(provider, previous); else runtimes.delete(provider); throw error; }
|
|
479
|
+
return getRuntime(provider);
|
|
480
|
+
}
|
|
481
|
+
const parzialeCloud = REGISTRO_FORNITORI[provider].cloud && !Object.hasOwn(value, 'endpoint');
|
|
482
|
+
const normalized = parzialeCloud ? previous?.endpoint ?? null : normalizeProviderEndpoint(provider, endpoint);
|
|
483
|
+
runtimes.set(provider, { endpoint: normalized, endpointConfigured: parzialeCloud ? previous?.endpointConfigured === true : true, timeoutSeconds: timeout, ...extra });
|
|
484
|
+
try { writeRuntimePreferences(runtimeFile, runtimes); }
|
|
485
|
+
catch (error) { if (previous) runtimes.set(provider, previous); else runtimes.delete(provider); throw error; }
|
|
486
|
+
return getRuntime(provider);
|
|
487
|
+
}
|
|
488
|
+
function resetEndpoint(provider) {
|
|
489
|
+
const definition = requireProvider(provider);
|
|
490
|
+
if (!definition.supportsEndpoint) throw new ProviderCredentialError('PROVIDER_RUNTIME_INVALID');
|
|
491
|
+
const current = runtimes.get(provider);
|
|
492
|
+
runtimes.set(provider, { ...current, endpoint: null, endpointConfigured: false, timeoutSeconds: current?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS });
|
|
493
|
+
try { writeRuntimePreferences(runtimeFile, runtimes); }
|
|
494
|
+
catch (error) { if (current) runtimes.set(provider, current); else runtimes.delete(provider); throw error; }
|
|
495
|
+
return getRuntime(provider);
|
|
496
|
+
}
|
|
497
|
+
function listPublic() {
|
|
498
|
+
return PROVIDER_IDS.map((provider) => {
|
|
499
|
+
const definition = PROVIDER_DEFINITIONS[provider];
|
|
500
|
+
const runtime = getRuntime(provider);
|
|
501
|
+
return {
|
|
502
|
+
id: provider,
|
|
503
|
+
label: definition.label,
|
|
504
|
+
requiresKey: definition.requiresKey,
|
|
505
|
+
keyConfigured: hasKey(provider),
|
|
506
|
+
pool: elencaPool(provider),
|
|
507
|
+
modelliDiRiserva: REGISTRO_FORNITORI[provider].modelliDiRiserva ?? [],
|
|
508
|
+
supportsEndpoint: definition.supportsEndpoint,
|
|
509
|
+
endpoint: runtime.endpoint,
|
|
510
|
+
endpointConfigured: runtime.endpointConfigured,
|
|
511
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
512
|
+
// P-K — schema e aiuto per i soli campi pubblici del pannello.
|
|
513
|
+
...(REGISTRO_FORNITORI[provider].cloud ? { cloud: REGISTRO_FORNITORI[provider].cloud,
|
|
514
|
+
modelli: runtime.modelli,
|
|
515
|
+
regione: runtime.regione ?? null, progetto: runtime.progetto ?? null,
|
|
516
|
+
endpointRisorsa: runtime.endpointRisorsa ?? null, versioneApi: runtime.versioneApi ?? 'v1' } : {}),
|
|
517
|
+
// P-K — fine
|
|
518
|
+
execution: definition.execution,
|
|
519
|
+
/*
|
|
520
|
+
* ⛔ PO-01 (10/09) — la guardia della UI: il pulsante «Accedi con …» compare solo dove il
|
|
521
|
+
* server sa servire il flusso. Un pulsante che apre un accesso inesistente è peggio di
|
|
522
|
+
* nessun pulsante.
|
|
523
|
+
*/
|
|
524
|
+
supportsOAuth: definition.supportsOAuth === true,
|
|
525
|
+
/*
|
|
526
|
+
* ⛔ Da DOVE viene la chiave in uso. Il portachiavi VINCE sull'ambiente (l'ambiente semina
|
|
527
|
+
* la mappa alla partenza, `loadFromKeyring` la sovrascrive), e questo campo lo dice
|
|
528
|
+
* invece di lasciarlo scoprire: senza, «Rimuovi chiave» sembra rotto quando la chiave
|
|
529
|
+
* dall'ambiente continua a funzionare dopo.
|
|
530
|
+
*/
|
|
531
|
+
origineChiave: daPortachiavi.has(provider) ? 'custodia' : (hasKey(provider) ? 'ambiente' : null),
|
|
532
|
+
};
|
|
533
|
+
}).concat({ id: 'esterno', label: REGISTRO_FORNITORI.esterno.etichetta, requiresKey: false,
|
|
534
|
+
keyConfigured: false, pool: [], modelliDiRiserva: [], supportsEndpoint: false, supportsOAuth: false,
|
|
535
|
+
origineChiave: null, execution: getRuntime('esterno').agente ? 'configurato' : 'da configurare',
|
|
536
|
+
agente: getRuntime('esterno').agente });
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return Object.freeze({ getKey, getKeySync: getKey, hasKey, setKey, clearKey, loadFromKeyring, getRuntime, setRuntime, resetEndpoint, listPublic,
|
|
540
|
+
aggiungiChiave, rimuoviChiave, elencaPool, scegliChiave, mettiInPanchina });
|
|
541
|
+
}
|