micro-models-agent 2.16.1 → 2.17.2

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 (428) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +8 -3
  3. package/dist/assets/app.js +1 -314
  4. package/dist/assets/hljs.js +5 -1244
  5. package/dist/assets/index.html +11 -0
  6. package/dist/assets/js/api.js +1 -28
  7. package/dist/assets/js/core/menus.js +1 -0
  8. package/dist/assets/js/core/store/feed.js +1 -0
  9. package/dist/assets/js/core/store/index.js +1 -0
  10. package/dist/assets/js/core/store/session.js +1 -0
  11. package/dist/assets/js/core/store/settings.js +1 -0
  12. package/dist/assets/js/core/store/tabs.js +1 -0
  13. package/dist/assets/js/dialog.js +1 -105
  14. package/dist/assets/js/dom.js +1 -178
  15. package/dist/assets/js/feed/core.js +1 -0
  16. package/dist/assets/js/feed/history.js +1 -0
  17. package/dist/assets/js/feed/plan.js +1 -0
  18. package/dist/assets/js/feed/question.js +3 -0
  19. package/dist/assets/js/feed/sidebar.js +1 -0
  20. package/dist/assets/js/feed/stream.js +1 -0
  21. package/dist/assets/js/feed/turn.js +5 -0
  22. package/dist/assets/js/feed.js +1 -1250
  23. package/dist/assets/js/format.js +2 -122
  24. package/dist/assets/js/i18n.js +1 -46
  25. package/dist/assets/js/rail.js +1 -114
  26. package/dist/assets/js/session/cmdk.js +1 -0
  27. package/dist/assets/js/session/composer.js +1 -0
  28. package/dist/assets/js/session/sessions-menu.js +1 -0
  29. package/dist/assets/js/session/tabs.js +1 -0
  30. package/dist/assets/js/settings/controls.js +1 -0
  31. package/dist/assets/js/settings/editors.js +1 -0
  32. package/dist/assets/js/settings/form.js +1 -0
  33. package/dist/assets/js/settings/modal.js +1 -0
  34. package/dist/assets/js/settings/panels.js +1 -0
  35. package/dist/assets/js/settings/providers.js +1 -0
  36. package/dist/assets/js/settings/search.js +1 -0
  37. package/dist/assets/js/settings/subdialog.js +1 -0
  38. package/dist/assets/js/settings/write.js +1 -0
  39. package/dist/assets/js/settings.js +1 -1914
  40. package/dist/assets/js/sidebar/core.js +1 -0
  41. package/dist/assets/js/sidebar/lsp.js +2 -0
  42. package/dist/assets/js/sidebar/map.js +1 -0
  43. package/dist/assets/js/sidebar/state.js +1 -0
  44. package/dist/assets/js/sidebar/trace.js +5 -0
  45. package/dist/assets/js/sidebar.js +1 -705
  46. package/dist/assets/js/store.js +1 -82
  47. package/dist/assets/js/subdialog.js +1 -0
  48. package/dist/assets/md.js +1 -291
  49. package/dist/assets/setup.js +3 -816
  50. package/dist/assets/smd.js +29 -1665
  51. package/dist/assets/styles.css +1 -1598
  52. package/dist/certification/certifications.json +1 -494
  53. package/dist/i18n/en.json +1 -1851
  54. package/dist/i18n/ru.json +1 -1854
  55. package/dist/main.js +815 -55797
  56. package/dist/modules/browser/bridge-server.mjs +2 -252
  57. package/package.json +10 -5
  58. package/dist/assets/js/settings-controls.js +0 -452
  59. package/dist/assets/js/tabs.js +0 -867
  60. package/dist/cli/cache-line.js +0 -31
  61. package/dist/cli/command-suggest.js +0 -39
  62. package/dist/cli/commands.js +0 -781
  63. package/dist/cli/completer.js +0 -268
  64. package/dist/cli/index.js +0 -3
  65. package/dist/cli/json-payload.js +0 -32
  66. package/dist/cli/main.js +0 -346
  67. package/dist/cli/onboarding-commands.js +0 -126
  68. package/dist/cli/plugin-commands.js +0 -38
  69. package/dist/cli/relaunch.js +0 -40
  70. package/dist/cli/repl-commands-lsp.js +0 -75
  71. package/dist/cli/repl-commands-skills.js +0 -105
  72. package/dist/cli/repl-commands.js +0 -1218
  73. package/dist/cli/repl.js +0 -1238
  74. package/dist/cli/run-result.js +0 -38
  75. package/dist/cli/security-commands.js +0 -177
  76. package/dist/cli/setup-order.js +0 -6
  77. package/dist/cli/setup-prompt.js +0 -50
  78. package/dist/cli/setup.js +0 -161
  79. package/dist/cli/web-command.js +0 -98
  80. package/dist/config/budget.js +0 -48
  81. package/dist/config/config.js +0 -345
  82. package/dist/config/defaults.js +0 -197
  83. package/dist/config/domains.js +0 -135
  84. package/dist/config/env.js +0 -46
  85. package/dist/config/experts.js +0 -17
  86. package/dist/config/index.js +0 -5
  87. package/dist/config/paths.js +0 -56
  88. package/dist/config/redact.js +0 -44
  89. package/dist/config/registry-extra.js +0 -334
  90. package/dist/config/registry.js +0 -584
  91. package/dist/config/security.js +0 -212
  92. package/dist/config/timeouts.js +0 -74
  93. package/dist/config/types.js +0 -1
  94. package/dist/config/utils.js +0 -57
  95. package/dist/core/agent/artifact-progress.js +0 -59
  96. package/dist/core/agent/artifact-resolve.js +0 -79
  97. package/dist/core/agent/audit-gate.js +0 -53
  98. package/dist/core/agent/compaction.js +0 -89
  99. package/dist/core/agent/constants.js +0 -90
  100. package/dist/core/agent/context-log.js +0 -454
  101. package/dist/core/agent/context-renderer.js +0 -45
  102. package/dist/core/agent/critic-gate.js +0 -132
  103. package/dist/core/agent/hallucination-gate.js +0 -197
  104. package/dist/core/agent/loop-state.js +0 -68
  105. package/dist/core/agent/prefix-monitor.js +0 -101
  106. package/dist/core/agent/reasoning-resolver.js +0 -65
  107. package/dist/core/agent/token-tracker.js +0 -99
  108. package/dist/core/agent/tool-batch.js +0 -312
  109. package/dist/core/agent/tool-images.js +0 -18
  110. package/dist/core/agent/tool-output.js +0 -116
  111. package/dist/core/agent/tool-pairing.js +0 -60
  112. package/dist/core/agent-moe.js +0 -248
  113. package/dist/core/agent.js +0 -1299
  114. package/dist/core/bootstrap/agent-phase.js +0 -66
  115. package/dist/core/bootstrap/config-phase.js +0 -86
  116. package/dist/core/bootstrap/modules-phase.js +0 -269
  117. package/dist/core/bootstrap/provider-phase.js +0 -183
  118. package/dist/core/bootstrap/session-phase.js +0 -204
  119. package/dist/core/bootstrap/state.js +0 -94
  120. package/dist/core/bootstrap/workspace-phase.js +0 -115
  121. package/dist/core/bootstrap.js +0 -112
  122. package/dist/core/crash-handler.js +0 -62
  123. package/dist/core/environment.js +0 -224
  124. package/dist/core/host-plan.js +0 -43
  125. package/dist/core/host.js +0 -23
  126. package/dist/core/index.js +0 -3
  127. package/dist/core/prompt-builder.js +0 -95
  128. package/dist/core/prompt-overflow.js +0 -306
  129. package/dist/core/session-logger.js +0 -375
  130. package/dist/core/system-prompt.js +0 -117
  131. package/dist/core/types.js +0 -1
  132. package/dist/core/version.js +0 -28
  133. package/dist/core/web-host.js +0 -34
  134. package/dist/core/workspace.js +0 -80
  135. package/dist/i18n/index.js +0 -80
  136. package/dist/index.js +0 -30
  137. package/dist/llm/cache-usage.js +0 -76
  138. package/dist/llm/image-utils.js +0 -138
  139. package/dist/llm/index.js +0 -4
  140. package/dist/llm/llm-errors.js +0 -50
  141. package/dist/llm/model-loader.js +0 -86
  142. package/dist/llm/models-dev.js +0 -121
  143. package/dist/llm/openai-compat.js +0 -756
  144. package/dist/llm/orchestrator.js +0 -294
  145. package/dist/llm/provider-budget.js +0 -68
  146. package/dist/llm/provider-context.js +0 -233
  147. package/dist/llm/provider.js +0 -9
  148. package/dist/llm/response.js +0 -44
  149. package/dist/llm/stream-state.js +0 -26
  150. package/dist/llm/token-counter.js +0 -83
  151. package/dist/llm/types.js +0 -1
  152. package/dist/logger/app-logger.js +0 -212
  153. package/dist/logger/file-log.js +0 -160
  154. package/dist/logger/index.js +0 -2
  155. package/dist/migration/backup.js +0 -56
  156. package/dist/migration/detect.js +0 -61
  157. package/dist/migration/index.js +0 -3
  158. package/dist/modules/artifacts/store.js +0 -62
  159. package/dist/modules/browser/actions.js +0 -79
  160. package/dist/modules/browser/bridge-client.js +0 -203
  161. package/dist/modules/browser/bridge-path.js +0 -10
  162. package/dist/modules/browser/console-buffer.js +0 -37
  163. package/dist/modules/browser/cookie-store.js +0 -29
  164. package/dist/modules/browser/driver-types.js +0 -1
  165. package/dist/modules/browser/driver.js +0 -186
  166. package/dist/modules/browser/index.js +0 -9
  167. package/dist/modules/browser/module.js +0 -30
  168. package/dist/modules/browser/session.js +0 -322
  169. package/dist/modules/browser/snapshot.js +0 -154
  170. package/dist/modules/browser/types.js +0 -13
  171. package/dist/modules/certification/cli.js +0 -264
  172. package/dist/modules/certification/fact-checker.js +0 -90
  173. package/dist/modules/certification/loader.js +0 -127
  174. package/dist/modules/certification/manifest.js +0 -231
  175. package/dist/modules/certification/runner.js +0 -233
  176. package/dist/modules/certification/scenarios.js +0 -586
  177. package/dist/modules/certification/syntax-scenarios.js +0 -50
  178. package/dist/modules/certification/types.js +0 -1
  179. package/dist/modules/context/chunk-query.js +0 -122
  180. package/dist/modules/context/fact-extractor.js +0 -170
  181. package/dist/modules/context/history.js +0 -15
  182. package/dist/modules/context/index.js +0 -2
  183. package/dist/modules/context/manager.js +0 -482
  184. package/dist/modules/critic/client.js +0 -176
  185. package/dist/modules/critic/evidence.js +0 -81
  186. package/dist/modules/critic/provider.js +0 -45
  187. package/dist/modules/critic/tools.js +0 -78
  188. package/dist/modules/critic/types.js +0 -1
  189. package/dist/modules/execution/audit-runners.js +0 -206
  190. package/dist/modules/execution/auditor.js +0 -222
  191. package/dist/modules/execution/execution-plugin.js +0 -478
  192. package/dist/modules/execution/index.js +0 -8
  193. package/dist/modules/execution/input-from.js +0 -70
  194. package/dist/modules/execution/module.js +0 -891
  195. package/dist/modules/execution/moe-executor.js +0 -443
  196. package/dist/modules/execution/plan-actions.js +0 -642
  197. package/dist/modules/execution/plan-checks.js +0 -35
  198. package/dist/modules/execution/plan-coverage.js +0 -71
  199. package/dist/modules/execution/plan-persister.js +0 -46
  200. package/dist/modules/execution/plan-steps.js +0 -31
  201. package/dist/modules/execution/plan-store.js +0 -274
  202. package/dist/modules/execution/plan-tool.js +0 -249
  203. package/dist/modules/execution/plan-validator.js +0 -197
  204. package/dist/modules/execution/planner.js +0 -103
  205. package/dist/modules/execution/stuck-detector.js +0 -834
  206. package/dist/modules/execution/tracker.js +0 -92
  207. package/dist/modules/execution/transient-error.js +0 -30
  208. package/dist/modules/execution/types.js +0 -1
  209. package/dist/modules/execution/verifier.js +0 -343
  210. package/dist/modules/execution/windows-commands.js +0 -52
  211. package/dist/modules/hallucination/artifact-claim.js +0 -93
  212. package/dist/modules/hallucination/confidence.js +0 -81
  213. package/dist/modules/hallucination/consistency.js +0 -37
  214. package/dist/modules/hallucination/detector.js +0 -57
  215. package/dist/modules/hallucination/factual.js +0 -205
  216. package/dist/modules/hallucination/index.js +0 -6
  217. package/dist/modules/hallucination/js-identifiers.js +0 -261
  218. package/dist/modules/hallucination/llm-judge.js +0 -116
  219. package/dist/modules/hallucination/success-claim.js +0 -59
  220. package/dist/modules/index.js +0 -5
  221. package/dist/modules/indexer/cache.js +0 -88
  222. package/dist/modules/indexer/index.js +0 -4
  223. package/dist/modules/indexer/map-command.js +0 -33
  224. package/dist/modules/indexer/map-select.js +0 -87
  225. package/dist/modules/indexer/module.js +0 -293
  226. package/dist/modules/indexer/project-profile.js +0 -184
  227. package/dist/modules/indexer/symbols.js +0 -189
  228. package/dist/modules/indexer/walker.js +0 -285
  229. package/dist/modules/lsp/check-tool.js +0 -61
  230. package/dist/modules/lsp/client.js +0 -406
  231. package/dist/modules/lsp/command.js +0 -53
  232. package/dist/modules/lsp/config.js +0 -189
  233. package/dist/modules/lsp/index.js +0 -4
  234. package/dist/modules/lsp/module.js +0 -295
  235. package/dist/modules/lsp/probe.js +0 -86
  236. package/dist/modules/lsp/project-root.js +0 -69
  237. package/dist/modules/lsp/startup-check.js +0 -154
  238. package/dist/modules/lsp/types.js +0 -1
  239. package/dist/modules/mcp/client.js +0 -445
  240. package/dist/modules/mcp/index.js +0 -4
  241. package/dist/modules/mcp/module.js +0 -295
  242. package/dist/modules/mcp/registry.js +0 -20
  243. package/dist/modules/memory/index.js +0 -2
  244. package/dist/modules/memory/module.js +0 -101
  245. package/dist/modules/memory/search.js +0 -45
  246. package/dist/modules/memory/store.js +0 -107
  247. package/dist/modules/onboarding/init.js +0 -47
  248. package/dist/modules/onboarding/meet.js +0 -30
  249. package/dist/modules/onboarding/mode.js +0 -30
  250. package/dist/modules/onboarding/prompts.js +0 -43
  251. package/dist/modules/pipelines/engine.js +0 -70
  252. package/dist/modules/pipelines/index.js +0 -4
  253. package/dist/modules/pipelines/parser.js +0 -59
  254. package/dist/modules/pipelines/template.js +0 -18
  255. package/dist/modules/plugins/builtin/lint-on-write.js +0 -377
  256. package/dist/modules/plugins/builtin/notify.js +0 -11
  257. package/dist/modules/plugins/index.js +0 -2
  258. package/dist/modules/plugins/loader.js +0 -85
  259. package/dist/modules/plugins/manager.js +0 -244
  260. package/dist/modules/plugins/types.js +0 -1
  261. package/dist/modules/pricing/index.js +0 -70
  262. package/dist/modules/pricing/prices.js +0 -147
  263. package/dist/modules/processes/detect.js +0 -34
  264. package/dist/modules/processes/index.js +0 -4
  265. package/dist/modules/processes/kill-tree.js +0 -52
  266. package/dist/modules/processes/registry.js +0 -294
  267. package/dist/modules/processes/runner.js +0 -23
  268. package/dist/modules/providers/cache.js +0 -23
  269. package/dist/modules/providers/capabilities.js +0 -28
  270. package/dist/modules/providers/create.js +0 -21
  271. package/dist/modules/providers/factory.js +0 -28
  272. package/dist/modules/providers/fallback.js +0 -81
  273. package/dist/modules/providers/health.js +0 -47
  274. package/dist/modules/providers/index.js +0 -6
  275. package/dist/modules/providers/manager.js +0 -207
  276. package/dist/modules/providers/naming.js +0 -15
  277. package/dist/modules/providers/preflight.js +0 -71
  278. package/dist/modules/providers/presets.js +0 -232
  279. package/dist/modules/providers/registry.js +0 -27
  280. package/dist/modules/providers/types.js +0 -1
  281. package/dist/modules/reasoning/policy.js +0 -42
  282. package/dist/modules/reasoning/probe.js +0 -116
  283. package/dist/modules/registry.js +0 -48
  284. package/dist/modules/security/audit-log.js +0 -119
  285. package/dist/modules/security/audit-notifier.js +0 -267
  286. package/dist/modules/security/command-validator.js +0 -272
  287. package/dist/modules/security/content-scanner.js +0 -53
  288. package/dist/modules/security/data-sanitizer.js +0 -82
  289. package/dist/modules/security/encryption.js +0 -216
  290. package/dist/modules/security/index.js +0 -15
  291. package/dist/modules/security/network-validator.js +0 -236
  292. package/dist/modules/security/path-validator.js +0 -261
  293. package/dist/modules/security/protected-paths.js +0 -73
  294. package/dist/modules/security/rate-limiter.js +0 -98
  295. package/dist/modules/security/security-policies.js +0 -499
  296. package/dist/modules/security/session-encryption.js +0 -182
  297. package/dist/modules/security/session-isolation.js +0 -75
  298. package/dist/modules/session/auto-name.js +0 -63
  299. package/dist/modules/session/export.js +0 -98
  300. package/dist/modules/session/index.js +0 -4
  301. package/dist/modules/session/manager.js +0 -234
  302. package/dist/modules/session/module.js +0 -33
  303. package/dist/modules/session/store.js +0 -295
  304. package/dist/modules/session/types.js +0 -1
  305. package/dist/modules/setup/answers.js +0 -35
  306. package/dist/modules/setup/apply.js +0 -79
  307. package/dist/modules/setup/discovery.js +0 -72
  308. package/dist/modules/skills/index.js +0 -3
  309. package/dist/modules/skills/loader.js +0 -86
  310. package/dist/modules/skills/matcher.js +0 -27
  311. package/dist/modules/skills/module.js +0 -141
  312. package/dist/modules/types.js +0 -1
  313. package/dist/modules/updater/changelog-reader.js +0 -93
  314. package/dist/modules/updater/checker.js +0 -101
  315. package/dist/modules/updater/dev-detect.js +0 -18
  316. package/dist/modules/updater/index.js +0 -4
  317. package/dist/modules/updater/module.js +0 -129
  318. package/dist/modules/user-profile/compressor.js +0 -18
  319. package/dist/modules/user-profile/index.js +0 -2
  320. package/dist/modules/user-profile/profile.js +0 -77
  321. package/dist/modules/webui/answer-api.js +0 -29
  322. package/dist/modules/webui/assets.js +0 -50
  323. package/dist/modules/webui/body.js +0 -21
  324. package/dist/modules/webui/commands-settings.js +0 -209
  325. package/dist/modules/webui/commands-shared.js +0 -60
  326. package/dist/modules/webui/commands.js +0 -174
  327. package/dist/modules/webui/config-api.js +0 -134
  328. package/dist/modules/webui/file-api.js +0 -202
  329. package/dist/modules/webui/forward.js +0 -159
  330. package/dist/modules/webui/history-api.js +0 -112
  331. package/dist/modules/webui/host-state.js +0 -60
  332. package/dist/modules/webui/i18n-api.js +0 -9
  333. package/dist/modules/webui/limits.js +0 -6
  334. package/dist/modules/webui/log-forward.js +0 -20
  335. package/dist/modules/webui/memory-commands.js +0 -64
  336. package/dist/modules/webui/model-cache.js +0 -18
  337. package/dist/modules/webui/module.js +0 -148
  338. package/dist/modules/webui/persist.js +0 -24
  339. package/dist/modules/webui/prompt-io.js +0 -97
  340. package/dist/modules/webui/providers-api.js +0 -337
  341. package/dist/modules/webui/providers-view.js +0 -56
  342. package/dist/modules/webui/reasoning-view.js +0 -15
  343. package/dist/modules/webui/run-agent.js +0 -81
  344. package/dist/modules/webui/server.js +0 -284
  345. package/dist/modules/webui/sessions-api.js +0 -178
  346. package/dist/modules/webui/settings-api.js +0 -561
  347. package/dist/modules/webui/setup-api.js +0 -201
  348. package/dist/modules/webui/state.js +0 -74
  349. package/dist/modules/webui/util.js +0 -261
  350. package/dist/output/bus.js +0 -37
  351. package/dist/output/channel.js +0 -285
  352. package/dist/output/format.js +0 -15
  353. package/dist/output/index.js +0 -8
  354. package/dist/output/json-sink.js +0 -24
  355. package/dist/output/machine.js +0 -9
  356. package/dist/output/session-sink.js +0 -27
  357. package/dist/output/types.js +0 -1
  358. package/dist/tools/approve.js +0 -41
  359. package/dist/tools/attach-image.js +0 -102
  360. package/dist/tools/auto-fixer.js +0 -180
  361. package/dist/tools/auto-tags.js +0 -83
  362. package/dist/tools/bash.js +0 -649
  363. package/dist/tools/browser.js +0 -160
  364. package/dist/tools/chunk-query.js +0 -112
  365. package/dist/tools/create-dir.js +0 -60
  366. package/dist/tools/delete-file.js +0 -67
  367. package/dist/tools/download-file.js +0 -142
  368. package/dist/tools/edit-file.js +0 -136
  369. package/dist/tools/enable-tools.js +0 -59
  370. package/dist/tools/executor.js +0 -304
  371. package/dist/tools/file-info.js +0 -49
  372. package/dist/tools/filter-tools.js +0 -17
  373. package/dist/tools/glob-tool.js +0 -37
  374. package/dist/tools/grep-tool.js +0 -142
  375. package/dist/tools/hidden-tools-block.js +0 -37
  376. package/dist/tools/index.js +0 -88
  377. package/dist/tools/list-dir.js +0 -53
  378. package/dist/tools/load-skill.js +0 -51
  379. package/dist/tools/mcp-call.js +0 -80
  380. package/dist/tools/meet-done.js +0 -14
  381. package/dist/tools/move-file.js +0 -94
  382. package/dist/tools/path-utils.js +0 -108
  383. package/dist/tools/pipeline-run.js +0 -167
  384. package/dist/tools/preview.js +0 -2
  385. package/dist/tools/process-ids.js +0 -31
  386. package/dist/tools/process-kill.js +0 -33
  387. package/dist/tools/process-list.js +0 -38
  388. package/dist/tools/process-log.js +0 -49
  389. package/dist/tools/process-wait.js +0 -63
  390. package/dist/tools/prompt-io.js +0 -29
  391. package/dist/tools/question.js +0 -288
  392. package/dist/tools/read-file.js +0 -200
  393. package/dist/tools/recall.js +0 -124
  394. package/dist/tools/registry.js +0 -48
  395. package/dist/tools/remember.js +0 -72
  396. package/dist/tools/scope-check.js +0 -32
  397. package/dist/tools/scope-request.js +0 -96
  398. package/dist/tools/search-history.js +0 -88
  399. package/dist/tools/session-info.js +0 -46
  400. package/dist/tools/set-thinking.js +0 -75
  401. package/dist/tools/subagent.js +0 -257
  402. package/dist/tools/syntax-validator.js +0 -379
  403. package/dist/tools/types.js +0 -1
  404. package/dist/tools/user-input.js +0 -134
  405. package/dist/tools/web-browse.js +0 -93
  406. package/dist/tools/web-fetch.js +0 -108
  407. package/dist/tools/web-search.js +0 -107
  408. package/dist/tools/write-file.js +0 -128
  409. package/dist/ui/action-log.js +0 -13
  410. package/dist/ui/box.js +0 -78
  411. package/dist/ui/colors.js +0 -6
  412. package/dist/ui/diff.js +0 -189
  413. package/dist/ui/index.js +0 -7
  414. package/dist/ui/line-editor.js +0 -1083
  415. package/dist/ui/line-math.js +0 -98
  416. package/dist/ui/md-formatter.js +0 -322
  417. package/dist/ui/output.js +0 -19
  418. package/dist/ui/plan-view.js +0 -109
  419. package/dist/ui/renderer.js +0 -589
  420. package/dist/ui/spinner.js +0 -91
  421. package/dist/ui/table.js +0 -142
  422. package/dist/ui/theme.js +0 -143
  423. package/dist/utils/child-run.js +0 -20
  424. package/dist/utils/error.js +0 -4
  425. package/dist/utils/index.js +0 -6
  426. package/dist/utils/retry.js +0 -17
  427. package/dist/utils/sleep.js +0 -23
  428. package/dist/utils/truncate.js +0 -9
@@ -1,756 +0,0 @@
1
- import { TokenCounter } from "./token-counter";
2
- import { t } from "../i18n/index";
3
- import { readMmaVersion } from "../core/version";
4
- import { createRateLimiter } from "../modules/security/rate-limiter";
5
- import { LlmError } from "./llm-errors";
6
- import { backoffDelay } from "../utils/retry";
7
- import { StreamState } from "./stream-state";
8
- import { parseCacheUsage } from "./cache-usage";
9
- import { LLM_REQUEST_TIMEOUT_MS as REQUEST_TIMEOUT_MS, LLM_MAX_RATE_WAIT_MS as MAX_RATE_WAIT_MS, } from "../config/timeouts";
10
- /** Cap completion-токенов в max_tokens, если конфиг не задал. */
11
- const DEFAULT_MAX_COMPLETION_TOKENS = 8192;
12
- /**
13
- * Client identity-заголовок. OpenCode Go просит клиентов представляться своим
14
- * user agent (не generic SDK/HTTP-библиотекой), чтобы запросы роутились в
15
- * coding-agent трафик. `readMmaVersion()` мемоизирован — иначе каждая сборка
16
- * провайдера перечитывала бы package.json.
17
- */
18
- let cachedUserAgent = null;
19
- function defaultUserAgent() {
20
- if (cachedUserAgent === null)
21
- cachedUserAgent = `micro-models-agent/${readMmaVersion()}`;
22
- return cachedUserAgent;
23
- }
24
- /**
25
- * Нормализует харнессный уровень reasoning под контракт провайдерского поля
26
- * `reasoning_effort` (strict OpenAI/o-series принимает только
27
- * low|medium|high). Иначе:
28
- * - "default"/undefined → не шлём (решает провайдерский дефолт);
29
- * - "none" → не шлём (strict OpenAI отвечает 400; отсутствие поля = не просим
30
- * повышать, а prompt-tag ещё и получает тег /no_think в `chat()`);
31
- * - "max" → клампим к "high" (наибольшее валидное значение).
32
- * Без нормализации неизвестное значение роняет ВЕСЬ запрос HTTP 400.
33
- */
34
- function normalizeReasoningEffort(level) {
35
- if (!level || level === "default" || level === "none")
36
- return undefined;
37
- if (level === "max")
38
- return "high";
39
- return level;
40
- }
41
- function buildRequestBody(opts) {
42
- const body = {
43
- model: opts.model,
44
- messages: opts.messages,
45
- stream: opts.stream,
46
- };
47
- if (opts.maxTokens !== undefined)
48
- body.max_tokens = opts.maxTokens;
49
- if (opts.cachePrompt)
50
- body.cache_prompt = true;
51
- if (opts.promptCacheKey)
52
- body.prompt_cache_key = opts.promptCacheKey;
53
- if (opts.sessionId)
54
- body.session_id = opts.sessionId;
55
- if (opts.stream && opts.streamUsage)
56
- body.stream_options = { include_usage: true };
57
- // Применяем effort через стратегию провайдера (уровень нормализуется под
58
- // контракт OpenAI: low|medium|high).
59
- const strategy = opts.reasoningStrategy ?? "openai-effort";
60
- const effort = normalizeReasoningEffort(opts.reasoningEffort);
61
- if (strategy === "openai-effort") {
62
- if (effort)
63
- body.reasoning_effort = effort;
64
- }
65
- else if (strategy === "template-kwarg") {
66
- body.chat_template_kwargs = { enable_thinking: opts.reasoningEffort !== "none" };
67
- }
68
- else if (strategy === "prompt-tag") {
69
- // Шлём reasoning_effort в теле (модели с API-поддержкой) И тег /no_think
70
- // в сообщение (модели только с prompt-сигналами; тег ставит `chat()`).
71
- if (effort)
72
- body.reasoning_effort = effort;
73
- }
74
- // "none" = не поддерживается, пропуск
75
- if (opts.tools && opts.tools.length > 0) {
76
- body.tools = opts.tools.map((t) => ({
77
- type: "function",
78
- function: {
79
- name: t.name,
80
- description: t.description,
81
- parameters: t.parameters,
82
- },
83
- }));
84
- body.tool_choice = "auto";
85
- }
86
- return body;
87
- }
88
- /** Провайдер OpenAI-совместимого API (стрим + non-stream фолбэки, ретраи). */
89
- export class OpenAICompatProvider {
90
- model;
91
- contextWindow;
92
- config;
93
- tokenCounter;
94
- retryConfig;
95
- rateLimiter;
96
- debug;
97
- getSessionId;
98
- userAgent;
99
- cache;
100
- cacheReport;
101
- constructor(config) {
102
- this.config = config;
103
- this.model = config.model;
104
- this.contextWindow = config.contextWindow ?? 32768;
105
- this.cache = config.cache;
106
- // Без capability всё равно читаем OpenAI-совместимые поля: это не меняет
107
- // запрос и работает для локальных/кастомных серверов.
108
- this.cacheReport = config.cache?.report ?? "openai";
109
- this.tokenCounter = new TokenCounter();
110
- this.retryConfig = config.retry ?? {
111
- maxRetries: 3,
112
- baseDelay: 1000,
113
- maxDelay: 30000,
114
- maxStreamRetries: 2,
115
- noDataTimeoutMs: 180000,
116
- };
117
- this.rateLimiter = createRateLimiter(config.rateLimits);
118
- this.getSessionId = config.getSessionId;
119
- this.userAgent = config.userAgent ?? defaultUserAgent();
120
- this.debug = config.logger ? config.logger.debug.bind(config.logger) : null;
121
- }
122
- /**
123
- * Стрим чата: rate-limit → стрим → фолбэк non-stream при пустом стриме.
124
- * @param messages история; @param tools определения тулов;
125
- * @param signal abort; @param options лимит токенов/reasoning.
126
- * @returns асинхронный итератор чанков.
127
- */
128
- async *chat(messages, tools, signal, options) {
129
- // Проверка лимита до запроса. Собственный лимит ждёт слота (ограниченно),
130
- // а не роняет здоровый ход; recoverable-ошибка наружу — только если слот
131
- // не освободился в окне (цикл скормит её модели, а не умрёт).
132
- if (!this.rateLimiter.canMakeRequest()) {
133
- const waitMs = Math.min(this.rateLimiter.msUntilRequest(), MAX_RATE_WAIT_MS);
134
- if (waitMs > 0) {
135
- await this.sleep(waitMs, signal);
136
- }
137
- if (!this.rateLimiter.canMakeRequest()) {
138
- throw new LlmError(`Rate limit exceeded: ${this.rateLimiter.getConfig().maxRequestsPerMinute} requests per minute`, { recoverable: true });
139
- }
140
- }
141
- // Фиксируем запрос
142
- this.rateLimiter.recordRequest();
143
- // Prompt-tag стратегия: дописать /no_think в последнее user-сообщение (только исходящая копия)
144
- let effectiveMessages = messages;
145
- if (options?.reasoningStrategy === "prompt-tag" && options?.reasoningEffort === "none") {
146
- effectiveMessages = this.applyPromptTag(messages, "/no_think");
147
- }
148
- const streamResult = this.doStream(effectiveMessages, tools, signal, options);
149
- let hasToolCall = false;
150
- let hasText = false;
151
- let hasReasoning = false;
152
- for await (const chunk of streamResult) {
153
- if (chunk.type === "tool_call")
154
- hasToolCall = true;
155
- if (chunk.type === "text")
156
- hasText = true;
157
- if (chunk.type === "reasoning")
158
- hasReasoning = true;
159
- yield chunk;
160
- }
161
- // Стрим кончился на reasoning без текста/tool_call: модель ПОДУМАЛА, но
162
- // ответа не дала (наблюдалось: qwen3.5-9b в LM Studio шлёт reasoning_content
163
- // и встаёт). Reasoning уже отдан; недостающий ответ добираем non-stream.
164
- // Reasoning-чанк фолбэка дропаем, чтобы не показать дважды — текста не было,
165
- // риск дубля ответа отсутствует.
166
- if (!hasToolCall && !hasText) {
167
- this.debug?.("LLM streaming produced no text/tool_call — falling back to non-streaming");
168
- const fallback = await this.doNonStreaming(effectiveMessages, tools, signal, options);
169
- this.debug?.("LLM non-streaming fallback result", { chunks: fallback.length });
170
- for (const chunk of fallback) {
171
- if (hasReasoning && chunk.type === "reasoning")
172
- continue;
173
- yield chunk;
174
- }
175
- }
176
- }
177
- /** Дописывает тег в последнее user-сообщение (исходящая копия, не в контекст). */
178
- applyPromptTag(messages, tag) {
179
- const copy = [...messages];
180
- for (let i = copy.length - 1; i >= 0; i--) {
181
- if (copy[i].role === "user") {
182
- const msg = { ...copy[i] };
183
- if (typeof msg.content === "string") {
184
- msg.content = `${msg.content}\n${tag}`;
185
- }
186
- else {
187
- // Массив частей — дописываем текстовую часть
188
- msg.content = [...msg.content, { type: "text", text: tag }];
189
- }
190
- copy[i] = msg;
191
- break;
192
- }
193
- }
194
- return copy;
195
- }
196
- async *doStream(messages, tools, signal, options) {
197
- const { baseDelay, maxDelay, maxStreamRetries, noDataTimeoutMs } = this.retryConfig;
198
- const streamRetries = maxStreamRetries ?? 2;
199
- const idleTimeoutMs = noDataTimeoutMs ?? 180000;
200
- let usedNonStreamingFallback = false;
201
- for (let attempt = 0;; attempt++) {
202
- let emitted = false;
203
- const onEmit = () => {
204
- emitted = true;
205
- };
206
- try {
207
- const sawDone = yield* this.streamOnce(messages, tools, signal, options, onEmit, idleTimeoutMs);
208
- // Чистый [DONE] или частичный контент — отдаём что есть.
209
- if (sawDone || emitted)
210
- return;
211
- // Стрим закрылся до [DONE] с нулевым контентом — ретраябельная обрезка.
212
- if (attempt >= streamRetries)
213
- return;
214
- }
215
- catch (err) {
216
- if (err?.name === "AbortError" || (err instanceof LlmError && err.terminal) || signal?.aborted)
217
- throw err;
218
- // Stall буферизованного SSE после tool_call-дельт: один раз переспрашиваем
219
- // non-stream. Стрим тут — битый путь, повтор тем же способом снова
220
- // встанет; а tool_call агент получить обязан.
221
- if (err?.stalledAfterToolCall === true && !usedNonStreamingFallback) {
222
- usedNonStreamingFallback = true;
223
- this.debug?.("LLM stream stalled after tool_call — falling back to non-streaming request");
224
- const fallback = await this.doNonStreaming(messages, tools, signal, options);
225
- this.debug?.("LLM non-streaming fallback result", { chunks: fallback.length });
226
- for (const chunk of fallback) {
227
- // Reasoning до stall уже, вероятно, устримлен; копию фолбэка дропаем,
228
- // чтобы не дублировать мышление.
229
- if (chunk.type === "reasoning")
230
- continue;
231
- yield chunk;
232
- }
233
- return;
234
- }
235
- // Контент уже отдан — перестрим продублировал бы чанки.
236
- if (emitted || attempt >= streamRetries)
237
- throw err;
238
- // Обрыв до любого контента — переспрашиваем целиком.
239
- }
240
- // Экспоненциальный backoff с джиттером (общий хелпер из src/utils).
241
- await this.sleep(backoffDelay(attempt, baseDelay, maxDelay, 0.1), signal);
242
- }
243
- }
244
- async *streamOnce(messages, tools, signal, options, onEmit, idleTimeoutMs) {
245
- const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
246
- const body = buildRequestBody({
247
- model: this.model,
248
- messages,
249
- tools,
250
- stream: true,
251
- maxTokens,
252
- reasoningEffort: options?.reasoningEffort,
253
- reasoningStrategy: options?.reasoningStrategy,
254
- ...this.cacheHints(),
255
- });
256
- this.debug?.("LLM stream request", {
257
- baseUrl: this.config.baseUrl,
258
- model: this.model,
259
- stream: true,
260
- maxTokens,
261
- toolsCount: tools?.length ?? 0,
262
- reasoningEffort: options?.reasoningEffort,
263
- reasoningStrategy: options?.reasoningStrategy,
264
- messagesCount: messages.length,
265
- });
266
- const { headers, abortSignal, cleanup, isTimeout, flagTimeout, controller } = this.buildRequestSetup(signal);
267
- let response;
268
- try {
269
- response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
270
- method: "POST",
271
- headers,
272
- body: JSON.stringify(body),
273
- signal: abortSignal,
274
- });
275
- }
276
- catch (err) {
277
- // Fetch-сбои уже HTTP-ретраятся внутри fetchWithRetry —
278
- // на уровне стрима повторно не ретраим.
279
- if (err?.name === "AbortError")
280
- throw err;
281
- const wrapped = err instanceof Error ? err : new Error(String(err));
282
- if (wrapped instanceof LlmError) {
283
- wrapped.markTerminal();
284
- throw wrapped;
285
- }
286
- throw new LlmError(wrapped.message).markTerminal();
287
- }
288
- if (!response.ok) {
289
- cleanup();
290
- const errorText = await response.text();
291
- throw new LlmError(t("error.llm_api", {
292
- status: response.status,
293
- statusText: response.statusText,
294
- errorText,
295
- }), { terminal: true });
296
- }
297
- const contentType = response.headers?.get?.("content-type") ?? "";
298
- this.debug?.("LLM stream response headers", {
299
- status: response.status,
300
- contentType,
301
- });
302
- const reader = response.body?.getReader();
303
- if (!reader) {
304
- cleanup();
305
- throw new Error(t("error.no_response_body"));
306
- }
307
- const decoder = new TextDecoder();
308
- let buffer = "";
309
- const toolCallAccs = new Map();
310
- // Единый контейнер состояния стрима (вместо 9 разрозненных флагов).
311
- const st = new StreamState();
312
- // Stall после старта tool_call-дельт обычно значит: провайдер буферизует
313
- // SSE вместо стрима дельт аргументов (видели на LM Studio). Помечаем ошибку,
314
- // чтобы `doStream` переспросил non-stream вместо провала всего прогона.
315
- const idleError = () => {
316
- const e = new Error(t(st.sawToolCallStart ? "error.llm_stream_idle_toolcall" : "error.llm_stream_idle", { timeout: idleTimeoutMs }));
317
- e.stalledAfterToolCall = st.sawToolCallStart;
318
- return e;
319
- };
320
- const readIdle = () => new Promise((resolve, reject) => {
321
- const idleTimer = setTimeout(() => {
322
- flagTimeout();
323
- controller.abort();
324
- }, idleTimeoutMs);
325
- reader.read().then((result) => {
326
- clearTimeout(idleTimer);
327
- if (isTimeout())
328
- reject(idleError());
329
- else
330
- resolve(result);
331
- }, (err) => {
332
- clearTimeout(idleTimer);
333
- if (isTimeout())
334
- reject(idleError());
335
- else
336
- reject(err);
337
- });
338
- });
339
- try {
340
- while (true) {
341
- const { done, value } = await readIdle();
342
- if (done)
343
- break;
344
- buffer += decoder.decode(value, { stream: true });
345
- const lines = buffer.split("\n");
346
- buffer = lines.pop() || "";
347
- for (const line of lines) {
348
- const trimmed = line.trim();
349
- if (!trimmed)
350
- continue;
351
- let data;
352
- if (trimmed.startsWith("data: ")) {
353
- data = trimmed.slice(6);
354
- if (data === "[DONE]") {
355
- st.sawDone = true;
356
- continue;
357
- }
358
- }
359
- else {
360
- // Не SSE-фрейм — Ollama (и часть бэкендов) стримят NDJSON или шлют
361
- // mid-stream ошибки голым JSON. Пробуем парсить сырую строку;
362
- // непарсящийся шум (SSE-комменты, keep-alive) считаем и пропускаем.
363
- st.nonDataLines++;
364
- if (st.nonDataSamples.length < 5)
365
- st.nonDataSamples.push(trimmed.slice(0, 200));
366
- data = trimmed;
367
- }
368
- try {
369
- const parsed = JSON.parse(data);
370
- st.parsedChunks++;
371
- const choice = parsed.choices?.[0];
372
- if (!choice) {
373
- // Usage приходит в последнем чанке с пустыми choices
374
- if (parsed.usage) {
375
- st.usage = {
376
- promptTokens: parsed.usage.prompt_tokens ?? 0,
377
- completionTokens: parsed.usage.completion_tokens ?? 0,
378
- totalTokens: parsed.usage.total_tokens ?? 0,
379
- cache: parseCacheUsage(parsed.usage, this.cacheReport),
380
- };
381
- this.debug?.("LLM stream usage", { ...st.usage });
382
- }
383
- else if (parsed.error) {
384
- // Mid-stream ошибка провайдера (Ollama шлёт {"error": "..."}
385
- // голым JSON после старта генерации). Показываем — глотание
386
- // даст молча пустой ответ.
387
- // Брошено ВНЕ try/catch JSON-парсинга ниже.
388
- st.midStreamError = String(parsed.error);
389
- }
390
- // NOTE: `continue` тут нет — он пропустил бы throw midStreamError
391
- // ниже (он вне этого try/catch).
392
- }
393
- else {
394
- if (st.parsedChunks <= 3 || st.parsedChunks % 50 === 0) {
395
- const d = choice.delta ?? {};
396
- this.debug?.("LLM stream chunk", {
397
- n: st.parsedChunks,
398
- finishReason: choice.finish_reason ?? null,
399
- deltaKeys: Object.keys(d),
400
- });
401
- }
402
- const delta = choice.delta || {};
403
- const finishReason = choice.finish_reason;
404
- if (finishReason)
405
- st.lastFinishReason = finishReason;
406
- // `reasoning` — алиас Ollama для openai-`reasoning_content`.
407
- const reasoningDelta = delta.reasoning_content ?? delta.reasoning;
408
- if (reasoningDelta) {
409
- onEmit();
410
- yield { type: "reasoning", content: reasoningDelta };
411
- }
412
- if (delta.tool_calls) {
413
- st.sawToolCallStart = true;
414
- for (const tc of delta.tool_calls) {
415
- const idx = tc.index ?? 0;
416
- if (!toolCallAccs.has(idx)) {
417
- toolCallAccs.set(idx, { id: "", name: "", arguments: "" });
418
- }
419
- const acc = toolCallAccs.get(idx);
420
- if (tc.id)
421
- acc.id = tc.id;
422
- if (tc.function?.name)
423
- acc.name = tc.function.name;
424
- if (tc.function?.arguments) {
425
- acc.arguments += tc.function.arguments;
426
- }
427
- }
428
- }
429
- if (delta.content) {
430
- onEmit();
431
- st.sawText = true;
432
- yield { type: "text", content: delta.content };
433
- }
434
- if (finishReason === "tool_calls" && toolCallAccs.size > 0) {
435
- for (const [, acc] of toolCallAccs) {
436
- if (acc.name) {
437
- onEmit();
438
- yield {
439
- type: "tool_call",
440
- toolCall: {
441
- id: acc.id,
442
- name: acc.name,
443
- arguments: acc.arguments || "{}",
444
- },
445
- };
446
- }
447
- }
448
- toolCallAccs.clear();
449
- }
450
- }
451
- }
452
- catch {
453
- // Битые JSON-строки пропускаем
454
- }
455
- if (st.midStreamError) {
456
- throw new Error(t("error.llm_provider_stream_error", { error: st.midStreamError.slice(0, 300) }));
457
- }
458
- }
459
- }
460
- if (st.usage) {
461
- onEmit();
462
- yield { type: "done", usage: st.usage };
463
- }
464
- // finish_reason "length": completion упёрся в лимит токенов. Незаконченный
465
- // tool_call иначе молча глотается (tool_calls отдаются только на
466
- // finish_reason "tool_calls") и всплывает пустым ответом → слепые
467
- // hallucination-ретраи. Показываем настоящую причину.
468
- if (st.lastFinishReason === "length") {
469
- const truncatedToolCall = st.sawToolCallStart && toolCallAccs.size > 0;
470
- if (truncatedToolCall || !st.sawText) {
471
- // Провайдер в порядке — модель просто превысила completion-лимит.
472
- // terminal: стрим не ретраим (он завершился корректно); recoverable:
473
- // агентный цикл вернёт причину модели, чтобы она адаптировалась
474
- // (разбила вывод) вместо смерти сессии.
475
- throw new LlmError(t(truncatedToolCall ? "error.llm_truncated_toolcall" : "error.llm_truncated", {
476
- tokens: maxTokens,
477
- }), { terminal: true, recoverable: true });
478
- }
479
- // Текст уже устримлен и не завершён — не глушим ответ, но и не
480
- // принимаем обрезок молча: отдаём видимое предупреждение.
481
- yield {
482
- type: "warning",
483
- content: t("error.llm_output_truncated", { tokens: maxTokens }),
484
- };
485
- }
486
- }
487
- finally {
488
- cleanup();
489
- reader.releaseLock();
490
- this.debug?.("LLM stream finished", st.snapshot());
491
- }
492
- return st.sawDone;
493
- }
494
- /**
495
- * Вычисляет запросные подсказки кеша из возможностей провайдера и текущего
496
- * session id. Без capability возвращает «ничего», сохраняя прежнее тело.
497
- */
498
- cacheHints() {
499
- const sessionId = this.getSessionId?.();
500
- return {
501
- cachePrompt: this.cache?.requestCachePrompt === true,
502
- promptCacheKey: this.cache?.requestPromptCacheKey ? sessionId : undefined,
503
- sessionId: this.cache?.requestSessionId ? sessionId : undefined,
504
- streamUsage: this.cache?.requestStreamUsage === true,
505
- };
506
- }
507
- /** Общая подготовка запроса: auth-заголовки + таймаут/abort-контроллер. */
508
- buildRequestSetup(signal) {
509
- const headers = {
510
- "Content-Type": "application/json",
511
- "User-Agent": this.userAgent,
512
- };
513
- if (this.config.apiKey && this.config.apiKey !== "not-needed") {
514
- headers.Authorization = `Bearer ${this.config.apiKey}`;
515
- }
516
- const sessionId = this.getSessionId?.();
517
- // Когда capability задана — шлём заголовок только тем, кому он нужен
518
- // (Zen/Go). Без capability сохраняем легаси-поведение.
519
- const sendSessionHeader = this.cache ? this.cache.sessionHeader : true;
520
- if (sessionId && sendSessionHeader)
521
- headers["x-opencode-session"] = sessionId;
522
- const controller = new AbortController();
523
- let timedOut = false;
524
- const timeoutId = setTimeout(() => {
525
- timedOut = true;
526
- controller.abort();
527
- }, REQUEST_TIMEOUT_MS);
528
- const abortSignal = (() => {
529
- if (!signal)
530
- return controller.signal;
531
- try {
532
- return AbortSignal.any([controller.signal, signal]);
533
- }
534
- catch {
535
- signal.addEventListener("abort", () => controller.abort(), {
536
- once: true,
537
- });
538
- return controller.signal;
539
- }
540
- })();
541
- return {
542
- headers,
543
- abortSignal,
544
- cleanup: () => clearTimeout(timeoutId),
545
- isTimeout: () => timedOut,
546
- flagTimeout: () => {
547
- timedOut = true;
548
- },
549
- controller,
550
- };
551
- }
552
- async doNonStreaming(messages, tools, signal, options) {
553
- const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
554
- const body = buildRequestBody({
555
- model: this.model,
556
- messages,
557
- tools,
558
- stream: false,
559
- maxTokens,
560
- reasoningEffort: options?.reasoningEffort,
561
- reasoningStrategy: options?.reasoningStrategy,
562
- ...this.cacheHints(),
563
- });
564
- const { headers, abortSignal, cleanup, isTimeout } = this.buildRequestSetup(signal);
565
- try {
566
- const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
567
- method: "POST",
568
- headers,
569
- body: JSON.stringify(body),
570
- signal: abortSignal,
571
- });
572
- if (!response.ok) {
573
- const errorText = await response.text();
574
- throw new Error(t("error.llm_api", {
575
- status: response.status,
576
- statusText: response.statusText,
577
- errorText: errorText.slice(0, 500),
578
- }));
579
- }
580
- const data = await response.json();
581
- // Часть бэкендов (напр. Ollama) отдают HTTP 200 с {"error": "..."}.
582
- if (data.error && !data.choices) {
583
- throw new Error(t("error.llm_provider_stream_error", { error: String(data.error).slice(0, 300) }));
584
- }
585
- const choice = data.choices?.[0];
586
- if (!choice) {
587
- return [];
588
- }
589
- const msg = choice.message || {};
590
- const chunks = [];
591
- const reasoning = msg.reasoning_content ?? msg.reasoning;
592
- if (reasoning) {
593
- chunks.push({ type: "reasoning", content: reasoning });
594
- }
595
- if (msg.content) {
596
- chunks.push({ type: "text", content: msg.content });
597
- }
598
- if (msg.tool_calls) {
599
- for (const tc of msg.tool_calls) {
600
- chunks.push({
601
- type: "tool_call",
602
- toolCall: {
603
- id: tc.id || "",
604
- name: tc.function?.name || "",
605
- arguments: tc.function?.arguments || "{}",
606
- },
607
- });
608
- }
609
- }
610
- // Текст готов, но упёрся в cap: видимый warning вместо молча
611
- // принятого обрезанного ответа (см. стрим-путь).
612
- if (choice.finish_reason === "length" && msg.content && !msg.tool_calls) {
613
- chunks.push({
614
- type: "warning",
615
- content: t("error.llm_output_truncated", { tokens: maxTokens }),
616
- });
617
- }
618
- // Дописываем usage из ответа API
619
- if (data.usage) {
620
- chunks.push({
621
- type: "done",
622
- usage: {
623
- promptTokens: data.usage.prompt_tokens ?? 0,
624
- completionTokens: data.usage.completion_tokens ?? 0,
625
- totalTokens: data.usage.total_tokens ?? 0,
626
- cache: parseCacheUsage(data.usage, this.cacheReport),
627
- },
628
- });
629
- }
630
- return chunks;
631
- }
632
- catch (err) {
633
- if (isTimeout() && err?.name === "AbortError") {
634
- throw new Error(t("error.llm_timeout", { timeout: REQUEST_TIMEOUT_MS }));
635
- }
636
- throw err instanceof Error ? err : new Error(String(err));
637
- }
638
- finally {
639
- cleanup();
640
- }
641
- }
642
- /**
643
- * Считает токены текста (tiktoken по дефолтной модели).
644
- * @param text текст; @returns число токенов.
645
- */
646
- countTokens(text) {
647
- return this.tokenCounter.count(text);
648
- }
649
- /**
650
- * Список моделей провайдера (`/models`: id/name/model).
651
- * @returns имена моделей; @throws при недоступности провайдера.
652
- */
653
- async listModels() {
654
- try {
655
- const url = `${this.config.baseUrl.replace(/\/+$/, "")}/models`;
656
- const headers = {
657
- "Content-Type": "application/json",
658
- "User-Agent": this.userAgent,
659
- };
660
- if (this.config.apiKey && this.config.apiKey !== "not-needed") {
661
- headers.Authorization = `Bearer ${this.config.apiKey}`;
662
- }
663
- const response = await fetch(url, {
664
- method: "GET",
665
- headers,
666
- });
667
- if (!response.ok) {
668
- // Провал наружу: health-пробы должны отличать мёртвый провайдер
669
- // от пустого списка моделей.
670
- throw new Error(`HTTP ${response.status} from ${url}`);
671
- }
672
- const data = await response.json();
673
- const rawList = Array.isArray(data)
674
- ? data
675
- : data && typeof data === "object" && Array.isArray(data.data)
676
- ? data.data
677
- : [];
678
- const models = rawList
679
- .map((m) => {
680
- if (m && typeof m === "object") {
681
- const obj = m;
682
- for (const key of ["id", "name", "model"]) {
683
- const v = obj[key];
684
- if (typeof v === "string" && v)
685
- return v;
686
- }
687
- }
688
- return "";
689
- })
690
- .filter(Boolean);
691
- return models;
692
- }
693
- catch (err) {
694
- throw err instanceof Error ? err : new Error(String(err));
695
- }
696
- }
697
- async fetchWithRetry(url, init) {
698
- const { maxRetries, baseDelay, maxDelay } = this.retryConfig;
699
- let lastStatus = 0;
700
- let lastRetryAfter = 0;
701
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
702
- lastRetryAfter = 0;
703
- try {
704
- const response = await fetch(url, init);
705
- if (!this.isRetryable(response.status))
706
- return response;
707
- lastStatus = response.status;
708
- const retryAfter = Number(response.headers.get("retry-after") ?? 0);
709
- lastRetryAfter = retryAfter > 0 ? retryAfter * 1000 : 0;
710
- }
711
- catch (err) {
712
- if (err.name === "AbortError") {
713
- throw err;
714
- }
715
- if (attempt === maxRetries)
716
- throw err;
717
- }
718
- if (attempt < maxRetries) {
719
- // Приоритет окну Retry-After провайдера (cap на maxDelay); иначе —
720
- // экспоненциальный backoff. 429 с free-тарифов требуют реального ожидания.
721
- const delay = lastRetryAfter > 0
722
- ? Math.min(lastRetryAfter, maxDelay)
723
- : backoffDelay(attempt, baseDelay, maxDelay, 0.1);
724
- await this.sleep(delay, init.signal ?? undefined);
725
- }
726
- }
727
- if (lastStatus === 429) {
728
- throw new LlmError(t("error.llm_429", {
729
- model: this.model,
730
- baseUrl: this.config.baseUrl,
731
- }), { llmStatus: lastStatus });
732
- }
733
- throw new LlmError(t("error.llm_retries"), { llmStatus: lastStatus });
734
- }
735
- isRetryable(status) {
736
- return status === 429 || status >= 500;
737
- }
738
- sleep(ms, signal) {
739
- return new Promise((resolve, reject) => {
740
- if (signal?.aborted) {
741
- reject(new DOMException("Aborted", "AbortError"));
742
- return;
743
- }
744
- let timer;
745
- const onAbort = () => {
746
- clearTimeout(timer);
747
- reject(new DOMException("Aborted", "AbortError"));
748
- };
749
- timer = setTimeout(() => {
750
- signal?.removeEventListener("abort", onAbort);
751
- resolve();
752
- }, ms);
753
- signal?.addEventListener("abort", onAbort, { once: true });
754
- });
755
- }
756
- }