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.
Files changed (401) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +111 -3
  3. package/THIRD_PARTY_NOTICES.md +40 -0
  4. package/dist/archive/zip.js +73 -0
  5. package/dist/args.js +97 -0
  6. package/dist/automations/history-store.js +34 -0
  7. package/dist/automations/policy-store.js +43 -0
  8. package/dist/automations/runner.js +107 -0
  9. package/dist/automations/schedule.js +85 -0
  10. package/dist/automations/windows-task.js +53 -0
  11. package/dist/commands/advanced-cli.js +233 -0
  12. package/dist/commands/checkpoint-cli.js +45 -0
  13. package/dist/commands/config-cli.js +107 -0
  14. package/dist/commands/context.js +17 -0
  15. package/dist/commands/extensions-cli.js +207 -0
  16. package/dist/commands/project-cli.js +74 -0
  17. package/dist/commands/project-commands.js +143 -0
  18. package/dist/commands/provider-cli.js +185 -0
  19. package/dist/commands/services-cli.js +248 -0
  20. package/dist/commands/session-cli.js +171 -0
  21. package/dist/commands/system-cli.js +405 -0
  22. package/dist/config/commands.js +60 -0
  23. package/dist/config/load.js +382 -0
  24. package/dist/config/migrations.js +48 -0
  25. package/dist/config/types.js +11 -0
  26. package/dist/diagnostics/development-log.js +154 -0
  27. package/dist/diagnostics/doctor.js +104 -0
  28. package/dist/diagnostics/redact.js +80 -0
  29. package/dist/diagnostics/zip.js +52 -0
  30. package/dist/errors.js +156 -0
  31. package/dist/events/bridge.js +130 -0
  32. package/dist/extensions/installer.js +178 -0
  33. package/dist/extensions/package-schema.js +24 -0
  34. package/dist/headless/result.js +84 -0
  35. package/dist/headless/run.js +230 -0
  36. package/dist/i18n/en/approval.js +43 -0
  37. package/dist/i18n/en/common.js +9 -0
  38. package/dist/i18n/en/credentials.js +82 -0
  39. package/dist/i18n/en/errors.js +246 -0
  40. package/dist/i18n/en/firstrun.js +89 -0
  41. package/dist/i18n/en/providers.js +37 -0
  42. package/dist/i18n/en/screen.js +736 -0
  43. package/dist/i18n/en/tools.js +127 -0
  44. package/dist/i18n/en.js +14 -0
  45. package/dist/i18n/error-view.js +307 -0
  46. package/dist/i18n/index.js +19 -0
  47. package/dist/i18n/kernel-map.js +139 -0
  48. package/dist/io.js +45 -0
  49. package/dist/main.js +330 -0
  50. package/dist/paths.js +41 -0
  51. package/dist/protocol/v2/codec.js +9 -0
  52. package/dist/protocol/v2/events.js +555 -0
  53. package/dist/protocol/v2/index.js +4 -0
  54. package/dist/protocol/v2/replay.js +28 -0
  55. package/dist/protocol/v2/types.js +1 -0
  56. package/dist/provider/control-plane.js +135 -0
  57. package/dist/provider/environment-keys.js +275 -0
  58. package/dist/provider/health.js +110 -0
  59. package/dist/provider/missing-key.js +48 -0
  60. package/dist/provider/model-catalog.js +168 -0
  61. package/dist/provider/provider-text.js +13 -0
  62. package/dist/provider/store.js +95 -0
  63. package/dist/provider/system-keyring.js +214 -0
  64. package/dist/runtime/active-run.js +63 -0
  65. package/dist/runtime/agent-tree.js +74 -0
  66. package/dist/runtime/attachments.js +84 -0
  67. package/dist/runtime/brokered-executor.js +395 -0
  68. package/dist/runtime/context-status.js +76 -0
  69. package/dist/runtime/create-runtime.js +111 -0
  70. package/dist/runtime/model-profile.js +361 -0
  71. package/dist/runtime/output-store.js +137 -0
  72. package/dist/runtime/provider-attempts.js +185 -0
  73. package/dist/runtime/replay-buffer.js +153 -0
  74. package/dist/runtime/repo.js +95 -0
  75. package/dist/runtime/session-facade.js +135 -0
  76. package/dist/runtime/session-summary.js +125 -0
  77. package/dist/runtime/supervisor.js +262 -0
  78. package/dist/runtime/talos-composition.js +1006 -0
  79. package/dist/runtime/types.js +5 -0
  80. package/dist/runtime/usage-snapshot.js +82 -0
  81. package/dist/security/auto-classifier.js +38 -0
  82. package/dist/security/credential-free-environment.js +54 -0
  83. package/dist/security/evaluate.js +243 -0
  84. package/dist/security/execution-backends.js +236 -0
  85. package/dist/security/execution-broker.js +102 -0
  86. package/dist/security/forge-scan.js +74 -0
  87. package/dist/security/from-approval.js +39 -0
  88. package/dist/security/mxc-execution-backend.js +281 -0
  89. package/dist/security/permission-engine.js +138 -0
  90. package/dist/security/permission-explanation.js +54 -0
  91. package/dist/security/persist.js +179 -0
  92. package/dist/security/plugin-guard.js +230 -0
  93. package/dist/security/process-tree-evidence-store.js +74 -0
  94. package/dist/security/process-tree-probe.js +554 -0
  95. package/dist/security/project-resource-inventory.js +186 -0
  96. package/dist/security/project-trust-gate.js +38 -0
  97. package/dist/security/project-trust.js +367 -0
  98. package/dist/security/rule-parser.js +201 -0
  99. package/dist/security/shell-segmentation.js +68 -0
  100. package/dist/security/trust-authority.js +324 -0
  101. package/dist/security/types.js +1 -0
  102. package/dist/security/workspace-identity.js +102 -0
  103. package/dist/services/automation-facade.js +59 -0
  104. package/dist/services/forge-facade.js +77 -0
  105. package/dist/services/hook-facade.js +240 -0
  106. package/dist/services/index.js +16 -0
  107. package/dist/services/library-facade.js +174 -0
  108. package/dist/services/mcp-facade.js +348 -0
  109. package/dist/services/memory-facade.js +126 -0
  110. package/dist/services/notes-facade.js +157 -0
  111. package/dist/services/plugin-facade.js +308 -0
  112. package/dist/services/research-facade.js +110 -0
  113. package/dist/services/task-board-facade.js +236 -0
  114. package/dist/services/workflow-catalog.js +246 -0
  115. package/dist/sessions/export-format.js +98 -0
  116. package/dist/sessions/metadata-store.js +160 -0
  117. package/dist/sessions/share.js +122 -0
  118. package/dist/sessions/transfer.js +16 -0
  119. package/dist/subcommands.js +62 -0
  120. package/dist/tui/agent-roster.js +36 -0
  121. package/dist/tui/app.js +3761 -0
  122. package/dist/tui/approval.js +36 -0
  123. package/dist/tui/boot/boot-sequence.js +76 -0
  124. package/dist/tui/boot/cinematic.js +243 -0
  125. package/dist/tui/boot/desktop-logo.js +82 -0
  126. package/dist/tui/boot.js +3 -0
  127. package/dist/tui/busy-input.js +58 -0
  128. package/dist/tui/catalog-service.js +559 -0
  129. package/dist/tui/components/assistant-stream.js +1 -0
  130. package/dist/tui/components/command-menu.js +68 -0
  131. package/dist/tui/components/composer.js +219 -0
  132. package/dist/tui/components/diff.js +35 -0
  133. package/dist/tui/components/footer.js +48 -0
  134. package/dist/tui/components/header.js +6 -0
  135. package/dist/tui/components/markdown.js +305 -0
  136. package/dist/tui/components/status-indicator.js +32 -0
  137. package/dist/tui/components/terminal-shell.js +205 -0
  138. package/dist/tui/components/thinking-row.js +19 -0
  139. package/dist/tui/components/tool-row.js +97 -0
  140. package/dist/tui/components/transcript-virtualizer.js +165 -0
  141. package/dist/tui/components/transcript.js +43 -0
  142. package/dist/tui/diff-model.js +77 -0
  143. package/dist/tui/editor-history.js +21 -0
  144. package/dist/tui/editor.js +210 -0
  145. package/dist/tui/event-adapter.js +436 -0
  146. package/dist/tui/exit-output.js +58 -0
  147. package/dist/tui/external-editor.js +53 -0
  148. package/dist/tui/file-completion.js +89 -0
  149. package/dist/tui/focus-manager.js +5 -0
  150. package/dist/tui/highlight.js +30 -0
  151. package/dist/tui/input-router.js +18 -0
  152. package/dist/tui/interrupt.js +99 -0
  153. package/dist/tui/keybindings.js +194 -0
  154. package/dist/tui/keymap-resolver.js +91 -0
  155. package/dist/tui/launch-state.js +139 -0
  156. package/dist/tui/line-diff.js +57 -0
  157. package/dist/tui/live-activity.js +45 -0
  158. package/dist/tui/metrics.js +123 -0
  159. package/dist/tui/onboarding.js +36 -0
  160. package/dist/tui/overlays/agent-tree.js +39 -0
  161. package/dist/tui/overlays/approval-dialog.js +640 -0
  162. package/dist/tui/overlays/automation-center.js +51 -0
  163. package/dist/tui/overlays/checkpoint-picker.js +47 -0
  164. package/dist/tui/overlays/context-inspector.js +36 -0
  165. package/dist/tui/overlays/effort-line.js +110 -0
  166. package/dist/tui/overlays/forge-center.js +46 -0
  167. package/dist/tui/overlays/help-dialog.js +16 -0
  168. package/dist/tui/overlays/history-picker.js +37 -0
  169. package/dist/tui/overlays/hook-center.js +70 -0
  170. package/dist/tui/overlays/library-center.js +55 -0
  171. package/dist/tui/overlays/mcp-center.js +60 -0
  172. package/dist/tui/overlays/memory-center.js +63 -0
  173. package/dist/tui/overlays/model-picker.js +72 -0
  174. package/dist/tui/overlays/notes-center.js +53 -0
  175. package/dist/tui/overlays/overlay-host.js +8 -0
  176. package/dist/tui/overlays/plugin-center.js +76 -0
  177. package/dist/tui/overlays/provider-picker.js +145 -0
  178. package/dist/tui/overlays/queue-editor.js +32 -0
  179. package/dist/tui/overlays/research-center.js +59 -0
  180. package/dist/tui/overlays/scroll-window.js +234 -0
  181. package/dist/tui/overlays/session-picker.js +115 -0
  182. package/dist/tui/overlays/tasks-center.js +83 -0
  183. package/dist/tui/overlays/theme-picker.js +21 -0
  184. package/dist/tui/overlays/transcript-search.js +58 -0
  185. package/dist/tui/overlays/trust-center.js +29 -0
  186. package/dist/tui/overlays/workflow-center.js +46 -0
  187. package/dist/tui/project-file-index.js +214 -0
  188. package/dist/tui/project-references.js +85 -0
  189. package/dist/tui/project-trust-prompt.js +287 -0
  190. package/dist/tui/prompt-history-store.js +116 -0
  191. package/dist/tui/queue-store.js +110 -0
  192. package/dist/tui/regions/budget.js +59 -0
  193. package/dist/tui/regions/views.js +96 -0
  194. package/dist/tui/render-coordinator.js +148 -0
  195. package/dist/tui/render-scheduler.js +4 -0
  196. package/dist/tui/run.js +69 -0
  197. package/dist/tui/selection-list.js +29 -0
  198. package/dist/tui/session-controller.js +778 -0
  199. package/dist/tui/session-export.js +54 -0
  200. package/dist/tui/shell-input.js +35 -0
  201. package/dist/tui/shell-layout.js +48 -0
  202. package/dist/tui/shell-model.js +98 -0
  203. package/dist/tui/slash-commands.js +80 -0
  204. package/dist/tui/state.js +151 -0
  205. package/dist/tui/status-bar.js +125 -0
  206. package/dist/tui/status-view.js +40 -0
  207. package/dist/tui/terminal-capabilities.js +9 -0
  208. package/dist/tui/terminal-session.js +11 -0
  209. package/dist/tui/text-width.js +66 -0
  210. package/dist/tui/theme-catalog.js +25 -0
  211. package/dist/tui/theme-store.js +23 -0
  212. package/dist/tui/theme.js +23 -0
  213. package/dist/tui/tool-display.js +135 -0
  214. package/dist/tui/tool-facts.js +1 -0
  215. package/dist/tui/tools/bash-renderer.js +32 -0
  216. package/dist/tui/tools/change.js +61 -0
  217. package/dist/tui/tools/edit-renderer.js +17 -0
  218. package/dist/tui/tools/generic-renderer.js +13 -0
  219. package/dist/tui/tools/list-renderer.js +14 -0
  220. package/dist/tui/tools/read-renderer.js +13 -0
  221. package/dist/tui/tools/registry.js +20 -0
  222. package/dist/tui/tools/row-format.js +187 -0
  223. package/dist/tui/tools/search-renderer.js +38 -0
  224. package/dist/tui/tools/shared.js +97 -0
  225. package/dist/tui/tools/write-renderer.js +15 -0
  226. package/dist/tui/transcript-model.js +441 -0
  227. package/dist/tui/ui-preferences.js +79 -0
  228. package/dist/tui/usage-view.js +77 -0
  229. package/dist/tui/vim-mode.js +99 -0
  230. package/dist/update/check.js +15 -0
  231. package/dist/update/npm.js +51 -0
  232. package/dist/update/run.js +129 -0
  233. package/dist/version.js +2 -0
  234. package/dist/workspace/checkpoint-store.js +210 -0
  235. package/dist/workspace/checkpoint.js +413 -0
  236. package/dist/workspace/restore.js +232 -0
  237. package/package.json +63 -5
  238. package/vendor/context-engine/package.json +11 -0
  239. package/vendor/context-engine/src/compaction-planner.mjs +57 -0
  240. package/vendor/context-engine/src/contracts.mjs +48 -0
  241. package/vendor/context-engine/src/engine.mjs +445 -0
  242. package/vendor/context-engine/src/node/context-export.mjs +164 -0
  243. package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
  244. package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
  245. package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
  246. package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
  247. package/vendor/context-engine/src/profiles.mjs +10 -0
  248. package/vendor/context-engine/src/retrieval.mjs +104 -0
  249. package/vendor/context-engine/src/summary.mjs +97 -0
  250. package/vendor/context-engine/src/usage.mjs +34 -0
  251. package/vendor/harness-ui/package.json +38 -0
  252. package/vendor/harness-ui/src/acp-agent.mjs +350 -0
  253. package/vendor/harness-ui/src/agent-service.mjs +2188 -0
  254. package/vendor/harness-ui/src/agui-events.mjs +452 -0
  255. package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
  256. package/vendor/harness-ui/src/artifact-store.mjs +39 -0
  257. package/vendor/harness-ui/src/assistenza.mjs +123 -0
  258. package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
  259. package/vendor/harness-ui/src/automation-store.mjs +145 -0
  260. package/vendor/harness-ui/src/browser-annota.mjs +639 -0
  261. package/vendor/harness-ui/src/browser-frame.mjs +211 -0
  262. package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
  263. package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
  264. package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
  265. package/vendor/harness-ui/src/browser-stream.mjs +445 -0
  266. package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
  267. package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
  268. package/vendor/harness-ui/src/config.mjs +697 -0
  269. package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
  270. package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
  271. package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
  272. package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
  273. package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
  274. package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
  275. package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
  276. package/vendor/harness-ui/src/context-runtime.mjs +118 -0
  277. package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
  278. package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
  279. package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
  280. package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
  281. package/vendor/harness-ui/src/custom-task.mjs +171 -0
  282. package/vendor/harness-ui/src/doctor.mjs +142 -0
  283. package/vendor/harness-ui/src/document-filename.mjs +97 -0
  284. package/vendor/harness-ui/src/document-generator.mjs +493 -0
  285. package/vendor/harness-ui/src/document-report.mjs +331 -0
  286. package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
  287. package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
  288. package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
  289. package/vendor/harness-ui/src/forge-contract.mjs +221 -0
  290. package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
  291. package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
  292. package/vendor/harness-ui/src/generation-idle.mjs +332 -0
  293. package/vendor/harness-ui/src/gguf-header.mjs +207 -0
  294. package/vendor/harness-ui/src/git-service.mjs +626 -0
  295. package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
  296. package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
  297. package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
  298. package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
  299. package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
  300. package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
  301. package/vendor/harness-ui/src/hook-registry.mjs +186 -0
  302. package/vendor/harness-ui/src/http-app.mjs +6259 -0
  303. package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
  304. package/vendor/harness-ui/src/id-archivio.mjs +27 -0
  305. package/vendor/harness-ui/src/image-generator.mjs +143 -0
  306. package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
  307. package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
  308. package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
  309. package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
  310. package/vendor/harness-ui/src/library-store.mjs +652 -0
  311. package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
  312. package/vendor/harness-ui/src/local-model-store.mjs +339 -0
  313. package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
  314. package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
  315. package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
  316. package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
  317. package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
  318. package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
  319. package/vendor/harness-ui/src/mcp-client.mjs +98 -0
  320. package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
  321. package/vendor/harness-ui/src/mcp-session.mjs +177 -0
  322. package/vendor/harness-ui/src/memory-store.mjs +227 -0
  323. package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
  324. package/vendor/harness-ui/src/model-catalog.mjs +129 -0
  325. package/vendor/harness-ui/src/model-destination.mjs +189 -0
  326. package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
  327. package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
  328. package/vendor/harness-ui/src/notes-store.mjs +250 -0
  329. package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
  330. package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
  331. package/vendor/harness-ui/src/path-policy.mjs +442 -0
  332. package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
  333. package/vendor/harness-ui/src/plugin-session.mjs +180 -0
  334. package/vendor/harness-ui/src/process-policy.mjs +345 -0
  335. package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
  336. package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
  337. package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
  338. package/vendor/harness-ui/src/provider-probe.mjs +582 -0
  339. package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
  340. package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
  341. package/vendor/harness-ui/src/public-problem.mjs +109 -0
  342. package/vendor/harness-ui/src/research/card.mjs +235 -0
  343. package/vendor/harness-ui/src/research/citations.mjs +142 -0
  344. package/vendor/harness-ui/src/research/collector.mjs +275 -0
  345. package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
  346. package/vendor/harness-ui/src/research/dossier.mjs +114 -0
  347. package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
  348. package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
  349. package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
  350. package/vendor/harness-ui/src/research/independence.mjs +159 -0
  351. package/vendor/harness-ui/src/research/ledger.mjs +166 -0
  352. package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
  353. package/vendor/harness-ui/src/research/narration.mjs +181 -0
  354. package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
  355. package/vendor/harness-ui/src/research/opposing.mjs +305 -0
  356. package/vendor/harness-ui/src/research/outline.mjs +111 -0
  357. package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
  358. package/vendor/harness-ui/src/research/pdf.mjs +291 -0
  359. package/vendor/harness-ui/src/research/plan.mjs +301 -0
  360. package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
  361. package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
  362. package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
  363. package/vendor/harness-ui/src/research/recheck.mjs +194 -0
  364. package/vendor/harness-ui/src/research/report.mjs +203 -0
  365. package/vendor/harness-ui/src/research/run.mjs +527 -0
  366. package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
  367. package/vendor/harness-ui/src/research/verification.mjs +572 -0
  368. package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
  369. package/vendor/harness-ui/src/research-store.mjs +1133 -0
  370. package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
  371. package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
  372. package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
  373. package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
  374. package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
  375. package/vendor/harness-ui/src/search-source-store.mjs +172 -0
  376. package/vendor/harness-ui/src/session-registry.mjs +6095 -0
  377. package/vendor/harness-ui/src/session-store.mjs +220 -0
  378. package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
  379. package/vendor/harness-ui/src/setup-stato.mjs +31 -0
  380. package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
  381. package/vendor/harness-ui/src/skill-registry.mjs +120 -0
  382. package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
  383. package/vendor/harness-ui/src/static-files.mjs +96 -0
  384. package/vendor/harness-ui/src/stream-partition.mjs +123 -0
  385. package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
  386. package/vendor/harness-ui/src/task-catalog.mjs +65 -0
  387. package/vendor/harness-ui/src/tasks-store.mjs +220 -0
  388. package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
  389. package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
  390. package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
  391. package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
  392. package/vendor/harness-ui/src/usage-cache.mjs +315 -0
  393. package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
  394. package/vendor/harness-ui/src/workspace-context.mjs +124 -0
  395. package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
  396. package/vendor/harness-ui/src/workspace-files.mjs +589 -0
  397. package/vendor/harness-ui/src/workspace-info.mjs +189 -0
  398. package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
  399. package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
  400. package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
  401. package/vendor/manifest.json +170 -0
@@ -0,0 +1,1633 @@
1
+ /**
2
+ * provider-registry.mjs — UN record per fornitore, e tutto il resto si deriva da qui.
3
+ *
4
+ * ## Perché esiste
5
+ *
6
+ * Inventario del 12/09/2026 (`.claude/INVENTARIO-PROVIDER-2026-09-12.md`, §3b): non c'era un
7
+ * record di fornitore. C'era lo stesso insieme di sette nomi **riscritto tredici volte**, in
8
+ * tredici forme diverse — array, oggetti congelati, una stringa dentro una regex, due regex di
9
+ * rotta HTTP duplicate, un `<select>` di `<option>` scritte a mano — e **nessun test** teneva
10
+ * insieme le tredici. Due bugie erano già lì, visibili a chiunque aprisse due file invece di uno:
11
+ *
12
+ * 1. `lmstudio` era **scoperto, sondato, caricabile e scaricabile** in
13
+ * `openai-compatible-runtime.mjs`, con capacità OSSERVATE che Ollama non dà — e non era in
14
+ * `FONTI_MODELLO`: non si poteva scegliere in chat. Il lavoro era fatto al 90% e non arrivava
15
+ * all'utente.
16
+ * 2. `deepseek` dichiarava `execution: 'in preparazione'` nel portachiavi mentre
17
+ * `model-destination.mjs` lo instradava **da sempre**. Due elenchi, due verità.
18
+ *
19
+ * ⛔ È la stessa forma della lezione del 02/09 (`viewport-desktop-non-solo-tablet`: «c'erano NOVE
20
+ * copie sparse, due le ha trovate il test»). La cura è identica: la verità sta in UN posto, e
21
+ * un **cancello di parità** (`tests/provider-registry-parita.test.mjs`) fallisce se un elenco
22
+ * derivato diverge. Senza quel cancello la grammatica unica ridiventa tredici elenchi al primo
23
+ * fornitore aggiunto di fretta — è esattamente ciò che Hermes dichiara di aver già vissuto
24
+ * (`hermes_cli/provider_catalog.py:35`: «ogni provider aggiunto dopo che quelle liste sono
25
+ * state scritte spariva in silenzio dalla GUI»).
26
+ *
27
+ * ## La forma, e da dove viene
28
+ *
29
+ * Lo schema è quello proposto nel §6a dell'inventario, che a sua volta ricalca il `ProviderProfile`
30
+ * di Hermes v0.21 (`providers/base.py:38-100`, letto nel codice clonato a commit fissato). Le
31
+ * differenze sono deliberate e dette:
32
+ *
33
+ * · **niente hook eseguibili** — qui è dato, non codice: un record che porta funzioni torna a
34
+ * essere codice sparso con un altro nome. Le uniche funzioni ammesse sono `conta` della sonda
35
+ * (una proiezione pura sul corpo della risposta) e i lettori della cache, che sono PERCORSI
36
+ * dichiarati, non logica;
37
+ * · **tre stati per le capacità**, mai un booleano solo: `osservato` (l'abbiamo misurato),
38
+ * `dichiarato` (lo dice la documentazione del fornitore), `ignoto`. È la lezione
39
+ * «`ok:false` su un elenco vero fa INVENTARE — gli stati sono TRE»;
40
+ * · **come si legge la cache** è una colonna del record, non un `??` sparso: vedi `usage-cache.mjs`.
41
+ *
42
+ * ## Ricerca fatta PRIMA di scrivere (12/09/2026)
43
+ *
44
+ * · DeepSeek — `https://api-docs.deepseek.com/api/create-chat-completion`: «prompt_tokens … It
45
+ * equals prompt_cache_hit_tokens + prompt_cache_miss_tokens», e `prompt_tokens_details.cached_tokens`
46
+ * ESISTE nella stessa risposta. ⇒ la forma DeepSeek è **alternativa**, non additiva: sommarla ai
47
+ * `prompt_tokens` conterebbe due volte.
48
+ * · DeepSeek — `https://api-docs.deepseek.com/guides/kv_cache`: definizione dei due campi.
49
+ * · Kimi/Moonshot — `https://platform.kimi.ai/docs/api/chat`: `cached_tokens` — «Number of tokens
50
+ * served from cache» — al PRIMO livello di `usage`, non dentro `prompt_tokens_details`.
51
+ * · OpenRouter — `https://openrouter.ai/docs/features/prompt-caching`: `usage.prompt_tokens_details.
52
+ * cached_tokens` e `cache_write_tokens`; **`cache_discount` sta al livello del corpo della
53
+ * risposta** ed è un risparmio in DENARO, non un conteggio di token («Some providers, like
54
+ * Anthropic, will have a negative discount on cache writes»).
55
+ * · LM Studio — `https://lmstudio.ai/docs/app/api/endpoints/openai`: `/v1/models`, `/v1/responses`,
56
+ * `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions` su `http://localhost:1234/v1`,
57
+ * nessuna chiave richiesta negli esempi.
58
+ * · LM Studio — `https://lmstudio.ai/docs/app/api/endpoints/rest`: l'API nativa `/api/v1/models`,
59
+ * `/api/v1/models/load`, `/api/v1/models/unload` (v0.4.0), con «authentication configuration
60
+ * with API tokens» ⇒ la chiave esiste ma è **facoltativa**.
61
+ *
62
+ * ⛔ Nessuna chiamata vera a un fornitore è stata fatta per scrivere questo file: sono documenti.
63
+ */
64
+
65
+ /** I trasporti che sappiamo parlare. ⛔ Un `wire` fuori da qui è un errore, non un ripiego. */
66
+ export const WIRE = Object.freeze([
67
+ 'openai-chat', // POST {base}{percorso} con Bearer, corpo OpenAI chat/completions
68
+ 'openai-responses', // POST {base}/responses — oggi via SDK fissato nel lock
69
+ 'anthropic-messages', // POST {base}/messages — oggi via SDK fissato nel lock
70
+ 'gemini', // :generateContent — oggi via SDK fissato nel lock
71
+ 'locale', // il supervisore llama-server: la chiave vive solo lì dentro
72
+ // P-L · protocollo v1 su processo stdio; ponte OpenAI nel confine Fetch.
73
+ 'acp',
74
+ // P-L · fine wire.
75
+ ]);
76
+
77
+ /** Gli schemi di autenticazione riconosciuti. */
78
+ export const AUTH = Object.freeze(['bearer', 'header', 'query', 'oauth-pkce', 'keyless', 'effimera']);
79
+
80
+ /** I tre stati di una capacità. ⛔ Mai un booleano: «non dichiarato» non è «no». */
81
+ export const STATI_CAPACITA = Object.freeze(['osservato', 'dichiarato', 'ignoto']);
82
+
83
+ const congela = (valore) => Object.freeze(valore);
84
+
85
+ /**
86
+ * ⛔ IL REGISTRO. Una riga per fornitore, e nient'altro al mondo che nomini un fornitore.
87
+ *
88
+ * L'ordine di dichiarazione è quello con cui i derivati si presentano: cambiarlo cambia l'ordine
89
+ * nel pannello Provider e nel prefisso ammesso dalla regex del modello, quindi si cambia apposta.
90
+ */
91
+ export const REGISTRO_FORNITORI = congela({
92
+ // P-L · fonte e data: https://agentclientprotocol.com/protocol/v1/initialization, 12/09/2026.
93
+ esterno: congela({
94
+ id: 'esterno', etichetta: 'Agente esterno', descrizione: 'Un agente installato su questo computer.',
95
+ modelliDiRiserva: null, modelloAusiliario: null, modelsDevId: null, paginaChiavi: null,
96
+ wire: 'acp', baseUrl: null, indirizzoModificabile: false, envIndirizzo: congela([]),
97
+ auth: congela({ tipo: 'keyless', header: null, nomeVariabile: congela([]) }),
98
+ chiaveObbligatoria: false, formaIdModello: 'nome', oauth: null,
99
+ endpoint: congela({ chat: '/chat/completions', modelli: null }),
100
+ streaming: 'dichiarato', toolCalling: 'ignoto',
101
+ cache: congela({ marcatore: null, letturaUsage: congela([]), scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null }),
102
+ catalogo: congela({ fonte: 'processo-esterno', forma: null, percorso: null, inUI: true }),
103
+ prezzi: congela({ fonte: 'nessuna' }),
104
+ limiti: congela({ timeoutPredefinitoSecondi: 180, tempoMassimoModificabile: false }),
105
+ sonda: congela({ attiva: false, auth: 'nessuna', percorso: null, urlAssoluto: null, conta: () => null }),
106
+ runtime: congela({ variabile: 'TALOS_AGENTE_ESTERNO', protocolVersion: 1,
107
+ campi: congela(['comando', 'argomenti', 'cwd', 'variabiliAmbiente', 'timeoutMs']) }),
108
+ destinazioneChat: true, credenziale: false, esecuzione: 'da configurare sul computer',
109
+ }),
110
+ // P-L · fine record.
111
+ // ── OpenAI — wire Responses, non chat/completions ──────────────────────────────────────────
112
+ openai: congela({
113
+ id: 'openai',
114
+ // P-F, fonti e strumenti verificati il 12/09/2026; parità con models.dev nel test.
115
+ modelliDiRiserva: congela([
116
+ // 12/09/2026 — https://developers.openai.com/api/docs/models/gpt-5-nano
117
+ congela({ id: 'gpt-5-nano', nome: 'GPT-5 Nano', toolCalling: true,
118
+ fonte: 'https://developers.openai.com/api/docs/models/gpt-5-nano', data: '2026-09-12' }),
119
+ // 12/09/2026 — https://developers.openai.com/api/docs/models/gpt-5-mini
120
+ congela({ id: 'gpt-5-mini', nome: 'GPT-5 Mini', toolCalling: true,
121
+ fonte: 'https://developers.openai.com/api/docs/models/gpt-5-mini', data: '2026-09-12' }),
122
+ ]),
123
+ // 12/09/2026 — Nano: 0,05/0,40 USD per milione ingresso/uscita; fonte prezzi nelle schede ufficiali.
124
+ modelloAusiliario: 'gpt-5-nano',
125
+ modelsDevId: 'openai',
126
+ etichetta: 'OpenAI',
127
+ descrizione: 'API diretta OpenAI, sul wire Responses.',
128
+ paginaChiavi: 'https://platform.openai.com/api-keys',
129
+ wire: 'openai-responses',
130
+ baseUrl: 'https://api.openai.com/v1',
131
+ indirizzoModificabile: true,
132
+ envIndirizzo: congela([]),
133
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['OPENAI_API_KEY']) }),
134
+ chiaveObbligatoria: true,
135
+ formaIdModello: 'nome',
136
+ oauth: null,
137
+ endpoint: congela({ chat: '/responses', modelli: '/models' }),
138
+ streaming: 'osservato',
139
+ toolCalling: 'osservato',
140
+ cache: congela({
141
+ marcatore: null, // automatica lato fornitore
142
+ letturaUsage: congela(['input_tokens_details.cached_tokens', 'prompt_tokens_details.cached_tokens']),
143
+ scritturaUsage: congela(['input_tokens_details.cache_write_tokens', 'input_tokens_details.cache_creation_tokens']),
144
+ inclusiNelTotale: true,
145
+ scontoDichiarato: null,
146
+ }),
147
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
148
+ prezzi: congela({ fonte: 'nessuna' }),
149
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
150
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null, conta: (c) => c?.data?.length }),
151
+ destinazioneChat: true,
152
+ credenziale: true,
153
+ esecuzione: 'collegato',
154
+ }),
155
+
156
+ // ── DeepSeek — la cache che già paghiamo (vedi usage-cache.mjs) ─────────────────────────────
157
+ deepseek: congela({
158
+ id: 'deepseek',
159
+ // P-F, fonti e strumenti verificati il 12/09/2026; parità con models.dev nel test.
160
+ modelliDiRiserva: congela([
161
+ // 12/09/2026 — https://api-docs.deepseek.com/quick_start/pricing/
162
+ congela({ id: 'deepseek-flash', nome: 'DeepSeek V4.1 Flash', toolCalling: true,
163
+ fonte: 'https://api-docs.deepseek.com/quick_start/pricing/', data: '2026-09-12' }),
164
+ // 12/09/2026 — https://api-docs.deepseek.com/quick_start/pricing/
165
+ congela({ id: 'deepseek-v4-pro', nome: 'DeepSeek V4 Pro', toolCalling: true,
166
+ fonte: 'https://api-docs.deepseek.com/quick_start/pricing/', data: '2026-09-12' }),
167
+ ]),
168
+ // 12/09/2026 — Flash costa meno di Pro sia al picco sia fuori picco; fonte nella tabella ufficiale.
169
+ modelloAusiliario: 'deepseek-flash',
170
+ modelsDevId: 'deepseek',
171
+ etichetta: 'DeepSeek',
172
+ descrizione: 'API diretta DeepSeek, wire OpenAI.',
173
+ paginaChiavi: 'https://platform.deepseek.com/api_keys',
174
+ wire: 'openai-chat',
175
+ /*
176
+ * ⛔ Senza `/v1`, e la concatenazione aggiunge `/chat/completions`. È il valore che il
177
+ * portachiavi ha SEMPRE avuto: cambiarlo qui cambierebbe l'indirizzo di chi ha già salvato.
178
+ * ❓ NON verificato dal vivo quale delle due forme risponda (§7.7 dell'inventario): questo
179
+ * record non scioglie quel dubbio, lo lascia dov'era e lo dichiara.
180
+ */
181
+ baseUrl: 'https://api.deepseek.com',
182
+ indirizzoModificabile: true,
183
+ envIndirizzo: congela([]),
184
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['DEEPSEEK_API_KEY']) }),
185
+ chiaveObbligatoria: true,
186
+ formaIdModello: 'nome',
187
+ oauth: null,
188
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
189
+ streaming: 'dichiarato',
190
+ toolCalling: 'dichiarato',
191
+ cache: congela({
192
+ marcatore: null, // automatica: nessun marcatore da mandare
193
+ /*
194
+ * ⛔ L'ORDINE È IL CONTRATTO. `prompt_tokens_details.cached_tokens` c'è ed è il nome
195
+ * canonico; `prompt_cache_hit_tokens` è la forma NATIVA di DeepSeek. Si legge il primo che
196
+ * risponde, MAI la somma: 🌐 «prompt_tokens … equals prompt_cache_hit_tokens +
197
+ * prompt_cache_miss_tokens» — sommarli conterebbe due volte lo stesso token.
198
+ */
199
+ letturaUsage: congela(['prompt_tokens_details.cached_tokens', 'prompt_cache_hit_tokens']),
200
+ scritturaUsage: congela([]),
201
+ inclusiNelTotale: true,
202
+ scontoDichiarato: null,
203
+ }),
204
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
205
+ prezzi: congela({ fonte: 'nessuna' }),
206
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
207
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null, conta: (c) => c?.data?.length }),
208
+ destinazioneChat: true,
209
+ credenziale: true,
210
+ /*
211
+ * ⛔ 12/09 — ERA `in preparazione`, E IL ROUTER LO INSTRADAVA DA SEMPRE.
212
+ * Lo stato lo calcola il record, non una stringa scritta a mano in un secondo elenco: se è
213
+ * una `destinazioneChat` con un `wire` che sappiamo parlare, allora è collegato. La sonda
214
+ * dice se la CHIAVE funziona — che è un'altra domanda, e ha già il suo posto.
215
+ */
216
+ esecuzione: 'collegato',
217
+ }),
218
+
219
+ // P-I, 12/09/2026 — API internazionali dirette. Dossier e impronta models.dev nel rapporto P-I.
220
+ kimi: congela({
221
+ id: 'kimi', etichetta: 'Kimi',
222
+ descrizione: 'Modelli Kimi, collegamento diretto internazionale.',
223
+ paginaChiavi: 'https://platform.moonshot.ai/console/api-keys',
224
+ modelliDiRiserva: congela([
225
+ congela({ id: 'kimi-k2.6', nome: 'Kimi K2.6', toolCalling: true,
226
+ fonte: 'https://platform.kimi.ai/docs/guide/kimi-k2-6-quickstart', data: '2026-09-12' }),
227
+ congela({ id: 'kimi-k2.7-code', nome: 'Kimi K2.7 Code', toolCalling: true,
228
+ fonte: 'https://platform.kimi.ai/docs/api/models-overview', data: '2026-09-12' }),
229
+ ]),
230
+ modelloAusiliario: 'kimi-k2.6', modelsDevId: 'moonshotai',
231
+ wire: 'openai-chat', baseUrl: 'https://api.moonshot.ai/v1',
232
+ // Cina: https://api.moonshot.cn/v1; nessun secondo record o cambio implicito di regione.
233
+ indirizzoModificabile: true, envIndirizzo: congela(['MOONSHOT_BASE_URL']),
234
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['MOONSHOT_API_KEY', 'KIMI_API_KEY']) }),
235
+ chiaveObbligatoria: true, formaIdModello: 'nome', oauth: null,
236
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
237
+ streaming: 'dichiarato', toolCalling: 'dichiarato',
238
+ cache: congela({ marcatore: null, letturaUsage: congela(['cached_tokens']),
239
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
240
+ etichetta: 'Cache dichiarata nella risposta; senza conteggio: non misurato.',
241
+ fonte: 'https://platform.kimi.ai/docs/api/chat', data: '2026-09-12' }),
242
+ richiestaCompatibile: congela({ limiteUscita: null, ragionamento: 'thinking',
243
+ modelli: congela({
244
+ 'kimi-k2.6': congela({ livelliRagionamento: congela([]), temperaturaServer: true, sceltaObbligata: false, sceltaForzataConThinking: false,
245
+ thinking: congela({ attivo: 'enabled', disattivabile: true, predefinito: true }) }),
246
+ 'kimi-k2.7-code': congela({ livelliRagionamento: congela([]), temperaturaServer: true, sceltaObbligata: false, sceltaForzataConThinking: false,
247
+ thinking: congela({ attivo: 'enabled', disattivabile: false, predefinito: true, conserva: 'all' }) }),
248
+ }), fonte: 'https://platform.kimi.ai/docs/api/models-overview', data: '2026-09-12' }),
249
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
250
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
251
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
252
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
253
+ conta: c => c?.data?.length, richiedeCatalogoValido: true }),
254
+ destinazioneChat: true, credenziale: true, esecuzione: 'collegato',
255
+ }),
256
+
257
+ minimax: congela({
258
+ id: 'minimax', etichetta: 'MiniMax',
259
+ descrizione: 'Modelli MiniMax, collegamento diretto internazionale.',
260
+ paginaChiavi: 'https://platform.minimax.io/user-center/basic-information/interface-key',
261
+ modelliDiRiserva: congela([
262
+ congela({ id: 'MiniMax-M3', nome: 'MiniMax M3', toolCalling: true,
263
+ fonte: 'https://platform.minimax.io/docs/api-reference/text-openai-api', data: '2026-09-12' }),
264
+ congela({ id: 'MiniMax-M2.5', nome: 'MiniMax M2.5', toolCalling: true,
265
+ fonte: 'https://platform.minimax.io/docs/api-reference/text-openai-api', data: '2026-09-12' }),
266
+ ]),
267
+ // Minimo ingresso/uscita condiviso con M3; M2.5 ha un prezzo di lettura cache inferiore.
268
+ modelloAusiliario: 'MiniMax-M2.5', modelsDevId: 'minimax',
269
+ // Anthropic raccomandato upstream; OpenAI è un percorso ufficialmente supportato.
270
+ wire: 'openai-chat', baseUrl: 'https://api.minimax.io/v1',
271
+ indirizzoModificabile: true, envIndirizzo: congela(['MINIMAX_BASE_URL']),
272
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['MINIMAX_API_KEY']) }),
273
+ chiaveObbligatoria: true, formaIdModello: 'nome', oauth: null,
274
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
275
+ streaming: 'dichiarato', toolCalling: 'dichiarato',
276
+ cache: congela({ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
277
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
278
+ etichetta: 'Cache dichiarata nella risposta; senza conteggio: non misurato.',
279
+ fonte: 'https://platform.minimax.io/docs/api-reference/text-prompt-caching', data: '2026-09-12' }),
280
+ richiestaCompatibile: congela({ limiteUscita: null, ragionamento: 'thinking',
281
+ modelli: congela({
282
+ 'MiniMax-M3': congela({ livelliRagionamento: congela([]),
283
+ thinking: congela({ attivo: 'adaptive', disattivabile: true, predefinito: true }) }),
284
+ 'MiniMax-M2.7': congela({ livelliRagionamento: congela([]),
285
+ thinking: congela({ attivo: null, disattivabile: false, predefinito: true }) }),
286
+ 'MiniMax-M2.5': congela({ livelliRagionamento: congela([]),
287
+ thinking: congela({ attivo: null, disattivabile: false, predefinito: true }) }),
288
+ }), fonte: 'https://platform.minimax.io/docs/api-reference/text-openai-api', data: '2026-09-12' }),
289
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
290
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
291
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
292
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
293
+ conta: c => c?.data?.length, richiedeCatalogoValido: true }),
294
+ destinazioneChat: true, credenziale: true, esecuzione: 'collegato',
295
+ }),
296
+
297
+ qwen: congela({
298
+ id: 'qwen', etichetta: 'Qwen',
299
+ descrizione: 'Modelli Qwen, collegamento diretto internazionale.',
300
+ paginaChiavi: 'https://modelstudio.console.alibabacloud.com/',
301
+ modelliDiRiserva: congela([
302
+ congela({ id: 'qwen-flash', nome: 'Qwen Flash', toolCalling: true,
303
+ fonte: 'https://www.alibabacloud.com/help/en/model-studio/qwen-function-calling', data: '2026-09-12' }),
304
+ congela({ id: 'qwen3.8-flash', nome: 'Qwen 3.8 Flash', toolCalling: true,
305
+ fonte: 'https://www.alibabacloud.com/help/en/model-studio/qwen3-8-flash', data: '2026-09-12' }),
306
+ ]),
307
+ // Turbo escluso: discordanza sul supporto agli strumenti tra scheda ufficiale e catalogo.
308
+ modelloAusiliario: 'qwen-flash', modelsDevId: 'alibaba',
309
+ wire: 'openai-chat', baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
310
+ // Cina: https://dashscope.aliyuncs.com/compatible-mode/v1; anche i domini workspace sono modificabili.
311
+ indirizzoModificabile: true, envIndirizzo: congela(['DASHSCOPE_BASE_URL']),
312
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['DASHSCOPE_API_KEY']) }),
313
+ chiaveObbligatoria: true, formaIdModello: 'nome', oauth: null,
314
+ endpoint: congela({ chat: '/chat/completions', modelli: '/api/v1/models' }),
315
+ streaming: 'dichiarato', toolCalling: 'dichiarato',
316
+ cache: congela({ marcatore: 'cache_control', letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
317
+ scritturaUsage: congela(['prompt_tokens_details.cache_creation_input_tokens']), inclusiNelTotale: true, scontoDichiarato: null,
318
+ etichetta: 'Cache dichiarata nella risposta; senza conteggio: non misurato.',
319
+ fonte: 'https://www.alibabacloud.com/help/en/model-studio/context-cache', data: '2026-09-12' }),
320
+ richiestaCompatibile: congela({ limiteUscita: null, ragionamento: 'enable_thinking',
321
+ modelli: congela({
322
+ 'qwen-flash': congela({ livelliRagionamento: congela([]), sceltaForzataConThinking: false,
323
+ thinking: congela({ attivo: true, disattivabile: true, predefinito: false }) }),
324
+ 'qwen3.8-flash': congela({ livelliRagionamento: congela([]), sceltaForzataConThinking: false,
325
+ thinking: congela({ attivo: true, disattivabile: true, predefinito: true }) }),
326
+ 'qwen3-32b': congela({ livelliRagionamento: congela([]), sceltaForzataConThinking: false,
327
+ thinking: congela({ attivo: true, disattivabile: true, predefinito: true, soloStreaming: true }) }),
328
+ }), fonte: 'https://www.alibabacloud.com/help/en/model-studio/deep-thinking', data: '2026-09-12' }),
329
+ catalogo: congela({ fonte: 'fornitore', forma: 'dashscope-output', percorso: '/api/v1/models', inUI: true }),
330
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
331
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
332
+ // Il catalogo nativo è relativo all'origine, non alla base del wire OpenAI.
333
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/api/v1/models', dallaRadice: true, urlAssoluto: null,
334
+ conta: c => c?.output?.models?.length, richiedeCatalogoValido: true }),
335
+ destinazioneChat: true, credenziale: true, esecuzione: 'collegato',
336
+ }),
337
+
338
+ // P-D — API diretta, contratto v4. Fonti ufficiali e snapshot: 12/09/2026.
339
+ // docs.z.ai/api-reference/llm/chat-completion e guides/overview/pricing.
340
+ zai: congela({
341
+ id: 'zai',
342
+ // P-F, fonti e strumenti verificati il 12/09/2026; parità con models.dev nel test.
343
+ modelliDiRiserva: congela([
344
+ // 12/09/2026 — https://docs.z.ai/guides/llm/glm-4.7
345
+ congela({ id: 'glm-4.7-flash', nome: 'GLM 4.7 Flash', toolCalling: true,
346
+ fonte: 'https://docs.z.ai/guides/llm/glm-4.7', data: '2026-09-12' }),
347
+ // 12/09/2026 — https://docs.z.ai/guides/llm/glm-4.7
348
+ congela({ id: 'glm-4.7', nome: 'GLM 4.7', toolCalling: true,
349
+ fonte: 'https://docs.z.ai/guides/llm/glm-4.7', data: '2026-09-12' }),
350
+ ]),
351
+ // 12/09/2026 — Flash: minimo gratuito a pari merito; https://docs.z.ai/guides/overview/pricing.
352
+ modelloAusiliario: 'glm-4.7-flash',
353
+ modelsDevId: 'zai',
354
+ etichetta: 'Z.AI',
355
+ descrizione: 'Modelli GLM tramite API diretta Z.AI.',
356
+ paginaChiavi: 'https://z.ai/manage-apikey/apikey-list',
357
+ wire: 'openai-chat',
358
+ baseUrl: 'https://api.z.ai/api/paas/v4',
359
+ indirizzoModificabile: true,
360
+ envIndirizzo: congela([]),
361
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['GLM_API_KEY', 'ZAI_API_KEY', 'Z_AI_API_KEY']) }),
362
+ chiaveObbligatoria: true,
363
+ formaIdModello: 'nome',
364
+ oauth: null,
365
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
366
+ profili: congela({
367
+ 'openai-chat': congela({ stato: 'dichiarato', versione: 'v4' }),
368
+ // P-J — scelta esplicita della porta, senza cambiare la destinazione OpenAI esistente.
369
+ 'anthropic-messages': congela({ stato: 'dichiarato', baseUrl: 'https://api.z.ai/api/anthropic', fornitoreId: 'zai-anthropic', lotto: 'P-J' }),
370
+ }),
371
+ streaming: 'dichiarato',
372
+ toolCalling: 'dichiarato',
373
+ cache: congela({
374
+ marcatore: null,
375
+ letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
376
+ scritturaUsage: congela([]),
377
+ inclusiNelTotale: true,
378
+ scontoDichiarato: null,
379
+ etichetta: 'Cache automatica; senza conteggio: non misurato.',
380
+ fonte: 'https://docs.z.ai/guides/capabilities/cache',
381
+ data: '2026-09-12',
382
+ }),
383
+ // GET /models non è documentato nell'indice ufficiale: il 404 resta esplicito.
384
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true, ripiegoSu404: 'documentazione' }),
385
+ prezzi: congela({ fonte: 'https://docs.z.ai/guides/overview/pricing', data: '2026-09-12', valuta: 'USD', unita: 'milione di token', archiviazioneCache: 'Gratuita temporaneamente; durata non dichiarata.' }),
386
+ // Vincolo esplicito P-D. La fonte oggi ammette anche low su 5.3/Flash:
387
+ // non lo inviamo, e il traduttore dichiara che è una scelta del profilo AVM.
388
+ ragionamento: congela({ formato: 'thinking', livelli: congela(['high', 'max']), fonte: 'https://docs.z.ai/api-reference/llm/chat-completion', data: '2026-09-12' }),
389
+ modelliNoti: congela([
390
+ congela({ id: 'glm-5.3-flash', nome: 'GLM-5.3-Flash', contextLength: 1_000_000, contestoDichiarato: '1M', maxOutputTokens: 131_072,
391
+ fonte: 'https://docs.z.ai/guides/vlm/glm-5.3-flash', data: '2026-09-12',
392
+ prezzi: congela({ ingresso: 0.15, cache: 0.03, uscita: 0.50 }),
393
+ ragionamento: congela({ livelli: congela(['high', 'max']), thinking: congela(['enabled']) }) }),
394
+ congela({ id: 'glm-5.3', nome: 'GLM-5.3', contextLength: 1_000_000, contestoDichiarato: '1M', maxOutputTokens: 131_072,
395
+ fonte: 'https://docs.z.ai/guides/llm/glm-5.3', data: '2026-09-12',
396
+ prezzi: congela({ ingresso: 1.40, cache: 0.26, uscita: 4.40 }),
397
+ ragionamento: congela({ livelli: congela(['high', 'max']), thinking: congela(['enabled']) }) }),
398
+ congela({ id: 'glm-5.2', nome: 'GLM-5.2', contextLength: 1_000_000, contestoDichiarato: '1M', maxOutputTokens: 131_072,
399
+ fonte: 'https://docs.z.ai/guides/llm/glm-5.2', data: '2026-09-12',
400
+ prezzi: congela({ ingresso: 1.40, cache: 0.26, uscita: 4.40 }),
401
+ ragionamento: congela({ livelli: congela(['high', 'max']), thinking: congela(['enabled', 'disabled']) }) }),
402
+ congela({ id: 'glm-5', nome: 'GLM-5', contextLength: 200_000, contestoDichiarato: '200K', maxOutputTokens: 131_072,
403
+ fonte: 'https://docs.z.ai/guides/llm/glm-5', data: '2026-09-12',
404
+ prezzi: congela({ ingresso: 1, cache: 0.20, uscita: 3.20 }),
405
+ ragionamento: congela({ livelli: congela([]), thinking: congela(['enabled', 'disabled']) }) }),
406
+ congela({ id: 'glm-4.6', nome: 'GLM-4.6', contextLength: 200_000, contestoDichiarato: '200K', maxOutputTokens: 131_072,
407
+ fonte: 'https://docs.z.ai/guides/llm/glm-4.6', data: '2026-09-12',
408
+ prezzi: congela({ ingresso: 0.60, cache: 0.11, uscita: 2.20 }),
409
+ ragionamento: congela({ livelli: congela([]), thinking: congela(['enabled', 'disabled']) }) }),
410
+ ]),
411
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true, frequenza: null, fonte: 'https://docs.z.ai/api-reference/rate-limit' }),
412
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null, conta: (c) => c?.data?.length, richiedeCatalogoValido: true }),
413
+ destinazioneChat: true,
414
+ credenziale: true,
415
+ esecuzione: 'collegato',
416
+ }),
417
+
418
+ // ── Anthropic ──────────────────────────────────────────────────────────────────────────────
419
+ anthropic: congela({
420
+ id: 'anthropic',
421
+ // P-F, fonti e strumenti verificati il 12/09/2026; parità con models.dev nel test.
422
+ modelliDiRiserva: congela([
423
+ // 12/09/2026 — https://platform.claude.com/docs/en/models/overview
424
+ congela({ id: 'claude-haiku-4-5-20251001', nome: 'Claude Haiku 4.5', toolCalling: true,
425
+ fonte: 'https://platform.claude.com/docs/en/models/overview', data: '2026-09-12' }),
426
+ // 12/09/2026 — https://platform.claude.com/docs/en/models/overview
427
+ congela({ id: 'claude-sonnet-5', nome: 'Claude Sonnet 5', toolCalling: true,
428
+ fonte: 'https://platform.claude.com/docs/en/models/overview', data: '2026-09-12' }),
429
+ ]),
430
+ // 12/09/2026 — Haiku: 1/5 USD per milione ingresso/uscita, minimo tra i modelli correnti della fonte.
431
+ modelloAusiliario: 'claude-haiku-4-5-20251001',
432
+ modelsDevId: 'anthropic',
433
+ etichetta: 'Anthropic',
434
+ descrizione: 'API diretta Anthropic, wire Messages.',
435
+ paginaChiavi: 'https://console.anthropic.com/settings/keys',
436
+ wire: 'anthropic-messages',
437
+ baseUrl: 'https://api.anthropic.com/v1',
438
+ indirizzoModificabile: false,
439
+ envIndirizzo: congela([]),
440
+ auth: congela({ tipo: 'header', header: 'x-api-key', nomeVariabile: congela(['ANTHROPIC_API_KEY']) }),
441
+ chiaveObbligatoria: true,
442
+ formaIdModello: 'nome',
443
+ oauth: null,
444
+ endpoint: congela({ chat: '/messages', modelli: '/models' }),
445
+ streaming: 'osservato',
446
+ toolCalling: 'osservato',
447
+ cache: congela({
448
+ marcatore: 'cache_control',
449
+ letturaUsage: congela(['cache_read_input_tokens', 'prompt_tokens_details.cached_tokens']),
450
+ scritturaUsage: congela(['cache_creation_input_tokens']),
451
+ /*
452
+ * ⛔⛔ QUI IL TOTALE **NON** LI INCLUDE — è l'opposto del wire OpenAI.
453
+ * 🌐 platform.claude.com/docs/…/prompt-caching: `total_input = cache_read +
454
+ * cache_creation + input`. Chi somma con la formula sbagliata sbaglia del doppio, e
455
+ * questo campo esiste per non far dipendere quel conto dalla memoria di chi legge.
456
+ */
457
+ inclusiNelTotale: false,
458
+ scontoDichiarato: null,
459
+ }),
460
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
461
+ prezzi: congela({ fonte: 'nessuna' }),
462
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
463
+ sonda: congela({ attiva: true, auth: 'x-api-key', percorso: '/models', urlAssoluto: null, conta: (c) => c?.data?.length }),
464
+ destinazioneChat: true,
465
+ credenziale: true,
466
+ esecuzione: 'collegato',
467
+ }),
468
+
469
+ // ── Gemini ─────────────────────────────────────────────────────────────────────────────────
470
+ gemini: congela({
471
+ id: 'gemini',
472
+ // P-F, fonti e strumenti verificati il 12/09/2026; parità con models.dev nel test.
473
+ modelliDiRiserva: congela([
474
+ // 12/09/2026 — https://ai.google.dev/gemini-api/docs/models/gemini-2.5-flash-lite
475
+ congela({ id: 'gemini-2.5-flash-lite', nome: 'Gemini 2.5 Flash-Lite', toolCalling: true,
476
+ fonte: 'https://ai.google.dev/gemini-api/docs/models/gemini-2.5-flash-lite', data: '2026-09-12' }),
477
+ // 12/09/2026 — https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-lite
478
+ congela({ id: 'gemini-3.1-flash-lite', nome: 'Gemini 3.1 Flash-Lite', toolCalling: true,
479
+ fonte: 'https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-lite', data: '2026-09-12' }),
480
+ ]),
481
+ // 12/09/2026 — 2.5: 0,10/0,40 USD per milione sul piano standard; https://ai.google.dev/gemini-api/docs/pricing.
482
+ modelloAusiliario: 'gemini-2.5-flash-lite',
483
+ modelsDevId: 'google',
484
+ etichetta: 'Google Gemini',
485
+ descrizione: 'API diretta Google AI Studio.',
486
+ paginaChiavi: 'https://aistudio.google.com/app/apikey',
487
+ wire: 'gemini',
488
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
489
+ indirizzoModificabile: false,
490
+ envIndirizzo: congela([]),
491
+ /* ⛔ Due schemi per lo stesso fornitore: la sonda usa `?key=`, il catalogo `x-goog-api-key`. */
492
+ auth: congela({ tipo: 'query', header: 'x-goog-api-key', nomeVariabile: congela(['GEMINI_API_KEY', 'GOOGLE_API_KEY']) }),
493
+ chiaveObbligatoria: true,
494
+ formaIdModello: 'nome',
495
+ oauth: null,
496
+ endpoint: congela({ chat: ':generateContent', modelli: '/models' }),
497
+ streaming: 'osservato',
498
+ toolCalling: 'osservato',
499
+ cache: congela({
500
+ marcatore: null,
501
+ /*
502
+ * ⛔ DUE NOMI PER LO STESSO NUMERO, a due livelli diversi: `usageMetadata.cachedContentTokenCount`
503
+ * sul REST, `total_cached_tokens` negli SDK. Chi ne legge uno solo trova 0. Noi passiamo
504
+ * dall'AI SDK, che appiattisce in `inputTokenDetails.cacheReadTokens`; gli altri due
505
+ * restano qui come ripiego, perché costano niente e coprono il giorno in cui il percorso cambia.
506
+ * ❓ NON verificato che l'adattatore fissato nel lock mappi davvero quel campo (§7.8).
507
+ */
508
+ letturaUsage: congela(['prompt_tokens_details.cached_tokens', 'usageMetadata.cachedContentTokenCount', 'total_cached_tokens']),
509
+ scritturaUsage: congela([]),
510
+ inclusiNelTotale: true,
511
+ scontoDichiarato: null,
512
+ }),
513
+ catalogo: congela({ fonte: 'fornitore', forma: 'gemini-models', percorso: '/models', inUI: true }),
514
+ prezzi: congela({ fonte: 'nessuna' }),
515
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
516
+ sonda: congela({ attiva: true, auth: 'query', percorso: '/models', urlAssoluto: null, conta: (c) => c?.models?.length }),
517
+ destinazioneChat: true,
518
+ credenziale: true,
519
+ esecuzione: 'collegato',
520
+ }),
521
+
522
+ // ── OpenRouter — l'aggregatore, e l'unico che oggi porta anche i PREZZI ────────────────────
523
+ openrouter: congela({
524
+ id: 'openrouter',
525
+ // P-F, fonti e strumenti verificati il 12/09/2026; parità con models.dev nel test.
526
+ modelliDiRiserva: congela([
527
+ // 12/09/2026 — https://openrouter.ai/api/v1/models
528
+ congela({ id: 'liquid/lfm-2.5-2.6b:free', nome: 'LiquidAI LFM 2.5 2.6B (gratuito)', toolCalling: true,
529
+ fonte: 'https://openrouter.ai/api/v1/models', data: '2026-09-12' }),
530
+ // 12/09/2026 — https://openrouter.ai/api/v1/models
531
+ congela({ id: 'openai/gpt-5-nano', nome: 'OpenAI GPT-5 Nano', toolCalling: true,
532
+ fonte: 'https://openrouter.ai/api/v1/models', data: '2026-09-12' }),
533
+ ]),
534
+ // 12/09/2026 — LFM: 0/0, minimo a pari merito tra SKU gratuiti; https://openrouter.ai/liquid/lfm-2.5-2.6b:free.
535
+ modelloAusiliario: 'liquid/lfm-2.5-2.6b:free',
536
+ modelsDevId: 'openrouter',
537
+ etichetta: 'OpenRouter',
538
+ descrizione: 'Aggregatore: centinaia di modelli dietro una sola chiave.',
539
+ paginaChiavi: 'https://openrouter.ai/keys',
540
+ wire: 'openai-chat',
541
+ baseUrl: 'https://openrouter.ai/api/v1',
542
+ indirizzoModificabile: true,
543
+ envIndirizzo: congela([]),
544
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['OPENROUTER_API_KEY']) }),
545
+ chiaveObbligatoria: true,
546
+ /* ⛔ `vendor/nome`: lo slash è OCCUPATO, ed è la ragione per cui la fonte si separa con `:`. */
547
+ formaIdModello: 'vendor/nome',
548
+ oauth: congela({ tipo: 'pkce', etichetta: 'OpenRouter' }),
549
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
550
+ streaming: 'osservato',
551
+ toolCalling: 'osservato',
552
+ cache: congela({
553
+ marcatore: 'cache_control', // sull'inviluppo del messaggio, per i modelli che lo onorano
554
+ letturaUsage: congela(['prompt_tokens_details.cached_tokens', 'cache_read_input_tokens']),
555
+ scritturaUsage: congela(['prompt_tokens_details.cache_write_tokens']),
556
+ inclusiNelTotale: true,
557
+ /* ⛔ `cache_discount` è DENARO, non token: sta fuori da `usage`, e non si somma mai ai conteggi. */
558
+ scontoDichiarato: 'cache_discount',
559
+ }),
560
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: false }),
561
+ prezzi: congela({ fonte: 'openrouter-models' }),
562
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
563
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null, conta: (c) => c?.data?.length }),
564
+ destinazioneChat: true,
565
+ credenziale: true,
566
+ esecuzione: 'collegato',
567
+ }),
568
+
569
+ // ── Ollama — in casa, senza account ────────────────────────────────────────────────────────
570
+ ollama: congela({
571
+ id: 'ollama',
572
+ // P-F, 12/09/2026: scoperta locale, nessuna inferenza sui modelli installati.
573
+ modelliDiRiserva: null,
574
+ modelloAusiliario: null,
575
+ modelsDevId: null, // Ollama Cloud è un servizio diverso dal runtime locale.
576
+ etichetta: 'Ollama Local',
577
+ descrizione: 'Motore locale Ollama su questo computer.',
578
+ paginaChiavi: 'https://ollama.com/download',
579
+ wire: 'openai-chat',
580
+ /* ⛔ Ollama espone il protocollo OpenAI sotto `/v1`, il suo indirizzo base no. */
581
+ baseUrl: 'http://127.0.0.1:11434',
582
+ indirizzoModificabile: true,
583
+ envIndirizzo: congela(['OLLAMA_BASE_URL']),
584
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['OLLAMA_API_KEY']) }),
585
+ chiaveObbligatoria: false,
586
+ formaIdModello: 'nome',
587
+ oauth: null,
588
+ endpoint: congela({ chat: '/v1/chat/completions', modelli: '/api/tags' }),
589
+ streaming: 'osservato',
590
+ toolCalling: 'ignoto', // ⛔ dipende dal modello caricato, non dal motore
591
+ cache: congela({ marcatore: null, letturaUsage: congela([]), scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null }),
592
+ catalogo: congela({ fonte: 'runtime-locale', forma: 'ollama-tags', percorso: '/api/tags', inUI: false }),
593
+ prezzi: congela({ fonte: 'nessuna' }),
594
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
595
+ sonda: congela({ attiva: true, auth: 'nessuna', percorso: '/api/tags', urlAssoluto: null, conta: (c) => c?.models?.length }),
596
+ destinazioneChat: true,
597
+ credenziale: true,
598
+ esecuzione: 'runtime locale',
599
+ }),
600
+
601
+ // ── LM Studio — P-C: scoperto e sondato da sempre, e fino a oggi non sceglibile in chat ─────
602
+ lmstudio: congela({
603
+ id: 'lmstudio',
604
+ // P-F, 12/09/2026: scoperta locale, nessuna inferenza sui modelli installati.
605
+ modelliDiRiserva: null,
606
+ modelloAusiliario: null,
607
+ modelsDevId: 'lmstudio', // Metadati pubblici: l'elenco installato resta del runtime.
608
+ etichetta: 'LM Studio',
609
+ descrizione: 'Motore locale LM Studio su questo computer.',
610
+ paginaChiavi: 'https://lmstudio.ai/download',
611
+ wire: 'openai-chat',
612
+ /* 🌐 lmstudio.ai/docs/app/api/endpoints/openai: `http://localhost:1234/v1`, porta 1234. */
613
+ baseUrl: 'http://127.0.0.1:1234',
614
+ indirizzoModificabile: true,
615
+ envIndirizzo: congela(['LMSTUDIO_BASE_URL', 'LM_BASE_URL']),
616
+ /*
617
+ * ⛔ Chiave FACOLTATIVA, non assente: la REST v1 di LM Studio 0.4 dichiara «authentication
618
+ * configuration with API tokens» 🌐, ma gli esempi OpenAI-compatibili non ne usano nessuna.
619
+ * Pretenderla lo escluderebbe per una regola che non lo riguarda — stessa scelta di Ollama.
620
+ */
621
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['LMSTUDIO_API_KEY', 'LM_API_KEY']) }),
622
+ chiaveObbligatoria: false,
623
+ formaIdModello: 'nome',
624
+ oauth: null,
625
+ endpoint: congela({ chat: '/v1/chat/completions', modelli: '/api/v1/models' }),
626
+ streaming: 'osservato',
627
+ /*
628
+ * ⭐ L'unico che dichiara le capacità OSSERVATE modello per modello
629
+ * (`capabilities.trained_for_tool_use`, `vision`, `reasoning.allowed_options`,
630
+ * `max_context_length`): `openai-compatible-runtime.mjs` le legge già. È la ragione per cui
631
+ * questo fornitore vale più di una riga in più nel selettore.
632
+ */
633
+ toolCalling: 'osservato',
634
+ cache: congela({ marcatore: null, letturaUsage: congela([]), scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null }),
635
+ catalogo: congela({ fonte: 'runtime-locale', forma: 'lmstudio-models', percorso: '/api/v1/models', inUI: true }),
636
+ prezzi: congela({ fonte: 'nessuna' }),
637
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
638
+ sonda: congela({ attiva: true, auth: 'nessuna', percorso: '/api/v1/models', urlAssoluto: null, conta: (c) => c?.models?.length }),
639
+ destinazioneChat: true,
640
+ credenziale: true,
641
+ esecuzione: 'runtime locale',
642
+ }),
643
+
644
+ // P-G, 12/09/2026 — Groq. Fonte HTTP: https://console.groq.com/docs/api-reference
645
+ groq: congela({
646
+ id: 'groq',
647
+ modelliDiRiserva: congela([
648
+ congela({ id: 'openai/gpt-oss-20b', nome: 'GPT OSS 20B', toolCalling: true,
649
+ fonte: 'https://console.groq.com/docs/tool-use/overview', data: '2026-09-12' }),
650
+ ]),
651
+ modelloAusiliario: null,
652
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
653
+ modelsDevId: 'groq',
654
+ etichetta: 'Groq',
655
+ descrizione: 'Modelli tramite Groq.',
656
+ paginaChiavi: 'https://console.groq.com/keys',
657
+ wire: 'openai-chat',
658
+ baseUrl: 'https://api.groq.com/openai/v1',
659
+ indirizzoModificabile: true,
660
+ envIndirizzo: congela([]),
661
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['GROQ_API_KEY']) }),
662
+ chiaveObbligatoria: true,
663
+ formaIdModello: 'vendor/nome',
664
+ oauth: null,
665
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
666
+ streaming: 'dichiarato',
667
+ toolCalling: 'dichiarato',
668
+ cache: congela({
669
+ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
670
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
671
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
672
+ fonte: 'https://console.groq.com/docs/prompt-caching', data: '2026-09-12',
673
+ }),
674
+ richiestaCompatibile: congela({
675
+ limiteUscita: 'max_completion_tokens', ragionamento: 'effort',
676
+ modelli: congela({
677
+ 'openai/gpt-oss-20b': congela({ livelliRagionamento: congela(['low', 'medium', 'high']), }),
678
+ 'openai/gpt-oss-120b': congela({ livelliRagionamento: congela(['low', 'medium', 'high']), }),
679
+ }),
680
+ fonte: 'https://console.groq.com/docs/api-reference', data: '2026-09-12',
681
+ }),
682
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
683
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
684
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
685
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
686
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
687
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true }),
688
+ destinazioneChat: true,
689
+ credenziale: true,
690
+ esecuzione: 'collegato',
691
+ }),
692
+
693
+ // P-G, 12/09/2026 — Cerebras. Fonte HTTP: https://inference-docs.cerebras.ai/api-reference/chat-completions
694
+ cerebras: congela({
695
+ id: 'cerebras',
696
+ modelliDiRiserva: congela([
697
+ congela({ id: 'gpt-oss-120b', nome: 'GPT OSS 120B', toolCalling: true,
698
+ fonte: 'https://inference-docs.cerebras.ai/capabilities/tool-use', data: '2026-09-12' }),
699
+ ]),
700
+ modelloAusiliario: null,
701
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
702
+ modelsDevId: 'cerebras',
703
+ etichetta: 'Cerebras',
704
+ descrizione: 'Modelli tramite Cerebras.',
705
+ paginaChiavi: 'https://cloud.cerebras.ai/platform',
706
+ wire: 'openai-chat',
707
+ baseUrl: 'https://api.cerebras.ai/v1',
708
+ indirizzoModificabile: true,
709
+ envIndirizzo: congela([]),
710
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['CEREBRAS_API_KEY']) }),
711
+ chiaveObbligatoria: true,
712
+ formaIdModello: 'nome',
713
+ oauth: null,
714
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
715
+ streaming: 'dichiarato',
716
+ toolCalling: 'dichiarato',
717
+ cache: congela({
718
+ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
719
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
720
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
721
+ fonte: 'https://inference-docs.cerebras.ai/capabilities/prompt-caching', data: '2026-09-12',
722
+ }),
723
+ richiestaCompatibile: congela({
724
+ limiteUscita: 'max_completion_tokens', ragionamento: 'effort',
725
+ modelli: congela({
726
+ 'gpt-oss-120b': congela({ livelliRagionamento: congela(['low', 'medium', 'high']), strumentiConFormato: false, fonte: 'https://inference-docs.cerebras.ai/resources/openai', }),
727
+ 'qwen-3.8-27b': congela({ livelliRagionamento: congela(['none', 'low', 'medium', 'high']), }),
728
+ }),
729
+ fonte: 'https://inference-docs.cerebras.ai/api-reference/chat-completions', data: '2026-09-12',
730
+ }),
731
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
732
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
733
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
734
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
735
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
736
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true }),
737
+ destinazioneChat: true,
738
+ credenziale: true,
739
+ esecuzione: 'collegato',
740
+ }),
741
+
742
+ // P-G, 12/09/2026 — Mistral. Fonte HTTP: https://docs.mistral.ai/api/endpoint/chat
743
+ mistral: congela({
744
+ id: 'mistral',
745
+ modelliDiRiserva: congela([
746
+ congela({ id: 'mistral-small-latest', nome: 'Mistral Small', toolCalling: true,
747
+ fonte: 'https://docs.mistral.ai/studio/conversations/function-calling', data: '2026-09-12' }),
748
+ ]),
749
+ modelloAusiliario: null,
750
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
751
+ modelsDevId: 'mistral',
752
+ etichetta: 'Mistral',
753
+ descrizione: 'Modelli tramite Mistral.',
754
+ paginaChiavi: 'https://console.mistral.ai/api-keys',
755
+ wire: 'openai-chat',
756
+ baseUrl: 'https://api.mistral.ai/v1',
757
+ indirizzoModificabile: true,
758
+ envIndirizzo: congela([]),
759
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['MISTRAL_API_KEY']) }),
760
+ chiaveObbligatoria: true,
761
+ formaIdModello: 'nome',
762
+ oauth: null,
763
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
764
+ streaming: 'dichiarato',
765
+ toolCalling: 'dichiarato',
766
+ cache: congela({
767
+ marcatore: 'prompt_cache_key', letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
768
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
769
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
770
+ fonte: 'https://docs.mistral.ai/studio/conversations/advanced/prompt-caching', data: '2026-09-12',
771
+ }),
772
+ richiestaCompatibile: congela({
773
+ limiteUscita: null, ragionamento: 'effort',
774
+ modelli: congela({}),
775
+ fonte: 'https://docs.mistral.ai/api/endpoint/chat', data: '2026-09-12',
776
+ }),
777
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
778
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
779
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
780
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
781
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
782
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true }),
783
+ destinazioneChat: true,
784
+ credenziale: true,
785
+ esecuzione: 'collegato',
786
+ }),
787
+
788
+ // P-G, 12/09/2026 — Together. Fonte HTTP: https://docs.together.ai/docs/inference/openai-compatibility
789
+ together: congela({
790
+ id: 'together',
791
+ modelliDiRiserva: congela([
792
+ congela({ id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', nome: 'Llama 3.3 70B', toolCalling: true,
793
+ fonte: 'https://docs.together.ai/docs/serverless/models', data: '2026-09-12' }),
794
+ ]),
795
+ modelloAusiliario: null,
796
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
797
+ modelsDevId: 'togetherai',
798
+ etichetta: 'Together',
799
+ descrizione: 'Modelli tramite Together.',
800
+ paginaChiavi: 'https://api.together.ai/settings/api-keys',
801
+ wire: 'openai-chat',
802
+ baseUrl: 'https://api.together.ai/v1',
803
+ indirizzoModificabile: true,
804
+ envIndirizzo: congela([]),
805
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['TOGETHER_API_KEY']) }),
806
+ chiaveObbligatoria: true,
807
+ formaIdModello: 'vendor/nome',
808
+ oauth: null,
809
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
810
+ streaming: 'dichiarato',
811
+ toolCalling: 'dichiarato',
812
+ cache: congela({
813
+ marcatore: null, letturaUsage: congela(['cached_tokens', 'prompt_tokens_details.cached_tokens']),
814
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
815
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
816
+ fonte: 'https://docs.together.ai/docs/inference/openai-compatibility', data: '2026-09-12',
817
+ }),
818
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
819
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
820
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
821
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
822
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
823
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true }),
824
+ destinazioneChat: true,
825
+ credenziale: true,
826
+ esecuzione: 'collegato',
827
+ }),
828
+
829
+ // P-G, 12/09/2026 — Fireworks. Fonte HTTP: https://docs.fireworks.ai/api-reference/post-chatcompletions
830
+ // GET /inference/v1/models non trovato nella documentazione: il 401 senza chiave non ne prova il funzionamento. Il 404 deve restare esplicito.
831
+ fireworks: congela({
832
+ id: 'fireworks',
833
+ modelliDiRiserva: congela([
834
+ congela({ id: 'accounts/fireworks/models/gpt-oss-120b', nome: 'GPT OSS 120B', toolCalling: true,
835
+ fonte: 'https://fireworks.ai/models/fireworks/gpt-oss-120b', data: '2026-09-12' }),
836
+ ]),
837
+ modelloAusiliario: null,
838
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
839
+ modelsDevId: 'fireworks-ai',
840
+ etichetta: 'Fireworks',
841
+ descrizione: 'Modelli tramite Fireworks.',
842
+ paginaChiavi: 'https://app.fireworks.ai/settings/users/api-keys',
843
+ wire: 'openai-chat',
844
+ baseUrl: 'https://api.fireworks.ai/inference/v1',
845
+ indirizzoModificabile: true,
846
+ envIndirizzo: congela([]),
847
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['FIREWORKS_API_KEY']) }),
848
+ chiaveObbligatoria: true,
849
+ formaIdModello: 'vendor/nome',
850
+ oauth: null,
851
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
852
+ streaming: 'dichiarato',
853
+ toolCalling: 'dichiarato',
854
+ cache: congela({
855
+ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
856
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
857
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
858
+ fonte: 'https://docs.fireworks.ai/api-reference/post-chatcompletions', data: '2026-09-12',
859
+ }),
860
+ richiestaCompatibile: congela({
861
+ limiteUscita: null, ragionamento: 'effort',
862
+ modelli: congela({}),
863
+ fonte: 'https://docs.fireworks.ai/api-reference/post-chatcompletions', data: '2026-09-12',
864
+ }),
865
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
866
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
867
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
868
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
869
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
870
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true }),
871
+ destinazioneChat: true,
872
+ credenziale: true,
873
+ esecuzione: 'collegato',
874
+ }),
875
+
876
+ // P-G, 12/09/2026 — DeepInfra. Fonte HTTP: https://docs.deepinfra.com/chat/overview
877
+ deepinfra: congela({
878
+ id: 'deepinfra',
879
+ modelliDiRiserva: congela([
880
+ congela({ id: 'openai/gpt-oss-20b', nome: 'GPT OSS 20B', toolCalling: true,
881
+ fonte: 'https://deepinfra.com/openai/gpt-oss-20b', data: '2026-09-12' }),
882
+ ]),
883
+ modelloAusiliario: null,
884
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
885
+ modelsDevId: 'deepinfra',
886
+ etichetta: 'DeepInfra',
887
+ descrizione: 'Modelli tramite DeepInfra.',
888
+ paginaChiavi: 'https://deepinfra.com/dash/api_keys',
889
+ wire: 'openai-chat',
890
+ baseUrl: 'https://api.deepinfra.com/v1/openai',
891
+ indirizzoModificabile: true,
892
+ envIndirizzo: congela([]),
893
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['DEEPINFRA_API_KEY', 'DEEPINFRA_TOKEN']) }),
894
+ chiaveObbligatoria: true,
895
+ formaIdModello: 'vendor/nome',
896
+ oauth: null,
897
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
898
+ streaming: 'dichiarato',
899
+ toolCalling: 'dichiarato',
900
+ cache: congela({
901
+ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
902
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
903
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
904
+ fonte: 'https://docs.deepinfra.com/chat/prompt-caching', data: '2026-09-12',
905
+ }),
906
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
907
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
908
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
909
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
910
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
911
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true, catalogoPubblico: true,
912
+ // CLI-REQ-06, 17/09/2026 — il catalogo è PUBBLICO: risponde 200 anche a una chiave sbagliata.
913
+ // L'unico modo documentato di provare questa chiave è generare. La sonda ordinaria continua a
914
+ // chiedere l'elenco; questa richiesta parte solo con `consentiGenerazione: true`.
915
+ // ⛔ Due fonti, non una: `fonte` qui sotto è quella dell'ENDPOINT, quella dentro
916
+ // `richiestaMinima` è del MODELLO. Sono due affermazioni diverse e vanno provate separate.
917
+ // Modello: `openai/gpt-oss-20b` — «Available» e distribuito il 17/09/2026, $0,03 per milione di
918
+ // token in ingresso: il più economico che la documentazione del fornitore confermi.
919
+ richiestaMinima: congela({ percorso: '/chat/completions',
920
+ corpo: congela({ model: 'openai/gpt-oss-20b', max_tokens: 1, stream: false, messages: congela([congela({ role: 'user', content: '.' })]) }),
921
+ fonte: 'https://deepinfra.com/openai/gpt-oss-20b', data: '2026-09-17' }),
922
+ fonte: 'https://docs.deepinfra.com/chat/overview', data: '2026-09-17' }),
923
+ destinazioneChat: true,
924
+ credenziale: true,
925
+ esecuzione: 'collegato',
926
+ }),
927
+
928
+ // P-G, 12/09/2026 — Novita. Fonte HTTP: https://docs.novita.ai/api-reference/model-apis-llm-create-chat-completion
929
+ // La documentazione HTTP usa /openai/v1; alcuni esempi SDK e models.dev indicano /openai. Qui si segue il riferimento HTTP, senza aggiungere un secondo /v1.
930
+ novita: congela({
931
+ id: 'novita',
932
+ modelliDiRiserva: congela([
933
+ congela({ id: 'qwen/qwen3-coder-30b-a3b-instruct', nome: 'Qwen3 Coder 30B', toolCalling: true,
934
+ fonte: 'https://novita.ai/models/model-detail/qwen-qwen3-coder-30b-a3b-instruct', data: '2026-09-12' }),
935
+ ]),
936
+ modelloAusiliario: null,
937
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
938
+ modelsDevId: 'novita-ai',
939
+ etichetta: 'Novita',
940
+ descrizione: 'Modelli tramite Novita.',
941
+ paginaChiavi: 'https://novita.ai/settings/key-management',
942
+ wire: 'openai-chat',
943
+ baseUrl: 'https://api.novita.ai/openai/v1',
944
+ indirizzoModificabile: true,
945
+ envIndirizzo: congela([]),
946
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['NOVITA_API_KEY']) }),
947
+ chiaveObbligatoria: true,
948
+ formaIdModello: 'vendor/nome',
949
+ oauth: null,
950
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
951
+ streaming: 'dichiarato',
952
+ toolCalling: 'dichiarato',
953
+ cache: congela({
954
+ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
955
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
956
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
957
+ fonte: 'https://docs.novita.ai/guides/llm-prompt-cache', data: '2026-09-12',
958
+ }),
959
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
960
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
961
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
962
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
963
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
964
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true, catalogoPubblico: true,
965
+ // CLI-REQ-06, 17/09/2026 — catalogo pubblico: la chiave si prova solo generando.
966
+ // Endpoint verificato il 17/09/2026 sull'esempio curl del fornitore:
967
+ // POST https://api.novita.ai/openai/v1/chat/completions con `Authorization: Bearer <chiave>`.
968
+ // ⛔ Modello: `meta-llama/llama-3.1-8b-instruct`, NON il `deepseek/deepseek-r1` dell'esempio
969
+ // curl di quella pagina. Rifatta la ricerca il 17/09: per lo slug nudo di R1 la scheda del
970
+ // modello non esiste più (esiste `…-deepseek-r1-0528`), e fonti terze danno il ritiro di R1
971
+ // ospitato nel luglio 2026 ⇒ un esempio di documentazione può sopravvivere al suo modello, e
972
+ // una prova su un modello ritirato non può MAI riuscire: a schermo sembrerebbe un guasto HTTP.
973
+ // Questo invece la sua scheda lo dà «Available Serverless» il 17/09/2026, a $0,02 per milione
974
+ // di token in ingresso — il più economico dei quattro — ed è istruito, non di ragionamento.
975
+ // 401 = «The API key is missing, invalid, or expired» (https://docs.novita.ai/api-reference/basic-error-code).
976
+ richiestaMinima: congela({ percorso: '/chat/completions',
977
+ corpo: congela({ model: 'meta-llama/llama-3.1-8b-instruct', max_tokens: 1, stream: false, messages: congela([congela({ role: 'user', content: '.' })]) }),
978
+ fonte: 'https://novita.ai/models/model-detail/meta-llama-llama-3.1-8b-instruct', data: '2026-09-17' }),
979
+ fonte: 'https://docs.novita.ai/guides/llm-api', data: '2026-09-17' }),
980
+ destinazioneChat: true,
981
+ credenziale: true,
982
+ esecuzione: 'collegato',
983
+ }),
984
+
985
+ // P-G, 12/09/2026 — Nebius. Fonte HTTP: https://docs.tokenfactory.nebius.com/api-reference/inference/create-chat-completion
986
+ nebius: congela({
987
+ id: 'nebius',
988
+ modelliDiRiserva: congela([
989
+ congela({ id: 'openai/gpt-oss-120b', nome: 'GPT OSS 120B', toolCalling: true,
990
+ fonte: 'https://nebius.com/services/token-factory', data: '2026-09-12' }),
991
+ ]),
992
+ modelloAusiliario: null,
993
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
994
+ modelsDevId: 'nebius',
995
+ etichetta: 'Nebius',
996
+ descrizione: 'Modelli tramite Nebius.',
997
+ paginaChiavi: 'https://tokenfactory.nebius.com',
998
+ wire: 'openai-chat',
999
+ baseUrl: 'https://api.tokenfactory.nebius.com/v1',
1000
+ indirizzoModificabile: true,
1001
+ envIndirizzo: congela([]),
1002
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['NEBIUS_API_KEY']) }),
1003
+ chiaveObbligatoria: true,
1004
+ formaIdModello: 'vendor/nome',
1005
+ oauth: null,
1006
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
1007
+ streaming: 'dichiarato',
1008
+ toolCalling: 'dichiarato',
1009
+ cache: congela({
1010
+ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
1011
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
1012
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
1013
+ fonte: 'https://docs.tokenfactory.nebius.com/api-reference/inference/create-chat-completion', data: '2026-09-12',
1014
+ }),
1015
+ richiestaCompatibile: congela({
1016
+ limiteUscita: null, ragionamento: 'effort',
1017
+ modelli: congela({}),
1018
+ fonte: 'https://docs.tokenfactory.nebius.com/api-reference/inference/create-chat-completion', data: '2026-09-12',
1019
+ }),
1020
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
1021
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
1022
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
1023
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1024
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
1025
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true }),
1026
+ destinazioneChat: true,
1027
+ credenziale: true,
1028
+ esecuzione: 'collegato',
1029
+ }),
1030
+
1031
+ // P-G, 12/09/2026 — xAI. Fonte HTTP: https://docs.x.ai/developers/rest-api-reference/inference/chat-completions
1032
+ xai: congela({
1033
+ id: 'xai',
1034
+ modelliDiRiserva: congela([
1035
+ congela({ id: 'grok-4.3', nome: 'Grok 4.3', toolCalling: true,
1036
+ fonte: 'https://docs.x.ai/developers/models/grok-4.3', data: '2026-09-12' }),
1037
+ ]),
1038
+ modelloAusiliario: null,
1039
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
1040
+ modelsDevId: 'xai',
1041
+ etichetta: 'xAI',
1042
+ descrizione: 'Modelli tramite xAI.',
1043
+ paginaChiavi: 'https://console.x.ai',
1044
+ wire: 'openai-chat',
1045
+ baseUrl: 'https://api.x.ai/v1',
1046
+ indirizzoModificabile: true,
1047
+ envIndirizzo: congela([]),
1048
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['XAI_API_KEY']) }),
1049
+ chiaveObbligatoria: true,
1050
+ formaIdModello: 'nome',
1051
+ oauth: null,
1052
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
1053
+ streaming: 'dichiarato',
1054
+ toolCalling: 'dichiarato',
1055
+ cache: congela({
1056
+ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']),
1057
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
1058
+ etichetta: 'Cache dichiarata dal fornitore; senza conteggio: non misurato.',
1059
+ fonte: 'https://docs.x.ai/developers/rest-api-reference/inference/chat-completions', data: '2026-09-12',
1060
+ }),
1061
+ richiestaCompatibile: congela({
1062
+ limiteUscita: 'max_completion_tokens', ragionamento: 'effort',
1063
+ modelli: congela({
1064
+ 'grok-4.3': congela({ livelliRagionamento: congela(['none', 'low', 'medium', 'high', 'xhigh']), fonte: 'https://docs.x.ai/developers/models/grok-4.3', }),
1065
+ 'grok-4.6': congela({ livelliRagionamento: congela(['low', 'medium', 'high', 'xhigh']), fonte: 'https://docs.x.ai/developers/models/grok-4.6', }),
1066
+ }),
1067
+ fonte: 'https://docs.x.ai/developers/rest-api-reference/inference/chat-completions', data: '2026-09-12',
1068
+ }),
1069
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
1070
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
1071
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
1072
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1073
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
1074
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true }),
1075
+ destinazioneChat: true,
1076
+ credenziale: true,
1077
+ esecuzione: 'collegato',
1078
+ }),
1079
+
1080
+ // P-G, 12/09/2026 — Ollama Cloud. Fonte HTTP: https://docs.ollama.com/api/openai-compatibility
1081
+ 'ollama-cloud': congela({
1082
+ id: 'ollama-cloud',
1083
+ modelliDiRiserva: congela([
1084
+ congela({ id: 'gpt-oss:20b', nome: 'GPT OSS 20B', toolCalling: true,
1085
+ fonte: 'https://ollama.com/library/gpt-oss', data: '2026-09-12' }),
1086
+ ]),
1087
+ modelloAusiliario: null,
1088
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
1089
+ modelsDevId: 'ollama-cloud',
1090
+ etichetta: 'Ollama Cloud',
1091
+ descrizione: 'Modelli tramite Ollama Cloud.',
1092
+ paginaChiavi: 'https://ollama.com/settings/keys',
1093
+ wire: 'openai-chat',
1094
+ baseUrl: 'https://ollama.com/v1',
1095
+ indirizzoModificabile: true,
1096
+ envIndirizzo: congela([]),
1097
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['OLLAMA_API_KEY']) }),
1098
+ chiaveObbligatoria: true,
1099
+ formaIdModello: 'nome',
1100
+ oauth: null,
1101
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
1102
+ streaming: 'dichiarato',
1103
+ toolCalling: 'dichiarato',
1104
+ cache: congela({
1105
+ marcatore: null, letturaUsage: congela([]),
1106
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
1107
+ etichetta: 'Conteggio della cache non documentato; senza dati: non misurato.',
1108
+ fonte: 'https://docs.ollama.com/api/openai-compatibility', data: '2026-09-12',
1109
+ }),
1110
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
1111
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
1112
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
1113
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1114
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
1115
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true, catalogoPubblico: true,
1116
+ // CLI-REQ-06, 17/09/2026 — catalogo pubblico: la chiave si prova solo generando.
1117
+ // Endpoint verificato il 17/09/2026: base https://ollama.com/v1, e la pagina di compatibilità
1118
+ // OpenAI elenca `max_tokens` e `stream` fra i campi accettati da /v1/chat/completions.
1119
+ // Modello: `gemma4:31b`. ⛔ NON `gpt-oss:20b`: sul cloud i nomi con `-cloud` valgono per l'app
1120
+ // e la CLI, mentre «for API requests to ollama.com, use the name returned by this list, such as
1121
+ // gemma4:31b» (https://docs.ollama.com/cloud, 17/09/2026) — `gemma4:31b` è l'unico nome che la
1122
+ // documentazione scrive per intero come modello cloud chiamabile via API.
1123
+ richiestaMinima: congela({ percorso: '/chat/completions',
1124
+ corpo: congela({ model: 'gemma4:31b', max_tokens: 1, stream: false, messages: congela([congela({ role: 'user', content: '.' })]) }),
1125
+ fonte: 'https://docs.ollama.com/cloud', data: '2026-09-17' }),
1126
+ fonte: 'https://docs.ollama.com/api/openai-compatibility', data: '2026-09-17' }),
1127
+ destinazioneChat: true,
1128
+ credenziale: true,
1129
+ esecuzione: 'collegato',
1130
+ }),
1131
+
1132
+ // P-G, 12/09/2026 — Hugging Face. Fonte HTTP: https://huggingface.co/docs/inference-providers/tasks/chat-completion
1133
+ // Lo stesso account del portachiavi resta disponibile per i download. La chiave è obbligatoria per l'inferenza, non per scaricare modelli pubblici.
1134
+ huggingface: congela({
1135
+ id: 'huggingface',
1136
+ modelliDiRiserva: congela([
1137
+ congela({ id: 'openai/gpt-oss-20b', nome: 'GPT OSS 20B', toolCalling: true,
1138
+ fonte: 'https://huggingface.co/openai/gpt-oss-20b', data: '2026-09-12' }),
1139
+ ]),
1140
+ modelloAusiliario: null,
1141
+ motivoAusiliario: 'Nessun modello ausiliario qualificato per questo fornitore.',
1142
+ modelsDevId: 'huggingface',
1143
+ etichetta: 'Hugging Face',
1144
+ descrizione: 'Inferenza, catalogo e scaricamento dei modelli Hugging Face.',
1145
+ paginaChiavi: 'https://huggingface.co/settings/tokens',
1146
+ wire: 'openai-chat',
1147
+ baseUrl: 'https://router.huggingface.co/v1',
1148
+ indirizzoModificabile: true,
1149
+ envIndirizzo: congela([]),
1150
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['HF_TOKEN', 'HUGGINGFACE_HUB_TOKEN']) }),
1151
+ chiaveObbligatoria: true,
1152
+ formaIdModello: 'vendor/nome',
1153
+ oauth: null,
1154
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
1155
+ streaming: 'dichiarato',
1156
+ toolCalling: 'dichiarato',
1157
+ cache: congela({
1158
+ marcatore: null, letturaUsage: congela([]),
1159
+ scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null,
1160
+ etichetta: 'Conteggio della cache non documentato; senza dati: non misurato.',
1161
+ fonte: 'https://huggingface.co/docs/inference-providers/tasks/chat-completion', data: '2026-09-12',
1162
+ }),
1163
+ richiestaCompatibile: congela({
1164
+ limiteUscita: null, ragionamento: 'effort',
1165
+ modelli: congela({}),
1166
+ fonte: 'https://huggingface.co/docs/inference-providers/tasks/chat-completion', data: '2026-09-12',
1167
+ }),
1168
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true }),
1169
+ prezzi: congela({ fonte: 'https://models.dev/api.json', data: '2026-09-12' }),
1170
+ // Tempo massimo applicativo già adottato; non è una misura o una promessa del fornitore.
1171
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1172
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null,
1173
+ conta: (c) => c?.data?.length, richiedeCatalogoValido: true, catalogoPubblico: true,
1174
+ // CLI-REQ-06, 17/09/2026 — catalogo pubblico: la chiave si prova solo generando.
1175
+ // Endpoint verificato il 17/09/2026 sull'esempio curl del router:
1176
+ // POST https://router.huggingface.co/v1/chat/completions con `Authorization: Bearer $HF_TOKEN`.
1177
+ // Modello: `openai/gpt-oss-120b` — è il modello della guida introduttiva del router, scritto
1178
+ // per intero in tutti e quattro gli esempi (Python, JS, fetch, curl). Senza suffisso di
1179
+ // instradamento il router sceglie da sé il fornitore più veloce, come dice quella pagina.
1180
+ // Con `max_tokens: 1` il costo è di un token di uscita: la taglia del modello non lo cambia.
1181
+ // Endpoint e modello vengono dalla STESSA pagina, quindi le due fonti coincidono: è un fatto,
1182
+ // non una scorciatoia — quella guida scrive entrambi nello stesso esempio curl.
1183
+ richiestaMinima: congela({ percorso: '/chat/completions',
1184
+ corpo: congela({ model: 'openai/gpt-oss-120b', max_tokens: 1, stream: false, messages: congela([congela({ role: 'user', content: '.' })]) }),
1185
+ fonte: 'https://huggingface.co/docs/inference-providers/index', data: '2026-09-17' }),
1186
+ fonte: 'https://huggingface.co/docs/inference-providers/index', data: '2026-09-17' }),
1187
+ destinazioneChat: true,
1188
+ credenziale: true,
1189
+ esecuzione: 'collegato',
1190
+ }),
1191
+
1192
+ // P-J — INIZIO porte Anthropic terze. Fonti consultate il 12/09/2026.
1193
+ // L'AI SDK aggiunge /messages: /v1 fa parte della base, diversamente dall'SDK Python.
1194
+ 'zai-anthropic': congela({
1195
+ id: 'zai-anthropic',
1196
+ etichetta: 'Z.AI (porta Anthropic)',
1197
+ descrizione: 'Modelli GLM tramite la porta del piano di programmazione Z.AI.',
1198
+ paginaChiavi: 'https://z.ai/manage-apikey/apikey-list',
1199
+ wire: 'anthropic-messages',
1200
+ baseUrl: 'https://api.z.ai/api/anthropic/v1',
1201
+ fonte: 'https://docs.z.ai/devpack/tool/others', data: '2026-09-12',
1202
+ indirizzoModificabile: true,
1203
+ envIndirizzo: congela(['ZAI_ANTHROPIC_BASE_URL']),
1204
+ // La guida ufficiale usa ANTHROPIC_AUTH_TOKEN: Bearer, senza ereditare segreti Anthropic.
1205
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['ZAI_ANTHROPIC_API_KEY']) }),
1206
+ chiaveObbligatoria: true, formaIdModello: 'nome', oauth: null,
1207
+ endpoint: congela({ chat: '/messages', modelli: null }),
1208
+ streaming: 'dichiarato', toolCalling: 'dichiarato',
1209
+ cache: congela({
1210
+ marcatore: null,
1211
+ letturaUsage: congela(['cache_read_input_tokens', 'prompt_tokens_details.cached_tokens']),
1212
+ scritturaUsage: congela(['cache_creation_input_tokens', 'prompt_tokens_details.cache_write_tokens']),
1213
+ inclusiNelTotale: false, scontoDichiarato: null,
1214
+ etichetta: 'Cache: senza conteggi nella risposta, non misurato.',
1215
+ fonte: 'https://docs.z.ai/devpack/tool/others', data: '2026-09-12',
1216
+ }),
1217
+ // Nessun GET modelli documentato su questa porta: riserva esplicita, mai GET inventato.
1218
+ catalogo: congela({ fonte: 'fornitore', forma: 'anthropic-data', percorso: null, inUI: true }),
1219
+ prezzi: congela({ fonte: 'https://docs.z.ai/devpack/overview', data: '2026-09-12', nota: 'Accesso legato al piano; costo della chiamata non misurato.' }),
1220
+ ragionamento: congela({ livelli: congela(['high', 'max']), fonte: 'https://docs.z.ai/devpack/latest-model', data: '2026-09-12' }),
1221
+ modelliDiRiserva: congela([
1222
+ congela({ id: 'glm-5.3-flash', nome: 'GLM 5.3 Flash', toolCalling: true, fonte: 'https://docs.z.ai/devpack/latest-model', data: '2026-09-12' }),
1223
+ congela({ id: 'glm-5.3', nome: 'GLM 5.3', toolCalling: true, fonte: 'https://docs.z.ai/devpack/latest-model', data: '2026-09-12' }),
1224
+ ]),
1225
+ modelloAusiliario: null, motivoAusiliario: 'Nessun modello ausiliario qualificato per questa porta.',
1226
+ modelsDevId: 'zai-coding-plan',
1227
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1228
+ sonda: congela({
1229
+ attiva: false, auth: 'bearer', percorso: null, urlAssoluto: null, conta: () => null,
1230
+ // La sonda ordinaria non genera. La richiesta minima richiede consentiGenerazione: true.
1231
+ // CLI-REQ-06, 17/09/2026 — aggiunta la fonte del MODELLO: `fonte` qui sotto documenta
1232
+ // l'endpoint, e questa documenta `glm-5.3-flash`. Erano già due affermazioni distinte; solo
1233
+ // che una delle due non era scritta da nessuna parte.
1234
+ richiestaMinima: congela({ percorso: '/messages',
1235
+ corpo: congela({ model: 'glm-5.3-flash', max_tokens: 1, stream: false, messages: congela([congela({ role: 'user', content: '.' })]) }),
1236
+ fonte: 'https://docs.z.ai/devpack/latest-model', data: '2026-09-12' }),
1237
+ fonte: 'https://code.claude.com/docs/en/llm-gateway-connect', data: '2026-09-12',
1238
+ }),
1239
+ destinazioneChat: true, credenziale: true, esecuzione: 'collegato',
1240
+ }),
1241
+ // 12/09, review: P-I ha già `minimax` sul wire OpenAI (documentato come supportato): questa è la SECONDA porta, come `zai-anthropic`.
1242
+ 'minimax-anthropic': congela({
1243
+ id: 'minimax-anthropic', etichetta: 'MiniMax (porta Anthropic)',
1244
+ descrizione: 'Modelli MiniMax tramite la porta compatibile Anthropic (accesso internazionale).',
1245
+ paginaChiavi: 'https://platform.minimax.io/user-center/basic-information/interface-key',
1246
+ wire: 'anthropic-messages', baseUrl: 'https://api.minimax.io/anthropic/v1',
1247
+ fonte: 'https://platform.minimax.io/docs/api-reference/text-anthropic-api', data: '2026-09-12',
1248
+ indirizzoModificabile: true, envIndirizzo: congela(['MINIMAX_BASE_URL']),
1249
+ auth: congela({ tipo: 'header', header: 'x-api-key', nomeVariabile: congela(['MINIMAX_API_KEY']) }),
1250
+ chiaveObbligatoria: true, formaIdModello: 'nome', oauth: null,
1251
+ endpoint: congela({ chat: '/messages', modelli: '/models' }),
1252
+ streaming: 'dichiarato', toolCalling: 'dichiarato',
1253
+ cache: congela({
1254
+ marcatore: 'cache_control',
1255
+ letturaUsage: congela(['cache_read_input_tokens', 'prompt_tokens_details.cached_tokens']),
1256
+ scritturaUsage: congela(['cache_creation_input_tokens', 'prompt_tokens_details.cache_write_tokens']),
1257
+ inclusiNelTotale: false, scontoDichiarato: null,
1258
+ etichetta: 'Cache dichiarata; senza conteggi nella risposta, non misurato.',
1259
+ fonte: 'https://platform.minimax.io/docs/api-reference/anthropic-api-compatible-cache', data: '2026-09-12',
1260
+ }),
1261
+ catalogo: congela({ fonte: 'fornitore', forma: 'anthropic-data', percorso: '/models', inUI: true }),
1262
+ prezzi: congela({ fonte: 'https://platform.minimax.io/docs/guides/pricing-paygo', data: '2026-09-12', valuta: 'USD', unita: 'milione di token' }),
1263
+ modelliDiRiserva: congela([
1264
+ congela({ id: 'MiniMax-M2.5', nome: 'MiniMax M2.5', toolCalling: true, fonte: 'https://platform.minimax.io/docs/api-reference/text-anthropic-api', data: '2026-09-12' }),
1265
+ congela({ id: 'MiniMax-M3', nome: 'MiniMax M3', toolCalling: true, fonte: 'https://platform.minimax.io/docs/api-reference/text-anthropic-api', data: '2026-09-12' }),
1266
+ ]),
1267
+ // Minimo a pari merito in/out; lettura cache più economica di M3/M2.7 (fonte prezzi sopra).
1268
+ modelloAusiliario: 'MiniMax-M2.5', modelsDevId: 'minimax',
1269
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1270
+ sonda: congela({ attiva: true, auth: 'x-api-key', percorso: '/models', urlAssoluto: null, conta: c => c?.data?.length, richiedeCatalogoValido: true,
1271
+ fonte: 'https://platform.minimax.io/docs/api-reference/models/anthropic/list-models', data: '2026-09-12' }),
1272
+ destinazioneChat: true, credenziale: true, esecuzione: 'collegato',
1273
+ }),
1274
+ // P-J — FINE porte Anthropic terze.
1275
+ // P-K — inizio: contratti ufficiali e fonti consultati il 12/09/2026, nessuna chiamata cloud.
1276
+ azure: congela({
1277
+ id: 'azure', etichetta: 'Azure AI Foundry', descrizione: 'Modelli della propria risorsa Azure.',
1278
+ paginaChiavi: 'https://ai.azure.com', wire: 'openai-chat', baseUrl: '', indirizzoModificabile: true,
1279
+ envIndirizzo: congela(['AZURE_OPENAI_ENDPOINT', 'AZURE_FOUNDRY_BASE_URL']),
1280
+ auth: congela({ tipo: 'header', header: 'api-key', nomeVariabile: congela(['AZURE_OPENAI_API_KEY', 'AZURE_FOUNDRY_API_KEY']) }),
1281
+ chiaveObbligatoria: true, formaIdModello: 'nome', oauth: null,
1282
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
1283
+ cloud: congela({ tipo: 'azure', versione: 'v1', campi: congela(['endpointRisorsa', 'versioneApi']),
1284
+ fonte: 'https://learn.microsoft.com/en-us/azure/foundry/openai/api-version-lifecycle', data: '2026-09-12',
1285
+ nota: 'Scegli il nome della distribuzione presente nella tua risorsa. Un modello nel catalogo non è una distribuzione.' }),
1286
+ streaming: 'dichiarato', toolCalling: 'dichiarato',
1287
+ cache: congela({ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']), scritturaUsage: congela([]),
1288
+ inclusiNelTotale: true, scontoDichiarato: null, etichetta: 'Cache senza conteggio: non misurato.',
1289
+ fonte: 'https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/prompt-caching', data: '2026-09-12' }),
1290
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true,
1291
+ riservaConfigurazione: 'I nomi delle distribuzioni appartengono alla risorsa: nessuna riserva universale.' }),
1292
+ modelliDiRiserva: congela([]), modelloAusiliario: null, modelsDevId: null,
1293
+ prezzi: congela({ fonte: 'nessuna' }), limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1294
+ sonda: congela({ attiva: true, auth: 'header', percorso: '/models', urlAssoluto: null, conta: c => c?.data?.length, richiedeCatalogoValido: true }),
1295
+ destinazioneChat: true, credenziale: true, esecuzione: 'collegato',
1296
+ }),
1297
+ bedrock: congela({
1298
+ id: 'bedrock', etichetta: 'Amazon Bedrock', descrizione: 'Modelli disponibili nella regione scelta.',
1299
+ paginaChiavi: 'https://console.aws.amazon.com/bedrock', wire: 'openai-chat', baseUrl: '', indirizzoModificabile: true,
1300
+ envIndirizzo: congela(['BEDROCK_OPENAI_BASE_URL']),
1301
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['AWS_BEARER_TOKEN_BEDROCK']) }),
1302
+ chiaveObbligatoria: true, formaIdModello: 'nome', oauth: null,
1303
+ endpoint: congela({ chat: '/chat/completions', modelli: '/models' }),
1304
+ cloud: congela({ tipo: 'bedrock', versione: 'v1', campi: congela(['regione']),
1305
+ fonte: 'https://docs.aws.amazon.com/bedrock/latest/userguide/inference-chat-completions-mantle.html', data: '2026-09-12',
1306
+ nota: 'Usa una chiave di Amazon Bedrock. I modelli disponibili dipendono dalla regione e dagli accessi.' }),
1307
+ streaming: 'dichiarato', toolCalling: 'dichiarato',
1308
+ cache: congela({ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens', 'cacheReadInputTokens']),
1309
+ scritturaUsage: congela(['prompt_tokens_details.cache_write_tokens', 'cacheWriteInputTokens']), inclusiNelTotale: true, scontoDichiarato: null,
1310
+ etichetta: 'Cache senza conteggio: non misurato.', fonte: 'https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_TokenUsage.html', data: '2026-09-12' }),
1311
+ catalogo: congela({ fonte: 'fornitore', forma: 'openai-data', percorso: '/models', inUI: true,
1312
+ riservaConfigurazione: 'Elenco limitato ai modelli compatibili restituiti dalla regione scelta.' }),
1313
+ modelliDiRiserva: congela([]), modelloAusiliario: null, modelsDevId: null,
1314
+ prezzi: congela({ fonte: 'nessuna' }), limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1315
+ sonda: congela({ attiva: true, auth: 'bearer', percorso: '/models', urlAssoluto: null, conta: c => c?.data?.length, richiedeCatalogoValido: true }),
1316
+ destinazioneChat: true, credenziale: true, esecuzione: 'collegato',
1317
+ }),
1318
+ vertex: congela({
1319
+ id: 'vertex', etichetta: 'Google Vertex AI', descrizione: 'Modelli del progetto e della regione scelti.',
1320
+ paginaChiavi: 'https://console.cloud.google.com/vertex-ai', wire: 'openai-chat', baseUrl: '', indirizzoModificabile: true,
1321
+ envIndirizzo: congela(['VERTEX_OPENAI_BASE_URL']),
1322
+ auth: congela({ tipo: 'bearer', header: 'Authorization', nomeVariabile: congela(['VERTEX_ACCESS_TOKEN']) }),
1323
+ chiaveObbligatoria: true, formaIdModello: 'vendor/nome', oauth: null,
1324
+ endpoint: congela({ chat: '/chat/completions', modelli: null }),
1325
+ cloud: congela({ tipo: 'vertex', versione: 'v1', campi: congela(['progetto', 'regione']),
1326
+ fonte: 'https://docs.cloud.google.com/vertex-ai/generative-ai/docs/samples/generativeaionvertexai-gemini-chat-completions-non-streaming', data: '2026-09-12',
1327
+ nota: 'Inserisci un accesso temporaneo già ottenuto da Google. Alla scadenza va sostituito; il rinnovo automatico non è collegato. Il catalogo non è verificabile da questo collegamento.' }),
1328
+ streaming: 'dichiarato', toolCalling: 'dichiarato',
1329
+ cache: congela({ marcatore: null, letturaUsage: congela(['prompt_tokens_details.cached_tokens']), scritturaUsage: congela([]), inclusiNelTotale: true,
1330
+ scontoDichiarato: null, etichetta: 'Cache senza conteggio: non misurato.', fonte: 'https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library', data: '2026-09-12' }),
1331
+ catalogo: congela({ fonte: 'fornitore', forma: 'configurazione', percorso: null, inUI: true,
1332
+ riservaConfigurazione: 'Nessun catalogo OpenAI documentato; scegliere il modello abilitato nel progetto.' }),
1333
+ modelliDiRiserva: congela([]), modelloAusiliario: null, modelsDevId: null,
1334
+ prezzi: congela({ fonte: 'nessuna' }), limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: true }),
1335
+ sonda: congela({ attiva: false, auth: 'bearer', percorso: null, urlAssoluto: null, conta: () => null }),
1336
+ destinazioneChat: true, credenziale: true, esecuzione: 'collegato',
1337
+ }),
1338
+ // P-K — fine
1339
+
1340
+ // ── Il motore locale llama-server — nessuna credenziale su disco, mai ───────────────────────
1341
+ local: congela({
1342
+ id: 'local',
1343
+ // P-F, 12/09/2026: scoperta locale, nessuna inferenza sui modelli installati.
1344
+ modelliDiRiserva: null,
1345
+ modelloAusiliario: null,
1346
+ modelsDevId: null,
1347
+ etichetta: 'Motore locale (llama.cpp)',
1348
+ descrizione: 'Il supervisore llama-server di questo computer.',
1349
+ paginaChiavi: null,
1350
+ wire: 'locale',
1351
+ baseUrl: null, // lo decide il supervisore, e non esce di lì
1352
+ indirizzoModificabile: false,
1353
+ envIndirizzo: congela([]),
1354
+ /*
1355
+ * ⛔⛔ LA CHIAVE È EFFIMERA E NON ESISTE SU DISCO: `randomBytes(32)` a ogni avvio, passata come
1356
+ * `--api-key` dal supervisore, che `status()` NON espone — quella risposta finisce nel
1357
+ * browser. Misurato costruendo l'URL a mano: HTTP 401 «Invalid API Key» in 4 ms.
1358
+ * ⇒ `credenziale: false`: questo fornitore non compare nel portachiavi, e non deve.
1359
+ */
1360
+ auth: congela({ tipo: 'effimera', header: 'Authorization', nomeVariabile: congela([]) }),
1361
+ chiaveObbligatoria: false,
1362
+ formaIdModello: 'nome',
1363
+ oauth: null,
1364
+ endpoint: congela({ chat: '/v1/chat/completions', modelli: null }),
1365
+ streaming: 'osservato',
1366
+ toolCalling: 'osservato',
1367
+ cache: congela({ marcatore: null, letturaUsage: congela([]), scritturaUsage: congela([]), inclusiNelTotale: true, scontoDichiarato: null }),
1368
+ catalogo: congela({ fonte: 'runtime-locale', forma: null, percorso: null, inUI: false }),
1369
+ prezzi: congela({ fonte: 'nessuna' }),
1370
+ limiti: congela({ timeoutPredefinitoSecondi: 60, tempoMassimoModificabile: false }),
1371
+ sonda: congela({ attiva: false, auth: 'nessuna', percorso: null, urlAssoluto: null, conta: () => null }),
1372
+ destinazioneChat: true,
1373
+ credenziale: false,
1374
+ esecuzione: 'motore locale',
1375
+ }),
1376
+ });
1377
+
1378
+ export class ProviderRegistryError extends Error {
1379
+ constructor(message, code = 'PROVIDER_REGISTRY_INVALID') {
1380
+ super(message);
1381
+ this.name = 'ProviderRegistryError';
1382
+ this.code = code;
1383
+ }
1384
+ }
1385
+
1386
+ /**
1387
+ * Controlla che ogni record sia dicibile. Gira all'import: un registro rotto non deve arrivare
1388
+ * a una sessione viva, deve rompere il processo mentre qualcuno sta ancora guardando.
1389
+ *
1390
+ * ⛔ Il verso contrario è provato nel test: un record con `wire` inventato, una destinazione di
1391
+ * chat senza `wire`, una capacità con un booleano al posto dei tre stati — tutti e tre devono
1392
+ * LANCIARE, non degradare.
1393
+ */
1394
+ export function verificaRegistro(registro = REGISTRO_FORNITORI) {
1395
+ const visti = new Set();
1396
+ for (const [chiave, record] of Object.entries(registro)) {
1397
+ const dove = `fornitore ${chiave}`;
1398
+ if (!record || typeof record !== 'object') throw new ProviderRegistryError(`${dove}: record assente`);
1399
+ if (record.id !== chiave) throw new ProviderRegistryError(`${dove}: id "${record.id}" diverso dalla chiave`);
1400
+ if (!/^[a-z][a-z0-9-]{0,31}$/u.test(record.id)) throw new ProviderRegistryError(`${dove}: id non utilizzabile come prefisso di fonte`);
1401
+ if (visti.has(record.id)) throw new ProviderRegistryError(`${dove}: id duplicato`);
1402
+ visti.add(record.id);
1403
+ if (typeof record.etichetta !== 'string' || record.etichetta.trim() === '') throw new ProviderRegistryError(`${dove}: etichetta mancante`);
1404
+ if (record.destinazioneChat === true && !WIRE.includes(record.wire)) throw new ProviderRegistryError(`${dove}: wire "${record.wire}" sconosciuto su una destinazione di chat`);
1405
+ if (record.destinazioneChat !== true && record.wire !== null && !WIRE.includes(record.wire)) throw new ProviderRegistryError(`${dove}: wire "${record.wire}" sconosciuto`);
1406
+ if (!AUTH.includes(record.auth?.tipo)) throw new ProviderRegistryError(`${dove}: auth "${record.auth?.tipo}" sconosciuta`);
1407
+ if (!Array.isArray(record.auth?.nomeVariabile)) throw new ProviderRegistryError(`${dove}: nomeVariabile deve essere una lista`);
1408
+ if (!['vendor/nome', 'nome'].includes(record.formaIdModello)) throw new ProviderRegistryError(`${dove}: formaIdModello sconosciuta`);
1409
+ for (const capacita of ['streaming', 'toolCalling']) {
1410
+ if (!STATI_CAPACITA.includes(record[capacita])) throw new ProviderRegistryError(`${dove}: ${capacita} deve essere uno dei tre stati, non "${record[capacita]}"`);
1411
+ }
1412
+ if (!Array.isArray(record.cache?.letturaUsage)) throw new ProviderRegistryError(`${dove}: cache.letturaUsage deve essere una lista di percorsi`);
1413
+ if (typeof record.cache?.inclusiNelTotale !== 'boolean') throw new ProviderRegistryError(`${dove}: cache.inclusiNelTotale deve dire se il totale li comprende`);
1414
+ if (record.credenziale === true && record.auth.tipo === 'effimera') throw new ProviderRegistryError(`${dove}: una credenziale effimera non può stare nel portachiavi`);
1415
+ /* ⛔ Una destinazione di chat DEVE sapere dove bussare, tranne il locale (lo sa il supervisore). */
1416
+ // P-L · entrambi i processi hanno un proprietario locale, senza base URL pubblica.
1417
+ if (record.destinazioneChat === true && !['locale', 'acp'].includes(record.wire) && (typeof record.baseUrl !== 'string' || !record.endpoint?.chat)) {
1418
+ throw new ProviderRegistryError(`${dove}: destinazione di chat senza indirizzo o senza endpoint`);
1419
+ }
1420
+ // P-L · fine controllo destinazione.
1421
+ if (record.sonda?.attiva === true && !record.sonda.percorso && !record.sonda.urlAssoluto) {
1422
+ throw new ProviderRegistryError(`${dove}: sonda attiva senza un indirizzo da chiamare`);
1423
+ }
1424
+ if (record.sonda?.dallaRadice !== undefined && (record.sonda.dallaRadice !== true
1425
+ || !/^\/(?!\/)/u.test(record.sonda.percorso ?? '') || /[\\\s]/u.test(record.sonda.percorso)
1426
+ || record.sonda.urlAssoluto)) {
1427
+ throw new ProviderRegistryError(`${dove}: percorso della sonda relativo all'origine non valido`);
1428
+ }
1429
+ /*
1430
+ * CLI-REQ-06, 17/09/2026 — la richiesta minima è l'unica cosa nel registro che COSTA: è una
1431
+ * generazione vera, pagata da chi ha salvato la chiave. Fino a oggi nessuno la controllava —
1432
+ * `zai-anthropic` la dichiarava dal 12/09 e `verificaRegistro` l'avrebbe accettata con
1433
+ * `max_tokens: 4000`, con dieci messaggi o con un modello inventato, senza un rosso.
1434
+ *
1435
+ * ⛔ Quindi si controlla quello che determina il costo (un token, un messaggio, niente flusso)
1436
+ * e quello che rende la spesa giustificabile (fonte datata da cui viene il modello). Il verso
1437
+ * contrario è provato: ognuno di questi campi storto deve LANCIARE.
1438
+ */
1439
+ if (record.sonda?.richiestaMinima !== undefined) {
1440
+ const m = record.sonda.richiestaMinima;
1441
+ const c = m?.corpo;
1442
+ if (!m || typeof m.percorso !== 'string' || !/^\/(?!\/)[^\s\\]*$/u.test(m.percorso)
1443
+ || !c || typeof c !== 'object'
1444
+ || typeof c.model !== 'string' || !c.model.trim()
1445
+ || c.max_tokens !== 1 || c.stream !== false
1446
+ || !Array.isArray(c.messages) || c.messages.length !== 1
1447
+ || c.messages[0]?.role !== 'user' || typeof c.messages[0]?.content !== 'string' || !c.messages[0].content.trim()
1448
+ || Object.keys(c).some(k => !['model', 'max_tokens', 'stream', 'messages'].includes(k))) {
1449
+ throw new ProviderRegistryError(`${dove}: la richiesta minima deve essere UNA generazione da un token, senza altri campi`);
1450
+ }
1451
+ // Il wire decide come si convalida la risposta: senza uno dei due la sonda non saprebbe leggerla.
1452
+ if (!['openai-chat', 'anthropic-messages'].includes(record.wire)) {
1453
+ throw new ProviderRegistryError(`${dove}: richiesta minima su un wire che la sonda non sa convalidare`);
1454
+ }
1455
+ /*
1456
+ * ⛔ DUE fonti, e il cancello dice esattamente quali: `sonda.fonte` per l'ENDPOINT,
1457
+ * `richiestaMinima.fonte` per il MODELLO. Nella prima stesura ce n'era una sola e il
1458
+ * messaggio prometteva «per l'endpoint e il modello»: per DeepInfra quella fonte
1459
+ * documentava solo l'endpoint (la pagina usa un altro id negli esempi) ⇒ il cancello
1460
+ * dichiarava più di ciò che controllava, che è il modo più silenzioso di mentire.
1461
+ */
1462
+ const datata = (f, d) => typeof f === 'string' && f.startsWith('https://') && /^\d{4}-\d{2}-\d{2}$/u.test(d ?? '');
1463
+ if (!datata(record.sonda.fonte, record.sonda.data)) {
1464
+ throw new ProviderRegistryError(`${dove}: richiesta minima senza fonte datata per l'endpoint`);
1465
+ }
1466
+ if (!datata(m.fonte, m.data)) {
1467
+ throw new ProviderRegistryError(`${dove}: richiesta minima senza fonte datata per il modello`);
1468
+ }
1469
+ }
1470
+ if (record.richiestaCompatibile !== undefined) {
1471
+ const p = record.richiestaCompatibile;
1472
+ if (!p || ![null, 'max_completion_tokens'].includes(p.limiteUscita) || ![null, 'effort', 'thinking', 'enable_thinking'].includes(p.ragionamento)
1473
+ || !p.modelli || typeof p.modelli !== 'object' || Array.isArray(p.modelli)
1474
+ || typeof p.fonte !== 'string' || !p.fonte.startsWith('https://') || !/^\d{4}-\d{2}-\d{2}$/u.test(p.data ?? '')) {
1475
+ throw new ProviderRegistryError(`${dove}: profilo di compatibilità senza contratto o fonte datata`);
1476
+ }
1477
+ for (const m of Object.values(p.modelli)) {
1478
+ if (!m || !Array.isArray(m.livelliRagionamento) || (!m.livelliRagionamento.length && !m.thinking)
1479
+ || new Set(m.livelliRagionamento).size !== m.livelliRagionamento.length
1480
+ || m.livelliRagionamento.some(l => !['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'default'].includes(l))
1481
+ || (m.strumentiConFormato !== undefined && typeof m.strumentiConFormato !== 'boolean')) {
1482
+ throw new ProviderRegistryError(`${dove}: particolarità del modello non valide`);
1483
+ }
1484
+ if (['thinking', 'enable_thinking'].includes(p.ragionamento)) {
1485
+ const t = m.thinking;
1486
+ if (!t || m.livelliRagionamento.length || typeof t.disattivabile !== 'boolean' || typeof t.predefinito !== 'boolean'
1487
+ || (p.ragionamento === 'enable_thinking' ? t.attivo !== true : ![null, 'enabled', 'adaptive'].includes(t.attivo))
1488
+ || (t.attivo === null && t.disattivabile) || (!t.disattivabile && !t.predefinito)
1489
+ || (t.conserva !== undefined && (t.conserva !== 'all' || t.attivo !== 'enabled'))
1490
+ || (t.soloStreaming !== undefined && typeof t.soloStreaming !== 'boolean')
1491
+ || ['temperaturaServer', 'sceltaObbligata', 'sceltaForzataConThinking'].some(k => m[k] !== undefined && typeof m[k] !== 'boolean')) {
1492
+ throw new ProviderRegistryError(`${dove}: controllo del ragionamento non valido`);
1493
+ }
1494
+ } else if (m.thinking !== undefined) {
1495
+ throw new ProviderRegistryError(`${dove}: controllo del ragionamento senza formato`);
1496
+ }
1497
+ }
1498
+ }
1499
+ for (const campo of ['modelliDiRiserva', 'modelloAusiliario']) {
1500
+ if (!Object.hasOwn(record, campo)) throw new ProviderRegistryError(`${dove}: ${campo} deve essere dichiarato`);
1501
+ }
1502
+ if (record.destinazioneChat && record.catalogo?.fonte === 'fornitore') {
1503
+ // P-K — una distribuzione/abilitazione dell'owner non si inventa come riserva pubblica.
1504
+ const riservaCloud = record.cloud?.tipo === record.id && ['azure', 'bedrock', 'vertex'].includes(record.id)
1505
+ && typeof record.catalogo.riservaConfigurazione === 'string' && record.catalogo.riservaConfigurazione.trim().length > 0;
1506
+ if (!Array.isArray(record.modelliDiRiserva) || (!record.modelliDiRiserva.length && !riservaCloud)) throw new ProviderRegistryError(`${dove}: riserva remota vuota`);
1507
+ if (riservaCloud && (record.modelliDiRiserva.length || record.modelloAusiliario !== null || record.modelsDevId !== null)) throw new ProviderRegistryError(`${dove}: catalogo cloud confuso con una riserva pubblica`);
1508
+ // P-K — fine
1509
+ const riserveViste = new Set();
1510
+ for (const m of record.modelliDiRiserva) {
1511
+ if (!m || typeof m.id !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9._:/-]*$/u.test(m.id) || riserveViste.has(m.id)
1512
+ || typeof m.nome !== 'string' || !m.nome.trim() || m.toolCalling !== true
1513
+ || typeof m.fonte !== 'string' || !m.fonte.startsWith('https://') || !/^\d{4}-\d{2}-\d{2}$/u.test(m.data ?? '')) {
1514
+ throw new ProviderRegistryError(`${dove}: riserva senza identità, strumenti o fonte datata`);
1515
+ }
1516
+ riserveViste.add(m.id);
1517
+ }
1518
+ if (record.modelloAusiliario !== null && !riserveViste.has(record.modelloAusiliario)) throw new ProviderRegistryError(`${dove}: ausiliario fuori dalla riserva dichiarata`);
1519
+ } else if (record.modelliDiRiserva !== null || record.modelloAusiliario !== null) {
1520
+ throw new ProviderRegistryError(`${dove}: locali e download devono dichiarare riserva e ausiliario null`);
1521
+ }
1522
+ if (record.modelliNoti !== undefined) {
1523
+ if (!Array.isArray(record.modelliNoti)) throw new ProviderRegistryError(`${dove}: modelliNoti deve essere una lista`);
1524
+ const modelliVisti = new Set();
1525
+ for (const modello of record.modelliNoti) {
1526
+ if (!modello?.id || modelliVisti.has(modello.id) || !modello.nome || !modello.fonte || !/^\d{4}-\d{2}-\d{2}$/u.test(modello.data ?? '')) throw new ProviderRegistryError(`${dove}: modello senza identità o fonte datata`);
1527
+ modelliVisti.add(modello.id);
1528
+ if (!Number.isSafeInteger(modello.contextLength) || modello.contextLength <= 0 || !Number.isSafeInteger(modello.maxOutputTokens) || modello.maxOutputTokens <= 0) throw new ProviderRegistryError(`${dove}: limiti del modello non validi`);
1529
+ if (['ingresso', 'cache', 'uscita'].some(k => typeof modello.prezzi?.[k] !== 'number' || !Number.isFinite(modello.prezzi[k]) || modello.prezzi[k] < 0)) throw new ProviderRegistryError(`${dove}: prezzi del modello non validi`);
1530
+ if (!Array.isArray(modello.ragionamento?.livelli) || modello.ragionamento.livelli.some(l => !record.ragionamento?.livelli?.includes(l)) || !Array.isArray(modello.ragionamento.thinking) || modello.ragionamento.thinking.some(t => !['enabled', 'disabled'].includes(t))) throw new ProviderRegistryError(`${dove}: opzioni di ragionamento non valide`);
1531
+ }
1532
+ }
1533
+ }
1534
+ return true;
1535
+ }
1536
+
1537
+ /*
1538
+ * ⛔ CLI-REQ-06, 17/09/2026 — QUANDO gira questo controllo, misurato e non ricordato.
1539
+ *
1540
+ * La revisione chiedeva di scrivere che «gira solo nei test, protegge la CI e non il runtime».
1541
+ * **Non è così, e l'ho verificato prima di scriverlo.** Questa chiamata sta al livello più alto del
1542
+ * modulo: si esegue a ogni `import` di `provider-registry.mjs`, quindi anche dentro il server vivo,
1543
+ * una volta sola, prima che qualunque altro codice possa leggere un record.
1544
+ *
1545
+ * Prova: mettendo `max_tokens: 2` nella richiesta minima di DeepInfra, il processo è morto
1546
+ * all'IMPORT con `ProviderRegistryError` — nessun test era ancora partito, e il file di prova è
1547
+ * fallito come file, non come test. Un registro rotto non arriva a una sessione viva: rompe il
1548
+ * processo mentre qualcuno sta ancora guardando, che è ciò che il commento della funzione promette.
1549
+ *
1550
+ * ⇒ Il cancello della richiesta minima protegge la CI **e** l'avvio del prodotto. Non protegge
1551
+ * invece ciò che il registro non può sapere: che un modello sia ancora servito oggi. Quello lo
1552
+ * dicono solo le fonti datate, e le rilegge una persona.
1553
+ */
1554
+ verificaRegistro();
1555
+
1556
+ /** Tutti gli id, nell'ordine di dichiarazione. */
1557
+ export const ID_FORNITORI = Object.freeze(Object.keys(REGISTRO_FORNITORI));
1558
+
1559
+ /** @returns {object|null} il record, o `null` se l'id non esiste. ⛔ Mai un ripiego inventato. */
1560
+ export function fornitore(id) {
1561
+ return Object.hasOwn(REGISTRO_FORNITORI, id) ? REGISTRO_FORNITORI[id] : null;
1562
+ }
1563
+
1564
+ /** Id upstream, senza prefisso di destinazione; nessuna selezione o chiamata implicita.
1565
+ * La compattazione futura deve qualificare questo esatto modello dal vivo: CTX_NATIVE_UNQUALIFIED.
1566
+ */
1567
+ export function modelloAusiliarioPer(fornitoreId) {
1568
+ return fornitore(fornitoreId)?.modelloAusiliario ?? null;
1569
+ }
1570
+
1571
+ /** Proiezione del solo registro nel contratto del selettore: nessun prezzo o limite inventato. */
1572
+ export function catalogoDiRiservaPer(fornitoreId) {
1573
+ const record = fornitore(fornitoreId);
1574
+ if (!record?.modelliDiRiserva?.length) return null;
1575
+ const dataRiserva = record.modelliDiRiserva.map(m => m.data).sort().at(-1);
1576
+ const [anno, mese, giorno] = dataRiserva.split('-');
1577
+ const motivo = `catalogo non raggiungibile: elenco di riserva del ${giorno}/${mese}/${anno}`;
1578
+ const metadati = { fonte: 'riserva', motivo, dataRiserva, aggiornatoAlle: null,
1579
+ verificatoAlle: null, etaCacheMs: null, daCache: false, fallbackRete: true, avvisi: [] };
1580
+ const modelli = record.modelliDiRiserva.map(m => ({
1581
+ id: record.id === 'openrouter' ? m.id : `${record.id}:${m.id}`,
1582
+ modelId: m.id, provider: record.id === 'openrouter' ? m.id.split('/')[0] : record.id,
1583
+ nome: m.nome, alias: false, contextLength: null, maxOutputTokens: null, maxInputTokens: null,
1584
+ contestoVerificato: false, prezzoPrompt: null, prezzoCompletion: null, prezzoCacheRead: null, prezzoCacheWrite: null,
1585
+ capacita: { toolCall: true, reasoning: null }, reasoning: null,
1586
+ inputModalities: null, outputModalities: null, supportedParameters: ['tools'],
1587
+ fonteDichiarazione: m.fonte, dataDichiarazione: m.data, catalogo: structuredClone(metadati),
1588
+ }));
1589
+ return { provider: record.id, modelsDevId: record.modelsDevId, disponibile: true,
1590
+ modelli, modelliDiRiserva: modelli, ...metadati };
1591
+ }
1592
+
1593
+ /** Gli id che soddisfano un predicato, nell'ordine del registro. */
1594
+ export function idFornitori(predicato) {
1595
+ return Object.freeze(ID_FORNITORI.filter((id) => predicato(REGISTRO_FORNITORI[id])));
1596
+ }
1597
+
1598
+ /** Chi ha una chiave da custodire: è l'universo del pannello Provider e del portachiavi. */
1599
+ export const ID_CON_CREDENZIALE = idFornitori((r) => r.credenziale === true);
1600
+
1601
+ /** Chi si può scegliere in chat: è l'universo del prefisso `fonte:modello`. */
1602
+ export const ID_DESTINAZIONE_CHAT = idFornitori((r) => r.destinazioneChat === true);
1603
+
1604
+ /** Chi parla un dato wire. */
1605
+ export function idPerWire(wire) {
1606
+ return idFornitori((r) => r.wire === wire);
1607
+ }
1608
+
1609
+ /** Chi espone un catalogo diretto nella UI (una scheda propria nel selettore modelli). */
1610
+ export const ID_CATALOGO_IN_UI = idFornitori((r) => r.catalogo?.inUI === true);
1611
+
1612
+ /**
1613
+ * Chi passa dagli SDK fissati nel lock (`native-provider-adapter.mjs`): tre wire, tre pacchetti.
1614
+ *
1615
+ * ⛔ Era la stessa terna `['openai','anthropic','gemini']` scritta a mano in CINQUE posti —
1616
+ * `model-destination.mjs`, `native-provider-adapter.mjs`, `context-provider-adapter.mjs`,
1617
+ * `context-token-counters.mjs` e due regex di rotta in `http-app.mjs`. Cinque copie di una
1618
+ * verità che cambia quando si aggiunge un SDK.
1619
+ */
1620
+ export const ID_NATIVI_SDK = Object.freeze([
1621
+ ...idPerWire('openai-responses'),
1622
+ ...idPerWire('anthropic-messages'),
1623
+ ...idPerWire('gemini'),
1624
+ ]);
1625
+
1626
+ /**
1627
+ * I motori che girano su questo computer e parlano il wire OpenAI (Ollama, LM Studio).
1628
+ *
1629
+ * ⛔ Serve in due posti che devono restare d'accordo: `openai-compatible-runtime.mjs` (chi sonda,
1630
+ * elenca, carica e scarica) e `server.mjs` (che gli passa l'indirizzo scelto nel pannello).
1631
+ * Erano due letterali `['ollama','lmstudio']` in attesa di divergere.
1632
+ */
1633
+ export const ID_MOTORI_LOCALI_OPENAI = idFornitori((r) => r.catalogo?.fonte === 'runtime-locale' && r.wire === 'openai-chat');