micro-models-agent 2.16.1 → 2.17.1

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 (427) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/assets/app.js +1 -314
  3. package/dist/assets/hljs.js +5 -1244
  4. package/dist/assets/index.html +11 -0
  5. package/dist/assets/js/api.js +1 -28
  6. package/dist/assets/js/core/menus.js +1 -0
  7. package/dist/assets/js/core/store/feed.js +1 -0
  8. package/dist/assets/js/core/store/index.js +1 -0
  9. package/dist/assets/js/core/store/session.js +1 -0
  10. package/dist/assets/js/core/store/settings.js +1 -0
  11. package/dist/assets/js/core/store/tabs.js +1 -0
  12. package/dist/assets/js/dialog.js +1 -105
  13. package/dist/assets/js/dom.js +1 -178
  14. package/dist/assets/js/feed/core.js +1 -0
  15. package/dist/assets/js/feed/history.js +1 -0
  16. package/dist/assets/js/feed/plan.js +1 -0
  17. package/dist/assets/js/feed/question.js +3 -0
  18. package/dist/assets/js/feed/sidebar.js +1 -0
  19. package/dist/assets/js/feed/stream.js +1 -0
  20. package/dist/assets/js/feed/turn.js +5 -0
  21. package/dist/assets/js/feed.js +1 -1250
  22. package/dist/assets/js/format.js +2 -122
  23. package/dist/assets/js/i18n.js +1 -46
  24. package/dist/assets/js/rail.js +1 -114
  25. package/dist/assets/js/session/cmdk.js +1 -0
  26. package/dist/assets/js/session/composer.js +1 -0
  27. package/dist/assets/js/session/sessions-menu.js +1 -0
  28. package/dist/assets/js/session/tabs.js +1 -0
  29. package/dist/assets/js/settings/controls.js +1 -0
  30. package/dist/assets/js/settings/editors.js +1 -0
  31. package/dist/assets/js/settings/form.js +1 -0
  32. package/dist/assets/js/settings/modal.js +1 -0
  33. package/dist/assets/js/settings/panels.js +1 -0
  34. package/dist/assets/js/settings/providers.js +1 -0
  35. package/dist/assets/js/settings/search.js +1 -0
  36. package/dist/assets/js/settings/subdialog.js +1 -0
  37. package/dist/assets/js/settings/write.js +1 -0
  38. package/dist/assets/js/settings.js +1 -1914
  39. package/dist/assets/js/sidebar/core.js +1 -0
  40. package/dist/assets/js/sidebar/lsp.js +2 -0
  41. package/dist/assets/js/sidebar/map.js +1 -0
  42. package/dist/assets/js/sidebar/state.js +1 -0
  43. package/dist/assets/js/sidebar/trace.js +5 -0
  44. package/dist/assets/js/sidebar.js +1 -705
  45. package/dist/assets/js/store.js +1 -82
  46. package/dist/assets/js/subdialog.js +1 -0
  47. package/dist/assets/md.js +1 -291
  48. package/dist/assets/setup.js +3 -816
  49. package/dist/assets/smd.js +29 -1665
  50. package/dist/assets/styles.css +1 -1598
  51. package/dist/certification/certifications.json +1 -494
  52. package/dist/i18n/en.json +1 -1851
  53. package/dist/i18n/ru.json +1 -1854
  54. package/dist/main.js +815 -55797
  55. package/dist/modules/browser/bridge-server.mjs +2 -252
  56. package/package.json +10 -5
  57. package/dist/assets/js/settings-controls.js +0 -452
  58. package/dist/assets/js/tabs.js +0 -867
  59. package/dist/cli/cache-line.js +0 -31
  60. package/dist/cli/command-suggest.js +0 -39
  61. package/dist/cli/commands.js +0 -781
  62. package/dist/cli/completer.js +0 -268
  63. package/dist/cli/index.js +0 -3
  64. package/dist/cli/json-payload.js +0 -32
  65. package/dist/cli/main.js +0 -346
  66. package/dist/cli/onboarding-commands.js +0 -126
  67. package/dist/cli/plugin-commands.js +0 -38
  68. package/dist/cli/relaunch.js +0 -40
  69. package/dist/cli/repl-commands-lsp.js +0 -75
  70. package/dist/cli/repl-commands-skills.js +0 -105
  71. package/dist/cli/repl-commands.js +0 -1218
  72. package/dist/cli/repl.js +0 -1238
  73. package/dist/cli/run-result.js +0 -38
  74. package/dist/cli/security-commands.js +0 -177
  75. package/dist/cli/setup-order.js +0 -6
  76. package/dist/cli/setup-prompt.js +0 -50
  77. package/dist/cli/setup.js +0 -161
  78. package/dist/cli/web-command.js +0 -98
  79. package/dist/config/budget.js +0 -48
  80. package/dist/config/config.js +0 -345
  81. package/dist/config/defaults.js +0 -197
  82. package/dist/config/domains.js +0 -135
  83. package/dist/config/env.js +0 -46
  84. package/dist/config/experts.js +0 -17
  85. package/dist/config/index.js +0 -5
  86. package/dist/config/paths.js +0 -56
  87. package/dist/config/redact.js +0 -44
  88. package/dist/config/registry-extra.js +0 -334
  89. package/dist/config/registry.js +0 -584
  90. package/dist/config/security.js +0 -212
  91. package/dist/config/timeouts.js +0 -74
  92. package/dist/config/types.js +0 -1
  93. package/dist/config/utils.js +0 -57
  94. package/dist/core/agent/artifact-progress.js +0 -59
  95. package/dist/core/agent/artifact-resolve.js +0 -79
  96. package/dist/core/agent/audit-gate.js +0 -53
  97. package/dist/core/agent/compaction.js +0 -89
  98. package/dist/core/agent/constants.js +0 -90
  99. package/dist/core/agent/context-log.js +0 -454
  100. package/dist/core/agent/context-renderer.js +0 -45
  101. package/dist/core/agent/critic-gate.js +0 -132
  102. package/dist/core/agent/hallucination-gate.js +0 -197
  103. package/dist/core/agent/loop-state.js +0 -68
  104. package/dist/core/agent/prefix-monitor.js +0 -101
  105. package/dist/core/agent/reasoning-resolver.js +0 -65
  106. package/dist/core/agent/token-tracker.js +0 -99
  107. package/dist/core/agent/tool-batch.js +0 -312
  108. package/dist/core/agent/tool-images.js +0 -18
  109. package/dist/core/agent/tool-output.js +0 -116
  110. package/dist/core/agent/tool-pairing.js +0 -60
  111. package/dist/core/agent-moe.js +0 -248
  112. package/dist/core/agent.js +0 -1299
  113. package/dist/core/bootstrap/agent-phase.js +0 -66
  114. package/dist/core/bootstrap/config-phase.js +0 -86
  115. package/dist/core/bootstrap/modules-phase.js +0 -269
  116. package/dist/core/bootstrap/provider-phase.js +0 -183
  117. package/dist/core/bootstrap/session-phase.js +0 -204
  118. package/dist/core/bootstrap/state.js +0 -94
  119. package/dist/core/bootstrap/workspace-phase.js +0 -115
  120. package/dist/core/bootstrap.js +0 -112
  121. package/dist/core/crash-handler.js +0 -62
  122. package/dist/core/environment.js +0 -224
  123. package/dist/core/host-plan.js +0 -43
  124. package/dist/core/host.js +0 -23
  125. package/dist/core/index.js +0 -3
  126. package/dist/core/prompt-builder.js +0 -95
  127. package/dist/core/prompt-overflow.js +0 -306
  128. package/dist/core/session-logger.js +0 -375
  129. package/dist/core/system-prompt.js +0 -117
  130. package/dist/core/types.js +0 -1
  131. package/dist/core/version.js +0 -28
  132. package/dist/core/web-host.js +0 -34
  133. package/dist/core/workspace.js +0 -80
  134. package/dist/i18n/index.js +0 -80
  135. package/dist/index.js +0 -30
  136. package/dist/llm/cache-usage.js +0 -76
  137. package/dist/llm/image-utils.js +0 -138
  138. package/dist/llm/index.js +0 -4
  139. package/dist/llm/llm-errors.js +0 -50
  140. package/dist/llm/model-loader.js +0 -86
  141. package/dist/llm/models-dev.js +0 -121
  142. package/dist/llm/openai-compat.js +0 -756
  143. package/dist/llm/orchestrator.js +0 -294
  144. package/dist/llm/provider-budget.js +0 -68
  145. package/dist/llm/provider-context.js +0 -233
  146. package/dist/llm/provider.js +0 -9
  147. package/dist/llm/response.js +0 -44
  148. package/dist/llm/stream-state.js +0 -26
  149. package/dist/llm/token-counter.js +0 -83
  150. package/dist/llm/types.js +0 -1
  151. package/dist/logger/app-logger.js +0 -212
  152. package/dist/logger/file-log.js +0 -160
  153. package/dist/logger/index.js +0 -2
  154. package/dist/migration/backup.js +0 -56
  155. package/dist/migration/detect.js +0 -61
  156. package/dist/migration/index.js +0 -3
  157. package/dist/modules/artifacts/store.js +0 -62
  158. package/dist/modules/browser/actions.js +0 -79
  159. package/dist/modules/browser/bridge-client.js +0 -203
  160. package/dist/modules/browser/bridge-path.js +0 -10
  161. package/dist/modules/browser/console-buffer.js +0 -37
  162. package/dist/modules/browser/cookie-store.js +0 -29
  163. package/dist/modules/browser/driver-types.js +0 -1
  164. package/dist/modules/browser/driver.js +0 -186
  165. package/dist/modules/browser/index.js +0 -9
  166. package/dist/modules/browser/module.js +0 -30
  167. package/dist/modules/browser/session.js +0 -322
  168. package/dist/modules/browser/snapshot.js +0 -154
  169. package/dist/modules/browser/types.js +0 -13
  170. package/dist/modules/certification/cli.js +0 -264
  171. package/dist/modules/certification/fact-checker.js +0 -90
  172. package/dist/modules/certification/loader.js +0 -127
  173. package/dist/modules/certification/manifest.js +0 -231
  174. package/dist/modules/certification/runner.js +0 -233
  175. package/dist/modules/certification/scenarios.js +0 -586
  176. package/dist/modules/certification/syntax-scenarios.js +0 -50
  177. package/dist/modules/certification/types.js +0 -1
  178. package/dist/modules/context/chunk-query.js +0 -122
  179. package/dist/modules/context/fact-extractor.js +0 -170
  180. package/dist/modules/context/history.js +0 -15
  181. package/dist/modules/context/index.js +0 -2
  182. package/dist/modules/context/manager.js +0 -482
  183. package/dist/modules/critic/client.js +0 -176
  184. package/dist/modules/critic/evidence.js +0 -81
  185. package/dist/modules/critic/provider.js +0 -45
  186. package/dist/modules/critic/tools.js +0 -78
  187. package/dist/modules/critic/types.js +0 -1
  188. package/dist/modules/execution/audit-runners.js +0 -206
  189. package/dist/modules/execution/auditor.js +0 -222
  190. package/dist/modules/execution/execution-plugin.js +0 -478
  191. package/dist/modules/execution/index.js +0 -8
  192. package/dist/modules/execution/input-from.js +0 -70
  193. package/dist/modules/execution/module.js +0 -891
  194. package/dist/modules/execution/moe-executor.js +0 -443
  195. package/dist/modules/execution/plan-actions.js +0 -642
  196. package/dist/modules/execution/plan-checks.js +0 -35
  197. package/dist/modules/execution/plan-coverage.js +0 -71
  198. package/dist/modules/execution/plan-persister.js +0 -46
  199. package/dist/modules/execution/plan-steps.js +0 -31
  200. package/dist/modules/execution/plan-store.js +0 -274
  201. package/dist/modules/execution/plan-tool.js +0 -249
  202. package/dist/modules/execution/plan-validator.js +0 -197
  203. package/dist/modules/execution/planner.js +0 -103
  204. package/dist/modules/execution/stuck-detector.js +0 -834
  205. package/dist/modules/execution/tracker.js +0 -92
  206. package/dist/modules/execution/transient-error.js +0 -30
  207. package/dist/modules/execution/types.js +0 -1
  208. package/dist/modules/execution/verifier.js +0 -343
  209. package/dist/modules/execution/windows-commands.js +0 -52
  210. package/dist/modules/hallucination/artifact-claim.js +0 -93
  211. package/dist/modules/hallucination/confidence.js +0 -81
  212. package/dist/modules/hallucination/consistency.js +0 -37
  213. package/dist/modules/hallucination/detector.js +0 -57
  214. package/dist/modules/hallucination/factual.js +0 -205
  215. package/dist/modules/hallucination/index.js +0 -6
  216. package/dist/modules/hallucination/js-identifiers.js +0 -261
  217. package/dist/modules/hallucination/llm-judge.js +0 -116
  218. package/dist/modules/hallucination/success-claim.js +0 -59
  219. package/dist/modules/index.js +0 -5
  220. package/dist/modules/indexer/cache.js +0 -88
  221. package/dist/modules/indexer/index.js +0 -4
  222. package/dist/modules/indexer/map-command.js +0 -33
  223. package/dist/modules/indexer/map-select.js +0 -87
  224. package/dist/modules/indexer/module.js +0 -293
  225. package/dist/modules/indexer/project-profile.js +0 -184
  226. package/dist/modules/indexer/symbols.js +0 -189
  227. package/dist/modules/indexer/walker.js +0 -285
  228. package/dist/modules/lsp/check-tool.js +0 -61
  229. package/dist/modules/lsp/client.js +0 -406
  230. package/dist/modules/lsp/command.js +0 -53
  231. package/dist/modules/lsp/config.js +0 -189
  232. package/dist/modules/lsp/index.js +0 -4
  233. package/dist/modules/lsp/module.js +0 -295
  234. package/dist/modules/lsp/probe.js +0 -86
  235. package/dist/modules/lsp/project-root.js +0 -69
  236. package/dist/modules/lsp/startup-check.js +0 -154
  237. package/dist/modules/lsp/types.js +0 -1
  238. package/dist/modules/mcp/client.js +0 -445
  239. package/dist/modules/mcp/index.js +0 -4
  240. package/dist/modules/mcp/module.js +0 -295
  241. package/dist/modules/mcp/registry.js +0 -20
  242. package/dist/modules/memory/index.js +0 -2
  243. package/dist/modules/memory/module.js +0 -101
  244. package/dist/modules/memory/search.js +0 -45
  245. package/dist/modules/memory/store.js +0 -107
  246. package/dist/modules/onboarding/init.js +0 -47
  247. package/dist/modules/onboarding/meet.js +0 -30
  248. package/dist/modules/onboarding/mode.js +0 -30
  249. package/dist/modules/onboarding/prompts.js +0 -43
  250. package/dist/modules/pipelines/engine.js +0 -70
  251. package/dist/modules/pipelines/index.js +0 -4
  252. package/dist/modules/pipelines/parser.js +0 -59
  253. package/dist/modules/pipelines/template.js +0 -18
  254. package/dist/modules/plugins/builtin/lint-on-write.js +0 -377
  255. package/dist/modules/plugins/builtin/notify.js +0 -11
  256. package/dist/modules/plugins/index.js +0 -2
  257. package/dist/modules/plugins/loader.js +0 -85
  258. package/dist/modules/plugins/manager.js +0 -244
  259. package/dist/modules/plugins/types.js +0 -1
  260. package/dist/modules/pricing/index.js +0 -70
  261. package/dist/modules/pricing/prices.js +0 -147
  262. package/dist/modules/processes/detect.js +0 -34
  263. package/dist/modules/processes/index.js +0 -4
  264. package/dist/modules/processes/kill-tree.js +0 -52
  265. package/dist/modules/processes/registry.js +0 -294
  266. package/dist/modules/processes/runner.js +0 -23
  267. package/dist/modules/providers/cache.js +0 -23
  268. package/dist/modules/providers/capabilities.js +0 -28
  269. package/dist/modules/providers/create.js +0 -21
  270. package/dist/modules/providers/factory.js +0 -28
  271. package/dist/modules/providers/fallback.js +0 -81
  272. package/dist/modules/providers/health.js +0 -47
  273. package/dist/modules/providers/index.js +0 -6
  274. package/dist/modules/providers/manager.js +0 -207
  275. package/dist/modules/providers/naming.js +0 -15
  276. package/dist/modules/providers/preflight.js +0 -71
  277. package/dist/modules/providers/presets.js +0 -232
  278. package/dist/modules/providers/registry.js +0 -27
  279. package/dist/modules/providers/types.js +0 -1
  280. package/dist/modules/reasoning/policy.js +0 -42
  281. package/dist/modules/reasoning/probe.js +0 -116
  282. package/dist/modules/registry.js +0 -48
  283. package/dist/modules/security/audit-log.js +0 -119
  284. package/dist/modules/security/audit-notifier.js +0 -267
  285. package/dist/modules/security/command-validator.js +0 -272
  286. package/dist/modules/security/content-scanner.js +0 -53
  287. package/dist/modules/security/data-sanitizer.js +0 -82
  288. package/dist/modules/security/encryption.js +0 -216
  289. package/dist/modules/security/index.js +0 -15
  290. package/dist/modules/security/network-validator.js +0 -236
  291. package/dist/modules/security/path-validator.js +0 -261
  292. package/dist/modules/security/protected-paths.js +0 -73
  293. package/dist/modules/security/rate-limiter.js +0 -98
  294. package/dist/modules/security/security-policies.js +0 -499
  295. package/dist/modules/security/session-encryption.js +0 -182
  296. package/dist/modules/security/session-isolation.js +0 -75
  297. package/dist/modules/session/auto-name.js +0 -63
  298. package/dist/modules/session/export.js +0 -98
  299. package/dist/modules/session/index.js +0 -4
  300. package/dist/modules/session/manager.js +0 -234
  301. package/dist/modules/session/module.js +0 -33
  302. package/dist/modules/session/store.js +0 -295
  303. package/dist/modules/session/types.js +0 -1
  304. package/dist/modules/setup/answers.js +0 -35
  305. package/dist/modules/setup/apply.js +0 -79
  306. package/dist/modules/setup/discovery.js +0 -72
  307. package/dist/modules/skills/index.js +0 -3
  308. package/dist/modules/skills/loader.js +0 -86
  309. package/dist/modules/skills/matcher.js +0 -27
  310. package/dist/modules/skills/module.js +0 -141
  311. package/dist/modules/types.js +0 -1
  312. package/dist/modules/updater/changelog-reader.js +0 -93
  313. package/dist/modules/updater/checker.js +0 -101
  314. package/dist/modules/updater/dev-detect.js +0 -18
  315. package/dist/modules/updater/index.js +0 -4
  316. package/dist/modules/updater/module.js +0 -129
  317. package/dist/modules/user-profile/compressor.js +0 -18
  318. package/dist/modules/user-profile/index.js +0 -2
  319. package/dist/modules/user-profile/profile.js +0 -77
  320. package/dist/modules/webui/answer-api.js +0 -29
  321. package/dist/modules/webui/assets.js +0 -50
  322. package/dist/modules/webui/body.js +0 -21
  323. package/dist/modules/webui/commands-settings.js +0 -209
  324. package/dist/modules/webui/commands-shared.js +0 -60
  325. package/dist/modules/webui/commands.js +0 -174
  326. package/dist/modules/webui/config-api.js +0 -134
  327. package/dist/modules/webui/file-api.js +0 -202
  328. package/dist/modules/webui/forward.js +0 -159
  329. package/dist/modules/webui/history-api.js +0 -112
  330. package/dist/modules/webui/host-state.js +0 -60
  331. package/dist/modules/webui/i18n-api.js +0 -9
  332. package/dist/modules/webui/limits.js +0 -6
  333. package/dist/modules/webui/log-forward.js +0 -20
  334. package/dist/modules/webui/memory-commands.js +0 -64
  335. package/dist/modules/webui/model-cache.js +0 -18
  336. package/dist/modules/webui/module.js +0 -148
  337. package/dist/modules/webui/persist.js +0 -24
  338. package/dist/modules/webui/prompt-io.js +0 -97
  339. package/dist/modules/webui/providers-api.js +0 -337
  340. package/dist/modules/webui/providers-view.js +0 -56
  341. package/dist/modules/webui/reasoning-view.js +0 -15
  342. package/dist/modules/webui/run-agent.js +0 -81
  343. package/dist/modules/webui/server.js +0 -284
  344. package/dist/modules/webui/sessions-api.js +0 -178
  345. package/dist/modules/webui/settings-api.js +0 -561
  346. package/dist/modules/webui/setup-api.js +0 -201
  347. package/dist/modules/webui/state.js +0 -74
  348. package/dist/modules/webui/util.js +0 -261
  349. package/dist/output/bus.js +0 -37
  350. package/dist/output/channel.js +0 -285
  351. package/dist/output/format.js +0 -15
  352. package/dist/output/index.js +0 -8
  353. package/dist/output/json-sink.js +0 -24
  354. package/dist/output/machine.js +0 -9
  355. package/dist/output/session-sink.js +0 -27
  356. package/dist/output/types.js +0 -1
  357. package/dist/tools/approve.js +0 -41
  358. package/dist/tools/attach-image.js +0 -102
  359. package/dist/tools/auto-fixer.js +0 -180
  360. package/dist/tools/auto-tags.js +0 -83
  361. package/dist/tools/bash.js +0 -649
  362. package/dist/tools/browser.js +0 -160
  363. package/dist/tools/chunk-query.js +0 -112
  364. package/dist/tools/create-dir.js +0 -60
  365. package/dist/tools/delete-file.js +0 -67
  366. package/dist/tools/download-file.js +0 -142
  367. package/dist/tools/edit-file.js +0 -136
  368. package/dist/tools/enable-tools.js +0 -59
  369. package/dist/tools/executor.js +0 -304
  370. package/dist/tools/file-info.js +0 -49
  371. package/dist/tools/filter-tools.js +0 -17
  372. package/dist/tools/glob-tool.js +0 -37
  373. package/dist/tools/grep-tool.js +0 -142
  374. package/dist/tools/hidden-tools-block.js +0 -37
  375. package/dist/tools/index.js +0 -88
  376. package/dist/tools/list-dir.js +0 -53
  377. package/dist/tools/load-skill.js +0 -51
  378. package/dist/tools/mcp-call.js +0 -80
  379. package/dist/tools/meet-done.js +0 -14
  380. package/dist/tools/move-file.js +0 -94
  381. package/dist/tools/path-utils.js +0 -108
  382. package/dist/tools/pipeline-run.js +0 -167
  383. package/dist/tools/preview.js +0 -2
  384. package/dist/tools/process-ids.js +0 -31
  385. package/dist/tools/process-kill.js +0 -33
  386. package/dist/tools/process-list.js +0 -38
  387. package/dist/tools/process-log.js +0 -49
  388. package/dist/tools/process-wait.js +0 -63
  389. package/dist/tools/prompt-io.js +0 -29
  390. package/dist/tools/question.js +0 -288
  391. package/dist/tools/read-file.js +0 -200
  392. package/dist/tools/recall.js +0 -124
  393. package/dist/tools/registry.js +0 -48
  394. package/dist/tools/remember.js +0 -72
  395. package/dist/tools/scope-check.js +0 -32
  396. package/dist/tools/scope-request.js +0 -96
  397. package/dist/tools/search-history.js +0 -88
  398. package/dist/tools/session-info.js +0 -46
  399. package/dist/tools/set-thinking.js +0 -75
  400. package/dist/tools/subagent.js +0 -257
  401. package/dist/tools/syntax-validator.js +0 -379
  402. package/dist/tools/types.js +0 -1
  403. package/dist/tools/user-input.js +0 -134
  404. package/dist/tools/web-browse.js +0 -93
  405. package/dist/tools/web-fetch.js +0 -108
  406. package/dist/tools/web-search.js +0 -107
  407. package/dist/tools/write-file.js +0 -128
  408. package/dist/ui/action-log.js +0 -13
  409. package/dist/ui/box.js +0 -78
  410. package/dist/ui/colors.js +0 -6
  411. package/dist/ui/diff.js +0 -189
  412. package/dist/ui/index.js +0 -7
  413. package/dist/ui/line-editor.js +0 -1083
  414. package/dist/ui/line-math.js +0 -98
  415. package/dist/ui/md-formatter.js +0 -322
  416. package/dist/ui/output.js +0 -19
  417. package/dist/ui/plan-view.js +0 -109
  418. package/dist/ui/renderer.js +0 -589
  419. package/dist/ui/spinner.js +0 -91
  420. package/dist/ui/table.js +0 -142
  421. package/dist/ui/theme.js +0 -143
  422. package/dist/utils/child-run.js +0 -20
  423. package/dist/utils/error.js +0 -4
  424. package/dist/utils/index.js +0 -6
  425. package/dist/utils/retry.js +0 -17
  426. package/dist/utils/sleep.js +0 -23
  427. 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
- }