micro-models-agent 2.10.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.
- package/CHANGELOG.md +33 -0
- package/dist/assets/app.js +22 -4
- package/dist/assets/index.html +29 -0
- package/dist/assets/js/dom.js +2 -1
- package/dist/assets/js/feed.js +38 -26
- package/dist/assets/js/format.js +1 -1
- package/dist/assets/js/rail.js +114 -0
- package/dist/assets/js/settings-controls.js +2 -2
- package/dist/assets/js/settings.js +246 -51
- package/dist/assets/js/store.js +3 -0
- package/dist/assets/js/tabs.js +104 -2
- package/dist/assets/styles.css +215 -66
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/en.json +106 -21
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +109 -21
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +24285 -22447
- package/dist/migration/backup.js +14 -3
- package/dist/migration/detect.js +12 -3
- package/dist/migration/index.js +1 -0
- package/dist/modules/artifacts/store.js +9 -8
- package/dist/modules/browser/actions.js +6 -0
- package/dist/modules/browser/bridge-client.js +14 -12
- package/dist/modules/browser/bridge-path.js +4 -4
- package/dist/modules/browser/console-buffer.js +37 -0
- package/dist/modules/browser/cookie-store.js +5 -0
- package/dist/modules/browser/driver-types.js +1 -0
- package/dist/modules/browser/driver.js +19 -11
- package/dist/modules/browser/index.js +3 -1
- package/dist/modules/browser/module.js +2 -1
- package/dist/modules/browser/session.js +10 -30
- package/dist/modules/browser/snapshot.js +19 -13
- package/dist/modules/browser/types.js +1 -0
- package/dist/modules/certification/cli.js +28 -20
- package/dist/modules/certification/fact-checker.js +9 -1
- package/dist/modules/certification/loader.js +9 -2
- package/dist/modules/certification/manifest.js +83 -21
- package/dist/modules/certification/runner.js +23 -13
- package/dist/modules/certification/scenarios.js +20 -20
- package/dist/modules/certification/syntax-scenarios.js +1 -2
- package/dist/modules/context/chunk-query.js +11 -9
- package/dist/modules/context/fact-extractor.js +37 -39
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +106 -80
- package/dist/modules/critic/client.js +176 -0
- package/dist/modules/critic/evidence.js +81 -0
- package/dist/modules/critic/provider.js +45 -0
- package/dist/modules/critic/tools.js +78 -0
- package/dist/modules/critic/types.js +1 -0
- package/dist/modules/execution/audit-runners.js +28 -34
- package/dist/modules/execution/auditor.js +53 -49
- package/dist/modules/execution/execution-plugin.js +142 -102
- package/dist/modules/execution/index.js +1 -1
- package/dist/modules/execution/input-from.js +37 -13
- package/dist/modules/execution/module.js +368 -191
- package/dist/modules/execution/moe-executor.js +53 -26
- package/dist/modules/execution/plan-actions.js +320 -202
- package/dist/modules/execution/plan-checks.js +35 -0
- package/dist/modules/execution/plan-coverage.js +36 -33
- package/dist/modules/execution/plan-steps.js +11 -3
- package/dist/modules/execution/plan-store.js +83 -17
- package/dist/modules/execution/plan-tool.js +89 -35
- package/dist/modules/execution/plan-validator.js +30 -10
- package/dist/modules/execution/planner.js +22 -13
- package/dist/modules/execution/stuck-detector.js +196 -93
- package/dist/modules/execution/tracker.js +22 -8
- package/dist/modules/execution/verifier.js +69 -48
- package/dist/modules/execution/windows-commands.js +12 -12
- package/dist/modules/hallucination/artifact-claim.js +93 -0
- package/dist/modules/hallucination/confidence.js +13 -11
- package/dist/modules/hallucination/consistency.js +10 -2
- package/dist/modules/hallucination/detector.js +10 -5
- package/dist/modules/hallucination/factual.js +43 -26
- package/dist/modules/hallucination/index.js +1 -0
- package/dist/modules/hallucination/js-identifiers.js +44 -45
- package/dist/modules/hallucination/llm-judge.js +17 -12
- package/dist/modules/hallucination/success-claim.js +59 -0
- package/dist/modules/indexer/cache.js +52 -4
- package/dist/modules/indexer/index.js +1 -0
- package/dist/modules/indexer/map-command.js +2 -4
- package/dist/modules/indexer/map-select.js +16 -16
- package/dist/modules/indexer/module.js +47 -12
- package/dist/modules/indexer/project-profile.js +8 -7
- package/dist/modules/indexer/symbols.js +15 -15
- package/dist/modules/indexer/walker.js +159 -29
- package/dist/modules/lsp/check-tool.js +7 -5
- package/dist/modules/lsp/client.js +39 -39
- package/dist/modules/lsp/command.js +17 -24
- package/dist/modules/lsp/config.js +12 -11
- package/dist/modules/lsp/index.js +1 -0
- package/dist/modules/lsp/module.js +48 -46
- package/dist/modules/lsp/probe.js +13 -14
- package/dist/modules/lsp/project-root.js +14 -17
- package/dist/modules/lsp/startup-check.js +18 -16
- package/dist/modules/mcp/client.js +59 -62
- package/dist/modules/mcp/index.js +1 -0
- package/dist/modules/mcp/module.js +66 -37
- package/dist/modules/mcp/registry.js +5 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +12 -8
- package/dist/modules/memory/search.js +4 -1
- package/dist/modules/memory/store.js +40 -2
- package/dist/modules/onboarding/init.js +47 -0
- package/dist/modules/onboarding/meet.js +30 -0
- package/dist/modules/onboarding/mode.js +30 -0
- package/dist/modules/onboarding/prompts.js +43 -0
- package/dist/modules/pipelines/engine.js +10 -0
- package/dist/modules/pipelines/index.js +1 -0
- package/dist/modules/pipelines/parser.js +4 -1
- package/dist/modules/pipelines/template.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
- package/dist/modules/plugins/builtin/notify.js +2 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +18 -3
- package/dist/modules/plugins/manager.js +37 -17
- package/dist/modules/pricing/index.js +10 -11
- package/dist/modules/pricing/prices.js +12 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +27 -31
- package/dist/modules/processes/registry.js +44 -25
- package/dist/modules/processes/runner.js +4 -4
- package/dist/modules/providers/capabilities.js +28 -0
- package/dist/modules/providers/create.js +3 -4
- package/dist/modules/providers/factory.js +7 -7
- package/dist/modules/providers/fallback.js +12 -12
- package/dist/modules/providers/health.js +4 -4
- package/dist/modules/providers/manager.js +50 -19
- package/dist/modules/providers/naming.js +15 -0
- package/dist/modules/providers/preflight.js +71 -0
- package/dist/modules/providers/presets.js +39 -8
- package/dist/modules/providers/registry.js +7 -2
- package/dist/modules/reasoning/policy.js +8 -6
- package/dist/modules/reasoning/probe.js +30 -25
- package/dist/modules/security/audit-log.js +19 -36
- package/dist/modules/security/audit-notifier.js +35 -75
- package/dist/modules/security/command-validator.js +99 -51
- package/dist/modules/security/content-scanner.js +8 -8
- package/dist/modules/security/data-sanitizer.js +9 -16
- package/dist/modules/security/encryption.js +32 -52
- package/dist/modules/security/index.js +2 -1
- package/dist/modules/security/network-validator.js +120 -43
- package/dist/modules/security/path-validator.js +61 -46
- package/dist/modules/security/protected-paths.js +73 -0
- package/dist/modules/security/rate-limiter.js +17 -49
- package/dist/modules/security/security-policies.js +25 -57
- package/dist/modules/security/session-encryption.js +32 -71
- package/dist/modules/security/session-isolation.js +15 -28
- package/dist/modules/session/auto-name.js +63 -0
- package/dist/modules/session/export.js +98 -0
- package/dist/modules/session/index.js +1 -0
- package/dist/modules/session/manager.js +79 -28
- package/dist/modules/session/module.js +4 -3
- package/dist/modules/session/store.js +57 -20
- package/dist/modules/setup/answers.js +35 -0
- package/dist/modules/setup/apply.js +79 -0
- package/dist/modules/setup/discovery.js +72 -0
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/loader.js +9 -1
- package/dist/modules/skills/module.js +11 -0
- package/dist/modules/updater/changelog-reader.js +15 -16
- package/dist/modules/updater/checker.js +9 -4
- package/dist/modules/updater/dev-detect.js +7 -6
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +17 -15
- package/dist/modules/user-profile/compressor.js +2 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +10 -1
- package/dist/modules/webui/answer-api.js +29 -0
- package/dist/modules/webui/assets.js +50 -0
- package/dist/modules/webui/body.js +21 -0
- package/dist/modules/webui/commands-settings.js +209 -0
- package/dist/modules/webui/commands-shared.js +60 -0
- package/dist/modules/webui/commands.js +174 -0
- package/dist/modules/webui/config-api.js +134 -0
- package/dist/modules/webui/file-api.js +202 -0
- package/dist/modules/webui/forward.js +159 -0
- package/dist/modules/webui/history-api.js +112 -0
- package/dist/modules/webui/host-state.js +60 -0
- package/dist/modules/webui/i18n-api.js +9 -0
- package/dist/modules/webui/limits.js +6 -0
- package/dist/modules/webui/log-forward.js +20 -0
- package/dist/modules/webui/memory-commands.js +64 -0
- package/dist/modules/webui/model-cache.js +18 -0
- package/dist/modules/webui/module.js +148 -0
- package/dist/modules/webui/persist.js +24 -0
- package/dist/modules/webui/prompt-io.js +97 -0
- package/dist/modules/webui/providers-api.js +337 -0
- package/dist/modules/webui/providers-view.js +56 -0
- package/dist/modules/webui/reasoning-view.js +15 -0
- package/dist/modules/webui/run-agent.js +81 -0
- package/dist/modules/webui/server.js +284 -0
- package/dist/modules/webui/sessions-api.js +178 -0
- package/dist/modules/webui/settings-api.js +561 -0
- package/dist/modules/webui/setup-api.js +201 -0
- package/dist/modules/webui/state.js +74 -0
- package/dist/modules/webui/util.js +261 -0
- package/dist/output/bus.js +7 -2
- package/dist/output/channel.js +80 -28
- package/dist/output/format.js +2 -1
- package/dist/output/index.js +1 -0
- package/dist/output/json-sink.js +5 -3
- package/dist/output/machine.js +4 -3
- package/dist/output/session-sink.js +4 -4
- package/dist/tools/approve.js +4 -0
- package/dist/tools/attach-image.js +5 -4
- package/dist/tools/auto-fixer.js +26 -44
- package/dist/tools/auto-tags.js +83 -0
- package/dist/tools/bash.js +193 -116
- package/dist/tools/browser.js +76 -31
- package/dist/tools/chunk-query.js +14 -6
- package/dist/tools/create-dir.js +9 -5
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/download-file.js +32 -7
- package/dist/tools/edit-file.js +52 -14
- package/dist/tools/enable-tools.js +6 -6
- package/dist/tools/executor.js +132 -27
- package/dist/tools/file-info.js +3 -1
- package/dist/tools/filter-tools.js +3 -3
- package/dist/tools/glob-tool.js +4 -1
- package/dist/tools/grep-tool.js +13 -10
- package/dist/tools/hidden-tools-block.js +6 -6
- package/dist/tools/index.js +16 -8
- package/dist/tools/list-dir.js +4 -2
- package/dist/tools/load-skill.js +7 -0
- package/dist/tools/mcp-call.js +15 -4
- package/dist/tools/meet-done.js +14 -0
- package/dist/tools/move-file.js +14 -7
- package/dist/tools/path-utils.js +29 -29
- package/dist/tools/pipeline-run.js +29 -7
- package/dist/tools/preview.js +1 -1
- package/dist/tools/process-ids.js +31 -0
- package/dist/tools/process-kill.js +9 -16
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +11 -16
- package/dist/tools/process-wait.js +63 -0
- package/dist/tools/prompt-io.js +4 -3
- package/dist/tools/question.js +172 -25
- package/dist/tools/read-file.js +55 -34
- package/dist/tools/recall.js +14 -8
- package/dist/tools/registry.js +6 -5
- package/dist/tools/remember.js +8 -4
- package/dist/tools/scope-request.js +11 -6
- package/dist/tools/search-history.js +5 -2
- package/dist/tools/session-info.js +2 -0
- package/dist/tools/set-thinking.js +8 -4
- package/dist/tools/subagent.js +59 -39
- package/dist/tools/syntax-validator.js +292 -90
- package/dist/tools/user-input.js +22 -18
- package/dist/tools/web-browse.js +9 -3
- package/dist/tools/web-fetch.js +12 -23
- package/dist/tools/web-search.js +7 -5
- package/dist/tools/write-file.js +45 -15
- package/dist/ui/action-log.js +13 -0
- package/dist/ui/box.js +4 -3
- package/dist/ui/colors.js +2 -0
- package/dist/ui/diff.js +6 -5
- package/dist/ui/index.js +1 -0
- package/dist/ui/line-editor.js +230 -122
- package/dist/ui/line-math.js +10 -2
- package/dist/ui/md-formatter.js +57 -37
- package/dist/ui/output.js +8 -3
- package/dist/ui/plan-view.js +21 -10
- package/dist/ui/renderer.js +393 -67
- package/dist/ui/spinner.js +16 -5
- package/dist/ui/table.js +15 -17
- package/dist/ui/theme.js +143 -0
- package/dist/utils/child-run.js +20 -0
- package/dist/utils/error.js +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/retry.js +2 -2
- package/dist/utils/truncate.js +1 -1
- 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
|
-
|
|
10
|
-
/**
|
|
11
|
-
const
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
//
|
|
57
|
+
// Применяем effort через стратегию провайдера (уровень нормализуется под
|
|
58
|
+
// контракт OpenAI: low|medium|high).
|
|
41
59
|
const strategy = opts.reasoningStrategy ?? "openai-effort";
|
|
42
|
-
const
|
|
43
|
-
if (strategy === "openai-effort"
|
|
44
|
-
|
|
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:
|
|
66
|
+
body.chat_template_kwargs = { enable_thinking: opts.reasoningEffort !== "none" };
|
|
48
67
|
}
|
|
49
|
-
else if (strategy === "prompt-tag"
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
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" =
|
|
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
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
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
|
-
//
|
|
141
|
+
// Фиксируем запрос
|
|
116
142
|
this.rateLimiter.recordRequest();
|
|
117
|
-
//
|
|
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
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
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
|
-
/**
|
|
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
|
|
184
|
+
msg.content = `${msg.content}\n${tag}`;
|
|
160
185
|
}
|
|
161
186
|
else {
|
|
162
|
-
//
|
|
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
|
-
//
|
|
208
|
+
// Чистый [DONE] или частичный контент — отдаём что есть.
|
|
183
209
|
if (sawDone || emitted)
|
|
184
210
|
return;
|
|
185
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 ??
|
|
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
|
|
235
|
-
//
|
|
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(
|
|
328
|
+
reject(idleError());
|
|
278
329
|
else
|
|
279
330
|
resolve(result);
|
|
280
331
|
}, (err) => {
|
|
281
332
|
clearTimeout(idleTimer);
|
|
282
333
|
if (isTimeout())
|
|
283
|
-
reject(
|
|
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
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
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
|
|
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
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
384
|
+
// Mid-stream ошибка провайдера (Ollama шлёт {"error": "..."}
|
|
385
|
+
// голым JSON после старта генерации). Показываем — глотание
|
|
386
|
+
// даст молча пустой ответ.
|
|
387
|
+
// Брошено ВНЕ try/catch JSON-парсинга ниже.
|
|
338
388
|
st.midStreamError = String(parsed.error);
|
|
339
389
|
}
|
|
340
|
-
// NOTE:
|
|
341
|
-
//
|
|
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`
|
|
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
|
-
//
|
|
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":
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
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
|
-
/**
|
|
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
|
|
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 ??
|
|
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
|
-
//
|
|
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
|
-
//
|
|
561
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
611
|
-
//
|
|
668
|
+
// Провал наружу: health-пробы должны отличать мёртвый провайдер
|
|
669
|
+
// от пустого списка моделей.
|
|
612
670
|
throw new Error(`HTTP ${response.status} from ${url}`);
|
|
613
671
|
}
|
|
614
|
-
const data =
|
|
615
|
-
const
|
|
616
|
-
|
|
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
|
-
//
|
|
647
|
-
//
|
|
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);
|
package/dist/llm/orchestrator.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
*
|
|
114
|
-
* contextWindow/retry/rateLimits
|
|
115
|
-
*
|
|
116
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
209
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
1
|
+
/** Баланс провайдера: запрос расхода/остатка (только OpenRouter по API). */
|
|
2
2
|
/**
|
|
3
3
|
* Запрос баланса/расхода у провайдера. Поддерживается только OpenRouter —
|
|
4
4
|
* единственный, кто отдаёт эти данные по API-ключу. Zen/Go баланс не
|
|
5
5
|
* возвращают (только веб-дашборд), поэтому для них честно `unsupported`.
|
|
6
6
|
*/
|
|
7
|
-
|
|
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;
|