micro-models-agent 0.63.0 → 1.1.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 +174 -0
- package/dist/cli/cache-line.js +30 -0
- package/dist/cli/command-suggest.js +38 -0
- package/dist/cli/commands.js +285 -60
- package/dist/cli/completer.js +16 -16
- package/dist/cli/json-payload.js +32 -0
- package/dist/cli/main.js +165 -77
- package/dist/cli/plugin-commands.js +5 -4
- package/dist/cli/relaunch.js +37 -0
- package/dist/cli/repl-commands.js +441 -307
- package/dist/cli/repl.js +360 -83
- package/dist/cli/run-result.js +12 -6
- package/dist/cli/security-commands.js +64 -60
- package/dist/cli/setup-order.js +57 -0
- package/dist/cli/setup-prompt.js +49 -0
- package/dist/cli/setup.js +52 -48
- package/dist/config/budget.js +48 -0
- package/dist/config/config.js +132 -70
- package/dist/config/defaults.js +37 -11
- package/dist/config/domains.js +9 -50
- package/dist/config/utils.js +56 -0
- package/dist/core/agent/audit-gate.js +49 -0
- package/dist/core/agent/compaction.js +89 -0
- package/dist/core/agent/constants.js +61 -0
- package/dist/core/agent/context-renderer.js +40 -0
- package/dist/core/agent/hallucination-gate.js +87 -0
- package/dist/core/agent/loop-state.js +53 -0
- package/dist/core/agent/prefix-monitor.js +101 -0
- package/dist/core/agent/reasoning-resolver.js +56 -0
- package/dist/core/agent/token-tracker.js +96 -0
- package/dist/core/agent/tool-batch.js +237 -0
- package/dist/core/agent/tool-output.js +62 -0
- package/dist/core/agent-moe.js +214 -69
- package/dist/core/agent.js +506 -546
- package/dist/core/bootstrap.js +297 -98
- package/dist/core/crash-handler.js +2 -1
- package/dist/core/prompt-builder.js +3 -0
- package/dist/core/prompt-overflow.js +307 -0
- package/dist/core/session-logger.js +34 -2
- package/dist/i18n/en.json +8 -4
- package/dist/i18n/ru.json +8 -4
- package/dist/index.js +5 -1
- package/dist/llm/cache-usage.js +76 -0
- package/dist/llm/image-utils.js +20 -16
- package/dist/llm/llm-errors.js +41 -0
- package/dist/llm/model-loader.js +30 -0
- package/dist/llm/openai-compat.js +287 -101
- package/dist/llm/orchestrator.js +140 -68
- package/dist/llm/provider-budget.js +68 -0
- package/dist/llm/provider.js +0 -1
- package/dist/llm/stream-state.js +26 -0
- package/dist/llm/token-counter.js +28 -0
- package/dist/logger/app-logger.js +12 -15
- package/dist/main.js +1755 -841
- package/dist/migration/detect.js +3 -1
- package/dist/modules/browser/actions.js +0 -3
- package/dist/modules/browser/bridge-client.js +2 -0
- package/dist/modules/browser/bridge-server.mjs +37 -4
- package/dist/modules/browser/driver.js +46 -4
- package/dist/modules/certification/cli.js +85 -42
- package/dist/modules/certification/loader.js +15 -1
- package/dist/modules/certification/manifest.js +126 -15
- package/dist/modules/certification/runner.js +4 -26
- package/dist/modules/certification/scenarios.js +184 -5
- package/dist/modules/certification/syntax-scenarios.js +51 -0
- package/dist/modules/context/chunk-query.js +25 -5
- package/dist/modules/context/fact-extractor.js +6 -2
- package/dist/modules/context/manager.js +23 -7
- package/dist/modules/execution/audit-runners.js +7 -1
- package/dist/modules/execution/auditor.js +3 -3
- package/dist/modules/execution/execution-plugin.js +22 -15
- package/dist/modules/execution/input-from.js +46 -0
- package/dist/modules/execution/module.js +107 -18
- package/dist/modules/execution/moe-executor.js +166 -54
- package/dist/modules/execution/plan-actions.js +524 -0
- package/dist/modules/execution/plan-steps.js +23 -0
- package/dist/modules/execution/plan-store.js +15 -3
- package/dist/modules/execution/plan-tool.js +6 -488
- package/dist/modules/execution/plan-validator.js +24 -0
- package/dist/modules/execution/stuck-detector.js +3 -18
- package/dist/modules/execution/tracker.js +14 -5
- package/dist/modules/execution/transient-error.js +30 -0
- package/dist/modules/execution/verifier.js +94 -7
- package/dist/modules/execution/windows-commands.js +11 -0
- package/dist/modules/hallucination/confidence.js +36 -23
- package/dist/modules/hallucination/consistency.js +3 -0
- package/dist/modules/hallucination/detector.js +8 -3
- package/dist/modules/hallucination/factual.js +26 -7
- package/dist/modules/hallucination/llm-judge.js +12 -2
- package/dist/modules/indexer/map-command.js +35 -0
- package/dist/modules/indexer/map-select.js +87 -0
- package/dist/modules/indexer/module.js +34 -22
- package/dist/modules/indexer/symbols.js +189 -0
- package/dist/modules/indexer/walker.js +96 -42
- package/dist/modules/lsp/check-tool.js +2 -1
- package/dist/modules/lsp/client.js +49 -32
- package/dist/modules/lsp/config.js +55 -2
- package/dist/modules/lsp/module.js +38 -5
- package/dist/modules/lsp/probe.js +4 -3
- package/dist/modules/lsp/project-root.js +41 -1
- package/dist/modules/lsp/startup-check.js +12 -4
- package/dist/modules/mcp/client.js +153 -104
- package/dist/modules/mcp/module.js +165 -41
- package/dist/modules/memory/module.js +4 -3
- package/dist/modules/plugins/builtin/lint-on-write.js +36 -6
- package/dist/modules/plugins/manager.js +47 -84
- package/dist/modules/pricing/index.js +17 -7
- package/dist/modules/pricing/prices.js +30 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +56 -0
- package/dist/modules/processes/registry.js +2 -54
- package/dist/modules/providers/cache.js +23 -0
- package/dist/modules/providers/factory.js +28 -0
- package/dist/modules/providers/fallback.js +7 -5
- package/dist/modules/providers/health.js +2 -1
- package/dist/modules/providers/index.js +1 -0
- package/dist/modules/providers/manager.js +17 -2
- package/dist/modules/providers/presets.js +79 -6
- package/dist/modules/reasoning/policy.js +40 -0
- package/dist/modules/reasoning/probe.js +111 -0
- package/dist/modules/security/audit-notifier.js +42 -27
- package/dist/modules/security/command-validator.js +25 -20
- package/dist/modules/security/encryption.js +6 -12
- package/dist/modules/security/network-validator.js +76 -5
- package/dist/modules/security/path-validator.js +77 -34
- package/dist/modules/security/rate-limiter.js +11 -0
- package/dist/modules/security/security-policies.js +1 -1
- package/dist/modules/security/session-encryption.js +13 -2
- package/dist/modules/security/session-isolation.js +2 -9
- package/dist/modules/session/manager.js +11 -0
- package/dist/modules/session/module.js +11 -3
- package/dist/modules/session/store.js +41 -5
- package/dist/modules/skills/loader.js +7 -1
- package/dist/modules/skills/module.js +2 -1
- package/dist/modules/updater/changelog-reader.js +94 -0
- package/dist/modules/updater/dev-detect.js +17 -0
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +14 -3
- package/dist/output/bus.js +32 -0
- package/dist/output/channel.js +233 -0
- package/dist/output/format.js +14 -0
- package/dist/output/index.js +7 -0
- package/dist/output/json-sink.js +22 -0
- package/dist/output/machine.js +8 -0
- package/dist/output/session-sink.js +27 -0
- package/dist/output/types.js +1 -0
- package/dist/tools/approve.js +6 -2
- package/dist/tools/attach-image.js +11 -11
- package/dist/tools/auto-fixer.js +198 -0
- package/dist/tools/bash.js +142 -89
- package/dist/tools/chunk-query.js +10 -6
- package/dist/tools/download-file.js +1 -1
- package/dist/tools/edit-file.js +20 -2
- package/dist/tools/executor.js +54 -9
- package/dist/tools/glob-tool.js +7 -0
- package/dist/tools/grep-tool.js +15 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/list-dir.js +3 -1
- package/dist/tools/load-skill.js +2 -1
- package/dist/tools/mcp-call.js +1 -1
- package/dist/tools/move-file.js +5 -4
- package/dist/tools/path-utils.js +7 -0
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/prompt-io.js +28 -0
- package/dist/tools/question.js +12 -12
- package/dist/tools/scope-request.js +91 -0
- package/dist/tools/session-info.js +44 -0
- package/dist/tools/set-thinking.js +71 -0
- package/dist/tools/subagent.js +50 -9
- package/dist/tools/syntax-validator.js +177 -0
- package/dist/tools/user-input.js +16 -9
- package/dist/tools/write-file.js +17 -1
- package/dist/ui/diff.js +10 -0
- package/dist/ui/line-editor.js +179 -26
- package/dist/ui/line-math.js +20 -3
- package/dist/ui/md-formatter.js +100 -10
- package/dist/ui/output.js +5 -4
- package/dist/ui/plan-view.js +2 -7
- package/dist/ui/renderer.js +89 -85
- package/dist/ui/spinner.js +14 -4
- package/dist/utils/error.js +4 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/retry.js +17 -0
- package/dist/utils/sleep.js +23 -0
- package/dist/utils/truncate.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// src/core/prompt-overflow.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { PromptBuilder } from "./prompt-builder";
|
|
6
|
+
import { estimateTokens } from "../llm/token-counter";
|
|
7
|
+
import { t } from "../i18n/index";
|
|
8
|
+
/** How many tokens the model-facing hint block costs (upper bound for allocation). */
|
|
9
|
+
export const HINT_BLOCK_TOKENS = 60;
|
|
10
|
+
/** Rough chars-per-token factor used for hard truncation fallback. */
|
|
11
|
+
const CHARS_PER_TOKEN = 4;
|
|
12
|
+
const KIND_ORDER = ["instructions", "project-map"];
|
|
13
|
+
const KIND_LABEL = {
|
|
14
|
+
instructions: "project instructions (AGENTS.md)",
|
|
15
|
+
"project-map": "project map",
|
|
16
|
+
};
|
|
17
|
+
const KIND_SOURCE_FILE = {
|
|
18
|
+
instructions: "AGENTS.md",
|
|
19
|
+
"project-map": "",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Static dry-run of the system-prompt budget: which kind-carrying blocks do
|
|
23
|
+
* not fit. Used by bootstrap for the startup banner and by Agent before the
|
|
24
|
+
* first LLM call (the real resolution).
|
|
25
|
+
*/
|
|
26
|
+
export function dryRunOverflow(allBlocks, systemBudget) {
|
|
27
|
+
const builder = new PromptBuilder(systemBudget);
|
|
28
|
+
builder.addBlocks(allBlocks);
|
|
29
|
+
const result = builder.build();
|
|
30
|
+
const includedTokens = result.blocks
|
|
31
|
+
.filter((b) => b.included)
|
|
32
|
+
.reduce((sum, b) => sum + b.tokens, 0);
|
|
33
|
+
const overflow = result.excludedBlocks.filter((b) => Boolean(b.kind));
|
|
34
|
+
return { includedTokens, overflow };
|
|
35
|
+
}
|
|
36
|
+
function cacheKey(kind, content, maxTokens) {
|
|
37
|
+
return createHash("sha256").update(`${kind}|${maxTokens}|${content}`).digest("hex").slice(0, 24);
|
|
38
|
+
}
|
|
39
|
+
function readCache(cacheDir, key) {
|
|
40
|
+
try {
|
|
41
|
+
const path = join(cacheDir, `${key}.md`);
|
|
42
|
+
if (!existsSync(path))
|
|
43
|
+
return null;
|
|
44
|
+
const raw = readFileSync(path, "utf-8").trim();
|
|
45
|
+
if (!raw)
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
if ((parsed.mode === "summary" || parsed.mode === "truncate") && typeof parsed.text === "string" && parsed.text) {
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Legacy plain-text entry — treat as a summary hit
|
|
55
|
+
return { mode: "summary", text: raw };
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Corrupted cache — fall through to re-summarize
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function writeCache(cacheDir, key, entry) {
|
|
65
|
+
try {
|
|
66
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
67
|
+
writeFileSync(join(cacheDir, `${key}.md`), JSON.stringify(entry), "utf-8");
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Best-effort — cache failure must not crash the agent
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Collect a full text response from the streaming provider API. */
|
|
74
|
+
async function collectText(provider, prompt, maxTokens) {
|
|
75
|
+
let text = "";
|
|
76
|
+
// Reasoning must be off: a thinking model would burn the completion cap on
|
|
77
|
+
// <think> before emitting any summary content (observed on qwen3.5-9b).
|
|
78
|
+
// 2x headroom over the target: token estimates are heuristics, and the
|
|
79
|
+
// clamp below enforces the real cap no matter what the model returns.
|
|
80
|
+
for await (const chunk of provider.chat([{ role: "user", content: prompt }], undefined, undefined, {
|
|
81
|
+
maxTokens: maxTokens * 2,
|
|
82
|
+
reasoningEffort: "none",
|
|
83
|
+
})) {
|
|
84
|
+
if (chunk.type === "text" && chunk.content)
|
|
85
|
+
text += chunk.content;
|
|
86
|
+
}
|
|
87
|
+
return text.trim();
|
|
88
|
+
}
|
|
89
|
+
function truncateToTokens(content, maxTokens) {
|
|
90
|
+
const maxChars = Math.max(200, maxTokens * CHARS_PER_TOKEN);
|
|
91
|
+
if (content.length <= maxChars)
|
|
92
|
+
return content;
|
|
93
|
+
let cut = content.slice(0, maxChars);
|
|
94
|
+
const lastBreak = cut.lastIndexOf("\n");
|
|
95
|
+
if (lastBreak > maxChars * 0.5)
|
|
96
|
+
cut = cut.slice(0, lastBreak);
|
|
97
|
+
return `${cut.trimEnd()}\n\n[...truncated — read the full file for details...]`;
|
|
98
|
+
}
|
|
99
|
+
/** One-line first-line label for warnings/logs. */
|
|
100
|
+
function blockLabel(content) {
|
|
101
|
+
const first = content.split("\n")[0].trim();
|
|
102
|
+
return first.length > 60 ? `${first.slice(0, 57)}...` : first;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Summarize (or, failing that, truncate) prompt blocks that exceeded the
|
|
106
|
+
* system-prompt budget so the project instructions / project map still reach
|
|
107
|
+
* the model. Summaries are cached on disk keyed by kind + content + budget.
|
|
108
|
+
* Blocks are resolved in KIND_ORDER priority; the first gets the full
|
|
109
|
+
* remaining budget, later blocks get whatever is left.
|
|
110
|
+
*/
|
|
111
|
+
export async function resolvePromptOverflow(opts) {
|
|
112
|
+
const { overflow, includedTokens, systemBudget, provider, cacheDir, logger } = opts;
|
|
113
|
+
const sorted = [...overflow].sort((a, b) => KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind));
|
|
114
|
+
const replacements = [];
|
|
115
|
+
const warnings = [];
|
|
116
|
+
let used = includedTokens;
|
|
117
|
+
let unresolved = [];
|
|
118
|
+
// The bootstrap dry-run only warns that blocks "will be summarized before the
|
|
119
|
+
// first run". Without a notice here the first prompt appears to hang while
|
|
120
|
+
// the model rewrites AGENTS.md / the project map — surface it.
|
|
121
|
+
if (sorted.length > 0) {
|
|
122
|
+
logger?.warn(t("prompt.overflow.compressing", { count: String(sorted.length) }));
|
|
123
|
+
}
|
|
124
|
+
for (const block of sorted) {
|
|
125
|
+
const kind = block.kind;
|
|
126
|
+
const maxTokens = systemBudget - used - HINT_BLOCK_TOKENS;
|
|
127
|
+
if (maxTokens < 100) {
|
|
128
|
+
unresolved.push(kind);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
let mode = "summary";
|
|
132
|
+
let text = "";
|
|
133
|
+
const key = cacheKey(kind, block.content, maxTokens);
|
|
134
|
+
const cached = readCache(cacheDir, key);
|
|
135
|
+
if (cached) {
|
|
136
|
+
text = cached.text;
|
|
137
|
+
mode = cached.mode;
|
|
138
|
+
logger?.debug(`Prompt overflow: cache hit for ${kind} (${key}, ${mode})`);
|
|
139
|
+
}
|
|
140
|
+
else if (provider) {
|
|
141
|
+
const targetChars = Math.floor(maxTokens * (CHARS_PER_TOKEN - 1));
|
|
142
|
+
logger?.warn(t("prompt.overflow.summarizing", {
|
|
143
|
+
label: blockLabel(block.content),
|
|
144
|
+
original: String(block.estimatedTokens),
|
|
145
|
+
budget: String(maxTokens),
|
|
146
|
+
}));
|
|
147
|
+
try {
|
|
148
|
+
const prompt = [
|
|
149
|
+
`Summarize the following ${KIND_LABEL[kind]} document.`,
|
|
150
|
+
// 3 chars/token (not 4): non-Latin text (Cyrillic etc.) is ~2-3 chars
|
|
151
|
+
// per token, so a 4x target lands the summary over the clamp.
|
|
152
|
+
`Target length: at most ${targetChars} characters (~${maxTokens} tokens). Be strict about it.`,
|
|
153
|
+
`Preserve concrete facts: file paths, commands, rules, naming conventions, workflow steps.`,
|
|
154
|
+
`Keep the structure (headings / bullet lists). Output ONLY the summary text.`,
|
|
155
|
+
``,
|
|
156
|
+
`--- DOCUMENT START ---`,
|
|
157
|
+
block.content,
|
|
158
|
+
`--- DOCUMENT END ---`,
|
|
159
|
+
].join("\n");
|
|
160
|
+
text = await collectText(provider, prompt, maxTokens);
|
|
161
|
+
// Small models routinely ignore length instructions (observed: the 9B
|
|
162
|
+
// model produced ~4700 chars against a 3170-char target). One
|
|
163
|
+
// self-compression retry on the overshoot — the shorter input makes
|
|
164
|
+
// compliance much likelier — before falling back to the hard clamp.
|
|
165
|
+
let retries = 0;
|
|
166
|
+
while (text && estimateTokens(text) > maxTokens && retries < 1) {
|
|
167
|
+
retries++;
|
|
168
|
+
logger?.debug(`Prompt overflow: ${kind} summary overshot (${estimateTokens(text)} > ${maxTokens} tok), compressing (retry ${retries})`);
|
|
169
|
+
text = await collectText(provider, [
|
|
170
|
+
`Compress the following text to at most ${targetChars} characters.`,
|
|
171
|
+
`Keep the structure (headings / bullet lists) and all concrete facts: file paths, commands, rules.`,
|
|
172
|
+
`Output ONLY the compressed text.`,
|
|
173
|
+
``,
|
|
174
|
+
`--- TEXT START ---`,
|
|
175
|
+
text,
|
|
176
|
+
`--- TEXT END ---`,
|
|
177
|
+
].join("\n"), maxTokens);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
logger?.warn(t("prompt.overflow.summarize_failed", {
|
|
182
|
+
label: blockLabel(block.content),
|
|
183
|
+
error: String(err?.message ?? err),
|
|
184
|
+
}));
|
|
185
|
+
text = "";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!text) {
|
|
189
|
+
// No provider or summarization failed — hard truncation fallback.
|
|
190
|
+
mode = "truncate";
|
|
191
|
+
logger?.warn(t("prompt.overflow.truncating", {
|
|
192
|
+
label: blockLabel(block.content),
|
|
193
|
+
original: String(block.estimatedTokens),
|
|
194
|
+
}));
|
|
195
|
+
text = truncateToTokens(block.content, maxTokens);
|
|
196
|
+
}
|
|
197
|
+
// LLMs overshoot: enforce the cap no matter what produced the text.
|
|
198
|
+
if (estimateTokens(text) > maxTokens) {
|
|
199
|
+
if (mode === "summary") {
|
|
200
|
+
mode = "truncate";
|
|
201
|
+
}
|
|
202
|
+
text = truncateToTokens(text, maxTokens);
|
|
203
|
+
}
|
|
204
|
+
const resolvedTokens = estimateTokens(text);
|
|
205
|
+
used += resolvedTokens;
|
|
206
|
+
writeCache(cacheDir, key, { mode, text });
|
|
207
|
+
replacements.push({
|
|
208
|
+
content: text,
|
|
209
|
+
priority: "high",
|
|
210
|
+
essential: false,
|
|
211
|
+
estimatedTokens: resolvedTokens,
|
|
212
|
+
kind,
|
|
213
|
+
});
|
|
214
|
+
warnings.push({
|
|
215
|
+
kind,
|
|
216
|
+
label: blockLabel(block.content),
|
|
217
|
+
originalTokens: block.estimatedTokens,
|
|
218
|
+
resolvedTokens,
|
|
219
|
+
mode,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const hintBlock = replacements.length > 0
|
|
223
|
+
? {
|
|
224
|
+
content: [
|
|
225
|
+
`[Instructions note] Some project blocks exceeded the system-prompt budget and were compressed:`,
|
|
226
|
+
...warnings.map((w) => `- ${KIND_LABEL[w.kind]}: ${w.originalTokens} tokens → ${w.resolvedTokens} tokens (${w.mode})`),
|
|
227
|
+
...(unresolved.length > 0
|
|
228
|
+
? unresolved.map((k) => `- ${KIND_LABEL[k]}: did not fit at all — read it yourself if needed`)
|
|
229
|
+
: []),
|
|
230
|
+
...warnings
|
|
231
|
+
.filter((w) => KIND_SOURCE_FILE[w.kind])
|
|
232
|
+
.map((w) => `Read the full ${KIND_SOURCE_FILE[w.kind]} with read_file if details are missing.`),
|
|
233
|
+
].join("\n"),
|
|
234
|
+
priority: "high",
|
|
235
|
+
essential: true,
|
|
236
|
+
estimatedTokens: HINT_BLOCK_TOKENS,
|
|
237
|
+
}
|
|
238
|
+
: null;
|
|
239
|
+
return { replacements, hintBlock, warnings };
|
|
240
|
+
}
|
|
241
|
+
export function resolveContextWindowSource(config) {
|
|
242
|
+
const entries = config.provider.entries ?? [];
|
|
243
|
+
const activeLabel = config.provider.active;
|
|
244
|
+
const activeEntry = entries.find((e) => e.label && e.label === activeLabel) ??
|
|
245
|
+
(entries.length === 1 ? entries[0] : undefined);
|
|
246
|
+
if (activeEntry?.contextWindow) {
|
|
247
|
+
return { source: "entry", label: activeEntry.label, value: activeEntry.contextWindow };
|
|
248
|
+
}
|
|
249
|
+
return { source: "global", value: config.contextWindow };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Human-facing hint on how to raise the context window, aware of whether the
|
|
253
|
+
* active value comes from the global config (`mma context <N>`) or from a
|
|
254
|
+
* provider entry override (must be edited in the provider domain file).
|
|
255
|
+
*/
|
|
256
|
+
export function contextWindowHint(config, configDir, recommended) {
|
|
257
|
+
const src = resolveContextWindowSource(config);
|
|
258
|
+
if (src.source === "global") {
|
|
259
|
+
return `run "mma context ${recommended}"`;
|
|
260
|
+
}
|
|
261
|
+
return `edit "${join(configDir, "config", "provider.json")}" → provider.entries[label="${src.label}"].contextWindow = ${recommended}`;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Recommend the next standard context size whose system budget (10%)
|
|
265
|
+
* accommodates `neededSystemTokens`. Falls back to the largest known size.
|
|
266
|
+
*/
|
|
267
|
+
export function recommendContextSize(neededSystemTokens) {
|
|
268
|
+
const sizes = [8192, 16384, 32768, 65536, 131072, 262144];
|
|
269
|
+
return (sizes.find((s) => Math.floor(s * 0.1) >= neededSystemTokens) ?? sizes[sizes.length - 1]);
|
|
270
|
+
}
|
|
271
|
+
/** Context window needed so that `systemFraction` of it covers `neededTokens`. */
|
|
272
|
+
export function requiredContextWindow(neededTokens, systemFraction) {
|
|
273
|
+
const fraction = systemFraction > 0 ? systemFraction : 0.1;
|
|
274
|
+
return Math.ceil(neededTokens / fraction);
|
|
275
|
+
}
|
|
276
|
+
/** System-prompt share of the CURRENT window needed to cover `neededTokens`. */
|
|
277
|
+
export function requiredSystemFraction(neededTokens, contextWindow) {
|
|
278
|
+
if (contextWindow <= 0)
|
|
279
|
+
return 1;
|
|
280
|
+
return Math.min(1, Math.ceil((neededTokens / contextWindow) * 100) / 100);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Localized, concrete fix for an oversized system prompt. Instead of only
|
|
284
|
+
* snapping to a coarse standard size (which makes a 32K project jump straight
|
|
285
|
+
* to 131072), it states the raw requirement and both levers: a bigger window
|
|
286
|
+
* or a bigger share of the current window.
|
|
287
|
+
*/
|
|
288
|
+
export function overflowHint(config, configDir, neededTokens) {
|
|
289
|
+
const fraction = config.contextBudget?.systemPrompt ?? 0.1;
|
|
290
|
+
const budget = Math.floor(config.contextWindow * fraction);
|
|
291
|
+
const recommended = recommendContextSize(neededTokens);
|
|
292
|
+
const requiredWindow = requiredContextWindow(neededTokens, fraction);
|
|
293
|
+
const requiredFraction = requiredSystemFraction(neededTokens, config.contextWindow);
|
|
294
|
+
const how = configDir
|
|
295
|
+
? contextWindowHint(config, configDir, recommended)
|
|
296
|
+
: `increase contextWindow (e.g. to ${recommended})`;
|
|
297
|
+
return [
|
|
298
|
+
t("prompt.overflow.hint_needed", {
|
|
299
|
+
needed: neededTokens,
|
|
300
|
+
window: config.contextWindow,
|
|
301
|
+
fraction,
|
|
302
|
+
budget,
|
|
303
|
+
}),
|
|
304
|
+
t("prompt.overflow.hint_window", { required: requiredWindow, recommended, how }),
|
|
305
|
+
t("prompt.overflow.hint_fraction", { fraction: requiredFraction }),
|
|
306
|
+
].join(" ");
|
|
307
|
+
}
|
|
@@ -88,7 +88,7 @@ export class SessionLogger {
|
|
|
88
88
|
const caller = this.getCaller?.();
|
|
89
89
|
this.session?.appendMessage({
|
|
90
90
|
role: "assistant",
|
|
91
|
-
content
|
|
91
|
+
content,
|
|
92
92
|
timestamp: new Date().toISOString(),
|
|
93
93
|
provider: caller?.provider,
|
|
94
94
|
model: caller?.model,
|
|
@@ -117,7 +117,7 @@ export class SessionLogger {
|
|
|
117
117
|
const caller = this.getCaller?.();
|
|
118
118
|
this.session?.appendMessage({
|
|
119
119
|
role: "tool",
|
|
120
|
-
content: sanitizeLogMessage(result.output
|
|
120
|
+
content: sanitizeLogMessage(result.output),
|
|
121
121
|
name: call.name,
|
|
122
122
|
timestamp: new Date().toISOString(),
|
|
123
123
|
provider: caller?.provider,
|
|
@@ -191,6 +191,11 @@ export class SessionLogger {
|
|
|
191
191
|
totalTokens: usage.totalTokens,
|
|
192
192
|
source: usage.source,
|
|
193
193
|
durationMs: usage.durationMs,
|
|
194
|
+
cacheStable: usage.prefix ? Number(usage.prefix.stableRatio.toFixed(4)) : undefined,
|
|
195
|
+
cacheCause: usage.prefix?.cause,
|
|
196
|
+
cachedTokens: usage.cache?.cachedTokens,
|
|
197
|
+
cacheWriteTokens: usage.cache?.cacheWriteTokens,
|
|
198
|
+
cacheSource: usage.cache?.source,
|
|
194
199
|
});
|
|
195
200
|
}
|
|
196
201
|
logError(message) {
|
|
@@ -248,4 +253,31 @@ export class SessionLogger {
|
|
|
248
253
|
skills: data.skills,
|
|
249
254
|
});
|
|
250
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Log a reasoning control event (level set, policy evaluation).
|
|
258
|
+
*/
|
|
259
|
+
logReasoningControl(data) {
|
|
260
|
+
this.session?.appendLog({
|
|
261
|
+
ts: new Date().toISOString(),
|
|
262
|
+
type: "reasoning_control",
|
|
263
|
+
content: `reasoning level: ${data.level} (source: ${data.source ?? "policy"})`,
|
|
264
|
+
iteration: data.iteration,
|
|
265
|
+
level: data.level,
|
|
266
|
+
strategy: data.strategy,
|
|
267
|
+
probePassed: data.probePassed,
|
|
268
|
+
source: data.source,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Log a MoE observability event (moe_plan / moe_subtask / moe_replan /
|
|
273
|
+
* moe_verify). Emitted by runWithMoE and MoEExecutor (Plan 4.2).
|
|
274
|
+
*/
|
|
275
|
+
logMoE(event, data) {
|
|
276
|
+
this.session?.appendLog({
|
|
277
|
+
ts: new Date().toISOString(),
|
|
278
|
+
type: event,
|
|
279
|
+
content: data.detail ?? data.explanation ?? event,
|
|
280
|
+
...data,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
251
283
|
}
|
package/dist/i18n/en.json
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"error.llm_stream_idle_toolcall": "LLM stream stalled after a tool_call started — no data for {timeout}ms. The provider most likely buffers SSE instead of streaming tool-call argument deltas (seen with LM Studio). Raise \"retry.noDataTimeoutMs\" in ~/.mma/config.json (restart required)",
|
|
45
45
|
"error.llm_truncated": "Response hit the completion token limit ({tokens}) and was cut off before any content arrived. Split the task into smaller outputs or raise \"maxCompletionTokens\" in the config",
|
|
46
46
|
"error.llm_truncated_toolcall": "Response hit the completion token limit ({tokens}) in the middle of a tool_call — its arguments were cut off. Write the file in smaller chunks (several write_file/edit_file calls) or raise \"maxCompletionTokens\" in the config",
|
|
47
|
+
"error.llm_output_truncated": "The response hit the completion token limit ({tokens}) and was cut off — this answer is incomplete. Raise \"maxCompletionTokens\" in the config to allow longer replies",
|
|
47
48
|
"error.llm_provider_stream_error": "Provider returned an error mid-stream: {error}",
|
|
48
49
|
"error.llm_timeout": "LLM request timed out ({timeout}ms)",
|
|
49
50
|
"env.runtime_node": "Running under Node (v{version}) — clipboard image paste, subagent performance and LSP spawn on Windows degrade. Install Bun (https://bun.sh) for full features.",
|
|
@@ -339,6 +340,7 @@
|
|
|
339
340
|
"cli.show_details": "Show session details",
|
|
340
341
|
"cli.delete_session": "Delete a session",
|
|
341
342
|
"cli.first_run": "First run detected. Running setup wizard...",
|
|
343
|
+
"cli.setup_saved_restart": "Settings saved. Restarting with the new configuration...",
|
|
342
344
|
"cli.unknown_cmd": "Unknown command: {name}.",
|
|
343
345
|
"cli.unknown_command": "Unknown command \"{input}\". Did you mean \"{suggestion}\"?",
|
|
344
346
|
"cli.help_hint": "Type /help for available commands.",
|
|
@@ -440,6 +442,8 @@
|
|
|
440
442
|
"repl.skill_usage": "Usage: /skill [list|loaded|load|unload|search]",
|
|
441
443
|
"repl.agent": "Agent: ",
|
|
442
444
|
"repl.you": "You: ",
|
|
445
|
+
"repl.you_reasoning": "You [{level}]: ",
|
|
446
|
+
"repl.you_reasoning_cont": "You [{level}]… ",
|
|
443
447
|
"repl.interrupt": "Interrupted (Esc)",
|
|
444
448
|
"repl.title": "MMA REPL v{version}",
|
|
445
449
|
"repl.model": "Model:",
|
|
@@ -508,6 +512,7 @@
|
|
|
508
512
|
"setup.title": "\n MMA Setup Wizard v2\n",
|
|
509
513
|
"setup.select_provider": "\n Select provider type:",
|
|
510
514
|
"setup.select_provider_num": "\n Select provider (1-{max})",
|
|
515
|
+
"setup.select_language_num": "\n Select language (1-{max})",
|
|
511
516
|
"setup.scanning": " Scanning local LLM servers...",
|
|
512
517
|
"setup.found_servers": " Found {count} server(s):",
|
|
513
518
|
"setup.no_servers": " No local servers found. Enter URL manually.",
|
|
@@ -532,8 +537,6 @@
|
|
|
532
537
|
"setup.scanning_spinner": "Scanning local LLM servers…",
|
|
533
538
|
"setup.fetching_spinner": "Fetching model list…",
|
|
534
539
|
"setup.testing_spinner": "Testing chat with \"{model}\"…",
|
|
535
|
-
"setup.summary_setting": "Setting",
|
|
536
|
-
"setup.summary_value": "Value",
|
|
537
540
|
"setup.security_header": "\n --- Security ---",
|
|
538
541
|
"setup.security_status_off": " Security: OFF (default — all commands allowed)",
|
|
539
542
|
"setup.security_status_on": " Security: ON (balanced policy by default)",
|
|
@@ -584,6 +587,7 @@
|
|
|
584
587
|
"exec.error_search_failed": "Web search returned nothing for \"{query}\".",
|
|
585
588
|
"exec.error_search_no_query": "Error output is not meaningful — skipping the web search.",
|
|
586
589
|
"exec.npm_exec_hint": "\"could not determine executable to run\" — no \"bin\" for that package/script. Use \"npm run <script>\" (script must exist in package.json) or \"bunx <pkg>\" for a package that declares a bin.",
|
|
590
|
+
"exec.interactive_hint": "the command needed interactive input but the shell has no TTY (prompt aborted). Re-run it non-interactively — pass every required flag/argument (e.g. `bun create vite <name> --template react-ts` instead of a bare interactive scaffold).",
|
|
587
591
|
"exec.hidden_tool_hint": "\"{tool}\" is not a shell command — it is an MMA tool that is currently hidden. Call the enable_tools tool with tags [\"shell\"] to unlock it; it becomes available on the next iteration.",
|
|
588
592
|
"exec.task_reminder": "Task: {task}. Continue making progress — do not repeat failed actions.",
|
|
589
593
|
"hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
|
|
@@ -761,9 +765,9 @@
|
|
|
761
765
|
"prompt.overflow.hint_needed": "the prompt needs ~{needed} system tokens (current budget: {window} × {fraction} = {budget})",
|
|
762
766
|
"prompt.overflow.hint_window": "raise contextWindow to at least {required} (standard size {recommended}) — {how}",
|
|
763
767
|
"prompt.overflow.hint_fraction": "or keep the window and raise contextBudget.systemPrompt to ~{fraction} (run: mma context --system {fraction})",
|
|
764
|
-
"prompt.overflow.exceeded": "Prompt overflow:
|
|
768
|
+
"prompt.overflow.exceeded": "Prompt overflow: the system prompt needs ~{needed} tok (budget {budget} tok); \"{label}\" ({original} tok) was {mode} to {resolved} tok. To fit everything fully, {hint}.",
|
|
765
769
|
"prompt.overflow.failed": "Prompt overflow resolution failed: {error} — oversized blocks will be dropped.",
|
|
766
|
-
"prompt.overflow.startup": "Startup check: {
|
|
770
|
+
"prompt.overflow.startup": "Startup check: the system prompt needs ~{needed} tok (budget: {budget} tok); {block} ({original} tok) will be summarized/truncated before the first run. To include everything fully, {hint}.",
|
|
767
771
|
"tool.session_info.no_active": "No active session",
|
|
768
772
|
"tool.session_info.result": "Session: \"{name}\" ({id})\nModel: {model}\nProvider: {provider}\nContext window: {contextWindow} tokens\nMessages: {messages}\nCreated: {createdAt}\nUpdated: {updatedAt}",
|
|
769
773
|
"tool.session_info.context": "Context used: {used} / {budget} tokens ({percent}%)",
|
package/dist/i18n/ru.json
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"error.llm_stream_idle_toolcall": "Поток LLM завис после начала tool_call — нет данных {timeout}мс. Скорее всего провайдер буферизирует SSE вместо потоковой передачи аргументов tool_call (наблюдается в LM Studio). Увеличьте \"retry.noDataTimeoutMs\" в ~/.mma/config.json (нужен рестарт)",
|
|
44
44
|
"error.llm_truncated": "Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан до какого-либо содержимого. Разбейте задачу на меньшие порции вывода или увеличьте \"maxCompletionTokens\" в конфиге",
|
|
45
45
|
"error.llm_truncated_toolcall": "Ответ упёрся в лимит токенов генерации ({tokens}) посреди tool_call — его аргументы обрезаны. Пишите файл частями (несколько вызовов write_file/edit_file) или увеличьте \"maxCompletionTokens\" в конфиге",
|
|
46
|
+
"error.llm_output_truncated": "Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан — этот ответ неполный. Увеличьте \"maxCompletionTokens\" в конфиге, чтобы разрешить длинные ответы",
|
|
46
47
|
"error.llm_provider_stream_error": "Провайдер вернул ошибку посреди стрима: {error}",
|
|
47
48
|
"error.llm_timeout": "Время запроса LLM истекло ({timeout}мс)",
|
|
48
49
|
"env.runtime_node": "Запущено под Node (v{version}) — вставка изображений из буфера и LSP на Windows работают урезанно. Установите Bun (https://bun.sh) для полного функционала.",
|
|
@@ -333,6 +334,7 @@
|
|
|
333
334
|
"cli.show_details": "Показать детали сессии",
|
|
334
335
|
"cli.delete_session": "Удалить сессию",
|
|
335
336
|
"cli.first_run": "Первый запуск. Запускаем мастер настройки...",
|
|
337
|
+
"cli.setup_saved_restart": "Настройки сохранены. Перезапуск с новой конфигурацией...",
|
|
336
338
|
"cli.unknown_cmd": "Неизвестная команда: {name}.",
|
|
337
339
|
"cli.unknown_command": "Неизвестная команда \"{input}\". Возможно, вы имели в виду \"{suggestion}\"?",
|
|
338
340
|
"cli.help_hint": "Введите /help для списка команд.",
|
|
@@ -434,6 +436,8 @@
|
|
|
434
436
|
"repl.skill_usage": "Использование: /skill [list|loaded|load|unload|search]",
|
|
435
437
|
"repl.agent": "Агент: ",
|
|
436
438
|
"repl.you": "Вы: ",
|
|
439
|
+
"repl.you_reasoning": "Вы [{level}]: ",
|
|
440
|
+
"repl.you_reasoning_cont": "Вы [{level}]… ",
|
|
437
441
|
"repl.interrupt": "Прервано (Esc)",
|
|
438
442
|
"repl.title": "MMA REPL v{version}",
|
|
439
443
|
"repl.model": "Модель:",
|
|
@@ -506,6 +510,7 @@
|
|
|
506
510
|
"setup.title": "\n Мастер настройки MMA v2\n",
|
|
507
511
|
"setup.select_provider": "\n Выберите тип провайдера:",
|
|
508
512
|
"setup.select_provider_num": "\n Выберите провайдер (1-{max})",
|
|
513
|
+
"setup.select_language_num": "\n Выберите язык (1-{max})",
|
|
509
514
|
"setup.scanning": " Сканирование локальных LLM-серверов...",
|
|
510
515
|
"setup.found_servers": " Найдено серверов: {count}",
|
|
511
516
|
"setup.no_servers": " Локальные серверы не найдены. Введите URL вручную.",
|
|
@@ -530,8 +535,6 @@
|
|
|
530
535
|
"setup.scanning_spinner": "Сканирование локальных LLM-серверов…",
|
|
531
536
|
"setup.fetching_spinner": "Загрузка списка моделей…",
|
|
532
537
|
"setup.testing_spinner": "Проверка чата с \"{model}\"…",
|
|
533
|
-
"setup.summary_setting": "Параметр",
|
|
534
|
-
"setup.summary_value": "Значение",
|
|
535
538
|
"setup.security_header": "\n --- Безопасность ---",
|
|
536
539
|
"setup.security_status_off": " Безопасность: ВЫКЛ (по умолчанию — все команды разрешены)",
|
|
537
540
|
"setup.security_status_on": " Безопасность: ВКЛ (по умолчанию — политика balanced)",
|
|
@@ -582,6 +585,7 @@
|
|
|
582
585
|
"exec.error_search_failed": "Поиск в интернете для \"{query}\" ничего не дал.",
|
|
583
586
|
"exec.error_search_no_query": "Текст ошибки незначимый — поиск в интернете пропущен.",
|
|
584
587
|
"exec.npm_exec_hint": "\"could not determine executable to run\" — у пакета/скрипта нет \"bin\". Используй \"npm run <script>\" (скрипт должен быть в package.json) или \"bunx <pkg>\" для пакета с объявленным bin.",
|
|
588
|
+
"exec.interactive_hint": "команде нужен интерактивный ввод, но у шелла нет TTY (запрос отменён). Запусти её неинтерактивно — передай все нужные флаги/аргументы (например, `bun create vite <name> --template react-ts` вместо голого интерактивного скаффолда).",
|
|
585
589
|
"exec.hidden_tool_hint": "\"{tool}\" — не команда оболочки, это инструмент MMA, который сейчас скрыт. Вызови тул enable_tools с tags [\"shell\"], чтобы включить его; он станет доступен на следующей итерации.",
|
|
586
590
|
"exec.task_reminder": "Задача: {task}. Продолжай работу — не повторяй неудачные действия.",
|
|
587
591
|
"hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
|
|
@@ -761,9 +765,9 @@
|
|
|
761
765
|
"prompt.overflow.hint_needed": "промпту нужно ~{needed} токенов системного бюджета (сейчас: {window} × {fraction} = {budget})",
|
|
762
766
|
"prompt.overflow.hint_window": "подними contextWindow минимум до {required} (стандартный размер {recommended}) — {how}",
|
|
763
767
|
"prompt.overflow.hint_fraction": "или оставь окно и подними contextBudget.systemPrompt до ~{fraction} (выполни: mma context --system {fraction})",
|
|
764
|
-
"prompt.overflow.exceeded": "Промпт переполнен:
|
|
768
|
+
"prompt.overflow.exceeded": "Промпт переполнен: системному промпту нужно ~{needed} токенов (бюджет {budget} токенов); \"{label}\" ({original} токенов) было {mode} до {resolved} токенов. Чтобы включить всё полностью, {hint}.",
|
|
765
769
|
"prompt.overflow.failed": "Не удалось разрешить переполнение промпта: {error} — слишком большие блоки будут отброшены.",
|
|
766
|
-
"prompt.overflow.startup": "Проверка при старте: {
|
|
770
|
+
"prompt.overflow.startup": "Проверка при старте: системному промпту нужно ~{needed} токенов (бюджет: {budget} токенов); {block} ({original} токенов) будет суммаризован/обрезан перед первым запуском. Чтобы включить всё полностью, {hint}.",
|
|
767
771
|
"tool.session_info.no_active": "Нет активной сессии",
|
|
768
772
|
"tool.session_info.result": "Сессия: \"{name}\" ({id})\nМодель: {model}\nПровайдер: {provider}\nКонтекстное окно: {contextWindow} токенов\nСообщений: {messages}\nСоздана: {createdAt}\nОбновлена: {updatedAt}",
|
|
769
773
|
"tool.session_info.context": "Использовано контекста: {used} / {budget} токенов ({percent}%)",
|
package/dist/index.js
CHANGED
|
@@ -7,16 +7,20 @@ export { MigrationDetector, BackupManager } from "./migration/index";
|
|
|
7
7
|
import { MigrationDetector } from "./migration/detect";
|
|
8
8
|
import { BackupManager } from "./migration/backup";
|
|
9
9
|
import { t } from "./i18n/index";
|
|
10
|
+
import { defaultOutputBus, getDefaultChannel } from "./output";
|
|
10
11
|
import { homedir } from "os";
|
|
11
12
|
import { join } from "path";
|
|
12
13
|
export function checkMigration(configDir) {
|
|
13
14
|
const dir = configDir || join(homedir(), ".mma");
|
|
14
15
|
const detector = new MigrationDetector(dir);
|
|
15
16
|
if (detector.needsMigration()) {
|
|
17
|
+
// Ensure a terminal subscriber exists before emitting to the bus; library
|
|
18
|
+
// consumers have no other eager channel creation.
|
|
19
|
+
getDefaultChannel();
|
|
16
20
|
const backup = new BackupManager(dir);
|
|
17
21
|
backup.backupConfig();
|
|
18
22
|
backup.backupAll();
|
|
19
23
|
const summary = backup.getBackupSummary();
|
|
20
|
-
|
|
24
|
+
defaultOutputBus.log("info", "mma", t("migration.summary", { summary }));
|
|
21
25
|
}
|
|
22
26
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Безопасно приводит значение к конечному числу; иначе undefined. */
|
|
2
|
+
function num(value) {
|
|
3
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
4
|
+
}
|
|
5
|
+
/** Объект ли это (для защиты от null/строк в ответе). */
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === "object" && value !== null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Разбирает usage-объект провайдера в нормализованный `CacheUsage` согласно
|
|
11
|
+
* формату отчёта. Возвращает undefined, когда провайдер не сообщает о кеше —
|
|
12
|
+
* вызывающий не должен выдавать отсутствие данных за «cache miss».
|
|
13
|
+
*
|
|
14
|
+
* `estimatedPromptTokens` нужен только для формата `ollama`, который не
|
|
15
|
+
* отдаёт cache-полей: кешированное вычисляется как разница оценки и
|
|
16
|
+
* `prompt_eval_count`.
|
|
17
|
+
*/
|
|
18
|
+
export function parseCacheUsage(usage, format, estimatedPromptTokens) {
|
|
19
|
+
if (!isRecord(usage))
|
|
20
|
+
return undefined;
|
|
21
|
+
switch (format) {
|
|
22
|
+
case "openai": {
|
|
23
|
+
const details = usage.prompt_tokens_details;
|
|
24
|
+
if (!isRecord(details))
|
|
25
|
+
return undefined;
|
|
26
|
+
const cached = num(details.cached_tokens);
|
|
27
|
+
if (cached === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
const prompt = num(usage.prompt_tokens) ?? cached;
|
|
30
|
+
const write = num(details.cache_write_tokens) ?? 0;
|
|
31
|
+
return {
|
|
32
|
+
cachedTokens: cached,
|
|
33
|
+
cacheWriteTokens: write,
|
|
34
|
+
uncachedTokens: Math.max(0, prompt - cached),
|
|
35
|
+
source: "api",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
case "deepseek": {
|
|
39
|
+
const hit = num(usage.prompt_cache_hit_tokens);
|
|
40
|
+
const miss = num(usage.prompt_cache_miss_tokens);
|
|
41
|
+
if (hit === undefined && miss === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
return {
|
|
44
|
+
cachedTokens: hit ?? 0,
|
|
45
|
+
cacheWriteTokens: 0,
|
|
46
|
+
uncachedTokens: miss ?? 0,
|
|
47
|
+
source: "api",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
case "anthropic": {
|
|
51
|
+
const read = num(usage.cache_read_input_tokens);
|
|
52
|
+
const creation = num(usage.cache_creation_input_tokens);
|
|
53
|
+
if (read === undefined && creation === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
return {
|
|
56
|
+
cachedTokens: read ?? 0,
|
|
57
|
+
cacheWriteTokens: creation ?? 0,
|
|
58
|
+
uncachedTokens: num(usage.input_tokens) ?? 0,
|
|
59
|
+
source: "api",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
case "ollama": {
|
|
63
|
+
const evaluated = num(usage.prompt_eval_count);
|
|
64
|
+
if (evaluated === undefined || estimatedPromptTokens === undefined)
|
|
65
|
+
return undefined;
|
|
66
|
+
return {
|
|
67
|
+
cachedTokens: Math.max(0, estimatedPromptTokens - evaluated),
|
|
68
|
+
cacheWriteTokens: 0,
|
|
69
|
+
uncachedTokens: evaluated,
|
|
70
|
+
source: "derived",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
package/dist/llm/image-utils.js
CHANGED
|
@@ -41,27 +41,31 @@ async function readClipboardFallback() {
|
|
|
41
41
|
const { execSync } = await import("child_process");
|
|
42
42
|
const { readFileSync, unlinkSync } = await import("fs");
|
|
43
43
|
const { join } = await import("path");
|
|
44
|
+
if (platform() !== "linux")
|
|
45
|
+
return null; // macOS/Windows use Bun.Image
|
|
44
46
|
const tmpPath = join(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
else {
|
|
52
|
-
return null; // macOS/Windows should use Bun.Image
|
|
53
|
-
}
|
|
54
|
-
const buf = readFileSync(tmpPath);
|
|
55
|
-
unlinkSync(tmpPath);
|
|
56
|
-
return buf.length > 0 ? buf : null;
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
47
|
+
// Try Wayland (wl-paste) first, then X11 (xclip)
|
|
48
|
+
const commands = [
|
|
49
|
+
`wl-paste --type image/png > "${tmpPath}" 2>/dev/null`,
|
|
50
|
+
`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`,
|
|
51
|
+
];
|
|
52
|
+
for (const cmd of commands) {
|
|
59
53
|
try {
|
|
54
|
+
execSync(cmd, { timeout: 5000 });
|
|
55
|
+
const buf = readFileSync(tmpPath);
|
|
60
56
|
unlinkSync(tmpPath);
|
|
57
|
+
if (buf.length > 0)
|
|
58
|
+
return buf;
|
|
61
59
|
}
|
|
62
|
-
catch {
|
|
63
|
-
|
|
60
|
+
catch {
|
|
61
|
+
// try next command
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
unlinkSync(tmpPath);
|
|
64
66
|
}
|
|
67
|
+
catch { }
|
|
68
|
+
return null;
|
|
65
69
|
}
|
|
66
70
|
/**
|
|
67
71
|
* Load an image from a file path, resize via Bun.Image, return as JPEG data URL.
|