micro-models-agent 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/assets/js/tabs.js +22 -3
  3. package/dist/cli/cache-line.js +1 -0
  4. package/dist/cli/command-suggest.js +5 -4
  5. package/dist/cli/commands.js +231 -122
  6. package/dist/cli/completer.js +132 -31
  7. package/dist/cli/index.js +1 -0
  8. package/dist/cli/json-payload.js +4 -4
  9. package/dist/cli/main.js +166 -61
  10. package/dist/cli/onboarding-commands.js +126 -0
  11. package/dist/cli/plugin-commands.js +2 -1
  12. package/dist/cli/relaunch.js +16 -13
  13. package/dist/cli/repl-commands-lsp.js +75 -0
  14. package/dist/cli/repl-commands-skills.js +105 -0
  15. package/dist/cli/repl-commands.js +589 -266
  16. package/dist/cli/repl.js +436 -177
  17. package/dist/cli/run-result.js +5 -6
  18. package/dist/cli/security-commands.js +37 -28
  19. package/dist/cli/setup-order.js +4 -55
  20. package/dist/cli/setup-prompt.js +3 -2
  21. package/dist/cli/setup.js +27 -107
  22. package/dist/cli/web-command.js +98 -0
  23. package/dist/config/budget.js +5 -5
  24. package/dist/config/config.js +103 -96
  25. package/dist/config/defaults.js +38 -8
  26. package/dist/config/domains.js +24 -27
  27. package/dist/config/env.js +46 -0
  28. package/dist/config/experts.js +2 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/paths.js +56 -0
  31. package/dist/config/redact.js +44 -0
  32. package/dist/config/registry-extra.js +334 -0
  33. package/dist/config/registry.js +584 -0
  34. package/dist/config/security.js +35 -36
  35. package/dist/config/timeouts.js +74 -0
  36. package/dist/config/utils.js +16 -15
  37. package/dist/core/agent/artifact-progress.js +59 -0
  38. package/dist/core/agent/artifact-resolve.js +79 -0
  39. package/dist/core/agent/audit-gate.js +7 -3
  40. package/dist/core/agent/compaction.js +2 -2
  41. package/dist/core/agent/constants.js +33 -4
  42. package/dist/core/agent/context-log.js +454 -0
  43. package/dist/core/agent/context-renderer.js +5 -0
  44. package/dist/core/agent/critic-gate.js +132 -0
  45. package/dist/core/agent/hallucination-gate.js +148 -38
  46. package/dist/core/agent/loop-state.js +15 -0
  47. package/dist/core/agent/prefix-monitor.js +1 -1
  48. package/dist/core/agent/reasoning-resolver.js +10 -1
  49. package/dist/core/agent/token-tracker.js +7 -4
  50. package/dist/core/agent/tool-batch.js +93 -18
  51. package/dist/core/agent/tool-images.js +18 -0
  52. package/dist/core/agent/tool-output.js +65 -11
  53. package/dist/core/agent/tool-pairing.js +60 -0
  54. package/dist/core/agent-moe.js +25 -24
  55. package/dist/core/agent.js +429 -108
  56. package/dist/core/bootstrap/agent-phase.js +66 -0
  57. package/dist/core/bootstrap/config-phase.js +86 -0
  58. package/dist/core/bootstrap/modules-phase.js +269 -0
  59. package/dist/core/bootstrap/provider-phase.js +183 -0
  60. package/dist/core/bootstrap/session-phase.js +204 -0
  61. package/dist/core/bootstrap/state.js +94 -0
  62. package/dist/core/bootstrap/workspace-phase.js +115 -0
  63. package/dist/core/bootstrap.js +92 -660
  64. package/dist/core/crash-handler.js +17 -7
  65. package/dist/core/environment.js +37 -12
  66. package/dist/core/host-plan.js +43 -0
  67. package/dist/core/host.js +23 -0
  68. package/dist/core/index.js +1 -0
  69. package/dist/core/prompt-builder.js +17 -1
  70. package/dist/core/prompt-overflow.js +42 -43
  71. package/dist/core/session-logger.js +122 -30
  72. package/dist/core/system-prompt.js +117 -0
  73. package/dist/core/version.js +6 -4
  74. package/dist/core/web-host.js +34 -0
  75. package/dist/core/workspace.js +19 -15
  76. package/dist/i18n/index.js +37 -3
  77. package/dist/index.js +7 -3
  78. package/dist/llm/image-utils.js +55 -64
  79. package/dist/llm/llm-errors.js +9 -0
  80. package/dist/llm/model-loader.js +10 -32
  81. package/dist/llm/models-dev.js +121 -0
  82. package/dist/llm/openai-compat.js +146 -73
  83. package/dist/llm/orchestrator.js +33 -11
  84. package/dist/llm/provider-budget.js +2 -2
  85. package/dist/llm/provider-context.js +233 -0
  86. package/dist/llm/provider.js +1 -1
  87. package/dist/llm/response.js +5 -0
  88. package/dist/llm/token-counter.js +27 -11
  89. package/dist/logger/app-logger.js +48 -22
  90. package/dist/logger/file-log.js +28 -19
  91. package/dist/logger/index.js +1 -0
  92. package/dist/main.js +273 -47
  93. package/dist/migration/backup.js +14 -3
  94. package/dist/migration/detect.js +12 -3
  95. package/dist/migration/index.js +1 -0
  96. package/dist/modules/artifacts/store.js +9 -8
  97. package/dist/modules/browser/actions.js +6 -0
  98. package/dist/modules/browser/bridge-client.js +14 -12
  99. package/dist/modules/browser/bridge-path.js +4 -4
  100. package/dist/modules/browser/console-buffer.js +37 -0
  101. package/dist/modules/browser/cookie-store.js +5 -0
  102. package/dist/modules/browser/driver-types.js +1 -0
  103. package/dist/modules/browser/driver.js +19 -11
  104. package/dist/modules/browser/index.js +3 -1
  105. package/dist/modules/browser/module.js +2 -1
  106. package/dist/modules/browser/session.js +10 -30
  107. package/dist/modules/browser/snapshot.js +19 -13
  108. package/dist/modules/browser/types.js +1 -0
  109. package/dist/modules/certification/cli.js +28 -20
  110. package/dist/modules/certification/fact-checker.js +9 -1
  111. package/dist/modules/certification/loader.js +9 -2
  112. package/dist/modules/certification/manifest.js +83 -21
  113. package/dist/modules/certification/runner.js +23 -13
  114. package/dist/modules/certification/scenarios.js +20 -20
  115. package/dist/modules/certification/syntax-scenarios.js +1 -2
  116. package/dist/modules/context/chunk-query.js +11 -9
  117. package/dist/modules/context/fact-extractor.js +37 -39
  118. package/dist/modules/context/index.js +1 -0
  119. package/dist/modules/context/manager.js +106 -80
  120. package/dist/modules/critic/client.js +176 -0
  121. package/dist/modules/critic/evidence.js +81 -0
  122. package/dist/modules/critic/provider.js +45 -0
  123. package/dist/modules/critic/tools.js +78 -0
  124. package/dist/modules/critic/types.js +1 -0
  125. package/dist/modules/execution/audit-runners.js +28 -34
  126. package/dist/modules/execution/auditor.js +53 -49
  127. package/dist/modules/execution/execution-plugin.js +142 -102
  128. package/dist/modules/execution/index.js +1 -1
  129. package/dist/modules/execution/input-from.js +37 -13
  130. package/dist/modules/execution/module.js +368 -191
  131. package/dist/modules/execution/moe-executor.js +53 -26
  132. package/dist/modules/execution/plan-actions.js +320 -202
  133. package/dist/modules/execution/plan-checks.js +35 -0
  134. package/dist/modules/execution/plan-coverage.js +36 -33
  135. package/dist/modules/execution/plan-steps.js +11 -3
  136. package/dist/modules/execution/plan-store.js +83 -17
  137. package/dist/modules/execution/plan-tool.js +89 -35
  138. package/dist/modules/execution/plan-validator.js +30 -10
  139. package/dist/modules/execution/planner.js +22 -13
  140. package/dist/modules/execution/stuck-detector.js +196 -93
  141. package/dist/modules/execution/tracker.js +22 -8
  142. package/dist/modules/execution/verifier.js +69 -48
  143. package/dist/modules/execution/windows-commands.js +12 -12
  144. package/dist/modules/hallucination/artifact-claim.js +93 -0
  145. package/dist/modules/hallucination/confidence.js +13 -11
  146. package/dist/modules/hallucination/consistency.js +10 -2
  147. package/dist/modules/hallucination/detector.js +10 -5
  148. package/dist/modules/hallucination/factual.js +43 -26
  149. package/dist/modules/hallucination/index.js +1 -0
  150. package/dist/modules/hallucination/js-identifiers.js +44 -45
  151. package/dist/modules/hallucination/llm-judge.js +17 -12
  152. package/dist/modules/hallucination/success-claim.js +59 -0
  153. package/dist/modules/indexer/cache.js +52 -4
  154. package/dist/modules/indexer/index.js +1 -0
  155. package/dist/modules/indexer/map-command.js +2 -4
  156. package/dist/modules/indexer/map-select.js +16 -16
  157. package/dist/modules/indexer/module.js +47 -12
  158. package/dist/modules/indexer/project-profile.js +8 -7
  159. package/dist/modules/indexer/symbols.js +15 -15
  160. package/dist/modules/indexer/walker.js +159 -29
  161. package/dist/modules/lsp/check-tool.js +7 -5
  162. package/dist/modules/lsp/client.js +39 -39
  163. package/dist/modules/lsp/command.js +17 -24
  164. package/dist/modules/lsp/config.js +12 -11
  165. package/dist/modules/lsp/index.js +1 -0
  166. package/dist/modules/lsp/module.js +48 -46
  167. package/dist/modules/lsp/probe.js +13 -14
  168. package/dist/modules/lsp/project-root.js +14 -17
  169. package/dist/modules/lsp/startup-check.js +18 -16
  170. package/dist/modules/mcp/client.js +59 -62
  171. package/dist/modules/mcp/index.js +1 -0
  172. package/dist/modules/mcp/module.js +66 -37
  173. package/dist/modules/mcp/registry.js +5 -0
  174. package/dist/modules/memory/index.js +1 -0
  175. package/dist/modules/memory/module.js +12 -8
  176. package/dist/modules/memory/search.js +4 -1
  177. package/dist/modules/memory/store.js +40 -2
  178. package/dist/modules/onboarding/init.js +47 -0
  179. package/dist/modules/onboarding/meet.js +30 -0
  180. package/dist/modules/onboarding/mode.js +30 -0
  181. package/dist/modules/onboarding/prompts.js +43 -0
  182. package/dist/modules/pipelines/engine.js +10 -0
  183. package/dist/modules/pipelines/index.js +1 -0
  184. package/dist/modules/pipelines/parser.js +4 -1
  185. package/dist/modules/pipelines/template.js +4 -0
  186. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  187. package/dist/modules/plugins/builtin/notify.js +2 -0
  188. package/dist/modules/plugins/index.js +1 -0
  189. package/dist/modules/plugins/loader.js +18 -3
  190. package/dist/modules/plugins/manager.js +37 -17
  191. package/dist/modules/pricing/index.js +10 -11
  192. package/dist/modules/pricing/prices.js +12 -12
  193. package/dist/modules/processes/index.js +1 -0
  194. package/dist/modules/processes/kill-tree.js +27 -31
  195. package/dist/modules/processes/registry.js +44 -25
  196. package/dist/modules/processes/runner.js +4 -4
  197. package/dist/modules/providers/capabilities.js +28 -0
  198. package/dist/modules/providers/create.js +3 -4
  199. package/dist/modules/providers/factory.js +7 -7
  200. package/dist/modules/providers/fallback.js +12 -12
  201. package/dist/modules/providers/health.js +4 -4
  202. package/dist/modules/providers/manager.js +50 -19
  203. package/dist/modules/providers/naming.js +15 -0
  204. package/dist/modules/providers/preflight.js +71 -0
  205. package/dist/modules/providers/presets.js +39 -8
  206. package/dist/modules/providers/registry.js +7 -2
  207. package/dist/modules/reasoning/policy.js +8 -6
  208. package/dist/modules/reasoning/probe.js +30 -25
  209. package/dist/modules/security/audit-log.js +19 -36
  210. package/dist/modules/security/audit-notifier.js +35 -75
  211. package/dist/modules/security/command-validator.js +99 -51
  212. package/dist/modules/security/content-scanner.js +8 -8
  213. package/dist/modules/security/data-sanitizer.js +9 -16
  214. package/dist/modules/security/encryption.js +32 -52
  215. package/dist/modules/security/index.js +2 -1
  216. package/dist/modules/security/network-validator.js +120 -43
  217. package/dist/modules/security/path-validator.js +61 -46
  218. package/dist/modules/security/protected-paths.js +73 -0
  219. package/dist/modules/security/rate-limiter.js +17 -49
  220. package/dist/modules/security/security-policies.js +25 -57
  221. package/dist/modules/security/session-encryption.js +32 -71
  222. package/dist/modules/security/session-isolation.js +15 -28
  223. package/dist/modules/session/auto-name.js +63 -0
  224. package/dist/modules/session/export.js +98 -0
  225. package/dist/modules/session/index.js +1 -0
  226. package/dist/modules/session/manager.js +79 -28
  227. package/dist/modules/session/module.js +4 -3
  228. package/dist/modules/session/store.js +57 -20
  229. package/dist/modules/setup/answers.js +35 -0
  230. package/dist/modules/setup/apply.js +79 -0
  231. package/dist/modules/setup/discovery.js +72 -0
  232. package/dist/modules/skills/index.js +1 -0
  233. package/dist/modules/skills/loader.js +9 -1
  234. package/dist/modules/skills/module.js +11 -0
  235. package/dist/modules/updater/changelog-reader.js +15 -16
  236. package/dist/modules/updater/checker.js +9 -4
  237. package/dist/modules/updater/dev-detect.js +7 -6
  238. package/dist/modules/updater/index.js +1 -0
  239. package/dist/modules/updater/module.js +17 -15
  240. package/dist/modules/user-profile/compressor.js +2 -0
  241. package/dist/modules/user-profile/index.js +1 -0
  242. package/dist/modules/user-profile/profile.js +10 -1
  243. package/dist/modules/webui/answer-api.js +29 -0
  244. package/dist/modules/webui/assets.js +50 -0
  245. package/dist/modules/webui/body.js +21 -0
  246. package/dist/modules/webui/commands-settings.js +209 -0
  247. package/dist/modules/webui/commands-shared.js +60 -0
  248. package/dist/modules/webui/commands.js +174 -0
  249. package/dist/modules/webui/config-api.js +134 -0
  250. package/dist/modules/webui/file-api.js +202 -0
  251. package/dist/modules/webui/forward.js +159 -0
  252. package/dist/modules/webui/history-api.js +112 -0
  253. package/dist/modules/webui/host-state.js +60 -0
  254. package/dist/modules/webui/i18n-api.js +9 -0
  255. package/dist/modules/webui/limits.js +6 -0
  256. package/dist/modules/webui/log-forward.js +20 -0
  257. package/dist/modules/webui/memory-commands.js +64 -0
  258. package/dist/modules/webui/model-cache.js +18 -0
  259. package/dist/modules/webui/module.js +148 -0
  260. package/dist/modules/webui/persist.js +24 -0
  261. package/dist/modules/webui/prompt-io.js +97 -0
  262. package/dist/modules/webui/providers-api.js +337 -0
  263. package/dist/modules/webui/providers-view.js +56 -0
  264. package/dist/modules/webui/reasoning-view.js +15 -0
  265. package/dist/modules/webui/run-agent.js +81 -0
  266. package/dist/modules/webui/server.js +284 -0
  267. package/dist/modules/webui/sessions-api.js +178 -0
  268. package/dist/modules/webui/settings-api.js +561 -0
  269. package/dist/modules/webui/setup-api.js +201 -0
  270. package/dist/modules/webui/state.js +74 -0
  271. package/dist/modules/webui/util.js +261 -0
  272. package/dist/output/bus.js +7 -2
  273. package/dist/output/channel.js +80 -28
  274. package/dist/output/format.js +2 -1
  275. package/dist/output/index.js +1 -0
  276. package/dist/output/json-sink.js +5 -3
  277. package/dist/output/machine.js +4 -3
  278. package/dist/output/session-sink.js +4 -4
  279. package/dist/tools/approve.js +4 -0
  280. package/dist/tools/attach-image.js +5 -4
  281. package/dist/tools/auto-fixer.js +26 -44
  282. package/dist/tools/auto-tags.js +83 -0
  283. package/dist/tools/bash.js +193 -116
  284. package/dist/tools/browser.js +76 -31
  285. package/dist/tools/chunk-query.js +14 -6
  286. package/dist/tools/create-dir.js +9 -5
  287. package/dist/tools/delete-file.js +9 -5
  288. package/dist/tools/download-file.js +32 -7
  289. package/dist/tools/edit-file.js +52 -14
  290. package/dist/tools/enable-tools.js +6 -6
  291. package/dist/tools/executor.js +132 -27
  292. package/dist/tools/file-info.js +3 -1
  293. package/dist/tools/filter-tools.js +3 -3
  294. package/dist/tools/glob-tool.js +4 -1
  295. package/dist/tools/grep-tool.js +13 -10
  296. package/dist/tools/hidden-tools-block.js +6 -6
  297. package/dist/tools/index.js +16 -8
  298. package/dist/tools/list-dir.js +4 -2
  299. package/dist/tools/load-skill.js +7 -0
  300. package/dist/tools/mcp-call.js +15 -4
  301. package/dist/tools/meet-done.js +14 -0
  302. package/dist/tools/move-file.js +14 -7
  303. package/dist/tools/path-utils.js +29 -29
  304. package/dist/tools/pipeline-run.js +29 -7
  305. package/dist/tools/preview.js +1 -1
  306. package/dist/tools/process-ids.js +31 -0
  307. package/dist/tools/process-kill.js +9 -16
  308. package/dist/tools/process-list.js +1 -0
  309. package/dist/tools/process-log.js +11 -16
  310. package/dist/tools/process-wait.js +63 -0
  311. package/dist/tools/prompt-io.js +4 -3
  312. package/dist/tools/question.js +172 -25
  313. package/dist/tools/read-file.js +55 -34
  314. package/dist/tools/recall.js +14 -8
  315. package/dist/tools/registry.js +6 -5
  316. package/dist/tools/remember.js +8 -4
  317. package/dist/tools/scope-request.js +11 -6
  318. package/dist/tools/search-history.js +5 -2
  319. package/dist/tools/session-info.js +2 -0
  320. package/dist/tools/set-thinking.js +8 -4
  321. package/dist/tools/subagent.js +59 -39
  322. package/dist/tools/syntax-validator.js +292 -90
  323. package/dist/tools/user-input.js +22 -18
  324. package/dist/tools/web-browse.js +9 -3
  325. package/dist/tools/web-fetch.js +12 -23
  326. package/dist/tools/web-search.js +7 -5
  327. package/dist/tools/write-file.js +45 -15
  328. package/dist/ui/action-log.js +13 -0
  329. package/dist/ui/box.js +4 -3
  330. package/dist/ui/colors.js +2 -0
  331. package/dist/ui/diff.js +6 -5
  332. package/dist/ui/index.js +1 -0
  333. package/dist/ui/line-editor.js +230 -122
  334. package/dist/ui/line-math.js +10 -2
  335. package/dist/ui/md-formatter.js +57 -37
  336. package/dist/ui/output.js +8 -3
  337. package/dist/ui/plan-view.js +21 -10
  338. package/dist/ui/renderer.js +393 -67
  339. package/dist/ui/spinner.js +16 -5
  340. package/dist/ui/table.js +15 -17
  341. package/dist/ui/theme.js +143 -0
  342. package/dist/utils/child-run.js +20 -0
  343. package/dist/utils/error.js +1 -1
  344. package/dist/utils/index.js +2 -0
  345. package/dist/utils/retry.js +2 -2
  346. package/dist/utils/truncate.js +1 -1
  347. package/package.json +1 -1
@@ -6,14 +6,14 @@ import { LlmError } from "./llm-errors";
6
6
  import { backoffDelay } from "../utils/retry";
7
7
  import { StreamState } from "./stream-state";
8
8
  import { parseCacheUsage } from "./cache-usage";
9
- const REQUEST_TIMEOUT_MS = 120000;
10
- /** Max wait (ms) for a self-inflicted rate-limit slot before giving up. */
11
- const MAX_RATE_WAIT_MS = 30_000;
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
12
  /**
13
- * Client identity header. OpenCode Go asks clients to identify themselves with
14
- * their own user agent (not a generic SDK/HTTP-library name) so requests route
15
- * to coding-agent traffic. `readMmaVersion()` is memoized — every provider
16
- * construction would otherwise re-read package.json.
13
+ * Client identity-заголовок. OpenCode Go просит клиентов представляться своим
14
+ * user agent (не generic SDK/HTTP-библиотекой), чтобы запросы роутились в
15
+ * coding-agent трафик. `readMmaVersion()` мемоизирован — иначе каждая сборка
16
+ * провайдера перечитывала бы package.json.
17
17
  */
18
18
  let cachedUserAgent = null;
19
19
  function defaultUserAgent() {
@@ -21,6 +21,23 @@ function defaultUserAgent() {
21
21
  cachedUserAgent = `micro-models-agent/${readMmaVersion()}`;
22
22
  return cachedUserAgent;
23
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
+ }
24
41
  function buildRequestBody(opts) {
25
42
  const body = {
26
43
  model: opts.model,
@@ -37,21 +54,24 @@ function buildRequestBody(opts) {
37
54
  body.session_id = opts.sessionId;
38
55
  if (opts.stream && opts.streamUsage)
39
56
  body.stream_options = { include_usage: true };
40
- // Apply reasoning effort via the provider's strategy
57
+ // Применяем effort через стратегию провайдера (уровень нормализуется под
58
+ // контракт OpenAI: low|medium|high).
41
59
  const strategy = opts.reasoningStrategy ?? "openai-effort";
42
- const level = opts.reasoningEffort;
43
- if (strategy === "openai-effort" && level && level !== "default") {
44
- body.reasoning_effort = level;
60
+ const effort = normalizeReasoningEffort(opts.reasoningEffort);
61
+ if (strategy === "openai-effort") {
62
+ if (effort)
63
+ body.reasoning_effort = effort;
45
64
  }
46
65
  else if (strategy === "template-kwarg") {
47
- body.chat_template_kwargs = { enable_thinking: level !== "none" };
66
+ body.chat_template_kwargs = { enable_thinking: opts.reasoningEffort !== "none" };
48
67
  }
49
- else if (strategy === "prompt-tag" && level && level !== "default") {
50
- // Send reasoning_effort in body (models that respect it via API) AND
51
- // add /no_think tag to message (models that only respect prompt signals).
52
- body.reasoning_effort = level;
68
+ else if (strategy === "prompt-tag") {
69
+ // Шлём reasoning_effort в теле (модели с API-поддержкой) И тег /no_think
70
+ // в сообщение (модели только с prompt-сигналами; тег ставит `chat()`).
71
+ if (effort)
72
+ body.reasoning_effort = effort;
53
73
  }
54
- // "none" = unsupported, skip
74
+ // "none" = не поддерживается, пропуск
55
75
  if (opts.tools && opts.tools.length > 0) {
56
76
  body.tools = opts.tools.map((t) => ({
57
77
  type: "function",
@@ -65,6 +85,7 @@ function buildRequestBody(opts) {
65
85
  }
66
86
  return body;
67
87
  }
88
+ /** Провайдер OpenAI-совместимого API (стрим + non-stream фолбэки, ретраи). */
68
89
  export class OpenAICompatProvider {
69
90
  model;
70
91
  contextWindow;
@@ -98,11 +119,16 @@ export class OpenAICompatProvider {
98
119
  this.userAgent = config.userAgent ?? defaultUserAgent();
99
120
  this.debug = config.logger ? config.logger.debug.bind(config.logger) : null;
100
121
  }
122
+ /**
123
+ * Стрим чата: rate-limit → стрим → фолбэк non-stream при пустом стриме.
124
+ * @param messages история; @param tools определения тулов;
125
+ * @param signal abort; @param options лимит токенов/reasoning.
126
+ * @returns асинхронный итератор чанков.
127
+ */
101
128
  async *chat(messages, tools, signal, options) {
102
- // Check rate limit before making request. A self-inflicted limit should
103
- // wait for a slot (bounded) instead of aborting a healthy turn; only if
104
- // no slot frees within the window do we surface a recoverable error the
105
- // agent loop can feed back (rather than dying).
129
+ // Проверка лимита до запроса. Собственный лимит ждёт слота (ограниченно),
130
+ // а не роняет здоровый ход; recoverable-ошибка наружу — только если слот
131
+ // не освободился в окне (цикл скормит её модели, а не умрёт).
106
132
  if (!this.rateLimiter.canMakeRequest()) {
107
133
  const waitMs = Math.min(this.rateLimiter.msUntilRequest(), MAX_RATE_WAIT_MS);
108
134
  if (waitMs > 0) {
@@ -112,9 +138,9 @@ export class OpenAICompatProvider {
112
138
  throw new LlmError(`Rate limit exceeded: ${this.rateLimiter.getConfig().maxRequestsPerMinute} requests per minute`, { recoverable: true });
113
139
  }
114
140
  }
115
- // Record this request
141
+ // Фиксируем запрос
116
142
  this.rateLimiter.recordRequest();
117
- // Apply prompt-tag strategy: append /no_think to last user message (outgoing copy only)
143
+ // Prompt-tag стратегия: дописать /no_think в последнее user-сообщение (только исходящая копия)
118
144
  let effectiveMessages = messages;
119
145
  if (options?.reasoningStrategy === "prompt-tag" && options?.reasoningEffort === "none") {
120
146
  effectiveMessages = this.applyPromptTag(messages, "/no_think");
@@ -132,12 +158,11 @@ export class OpenAICompatProvider {
132
158
  hasReasoning = true;
133
159
  yield chunk;
134
160
  }
135
- // A stream that ended with reasoning but no text/tool_call means the model
136
- // THOUGHT but never produced an answer (observed: qwen3.5-9b in LM Studio
137
- // emits reasoning_content then stops). The streamed reasoning was already
138
- // delivered; fetch the missing answer via non-streaming. The fallback's
139
- // own reasoning chunk is dropped so thinking is not shown twice — no text
140
- // was emitted, so there is no duplicate-answer risk.
161
+ // Стрим кончился на reasoning без текста/tool_call: модель ПОДУМАЛА, но
162
+ // ответа не дала (наблюдалось: qwen3.5-9b в LM Studio шлёт reasoning_content
163
+ // и встаёт). Reasoning уже отдан; недостающий ответ добираем non-stream.
164
+ // Reasoning-чанк фолбэка дропаем, чтобы не показать дважды — текста не было,
165
+ // риск дубля ответа отсутствует.
141
166
  if (!hasToolCall && !hasText) {
142
167
  this.debug?.("LLM streaming produced no text/tool_call — falling back to non-streaming");
143
168
  const fallback = await this.doNonStreaming(effectiveMessages, tools, signal, options);
@@ -149,17 +174,17 @@ export class OpenAICompatProvider {
149
174
  }
150
175
  }
151
176
  }
152
- /** Append a tag to the last user message (outgoing copy, never stored in context). */
177
+ /** Дописывает тег в последнее user-сообщение (исходящая копия, не в контекст). */
153
178
  applyPromptTag(messages, tag) {
154
179
  const copy = [...messages];
155
180
  for (let i = copy.length - 1; i >= 0; i--) {
156
181
  if (copy[i].role === "user") {
157
182
  const msg = { ...copy[i] };
158
183
  if (typeof msg.content === "string") {
159
- msg.content = msg.content + "\n" + tag;
184
+ msg.content = `${msg.content}\n${tag}`;
160
185
  }
161
186
  else {
162
- // Array of parts — append a text part
187
+ // Массив частей — дописываем текстовую часть
163
188
  msg.content = [...msg.content, { type: "text", text: tag }];
164
189
  }
165
190
  copy[i] = msg;
@@ -172,6 +197,7 @@ export class OpenAICompatProvider {
172
197
  const { baseDelay, maxDelay, maxStreamRetries, noDataTimeoutMs } = this.retryConfig;
173
198
  const streamRetries = maxStreamRetries ?? 2;
174
199
  const idleTimeoutMs = noDataTimeoutMs ?? 180000;
200
+ let usedNonStreamingFallback = false;
175
201
  for (let attempt = 0;; attempt++) {
176
202
  let emitted = false;
177
203
  const onEmit = () => {
@@ -179,27 +205,44 @@ export class OpenAICompatProvider {
179
205
  };
180
206
  try {
181
207
  const sawDone = yield* this.streamOnce(messages, tools, signal, options, onEmit, idleTimeoutMs);
182
- // Clean [DONE] or any partial content → deliver what we have.
208
+ // Чистый [DONE] или частичный контент — отдаём что есть.
183
209
  if (sawDone || emitted)
184
210
  return;
185
- // Stream closed before [DONE] with zero content → retryable truncation.
211
+ // Стрим закрылся до [DONE] с нулевым контентом — ретраябельная обрезка.
186
212
  if (attempt >= streamRetries)
187
213
  return;
188
214
  }
189
215
  catch (err) {
190
216
  if (err?.name === "AbortError" || (err instanceof LlmError && err.terminal) || signal?.aborted)
191
217
  throw err;
192
- // Content was already delivered — a re-stream would duplicate chunks.
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
+ // Контент уже отдан — перестрим продублировал бы чанки.
193
236
  if (emitted || attempt >= streamRetries)
194
237
  throw err;
195
- // Connection dropped before any content → retry the whole request.
238
+ // Обрыв до любого контента — переспрашиваем целиком.
196
239
  }
197
240
  // Экспоненциальный backoff с джиттером (общий хелпер из src/utils).
198
241
  await this.sleep(backoffDelay(attempt, baseDelay, maxDelay, 0.1), signal);
199
242
  }
200
243
  }
201
244
  async *streamOnce(messages, tools, signal, options, onEmit, idleTimeoutMs) {
202
- const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096;
245
+ const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
203
246
  const body = buildRequestBody({
204
247
  model: this.model,
205
248
  messages,
@@ -231,8 +274,8 @@ export class OpenAICompatProvider {
231
274
  });
232
275
  }
233
276
  catch (err) {
234
- // Fetch-level failures are already HTTP-retried inside fetchWithRetry —
235
- // don't double-retry them at the stream level.
277
+ // Fetch-сбои уже HTTP-ретраятся внутри fetchWithRetry —
278
+ // на уровне стрима повторно не ретраим.
236
279
  if (err?.name === "AbortError")
237
280
  throw err;
238
281
  const wrapped = err instanceof Error ? err : new Error(String(err));
@@ -266,6 +309,14 @@ export class OpenAICompatProvider {
266
309
  const toolCallAccs = new Map();
267
310
  // Единый контейнер состояния стрима (вместо 9 разрозненных флагов).
268
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
+ };
269
320
  const readIdle = () => new Promise((resolve, reject) => {
270
321
  const idleTimer = setTimeout(() => {
271
322
  flagTimeout();
@@ -274,13 +325,13 @@ export class OpenAICompatProvider {
274
325
  reader.read().then((result) => {
275
326
  clearTimeout(idleTimer);
276
327
  if (isTimeout())
277
- reject(new Error(t(st.sawToolCallStart ? "error.llm_stream_idle_toolcall" : "error.llm_stream_idle", { timeout: idleTimeoutMs })));
328
+ reject(idleError());
278
329
  else
279
330
  resolve(result);
280
331
  }, (err) => {
281
332
  clearTimeout(idleTimer);
282
333
  if (isTimeout())
283
- reject(new Error(t(st.sawToolCallStart ? "error.llm_stream_idle_toolcall" : "error.llm_stream_idle", { timeout: idleTimeoutMs })));
334
+ reject(idleError());
284
335
  else
285
336
  reject(err);
286
337
  });
@@ -306,10 +357,9 @@ export class OpenAICompatProvider {
306
357
  }
307
358
  }
308
359
  else {
309
- // Not SSE-framed — Ollama (and some other backends) may stream
310
- // NDJSON or send mid-stream errors as bare JSON. Try to parse the
311
- // raw line; unparseable noise (SSE comments, keep-alives) is
312
- // counted and skipped.
360
+ // Не SSE-фрейм — Ollama (и часть бэкендов) стримят NDJSON или шлют
361
+ // mid-stream ошибки голым JSON. Пробуем парсить сырую строку;
362
+ // непарсящийся шум (SSE-комменты, keep-alive) считаем и пропускаем.
313
363
  st.nonDataLines++;
314
364
  if (st.nonDataSamples.length < 5)
315
365
  st.nonDataSamples.push(trimmed.slice(0, 200));
@@ -320,7 +370,7 @@ export class OpenAICompatProvider {
320
370
  st.parsedChunks++;
321
371
  const choice = parsed.choices?.[0];
322
372
  if (!choice) {
323
- // Usage comes in the last chunk with empty choices
373
+ // Usage приходит в последнем чанке с пустыми choices
324
374
  if (parsed.usage) {
325
375
  st.usage = {
326
376
  promptTokens: parsed.usage.prompt_tokens ?? 0,
@@ -331,14 +381,14 @@ export class OpenAICompatProvider {
331
381
  this.debug?.("LLM stream usage", { ...st.usage });
332
382
  }
333
383
  else if (parsed.error) {
334
- // Mid-stream provider error (Ollama sends {"error": "..."} as
335
- // bare JSON once generation already started). Surface it —
336
- // swallowing it makes the response look silently empty.
337
- // Thrown OUTSIDE the JSON-parse try/catch below.
384
+ // Mid-stream ошибка провайдера (Ollama шлёт {"error": "..."}
385
+ // голым JSON после старта генерации). Показываем — глотание
386
+ // даст молча пустой ответ.
387
+ // Брошено ВНЕ try/catch JSON-парсинга ниже.
338
388
  st.midStreamError = String(parsed.error);
339
389
  }
340
- // NOTE: no `continue` here — it would skip the midStreamError
341
- // throw below (which lives outside this try/catch).
390
+ // NOTE: `continue` тут нет — он пропустил бы throw midStreamError
391
+ // ниже (он вне этого try/catch).
342
392
  }
343
393
  else {
344
394
  if (st.parsedChunks <= 3 || st.parsedChunks % 50 === 0) {
@@ -353,7 +403,7 @@ export class OpenAICompatProvider {
353
403
  const finishReason = choice.finish_reason;
354
404
  if (finishReason)
355
405
  st.lastFinishReason = finishReason;
356
- // `reasoning` is the Ollama alias for OpenAI's `reasoning_content`.
406
+ // `reasoning` — алиас Ollama для openai-`reasoning_content`.
357
407
  const reasoningDelta = delta.reasoning_content ?? delta.reasoning;
358
408
  if (reasoningDelta) {
359
409
  onEmit();
@@ -400,7 +450,7 @@ export class OpenAICompatProvider {
400
450
  }
401
451
  }
402
452
  catch {
403
- // Skip malformed JSON lines
453
+ // Битые JSON-строки пропускаем
404
454
  }
405
455
  if (st.midStreamError) {
406
456
  throw new Error(t("error.llm_provider_stream_error", { error: st.midStreamError.slice(0, 300) }));
@@ -411,10 +461,10 @@ export class OpenAICompatProvider {
411
461
  onEmit();
412
462
  yield { type: "done", usage: st.usage };
413
463
  }
414
- // finish_reason "length": the completion hit the token limit. An
415
- // unfinished tool_call would otherwise be silently swallowed (tool_calls
416
- // are only yielded on finish_reason "tool_calls") and surface as an
417
- // empty response → blind hallucination retries. Surface the real cause.
464
+ // finish_reason "length": completion упёрся в лимит токенов. Незаконченный
465
+ // tool_call иначе молча глотается (tool_calls отдаются только на
466
+ // finish_reason "tool_calls") и всплывает пустым ответом → слепые
467
+ // hallucination-ретраи. Показываем настоящую причину.
418
468
  if (st.lastFinishReason === "length") {
419
469
  const truncatedToolCall = st.sawToolCallStart && toolCallAccs.size > 0;
420
470
  if (truncatedToolCall || !st.sawText) {
@@ -454,14 +504,14 @@ export class OpenAICompatProvider {
454
504
  streamUsage: this.cache?.requestStreamUsage === true,
455
505
  };
456
506
  }
457
- /** Shared request setup: auth headers + timeout/abort controller wiring. */
507
+ /** Общая подготовка запроса: auth-заголовки + таймаут/abort-контроллер. */
458
508
  buildRequestSetup(signal) {
459
509
  const headers = {
460
510
  "Content-Type": "application/json",
461
511
  "User-Agent": this.userAgent,
462
512
  };
463
513
  if (this.config.apiKey && this.config.apiKey !== "not-needed") {
464
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
514
+ headers.Authorization = `Bearer ${this.config.apiKey}`;
465
515
  }
466
516
  const sessionId = this.getSessionId?.();
467
517
  // Когда capability задана — шлём заголовок только тем, кому он нужен
@@ -500,7 +550,7 @@ export class OpenAICompatProvider {
500
550
  };
501
551
  }
502
552
  async doNonStreaming(messages, tools, signal, options) {
503
- const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096;
553
+ const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
504
554
  const body = buildRequestBody({
505
555
  model: this.model,
506
556
  messages,
@@ -528,7 +578,7 @@ export class OpenAICompatProvider {
528
578
  }));
529
579
  }
530
580
  const data = await response.json();
531
- // Some backends (e.g. Ollama) return HTTP 200 with {"error": "..."}.
581
+ // Часть бэкендов (напр. Ollama) отдают HTTP 200 с {"error": "..."}.
532
582
  if (data.error && !data.choices) {
533
583
  throw new Error(t("error.llm_provider_stream_error", { error: String(data.error).slice(0, 300) }));
534
584
  }
@@ -557,15 +607,15 @@ export class OpenAICompatProvider {
557
607
  });
558
608
  }
559
609
  }
560
- // Completed text but hit the cap: surface a visible warning instead of
561
- // silently accepting a cut-off answer (see the streaming path).
610
+ // Текст готов, но упёрся в cap: видимый warning вместо молча
611
+ // принятого обрезанного ответа (см. стрим-путь).
562
612
  if (choice.finish_reason === "length" && msg.content && !msg.tool_calls) {
563
613
  chunks.push({
564
614
  type: "warning",
565
615
  content: t("error.llm_output_truncated", { tokens: maxTokens }),
566
616
  });
567
617
  }
568
- // Append usage from API response
618
+ // Дописываем usage из ответа API
569
619
  if (data.usage) {
570
620
  chunks.push({
571
621
  type: "done",
@@ -589,9 +639,17 @@ export class OpenAICompatProvider {
589
639
  cleanup();
590
640
  }
591
641
  }
642
+ /**
643
+ * Считает токены текста (tiktoken по дефолтной модели).
644
+ * @param text текст; @returns число токенов.
645
+ */
592
646
  countTokens(text) {
593
647
  return this.tokenCounter.count(text);
594
648
  }
649
+ /**
650
+ * Список моделей провайдера (`/models`: id/name/model).
651
+ * @returns имена моделей; @throws при недоступности провайдера.
652
+ */
595
653
  async listModels() {
596
654
  try {
597
655
  const url = `${this.config.baseUrl.replace(/\/+$/, "")}/models`;
@@ -600,20 +658,35 @@ export class OpenAICompatProvider {
600
658
  "User-Agent": this.userAgent,
601
659
  };
602
660
  if (this.config.apiKey && this.config.apiKey !== "not-needed") {
603
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
661
+ headers.Authorization = `Bearer ${this.config.apiKey}`;
604
662
  }
605
663
  const response = await fetch(url, {
606
664
  method: "GET",
607
665
  headers,
608
666
  });
609
667
  if (!response.ok) {
610
- // Surface the failure: health probes must distinguish a dead
611
- // provider from one that serves an empty model list.
668
+ // Провал наружу: health-пробы должны отличать мёртвый провайдер
669
+ // от пустого списка моделей.
612
670
  throw new Error(`HTTP ${response.status} from ${url}`);
613
671
  }
614
- const data = (await response.json());
615
- const models = (data.data || data || [])
616
- .map((m) => m.id || m.name || m.model || "")
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
+ })
617
690
  .filter(Boolean);
618
691
  return models;
619
692
  }
@@ -643,8 +716,8 @@ export class OpenAICompatProvider {
643
716
  throw err;
644
717
  }
645
718
  if (attempt < maxRetries) {
646
- // Prefer the provider's Retry-After window (saturating at maxDelay);
647
- // otherwise exponential backoff. 429s from free tiers need a real wait.
719
+ // Приоритет окну Retry-After провайдера (cap на maxDelay); иначе —
720
+ // экспоненциальный backoff. 429 с free-тарифов требуют реального ожидания.
648
721
  const delay = lastRetryAfter > 0
649
722
  ? Math.min(lastRetryAfter, maxDelay)
650
723
  : backoffDelay(attempt, baseDelay, maxDelay, 0.1);
@@ -85,8 +85,9 @@ Respond with JSON only (no markdown fences):
85
85
  or
86
86
  {"action": "reject", "explanation": "why"}`;
87
87
  const DEFAULT_BASE_URL = "http://localhost:1234/v1";
88
- /** Fallback context window when neither the provider entry nor the config sets one. */
88
+ /** Запасное окно контекста, если ни запись провайдера, ни конфиг его не задают. */
89
89
  const DEFAULT_ORCH_CONTEXT_WINDOW = 32768;
90
+ /** Клиент оркестратора: строит план, сливает результаты, роутит scope-запросы. */
90
91
  export class OrchestratorClient {
91
92
  config;
92
93
  provider = null;
@@ -110,11 +111,10 @@ export class OrchestratorClient {
110
111
  }
111
112
  }
112
113
  /**
113
- * Build the orchestrator provider through ProviderManager so per-entry
114
- * contextWindow/retry/rateLimits and failover entries are honored
115
- * (legacy {type,baseUrl,apiKey} configs are normalized by the manager).
116
- * Previously the provider was built directly with a hardcoded 32768 window
117
- * and no retry/rate-limit/failover support at all.
114
+ * Строит провайдер оркестратора через ProviderManager: учитываются
115
+ * per-entry contextWindow/retry/rateLimits и failover-записи (легаси
116
+ * {type,baseUrl,apiKey} менеджер нормализует). Раньше провайдер строился
117
+ * напрямую с хардкодом окна 32768 — без ретраев/лимитов/failover вовсе.
118
118
  */
119
119
  buildManagedProvider(config) {
120
120
  const providerCfg = config.provider;
@@ -134,6 +134,10 @@ export class OrchestratorClient {
134
134
  }
135
135
  return provider;
136
136
  }
137
+ /**
138
+ * Включён ли оркестратор (задана ли модель).
139
+ * @returns true, если провайдер построен.
140
+ */
137
141
  isEnabled() {
138
142
  return this.provider !== null;
139
143
  }
@@ -171,6 +175,11 @@ export class OrchestratorClient {
171
175
  }
172
176
  }
173
177
  }
178
+ /**
179
+ * Строит MoE-план из задачи пользователя.
180
+ * @param userPrompt задача; @param _context неиспользуемый контекст;
181
+ * @returns план с сырым ответом либо ошибку.
182
+ */
174
183
  async plan(userPrompt, _context, signal) {
175
184
  if (!this.provider)
176
185
  return { error: "Orchestrator not enabled — no orchestrator model configured" };
@@ -180,11 +189,16 @@ export class OrchestratorClient {
180
189
  ];
181
190
  const text = await this.chat(messages, signal);
182
191
  const parsed = this.parseJSON(text);
183
- if (parsed && parsed.subtasks && parsed.subtasks.length > 0) {
192
+ if (parsed?.subtasks && parsed.subtasks.length > 0) {
184
193
  return { plan: parsed, raw: text };
185
194
  }
186
195
  return { error: `Failed to parse plan from LLM output. Raw: ${text.slice(0, 500)}` };
187
196
  }
197
+ /**
198
+ * Сливает результаты сабтасков: финал либо запрос re-plan.
199
+ * @param input план, результаты, ошибки/варнинги верификатора;
200
+ * @returns финал с ответом либо replan с обновлённым планом.
201
+ */
188
202
  async verifyAndMerge(input, signal) {
189
203
  if (!this.provider)
190
204
  return { type: "final", finalAnswer: input.results.map((r) => r.summary).join("\n") };
@@ -195,7 +209,7 @@ export class OrchestratorClient {
195
209
  ];
196
210
  const text = await this.chat(messages, signal);
197
211
  const parsed = this.parseJSON(text);
198
- if (parsed && parsed.type) {
212
+ if (parsed?.type) {
199
213
  return parsed;
200
214
  }
201
215
  return {
@@ -205,8 +219,8 @@ export class OrchestratorClient {
205
219
  };
206
220
  }
207
221
  /**
208
- * Router decision on a sub-agent's scope request. Defaults to reject on any
209
- * parse failure — scope must never expand silently.
222
+ * Решение роутера по scope-запросу субагента. Дефолт на любой ошибке
223
+ * парсинга — reject: scope молча не расширяется никогда.
210
224
  */
211
225
  async resolveScopeRequest(subtaskId, request, signal) {
212
226
  if (!this.provider)
@@ -223,7 +237,7 @@ export class OrchestratorClient {
223
237
  if (parsed && parsed.action === "approve") {
224
238
  const write = Array.isArray(parsed.write) ? parsed.write.map(String) : [];
225
239
  const read = Array.isArray(parsed.read) ? parsed.read.map(String) : [];
226
- // An approve without any concrete files is meaningless — treat as reject.
240
+ // Approve без конкретных файлов бессмыслен — считаем reject.
227
241
  if (write.length === 0 && read.length === 0) {
228
242
  return { action: "reject", explanation: "approve carried no files" };
229
243
  }
@@ -236,6 +250,10 @@ export class OrchestratorClient {
236
250
  : `unparseable router decision: ${text.slice(0, 200)}`,
237
251
  };
238
252
  }
253
+ /**
254
+ * Строит простой план-список шагов (легаси-поверхность).
255
+ * @param task задача; @returns шаги плана (фолбэк — сама задача).
256
+ */
239
257
  async createPlan(task) {
240
258
  if (!this.provider)
241
259
  throw new Error("Orchestrator not enabled");
@@ -252,6 +270,10 @@ export class OrchestratorClient {
252
270
  return parsed;
253
271
  return { steps: [task] };
254
272
  }
273
+ /**
274
+ * Рекомендует путь разрешения конфликта.
275
+ * @param context описание ситуации; @returns рекомендация текстом.
276
+ */
255
277
  async resolveConflict(context) {
256
278
  if (!this.provider)
257
279
  throw new Error("Orchestrator not enabled");
@@ -1,10 +1,10 @@
1
- // src/llm/provider-budget
1
+ /** Баланс провайдера: запрос расхода/остатка (только OpenRouter по API). */
2
2
  /**
3
3
  * Запрос баланса/расхода у провайдера. Поддерживается только OpenRouter —
4
4
  * единственный, кто отдаёт эти данные по API-ключу. Zen/Go баланс не
5
5
  * возвращают (только веб-дашборд), поэтому для них честно `unsupported`.
6
6
  */
7
- const DEFAULT_TIMEOUT_MS = 5000;
7
+ import { PROVIDER_BUDGET_TIMEOUT_MS as DEFAULT_TIMEOUT_MS } from "../config/timeouts";
8
8
  /** Приводит значение к конечному числу; иначе undefined. */
9
9
  function num(value) {
10
10
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;