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.
- package/CHANGELOG.md +10 -0
- package/dist/assets/js/tabs.js +22 -3
- 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/index.js +37 -3
- 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 +273 -47
- 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
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { lookupModelsDevContext } from "./models-dev";
|
|
2
|
+
/**
|
|
3
|
+
* Спрашивает нативный API бэкенда (LM Studio `/api/v1/models`) о длине
|
|
4
|
+
* контекста загруженной модели И заявленных capabilities. Длина — из живого
|
|
5
|
+
* инстанса (`loaded_instances[].config.context_length`), фолбэк —
|
|
6
|
+
* `max_context_length`. Capabilities читаются из `capabilities` (`vision`,
|
|
7
|
+
* `trained_for_tool_use`), если есть — это единый источник правды вместо
|
|
8
|
+
* гадания по имени.
|
|
9
|
+
*
|
|
10
|
+
* Null, если эндпоинт недоступен (не LM Studio), модели нет/снята или запрос
|
|
11
|
+
* упал — вызывающий деградирует молча.
|
|
12
|
+
*/
|
|
13
|
+
export async function probeModel(baseUrl, model, timeoutMs = 3000) {
|
|
14
|
+
const base = baseUrl.replace(/\/$/, "").replace(/\/v1\/?$/, "");
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(`${base}/api/v1/models`, {
|
|
17
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok)
|
|
20
|
+
return null;
|
|
21
|
+
const data = (await response.json());
|
|
22
|
+
const models = data.models ?? [];
|
|
23
|
+
const found = models.find((m) => m.key === model || m.name === model || (m.key ? model.includes(m.key) : false));
|
|
24
|
+
if (!found)
|
|
25
|
+
return null;
|
|
26
|
+
const loadedInstance = (found.loaded_instances ?? [])[0];
|
|
27
|
+
const contextLength = loadedInstance?.config?.context_length ?? found.max_context_length;
|
|
28
|
+
if (typeof contextLength !== "number" || contextLength <= 0)
|
|
29
|
+
return null;
|
|
30
|
+
const raw = found.capabilities;
|
|
31
|
+
let capabilities;
|
|
32
|
+
if (raw && typeof raw.vision === "boolean") {
|
|
33
|
+
capabilities = { vision: raw.vision };
|
|
34
|
+
if (typeof raw.trained_for_tool_use === "boolean") {
|
|
35
|
+
capabilities.tools = raw.trained_for_tool_use;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { model: found.key ?? found.name ?? model, contextLength, capabilities };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Тонкая context-only проекция {@link probeModel}. Для вызывающих, кому нужно
|
|
46
|
+
* только окно контекста (баннер, диагностика context-probe).
|
|
47
|
+
*/
|
|
48
|
+
export async function getLoadedContextLength(baseUrl, model, timeoutMs = 3000) {
|
|
49
|
+
const probe = await probeModel(baseUrl, model, timeoutMs);
|
|
50
|
+
return probe ? { model: probe.model, actual: probe.contextLength } : null;
|
|
51
|
+
}
|
|
52
|
+
/** Числовые context-подобные поля, что может нести запись `/models`. */
|
|
53
|
+
const CONTEXT_KEYS = [
|
|
54
|
+
"context_length",
|
|
55
|
+
"context_window",
|
|
56
|
+
"max_context_length",
|
|
57
|
+
"max_context_window",
|
|
58
|
+
"max_model_len",
|
|
59
|
+
"maxModelLen",
|
|
60
|
+
"n_ctx",
|
|
61
|
+
"num_ctx",
|
|
62
|
+
"contextLength",
|
|
63
|
+
"contextWindow",
|
|
64
|
+
];
|
|
65
|
+
function asPositiveInt(v) {
|
|
66
|
+
const n = typeof v === "string" && v.trim() !== "" ? Number(v) : v;
|
|
67
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Первое положительное среди известных context-полей объекта. OpenRouter
|
|
71
|
+
* кладёт per-endpoint значение в `top_provider.context_length`, поэтому один
|
|
72
|
+
* уровень вложенности тоже покрыт.
|
|
73
|
+
*/
|
|
74
|
+
function pickContextField(obj) {
|
|
75
|
+
for (const k of CONTEXT_KEYS) {
|
|
76
|
+
const n = asPositiveInt(obj[k]);
|
|
77
|
+
if (n !== null)
|
|
78
|
+
return n;
|
|
79
|
+
}
|
|
80
|
+
const top = obj.top_provider ?? obj.topProvider;
|
|
81
|
+
if (top && typeof top === "object") {
|
|
82
|
+
for (const k of CONTEXT_KEYS) {
|
|
83
|
+
const n = asPositiveInt(top[k]);
|
|
84
|
+
if (n !== null)
|
|
85
|
+
return n;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function matchesModel(entry, model) {
|
|
91
|
+
for (const k of ["id", "name", "model", "key"]) {
|
|
92
|
+
const v = entry[k];
|
|
93
|
+
if (typeof v === "string" && v && (v === model || model.includes(v) || v.includes(model))) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
async function fetchJson(url, timeoutMs, init) {
|
|
100
|
+
try {
|
|
101
|
+
const response = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
102
|
+
if (!response.ok)
|
|
103
|
+
return null;
|
|
104
|
+
return (await response.json());
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Context-поля живого инстанса из Ollama `/api/ps`, если есть. */
|
|
111
|
+
async function probeOllamaPs(origin, model, timeoutMs) {
|
|
112
|
+
const ps = await fetchJson(`${origin}/api/ps`, timeoutMs);
|
|
113
|
+
const running = ps?.models;
|
|
114
|
+
if (!Array.isArray(running))
|
|
115
|
+
return null;
|
|
116
|
+
const found = running.find((m) => m && typeof m === "object" && matchesModel(m, model));
|
|
117
|
+
if (!found)
|
|
118
|
+
return null;
|
|
119
|
+
return pickContextField(found);
|
|
120
|
+
}
|
|
121
|
+
function contextFromModelInfo(bag) {
|
|
122
|
+
for (const [k, v] of Object.entries(bag)) {
|
|
123
|
+
if (k.toLowerCase().includes("context_length") || k.toLowerCase().endsWith("n_ctx")) {
|
|
124
|
+
const n = asPositiveInt(v);
|
|
125
|
+
if (n !== null)
|
|
126
|
+
return n;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return pickContextField(bag);
|
|
130
|
+
}
|
|
131
|
+
/** Ollama `/api/show`: детали модели, пробуем полное и короткое имя. */
|
|
132
|
+
async function probeOllamaShow(origin, model, timeoutMs) {
|
|
133
|
+
const short = model.split(":")[0];
|
|
134
|
+
for (const name of [model, short]) {
|
|
135
|
+
const show = await fetchJson(`${origin}/api/show`, timeoutMs, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Content-Type": "application/json" },
|
|
138
|
+
body: JSON.stringify({ name }),
|
|
139
|
+
});
|
|
140
|
+
const info = show;
|
|
141
|
+
const bag = info?.model_info ?? info?.modelInfo;
|
|
142
|
+
if (bag && typeof bag === "object") {
|
|
143
|
+
const n = contextFromModelInfo(bag);
|
|
144
|
+
if (n !== null)
|
|
145
|
+
return n;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/** Нативный Ollama: живые инстансы (`/api/ps`) + детали (`/api/show`). */
|
|
151
|
+
async function probeOllama(origin, model, timeoutMs) {
|
|
152
|
+
// `/api/show` сообщает контекст модели (напр. `llama.context_length` в
|
|
153
|
+
// `model_info`), даже когда `/api/ps` размерных полей не несёт.
|
|
154
|
+
return ((await probeOllamaPs(origin, model, timeoutMs)) ??
|
|
155
|
+
(await probeOllamaShow(origin, model, timeoutMs)));
|
|
156
|
+
}
|
|
157
|
+
/** Сервер llama.cpp: одномодельный `/props` несёт загруженный `n_ctx`. */
|
|
158
|
+
async function probeLlamaCpp(origin, timeoutMs) {
|
|
159
|
+
const props = await fetchJson(`${origin}/props`, timeoutMs);
|
|
160
|
+
if (!props || typeof props !== "object")
|
|
161
|
+
return null;
|
|
162
|
+
const bag = props;
|
|
163
|
+
const direct = pickContextField(bag);
|
|
164
|
+
if (direct !== null)
|
|
165
|
+
return direct;
|
|
166
|
+
const gen = bag.default_generation_settings;
|
|
167
|
+
if (gen && typeof gen === "object") {
|
|
168
|
+
return pickContextField(gen);
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* OpenAI-совместимый `/models` с расширенными метаданными. Серверы с голыми id
|
|
174
|
+
* (чистый OpenAI, Zen/Go) context-полей не несут → null.
|
|
175
|
+
*/
|
|
176
|
+
async function probeOpenAICompatModels(baseUrl, model, apiKey, timeoutMs) {
|
|
177
|
+
const url = `${baseUrl.replace(/\/+$/, "")}/models`;
|
|
178
|
+
const headers = { "Content-Type": "application/json" };
|
|
179
|
+
if (apiKey && apiKey !== "not-needed")
|
|
180
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
181
|
+
const data = await fetchJson(url, timeoutMs, { headers });
|
|
182
|
+
const list = data?.data;
|
|
183
|
+
if (!Array.isArray(list))
|
|
184
|
+
return null;
|
|
185
|
+
const found = list.find((m) => !!m && typeof m === "object" && matchesModel(m, model));
|
|
186
|
+
if (!found)
|
|
187
|
+
return null;
|
|
188
|
+
return pickContextField(found);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Лукап каталога models.dev — последний шанс для OpenCode Zen/Go. Их `/models`
|
|
192
|
+
* отдаёт голые id, а models.dev (открытая БД, что использует сам opencode)
|
|
193
|
+
* держит модели провайдера `opencode` под теми же id с `limit.context`.
|
|
194
|
+
* Гейт на Zen-URL/тип: локальные бэкенды ради этого в сеть не ходят.
|
|
195
|
+
*/
|
|
196
|
+
async function probeModelsDev(baseUrl, model, providerType) {
|
|
197
|
+
const isZen = providerType === "opencode-zen" ||
|
|
198
|
+
providerType === "opencode-go" ||
|
|
199
|
+
/opencode\.ai\/zen/.test(baseUrl);
|
|
200
|
+
if (!isZen)
|
|
201
|
+
return null;
|
|
202
|
+
return lookupModelsDevContext(model);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Пробует все источники по порядку, возвращает первый давший окно.
|
|
206
|
+
* @param baseUrl базовый URL провайдера; @param model имя модели;
|
|
207
|
+
* @param opts ключ/таймаут/тип; @returns окно контекста либо null (fail-open).
|
|
208
|
+
*/
|
|
209
|
+
export async function probeProviderContext(baseUrl, model, opts = {}) {
|
|
210
|
+
const timeoutMs = opts.timeoutMs ?? 3000;
|
|
211
|
+
const origin = baseUrl.replace(/\/$/, "").replace(/\/v1\/?$/, "");
|
|
212
|
+
try {
|
|
213
|
+
const lm = await probeModel(baseUrl, model, timeoutMs);
|
|
214
|
+
if (lm)
|
|
215
|
+
return { model: lm.model, actual: lm.contextLength };
|
|
216
|
+
const ollama = await probeOllama(origin, model, timeoutMs);
|
|
217
|
+
if (ollama !== null)
|
|
218
|
+
return { model, actual: ollama };
|
|
219
|
+
const llama = await probeLlamaCpp(origin, timeoutMs);
|
|
220
|
+
if (llama !== null)
|
|
221
|
+
return { model, actual: llama };
|
|
222
|
+
const compat = await probeOpenAICompatModels(baseUrl, model, opts.apiKey, timeoutMs);
|
|
223
|
+
if (compat !== null)
|
|
224
|
+
return { model, actual: compat };
|
|
225
|
+
const catalog = await probeModelsDev(baseUrl, model, opts.providerType);
|
|
226
|
+
if (catalog !== null)
|
|
227
|
+
return { model, actual: catalog };
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Fail-open: любая ошибка пробы оставляет настроенный contextWindow.
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
package/dist/llm/provider.js
CHANGED
package/dist/llm/response.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Сворачивает чанки стрима в ответ (текст склеивается, reasoning копится,
|
|
3
|
+
* аргументы тулов JSON-парсятся с фолбэком {}).
|
|
4
|
+
* @param chunks чанки стрима; @returns распарсенный ответ.
|
|
5
|
+
*/
|
|
1
6
|
export function parseChunks(chunks) {
|
|
2
7
|
let text = "";
|
|
3
8
|
let reasoning;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/** Подсчёт токенов: точный (tiktoken) и дешёвая эвристика для блоков. */
|
|
2
2
|
import { encodingForModel, getEncoding } from "js-tiktoken";
|
|
3
3
|
import { getMessageText } from "./provider";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* tiktoken for Russian text.
|
|
5
|
+
* Скриптозависимая ОЦЕНКА токенов (дешёвая, без полной токенизации). Достаточно
|
|
6
|
+
* дешёвая для каждого PromptBlock; на этапе сборки точного TokenCounter может
|
|
7
|
+
* не быть в scope. Замерено на cl100k_base: латиница И кириллица — ~3.3–5
|
|
8
|
+
* символов/токен (старый плоский len/4 для кириллицы был почти верен —
|
|
9
|
+
* гипотеза «кириллица ≈ /2» ошибочна); CJK/хангыль — выбросы ~1–1.5 токена/
|
|
10
|
+
* символ, им /1.5. В пределах ±30% от tiktoken на русском.
|
|
12
11
|
*/
|
|
13
12
|
export function estimateTokens(text) {
|
|
14
13
|
if (!text)
|
|
@@ -29,6 +28,7 @@ export function estimateTokens(text) {
|
|
|
29
28
|
}
|
|
30
29
|
return Math.ceil(cjk / 1.5 + rest / 4);
|
|
31
30
|
}
|
|
31
|
+
/** Точный счётчик токенов поверх tiktoken (энкодинг по модели). */
|
|
32
32
|
export class TokenCounter {
|
|
33
33
|
encoder;
|
|
34
34
|
constructor(model = "gpt-4o") {
|
|
@@ -36,31 +36,47 @@ export class TokenCounter {
|
|
|
36
36
|
this.encoder = encodingForModel(model);
|
|
37
37
|
}
|
|
38
38
|
catch {
|
|
39
|
-
//
|
|
39
|
+
// Фолбэк на cl100k_base для неизвестных моделей
|
|
40
40
|
this.encoder = getEncoding("cl100k_base");
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Считает токены текста.
|
|
45
|
+
* @param text текст; @returns число токенов.
|
|
46
|
+
*/
|
|
43
47
|
count(text) {
|
|
44
48
|
if (!text)
|
|
45
49
|
return 0;
|
|
46
50
|
return this.encoder.encode(text).length;
|
|
47
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Считает токены сообщений с оверхедом ролей/границ (~4/сообщение).
|
|
54
|
+
* @param messages сообщения чата; @returns суммарные токены.
|
|
55
|
+
*/
|
|
48
56
|
countMessages(messages) {
|
|
49
57
|
let total = 0;
|
|
50
58
|
for (const msg of messages) {
|
|
51
|
-
// ~4
|
|
59
|
+
// ~4 токена оверхеда на сообщение (роль, границы)
|
|
52
60
|
total += 4;
|
|
53
61
|
const text = getMessageText(msg.content);
|
|
54
62
|
total += this.count(text);
|
|
55
63
|
if (msg.role === "tool")
|
|
56
64
|
total += 2; // tool_call_id
|
|
57
65
|
}
|
|
58
|
-
total += 2; //
|
|
66
|
+
total += 2; // прайминг ассистента
|
|
59
67
|
return total;
|
|
60
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Кодирует текст в токены.
|
|
71
|
+
* @param text текст; @returns массив id токенов.
|
|
72
|
+
*/
|
|
61
73
|
encode(text) {
|
|
62
74
|
return this.encoder.encode(text);
|
|
63
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Декодирует токены в текст.
|
|
78
|
+
* @param tokens id токенов; @returns текст.
|
|
79
|
+
*/
|
|
64
80
|
decode(tokens) {
|
|
65
81
|
return this.encoder.decode(tokens);
|
|
66
82
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
// Логер: шина вывода + файловые логи (.log, app.jsonl), чистка секретов.
|
|
1
2
|
import { appendFileSync, mkdirSync, existsSync } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { sanitizeLogMessage } from "../modules/security/data-sanitizer";
|
|
4
5
|
import { FileLogWriter } from "./file-log";
|
|
5
6
|
import { defaultOutputBus } from "../output";
|
|
6
7
|
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
8
|
+
/** Центральный логер: консоль через шину, файлы, JSONL, тегированные хелперы. */
|
|
7
9
|
export class Logger {
|
|
8
10
|
level;
|
|
9
11
|
prefix;
|
|
@@ -36,36 +38,47 @@ export class Logger {
|
|
|
36
38
|
clearSessionDir() {
|
|
37
39
|
this.sessionDir = null;
|
|
38
40
|
}
|
|
39
|
-
/**
|
|
41
|
+
/** Открывает посессионный `.log`-файл (поведение legacy v1). */
|
|
40
42
|
initSessionLog(sessionId) {
|
|
41
43
|
this.fileLog.initSessionLog(sessionId);
|
|
42
44
|
}
|
|
43
|
-
/**
|
|
45
|
+
/** Возврат к дневным лог-файлам, когда нет активной сессии. */
|
|
44
46
|
closeSessionLog() {
|
|
45
47
|
this.fileLog.closeSessionLog();
|
|
46
48
|
}
|
|
49
|
+
/** Путь к текущему лог-файлу (сессионный или дневной). */
|
|
47
50
|
getLogPath() {
|
|
48
51
|
return this.fileLog.getLogPath();
|
|
49
52
|
}
|
|
53
|
+
/** Удаляет старые логи по возрасту/количеству. */
|
|
50
54
|
cleanupOldLogs(maxDays, maxFiles) {
|
|
51
55
|
this.fileLog.cleanupOldLogs(maxDays, maxFiles);
|
|
52
56
|
}
|
|
53
|
-
//
|
|
57
|
+
// Тегированные хелперы (API legacy v1).
|
|
58
|
+
/** Пишет запрос к LLM в файловый лог. */
|
|
54
59
|
logLLMRequest(model, messagesCount, promptPreview, caller) {
|
|
55
60
|
this.fileLog.logLLMRequest(model, messagesCount, sanitizeLogMessage(promptPreview), caller);
|
|
56
61
|
}
|
|
62
|
+
/** Пишет ответ LLM (или ошибку) в файловый лог. */
|
|
57
63
|
logLLMResponse(model, responseLength, genTimeMs, error, caller) {
|
|
58
64
|
this.fileLog.logLLMResponse(model, responseLength, genTimeMs, error, caller);
|
|
59
65
|
}
|
|
66
|
+
/** Пишет вызов тула и превью результата в файловый лог. */
|
|
60
67
|
logToolCall(tool, preview, result) {
|
|
61
68
|
this.fileLog.logToolCall(tool, sanitizeLogMessage(preview), result !== undefined ? sanitizeLogMessage(result) : undefined);
|
|
62
69
|
}
|
|
70
|
+
/** Пишет вывод тула с кодом выхода в файловый лог. */
|
|
63
71
|
logToolOutput(tool, output, exitCode) {
|
|
64
72
|
this.fileLog.logToolOutput(tool, sanitizeLogMessage(output), exitCode);
|
|
65
73
|
}
|
|
74
|
+
/** Пишет реплику REPL (user/assistant/system) в файловый лог. */
|
|
66
75
|
logREPL(tag, content) {
|
|
67
76
|
this.fileLog.logREPL(tag, sanitizeLogMessage(content));
|
|
68
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Дочерний логер с расширенным префиксом; наследует уровень, sink и каталоги.
|
|
80
|
+
* @param prefix суффикс префикса. @returns новый Logger.
|
|
81
|
+
*/
|
|
69
82
|
child(prefix) {
|
|
70
83
|
const childLogger = new Logger(this.level, this.prefix ? `${this.prefix}:${prefix}` : prefix, this.sink);
|
|
71
84
|
if (this.logDir)
|
|
@@ -86,42 +99,55 @@ export class Logger {
|
|
|
86
99
|
error(msg, meta) {
|
|
87
100
|
this.log("error", msg, meta);
|
|
88
101
|
}
|
|
89
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Внутренняя диагностика петли (ретраи галлюцинаций, аудит/компакция,
|
|
104
|
+
* вердикты критика, …). Персистентность та же, что у warn/info, но событие
|
|
105
|
+
* шины несёт `data.diagnostic = true`, и чат дропает его, пока
|
|
106
|
+
* `ui.verbosity !== "verbose"`.
|
|
107
|
+
*/
|
|
108
|
+
diagnostic(level, msg, meta) {
|
|
109
|
+
this.log(level, msg, meta, true);
|
|
110
|
+
}
|
|
111
|
+
log(level, msg, meta, diagnostic) {
|
|
90
112
|
if (LEVELS[level] < LEVELS[this.level])
|
|
91
113
|
return;
|
|
92
|
-
//
|
|
114
|
+
// Чистим сообщение — вырезаем секреты.
|
|
93
115
|
const sanitizedMsg = sanitizeLogMessage(msg);
|
|
94
116
|
const sanitizedMeta = meta ? this.sanitizeMeta(meta) : undefined;
|
|
95
117
|
const ts = new Date().toISOString();
|
|
96
118
|
const metaStr = sanitizedMeta ? ` ${JSON.stringify(sanitizedMeta)}` : "";
|
|
97
119
|
const line = `${sanitizedMsg}${metaStr}`;
|
|
98
120
|
try {
|
|
99
|
-
this.sink.emit({
|
|
121
|
+
this.sink.emit({
|
|
122
|
+
level,
|
|
123
|
+
source: this.prefix || "mma",
|
|
124
|
+
kind: "log",
|
|
125
|
+
text: line,
|
|
126
|
+
data: diagnostic ? { diagnostic: true } : undefined,
|
|
127
|
+
});
|
|
100
128
|
}
|
|
101
129
|
catch {
|
|
102
130
|
/* terminal output is best-effort; file logging below must still run */
|
|
103
131
|
}
|
|
104
|
-
//
|
|
132
|
+
// Поведение legacy v1: дубль в тегированный `.log` (сессионный или дневной).
|
|
105
133
|
this.fileLog.log(level.toUpperCase(), this.prefix || "MMA", `${ts} — ${sanitizedMsg}${metaStr}`);
|
|
106
134
|
const logTarget = this.sessionDir ?? this.logDir;
|
|
107
135
|
if (logTarget) {
|
|
108
136
|
try {
|
|
109
|
-
appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
|
|
137
|
+
appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
|
|
110
138
|
level,
|
|
111
139
|
ts,
|
|
112
140
|
prefix: this.prefix,
|
|
113
141
|
msg: sanitizedMsg,
|
|
114
142
|
meta: sanitizedMeta ?? null,
|
|
115
|
-
})
|
|
143
|
+
})}\n`, "utf-8");
|
|
116
144
|
}
|
|
117
145
|
catch {
|
|
118
146
|
/* file logging is best-effort */
|
|
119
147
|
}
|
|
120
148
|
}
|
|
121
149
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Sanitize metadata object to remove sensitive data
|
|
124
|
-
*/
|
|
150
|
+
/** Рекурсивно чистит секреты в объекте метаданных. */
|
|
125
151
|
sanitizeMeta(meta) {
|
|
126
152
|
const sanitized = {};
|
|
127
153
|
for (const [key, value] of Object.entries(meta)) {
|
|
@@ -138,30 +164,30 @@ export class Logger {
|
|
|
138
164
|
return sanitized;
|
|
139
165
|
}
|
|
140
166
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
167
|
+
* Структурная запись только в app.jsonl (без консоли и .log).
|
|
168
|
+
* Для машиночитаемых отчётов (окружение и т.п.), слишком шумных для терминала,
|
|
169
|
+
* но ценных для кросс-девайс диагностики.
|
|
144
170
|
*/
|
|
145
171
|
logStructured(type, data) {
|
|
146
172
|
const logTarget = this.sessionDir ?? this.logDir;
|
|
147
173
|
if (!logTarget)
|
|
148
174
|
return;
|
|
149
175
|
try {
|
|
150
|
-
appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
|
|
176
|
+
appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
|
|
151
177
|
level: "info",
|
|
152
178
|
ts: new Date().toISOString(),
|
|
153
179
|
type,
|
|
154
180
|
meta: this.sanitizeMeta(data),
|
|
155
|
-
})
|
|
181
|
+
})}\n`, "utf-8");
|
|
156
182
|
}
|
|
157
183
|
catch {
|
|
158
184
|
/* file logging is best-effort */
|
|
159
185
|
}
|
|
160
186
|
}
|
|
161
187
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
188
|
+
* Запись только в app.jsonl — без консоли и .log. Для шумных подсистем
|
|
189
|
+
* (LSP, пробники): предупреждения диагностируются по файлам сессии,
|
|
190
|
+
* но не мусорят в терминале.
|
|
165
191
|
*/
|
|
166
192
|
logSilent(level, msg, meta) {
|
|
167
193
|
const logTarget = this.sessionDir ?? this.logDir;
|
|
@@ -171,13 +197,13 @@ export class Logger {
|
|
|
171
197
|
const sanitizedMsg = sanitizeLogMessage(msg);
|
|
172
198
|
const sanitizedMeta = meta ? this.sanitizeMeta(meta) : undefined;
|
|
173
199
|
try {
|
|
174
|
-
appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
|
|
200
|
+
appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
|
|
175
201
|
level,
|
|
176
202
|
ts,
|
|
177
203
|
prefix: this.prefix,
|
|
178
204
|
msg: sanitizedMsg,
|
|
179
205
|
meta: sanitizedMeta ?? null,
|
|
180
|
-
})
|
|
206
|
+
})}\n`, "utf-8");
|
|
181
207
|
}
|
|
182
208
|
catch {
|
|
183
209
|
/* file logging is best-effort */
|
package/dist/logger/file-log.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Файловый логер legacy v1: тегированные строки, ротация, чистка старых логов.
|
|
1
2
|
import { appendFileSync, mkdirSync, existsSync, readdirSync, renameSync, statSync, unlinkSync, } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
const MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
@@ -5,27 +6,28 @@ const MAX_LOG_FILES = 30;
|
|
|
5
6
|
const DEFAULT_MAX_DAYS = 30;
|
|
6
7
|
const TOOL_OUTPUT_LIMIT = 2000;
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
+
* Тегированный файловый логер (поведение legacy MMA v1).
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
* - `<sessionId>.log
|
|
12
|
-
* - `YYYY-MM-DD.log`
|
|
11
|
+
* Пишет читаемые строки в `~/.mma/logs/`:
|
|
12
|
+
* - `<sessionId>.log`, пока сессия активна;
|
|
13
|
+
* - `YYYY-MM-DD.log` для всего вне сессии.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
15
|
+
* Фичи из v1:
|
|
16
|
+
* - ротация по размеру (5 МБ → file.log.1, file.log.2, …);
|
|
17
|
+
* - чистка старых логов (возраст + лимит файлов);
|
|
18
|
+
* - тегированные хелперы: запрос/ответ LLM, вызовы тулов, REPL.
|
|
18
19
|
*/
|
|
19
20
|
export class FileLogWriter {
|
|
20
21
|
logDir = null;
|
|
21
22
|
sessionLogPath = null;
|
|
23
|
+
/** Задаёт каталог логов (создаёт при отсутствии). */
|
|
22
24
|
setLogDir(dir) {
|
|
23
25
|
this.logDir = dir;
|
|
24
26
|
if (!existsSync(dir)) {
|
|
25
27
|
mkdirSync(dir, { recursive: true });
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
|
-
/**
|
|
30
|
+
/** Открывает посессионный лог-файл. Триггерит чистку старых логов. */
|
|
29
31
|
initSessionLog(sessionId) {
|
|
30
32
|
if (!this.logDir)
|
|
31
33
|
return;
|
|
@@ -35,10 +37,11 @@ export class FileLogWriter {
|
|
|
35
37
|
this.sessionLogPath = join(this.logDir, `${sessionId}.log`);
|
|
36
38
|
this.cleanupOldLogs();
|
|
37
39
|
}
|
|
38
|
-
/**
|
|
40
|
+
/** Возврат к дневным лог-файлам, когда нет активной сессии. */
|
|
39
41
|
closeSessionLog() {
|
|
40
42
|
this.sessionLogPath = null;
|
|
41
43
|
}
|
|
44
|
+
/** Путь к текущему лог-файлу (сессионный или дневной). */
|
|
42
45
|
getLogPath() {
|
|
43
46
|
if (this.sessionLogPath)
|
|
44
47
|
return this.sessionLogPath;
|
|
@@ -47,7 +50,7 @@ export class FileLogWriter {
|
|
|
47
50
|
const date = new Date().toISOString().slice(0, 10);
|
|
48
51
|
return join(this.logDir, `${date}.log`);
|
|
49
52
|
}
|
|
50
|
-
/**
|
|
53
|
+
/** Ротирует файл при превышении лимита размера. */
|
|
51
54
|
rotateIfNeeded(filePath) {
|
|
52
55
|
if (!existsSync(filePath))
|
|
53
56
|
return;
|
|
@@ -59,6 +62,7 @@ export class FileLogWriter {
|
|
|
59
62
|
idx++;
|
|
60
63
|
renameSync(filePath, `${filePath}.${idx}`);
|
|
61
64
|
}
|
|
65
|
+
/** Пишет одну строку. Файловый лог — best-effort, ошибки глотаются. */
|
|
62
66
|
log(level, tag, message) {
|
|
63
67
|
const fp = this.getLogPath();
|
|
64
68
|
if (!fp)
|
|
@@ -75,10 +79,12 @@ export class FileLogWriter {
|
|
|
75
79
|
/* file logging is best-effort */
|
|
76
80
|
}
|
|
77
81
|
}
|
|
82
|
+
/** Пишет запрос к LLM (модель, число сообщений, превью промпта). */
|
|
78
83
|
logLLMRequest(model, messagesCount, promptPreview, caller) {
|
|
79
84
|
const tag = caller ? `LLM/${caller}` : "LLM";
|
|
80
85
|
this.log("INFO", tag, `→ ${model} | ${messagesCount} messages | "${promptPreview.slice(0, 100)}"`);
|
|
81
86
|
}
|
|
87
|
+
/** Пишет ответ LLM (длина, время) или ошибку. */
|
|
82
88
|
logLLMResponse(model, responseLength, genTimeMs, error, caller) {
|
|
83
89
|
const tag = caller ? `LLM/${caller}` : "LLM";
|
|
84
90
|
if (error) {
|
|
@@ -88,16 +94,18 @@ export class FileLogWriter {
|
|
|
88
94
|
this.log("INFO", tag, `← ${model} | ${responseLength} chars | ${genTimeMs}ms`);
|
|
89
95
|
}
|
|
90
96
|
}
|
|
97
|
+
/** Пишет вызов тула с обрезанными превью аргументов и результата. */
|
|
91
98
|
logToolCall(tool, preview, result) {
|
|
92
|
-
const previewTrimmed = preview.length > 200 ? preview.slice(0, 200)
|
|
99
|
+
const previewTrimmed = preview.length > 200 ? `${preview.slice(0, 200)}...` : preview;
|
|
93
100
|
if (result !== undefined) {
|
|
94
|
-
const resultTrimmed = result.length > 200 ? result.slice(0, 200)
|
|
101
|
+
const resultTrimmed = result.length > 200 ? `${result.slice(0, 200)}...` : result;
|
|
95
102
|
this.log("INFO", "TOOL", `${tool} | ${previewTrimmed} → ${resultTrimmed}`);
|
|
96
103
|
}
|
|
97
104
|
else {
|
|
98
105
|
this.log("INFO", "TOOL", `${tool} | ${previewTrimmed}`);
|
|
99
106
|
}
|
|
100
107
|
}
|
|
108
|
+
/** Пишет вывод тула с кодом выхода (тело режется лимитом). */
|
|
101
109
|
logToolOutput(tool, output, exitCode) {
|
|
102
110
|
const trimmed = output.length > TOOL_OUTPUT_LIMIT
|
|
103
111
|
? output.slice(0, TOOL_OUTPUT_LIMIT) +
|
|
@@ -105,14 +113,15 @@ export class FileLogWriter {
|
|
|
105
113
|
: output;
|
|
106
114
|
this.log("DEBUG", "TOOL_OUTPUT", `${tool} exit:${exitCode}\n${trimmed}`);
|
|
107
115
|
}
|
|
116
|
+
/** Пишет реплику REPL (тело режется до 400 символов). */
|
|
108
117
|
logREPL(tag, content) {
|
|
109
|
-
const trimmed = content.length > 400 ? content.slice(0, 400)
|
|
118
|
+
const trimmed = content.length > 400 ? `${content.slice(0, 400)}...` : content;
|
|
110
119
|
this.log("INFO", `REPL/${tag}`, trimmed);
|
|
111
120
|
}
|
|
112
121
|
/**
|
|
113
|
-
*
|
|
114
|
-
* -
|
|
115
|
-
* -
|
|
122
|
+
* Чистит старые лог-файлы.
|
|
123
|
+
* - удаляет старше maxDays;
|
|
124
|
+
* - оставляет не более maxFiles самых свежих .log-файлов.
|
|
116
125
|
*/
|
|
117
126
|
cleanupOldLogs(maxDays = DEFAULT_MAX_DAYS, maxFiles = MAX_LOG_FILES) {
|
|
118
127
|
if (!this.logDir || !existsSync(this.logDir))
|
|
@@ -126,7 +135,7 @@ export class FileLogWriter {
|
|
|
126
135
|
mtime: statSync(join(this.logDir, f)).mtimeMs,
|
|
127
136
|
}))
|
|
128
137
|
.sort((a, b) => b.mtime - a.mtime);
|
|
129
|
-
// 1.
|
|
138
|
+
// 1. Удаление по возрасту.
|
|
130
139
|
for (const f of list()) {
|
|
131
140
|
if (now - f.mtime > maxDays * 24 * 60 * 60 * 1000) {
|
|
132
141
|
try {
|
|
@@ -137,7 +146,7 @@ export class FileLogWriter {
|
|
|
137
146
|
}
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
|
-
// 2.
|
|
149
|
+
// 2. Удаление излишков (свежие, но сверх maxFiles).
|
|
141
150
|
const remaining = list();
|
|
142
151
|
for (let i = maxFiles; i < remaining.length; i++) {
|
|
143
152
|
try {
|
package/dist/logger/index.js
CHANGED