blun-king-cli 9.1.119 → 9.1.121
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/LIESMICH.txt +4 -0
- package/README.md +4 -0
- package/bin/assistant-message-offload-policy.cjs +51 -0
- package/bin/tool-result-offload-policy.cjs +39 -0
- package/blun.mjs +210 -10
- package/package.json +1 -1
package/LIESMICH.txt
CHANGED
|
@@ -117,6 +117,10 @@ Ab BLUN King 9.1.118 bleibt der Sitzungszeitstempel im Systemprompt während ein
|
|
|
117
117
|
|
|
118
118
|
Ab BLUN King 9.1.119 enthält der bei jedem Modellschritt erneut gesendete Prompt nur noch eine kleine Auswahl häufig benötigter Skills. Alle übrigen registrierten Skills bleiben verfügbar und lassen sich über das Skill-Werkzeug anhand ihres Namens oder Zwecks suchen; der ausgewählte Skill wird anschließend bei Bedarf geladen. Dadurch sinkt die wiederholt übertragene Promptlast, ohne Skills zu löschen oder die vollständige Ansicht unter `/skills` einzuschränken.
|
|
119
119
|
|
|
120
|
+
Ab BLUN King 9.1.120 lagert die Konsole auch große ältere Assistentenantworten aus dem aktiven Modellkontext in private Sitzungsdateien aus. Der vollständige Gesprächsverlauf bleibt erhalten; im Modellkontext verbleiben eine begrenzte Vorschau und der Dateipfad. Die 20 neuesten Nachrichten sowie Assistentenantworten mit Werkzeugaufrufen bleiben unverändert, damit laufende Arbeit und Aufrufketten vollständig verfügbar sind.
|
|
121
|
+
|
|
122
|
+
Ab BLUN King 9.1.121 begrenzt die Modellprojektion auch die Gesamtgröße älterer, mittelgroßer Werkzeugergebnisse, die ausschließlich Text enthalten. Überschreiten Werkzeugergebnisse außerhalb der letzten 20 Nachrichten zusammen 12.000 Zeichen, archiviert King so viele der größten geeigneten Ergebnisse wie nötig und behält nur kompakte, lesbare Dateiverweise im Modellkontext. Der nur ergänzte Rohverlauf und die vollständigen Ergebnisse bleiben unverändert. Gemischte Medienergebnisse und die letzten 20 Nachrichten werden nicht angetastet. In einer gemessenen Sitzung von Fredrik wählte dieser Schnitt 12 ältere Ergebnisse aus und verringerte die projizierte alte Werkzeugausgabe um mindestens 63.444 Zeichen, also um etwa 15.861 geschätzte Token.
|
|
123
|
+
|
|
120
124
|
Verliert ein delegierter Einzelagent oder ein Mitglied eines Agentenschwarms die Provider-Verbindung, erhält es eine leere Provider-Antwort, bricht sein Stream wegen Leerlaufs ab oder meldet der Server HTTP 408/500/502/503/504, setzt BLUN King denselben Agenten anhand seines dauerhaften Verlaufs fort. Bei Einzelagenten sowie bei Schwarm-Unterbrechungen außerhalb von HTTP 429 wartet die erste Fortsetzung zwei Sekunden und die zweite fünf Sekunden; danach bleibt der ursprüngliche Fehler sichtbar. HTTP 429 behält im Schwarm seine getrennte, längere Überlastungsregel. Authentifizierungs-, Zahlungs- oder Kontingent-, Richtlinien-, Kontext-, Werkzeug- und Codefehler sowie manuelle Abbrüche lösen keine automatische Fortsetzung aus. Jeder Fortsetzungsversuch verwendet die bestehende Agenten-ID, damit bereits abgeschlossene Arbeit nicht wiederholt wird.
|
|
121
125
|
|
|
122
126
|
Rein lesende Sitzungsdiagnose
|
package/README.md
CHANGED
|
@@ -123,6 +123,10 @@ Ab BLUN King 9.1.118 bleibt der Sitzungszeitstempel im Systemprompt während ein
|
|
|
123
123
|
|
|
124
124
|
Ab BLUN King 9.1.119 enthält der bei jedem Modellschritt erneut gesendete Prompt nur noch eine kleine Auswahl häufig benötigter Skills. Alle übrigen registrierten Skills bleiben verfügbar und lassen sich über das Skill-Werkzeug anhand ihres Namens oder Zwecks suchen; der ausgewählte Skill wird anschließend bei Bedarf geladen. Dadurch sinkt die wiederholt übertragene Promptlast, ohne Skills zu löschen oder die vollständige Ansicht unter `/skills` einzuschränken.
|
|
125
125
|
|
|
126
|
+
Ab BLUN King 9.1.120 lagert die Konsole auch große ältere Assistentenantworten aus dem aktiven Modellkontext in private Sitzungsdateien aus. Der vollständige Gesprächsverlauf bleibt erhalten; im Modellkontext verbleiben eine begrenzte Vorschau und der Dateipfad. Die 20 neuesten Nachrichten sowie Assistentenantworten mit Werkzeugaufrufen bleiben unverändert, damit laufende Arbeit und Aufrufketten vollständig verfügbar sind.
|
|
127
|
+
|
|
128
|
+
Ab BLUN King 9.1.121 begrenzt die Modellprojektion auch die Gesamtgröße älterer, mittelgroßer Werkzeugergebnisse, die ausschließlich Text enthalten. Überschreiten Werkzeugergebnisse außerhalb der letzten 20 Nachrichten zusammen 12.000 Zeichen, archiviert King so viele der größten geeigneten Ergebnisse wie nötig und behält nur kompakte, lesbare Dateiverweise im Modellkontext. Der nur ergänzte Rohverlauf und die vollständigen Ergebnisse bleiben unverändert. Gemischte Medienergebnisse und die letzten 20 Nachrichten werden nicht angetastet. In einer gemessenen Sitzung von Fredrik wählte dieser Schnitt 12 ältere Ergebnisse aus und verringerte die projizierte alte Werkzeugausgabe um mindestens 63.444 Zeichen, also um etwa 15.861 geschätzte Token.
|
|
129
|
+
|
|
126
130
|
Ab BLUN King 9.1.99 werden auch direkt aufeinanderfolgende reine Textergebnisse als Stapel betrachtet. Enthalten sie zusammen mehr als 12.000 Zeichen, obwohl kein einzelnes Ergebnis diese Grenze überschreitet, speichert King so viele der größten geeigneten Ergebnisse wie nötig in privaten Dateien. In der Modellprojektion verbleiben lesbare Verweise. Der unveränderte Sitzungsrohverlauf bleibt vollständig erhalten. Gemischte Medienergebnisse, einzelne Ergebnisse unter 3.000 Zeichen und Stapel bis einschließlich 12.000 Zeichen bleiben unverändert. Beispiel: Bei zwei Suchergebnissen mit 7.000 und 6.000 Zeichen wird das größere Ergebnis privat gespeichert; Anfang, Ende, ausgelassene Zeichenzahl und `output_path` bleiben für den Agenten sichtbar.
|
|
127
131
|
|
|
128
132
|
Ab BLUN King 9.1.100 beendet eine begrenzte Grep-Inhaltssuche ripgrep, sobald der Versatz, die angeforderten Zeilen und eine zusätzliche Vorschauzeile vollständig vorliegen. Die Vorschauzeile belegt, ob eine weitere Seite existiert, ohne vorher bis zu 10 MB einzulesen. Unbegrenzte Suchen, Trefferzählungen und nach Änderungszeit sortierte Dateilisten laufen weiterhin vollständig durch.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ASSISTANT_MESSAGE_MAX_CHARS = 4_000;
|
|
4
|
+
const ASSISTANT_MESSAGE_KEEP_RECENT_MESSAGES = 20;
|
|
5
|
+
const ASSISTANT_MESSAGE_PREVIEW_LINES = 5;
|
|
6
|
+
const ASSISTANT_MESSAGE_PREVIEW_LINE_CHARS = 1_000;
|
|
7
|
+
const ASSISTANT_MESSAGE_OFFLOAD_MARKER = '[Assistant message offloaded]';
|
|
8
|
+
|
|
9
|
+
function shouldOffloadHistoricalAssistantMessage(options = {}) {
|
|
10
|
+
const historyIndex = Number(options.historyIndex);
|
|
11
|
+
const historyLength = Number(options.historyLength);
|
|
12
|
+
const textChars = Number(options.textChars);
|
|
13
|
+
if (
|
|
14
|
+
!Number.isSafeInteger(historyIndex)
|
|
15
|
+
|| !Number.isSafeInteger(historyLength)
|
|
16
|
+
|| !Number.isSafeInteger(textChars)
|
|
17
|
+
|| historyIndex < 0
|
|
18
|
+
|| historyLength < 0
|
|
19
|
+
|| historyIndex >= historyLength
|
|
20
|
+
) return false;
|
|
21
|
+
const cutoff = Math.max(0, historyLength - ASSISTANT_MESSAGE_KEEP_RECENT_MESSAGES);
|
|
22
|
+
return historyIndex < cutoff && textChars > ASSISTANT_MESSAGE_MAX_CHARS;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createAssistantMessagePreview(text) {
|
|
26
|
+
const lines = String(text).split(/\r?\n/u);
|
|
27
|
+
const render = (line, index) => `${index}: ${line.slice(0, ASSISTANT_MESSAGE_PREVIEW_LINE_CHARS)}`;
|
|
28
|
+
if (lines.length <= ASSISTANT_MESSAGE_PREVIEW_LINES * 2) {
|
|
29
|
+
return lines.map((line, index) => render(line, index + 1)).join('\n');
|
|
30
|
+
}
|
|
31
|
+
const head = lines.slice(0, ASSISTANT_MESSAGE_PREVIEW_LINES)
|
|
32
|
+
.map((line, index) => render(line, index + 1));
|
|
33
|
+
const tailStart = lines.length - ASSISTANT_MESSAGE_PREVIEW_LINES;
|
|
34
|
+
const tail = lines.slice(tailStart)
|
|
35
|
+
.map((line, index) => render(line, tailStart + index + 1));
|
|
36
|
+
return [
|
|
37
|
+
...head,
|
|
38
|
+
`... [${lines.length - ASSISTANT_MESSAGE_PREVIEW_LINES * 2} lines omitted] ...`,
|
|
39
|
+
...tail,
|
|
40
|
+
].join('\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
ASSISTANT_MESSAGE_KEEP_RECENT_MESSAGES,
|
|
45
|
+
ASSISTANT_MESSAGE_MAX_CHARS,
|
|
46
|
+
ASSISTANT_MESSAGE_OFFLOAD_MARKER,
|
|
47
|
+
ASSISTANT_MESSAGE_PREVIEW_LINES,
|
|
48
|
+
ASSISTANT_MESSAGE_PREVIEW_LINE_CHARS,
|
|
49
|
+
createAssistantMessagePreview,
|
|
50
|
+
shouldOffloadHistoricalAssistantMessage,
|
|
51
|
+
};
|
|
@@ -6,6 +6,10 @@ const TOOL_RESULT_OFFLOAD_MARKER = '[Tool result offloaded]';
|
|
|
6
6
|
const TOOL_RESULT_BATCH_MAX_CHARS = TOOL_RESULT_MAX_CHARS;
|
|
7
7
|
const TOOL_RESULT_BATCH_MIN_ITEM_CHARS = 3_000;
|
|
8
8
|
const TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS = 2_500;
|
|
9
|
+
const TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES = 20;
|
|
10
|
+
const TOOL_RESULT_HISTORICAL_MAX_CHARS = TOOL_RESULT_BATCH_MAX_CHARS;
|
|
11
|
+
const TOOL_RESULT_HISTORICAL_MIN_ITEM_CHARS = 600;
|
|
12
|
+
const TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS = 600;
|
|
9
13
|
|
|
10
14
|
function shouldOffloadToolResult(textLength) {
|
|
11
15
|
return Number.isFinite(textLength) && textLength > TOOL_RESULT_MAX_CHARS;
|
|
@@ -62,15 +66,50 @@ function selectToolResultBatchOffloads(textLengths) {
|
|
|
62
66
|
: [];
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
function selectHistoricalToolResultOffloads(textLengths) {
|
|
70
|
+
if (!Array.isArray(textLengths) || textLengths.length === 0) return [];
|
|
71
|
+
|
|
72
|
+
const normalized = textLengths.map((length) => (
|
|
73
|
+
Number.isFinite(length) && length > 0 ? Math.floor(length) : 0
|
|
74
|
+
));
|
|
75
|
+
const totalChars = normalized.reduce((total, length) => total + length, 0);
|
|
76
|
+
if (totalChars <= TOOL_RESULT_HISTORICAL_MAX_CHARS) return [];
|
|
77
|
+
|
|
78
|
+
let projectedChars = totalChars;
|
|
79
|
+
const selected = [];
|
|
80
|
+
const candidates = normalized
|
|
81
|
+
.map((length, index) => ({ index, length }))
|
|
82
|
+
.filter(({ length }) => (
|
|
83
|
+
length >= TOOL_RESULT_HISTORICAL_MIN_ITEM_CHARS
|
|
84
|
+
&& length > TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS
|
|
85
|
+
))
|
|
86
|
+
.sort((left, right) => right.length - left.length || left.index - right.index);
|
|
87
|
+
|
|
88
|
+
for (const candidate of candidates) {
|
|
89
|
+
if (projectedChars <= TOOL_RESULT_HISTORICAL_MAX_CHARS) break;
|
|
90
|
+
projectedChars -= candidate.length - TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS;
|
|
91
|
+
selected.push(candidate.index);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return projectedChars <= TOOL_RESULT_HISTORICAL_MAX_CHARS
|
|
95
|
+
? selected.sort((left, right) => left - right)
|
|
96
|
+
: [];
|
|
97
|
+
}
|
|
98
|
+
|
|
65
99
|
module.exports = {
|
|
66
100
|
TOOL_RESULT_BATCH_MAX_CHARS,
|
|
67
101
|
TOOL_RESULT_BATCH_MIN_ITEM_CHARS,
|
|
68
102
|
TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS,
|
|
103
|
+
TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES,
|
|
104
|
+
TOOL_RESULT_HISTORICAL_MAX_CHARS,
|
|
105
|
+
TOOL_RESULT_HISTORICAL_MIN_ITEM_CHARS,
|
|
106
|
+
TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS,
|
|
69
107
|
TOOL_RESULT_MAX_CHARS,
|
|
70
108
|
TOOL_RESULT_PREVIEW_CHARS,
|
|
71
109
|
TOOL_RESULT_OFFLOAD_MARKER,
|
|
72
110
|
createToolResultPreview,
|
|
73
111
|
isPersistedToolResultReference,
|
|
112
|
+
selectHistoricalToolResultOffloads,
|
|
74
113
|
selectToolResultBatchOffloads,
|
|
75
114
|
shouldOffloadToolResult,
|
|
76
115
|
};
|
package/blun.mjs
CHANGED
|
@@ -78799,6 +78799,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78799
78799
|
this.agent.microCompaction.reset(this._history.length);
|
|
78800
78800
|
this.agent.toolResultBatchOffload.reset(this._history);
|
|
78801
78801
|
this.agent.userMessageOffload.reset(this._history);
|
|
78802
|
+
this.agent.assistantMessageOffload.reset(this._history);
|
|
78802
78803
|
this.agent.emitStatusUpdated();
|
|
78803
78804
|
return removedMessages.size;
|
|
78804
78805
|
}
|
|
@@ -78814,6 +78815,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78814
78815
|
this.agent.microCompaction.reset();
|
|
78815
78816
|
this.agent.toolResultBatchOffload.clear();
|
|
78816
78817
|
this.agent.userMessageOffload.clear();
|
|
78818
|
+
this.agent.assistantMessageOffload.clear();
|
|
78817
78819
|
this.agent.injection.onContextClear();
|
|
78818
78820
|
this.agent.emitStatusUpdated();
|
|
78819
78821
|
}
|
|
@@ -78854,6 +78856,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78854
78856
|
this.agent.microCompaction.reset(this._history.length);
|
|
78855
78857
|
this.agent.toolResultBatchOffload.reset(this._history);
|
|
78856
78858
|
this.agent.userMessageOffload.reset(this._history);
|
|
78859
|
+
this.agent.assistantMessageOffload.reset(this._history);
|
|
78857
78860
|
this.agent.emitStatusUpdated();
|
|
78858
78861
|
if (!this.agent.records.restoring && (stoppedAtBoundary || removedUserCount < count)) throw new BlunError(ErrorCodes.REQUEST_INVALID, formatUndoUnavailableMessage(count, removedUserCount, stoppedAtBoundary), { details: {
|
|
78859
78862
|
reason: "undo_limit",
|
|
@@ -78934,6 +78937,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78934
78937
|
this.agent.microCompaction.reset();
|
|
78935
78938
|
this.agent.toolResultBatchOffload.reset(this._history);
|
|
78936
78939
|
this.agent.userMessageOffload.reset(this._history);
|
|
78940
|
+
this.agent.assistantMessageOffload.reset(this._history);
|
|
78937
78941
|
this.agent.injection.onContextCompacted();
|
|
78938
78942
|
this.agent.emitStatusUpdated();
|
|
78939
78943
|
return result;
|
|
@@ -78956,7 +78960,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78956
78960
|
}
|
|
78957
78961
|
project(messages, options) {
|
|
78958
78962
|
const anomalies = [];
|
|
78959
|
-
const result = project(this.agent.userMessageOffload.compact(this.agent.microCompaction.compact(this.agent.toolResultBatchOffload.compact(dedupeRepeatedInjections(messages)))), {
|
|
78963
|
+
const result = project(this.agent.assistantMessageOffload.compact(this.agent.userMessageOffload.compact(this.agent.microCompaction.compact(this.agent.toolResultBatchOffload.compact(dedupeRepeatedInjections(messages))))), {
|
|
78960
78964
|
...options,
|
|
78961
78965
|
onAnomaly: (anomaly) => {
|
|
78962
78966
|
anomalies.push(anomaly);
|
|
@@ -234883,6 +234887,9 @@ function restoreAgentRecord(agent, input) {
|
|
|
234883
234887
|
case "user_message_offload.apply":
|
|
234884
234888
|
agent.userMessageOffload.apply(input.replacement);
|
|
234885
234889
|
return;
|
|
234890
|
+
case "assistant_message_offload.apply":
|
|
234891
|
+
agent.assistantMessageOffload.apply(input.replacement);
|
|
234892
|
+
return;
|
|
234886
234893
|
case "plan_mode.enter":
|
|
234887
234894
|
agent.planMode.restoreEnter(input);
|
|
234888
234895
|
return;
|
|
@@ -260322,10 +260329,53 @@ function renderReadSourceReference(toolName, toolCallId, text, outputPath) {
|
|
|
260322
260329
|
lines.push("", "[preview: head and tail]", createToolResultPreview(text));
|
|
260323
260330
|
return lines.join("\n");
|
|
260324
260331
|
}
|
|
260332
|
+
async function persistHistoricalToolResultForModel(options, knownText) {
|
|
260333
|
+
const text = knownText ?? persistableToolResultText(options.result.output);
|
|
260334
|
+
if (text === void 0 || options.result.truncated === true) return options.result;
|
|
260335
|
+
const readSourcePath = reusableReadSourcePath(options);
|
|
260336
|
+
let outputPath = readSourcePath;
|
|
260337
|
+
let storageMode = "original_file";
|
|
260338
|
+
if (outputPath === void 0) {
|
|
260339
|
+
if (options.homedir === void 0) return options.result;
|
|
260340
|
+
outputPath = await saveToolResult({
|
|
260341
|
+
homedir: options.homedir,
|
|
260342
|
+
toolName: options.toolName,
|
|
260343
|
+
toolCallId: options.toolCallId
|
|
260344
|
+
}, text);
|
|
260345
|
+
storageMode = "private_archive";
|
|
260346
|
+
}
|
|
260347
|
+
if (outputPath === void 0) return options.result;
|
|
260348
|
+
options.telemetry?.track("tool_result_historical_offloaded", {
|
|
260349
|
+
...buildToolResultOffloadTelemetry({
|
|
260350
|
+
toolName: options.toolName,
|
|
260351
|
+
text,
|
|
260352
|
+
previewChars: 0
|
|
260353
|
+
}),
|
|
260354
|
+
storage_mode: storageMode
|
|
260355
|
+
});
|
|
260356
|
+
return {
|
|
260357
|
+
...options.result,
|
|
260358
|
+
output: renderHistoricalToolResultReference(options.toolName, options.toolCallId, text, outputPath, storageMode),
|
|
260359
|
+
...(options.result.isError === true ? { isError: true } : {})
|
|
260360
|
+
};
|
|
260361
|
+
}
|
|
260362
|
+
function renderHistoricalToolResultReference(toolName, toolCallId, text, outputPath, storageMode) {
|
|
260363
|
+
return [
|
|
260364
|
+
TOOL_RESULT_OFFLOAD_MARKER,
|
|
260365
|
+
"An older tool result was archived to keep the active context fast.",
|
|
260366
|
+
`storage_mode: ${storageMode}`,
|
|
260367
|
+
`tool_name: ${toolName}`,
|
|
260368
|
+
`tool_call_id: ${toolCallId}`,
|
|
260369
|
+
`output_size_chars: ${String(text.length)}`,
|
|
260370
|
+
`output_size_bytes: ${String(Buffer.byteLength(text, "utf8"))}`,
|
|
260371
|
+
`output_path: ${outputPath}`,
|
|
260372
|
+
"next_step: Use Read with output_path if the full result is needed again."
|
|
260373
|
+
].join("\n");
|
|
260374
|
+
}
|
|
260325
260375
|
function safeToolResultFileStem(toolName, toolCallId) {
|
|
260326
260376
|
return `${toolName}-${toolCallId}`.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80) || "tool-result";
|
|
260327
260377
|
}
|
|
260328
|
-
var TOOL_RESULT_MAX_CHARS, TOOL_RESULT_PREVIEW_CHARS, TOOL_RESULT_OFFLOAD_MARKER, shouldOffloadToolResult, createToolResultPreview, selectToolResultBatchOffloads, buildToolResultOffloadTelemetry;
|
|
260378
|
+
var TOOL_RESULT_MAX_CHARS, TOOL_RESULT_PREVIEW_CHARS, TOOL_RESULT_OFFLOAD_MARKER, TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES, shouldOffloadToolResult, createToolResultPreview, selectToolResultBatchOffloads, selectHistoricalToolResultOffloads, buildToolResultOffloadTelemetry;
|
|
260329
260379
|
var init_tool_result_budget = __esmMin((() => {
|
|
260330
260380
|
init_dist$6();
|
|
260331
260381
|
const toolResultOffloadPolicy = createRequire(import.meta.url)("./bin/tool-result-offload-policy.cjs");
|
|
@@ -260333,9 +260383,11 @@ var init_tool_result_budget = __esmMin((() => {
|
|
|
260333
260383
|
TOOL_RESULT_MAX_CHARS = toolResultOffloadPolicy.TOOL_RESULT_MAX_CHARS;
|
|
260334
260384
|
TOOL_RESULT_PREVIEW_CHARS = toolResultOffloadPolicy.TOOL_RESULT_PREVIEW_CHARS;
|
|
260335
260385
|
TOOL_RESULT_OFFLOAD_MARKER = toolResultOffloadPolicy.TOOL_RESULT_OFFLOAD_MARKER;
|
|
260386
|
+
TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES = toolResultOffloadPolicy.TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES;
|
|
260336
260387
|
shouldOffloadToolResult = toolResultOffloadPolicy.shouldOffloadToolResult;
|
|
260337
260388
|
createToolResultPreview = toolResultOffloadPolicy.createToolResultPreview;
|
|
260338
260389
|
selectToolResultBatchOffloads = toolResultOffloadPolicy.selectToolResultBatchOffloads;
|
|
260390
|
+
selectHistoricalToolResultOffloads = toolResultOffloadPolicy.selectHistoricalToolResultOffloads;
|
|
260339
260391
|
}));
|
|
260340
260392
|
//#endregion
|
|
260341
260393
|
//#region ../../packages/agent-core/src/agent/turn/tool-result-batch-offload.ts
|
|
@@ -260353,7 +260405,20 @@ var ToolResultBatchOffload = class {
|
|
|
260353
260405
|
if (message?.role !== "tool") break;
|
|
260354
260406
|
tail.unshift(message);
|
|
260355
260407
|
}
|
|
260356
|
-
const
|
|
260408
|
+
const tailIds = new Set(tail.map((message) => message.toolCallId).filter((id) => id !== void 0));
|
|
260409
|
+
const recentStart = Math.max(0, history.length - TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES);
|
|
260410
|
+
const historical = history.slice(0, recentStart).filter((message) => message?.role === "tool" && !tailIds.has(message.toolCallId));
|
|
260411
|
+
const candidateBatches = [{
|
|
260412
|
+
messages: tail,
|
|
260413
|
+
historical: false,
|
|
260414
|
+
select: selectToolResultBatchOffloads
|
|
260415
|
+
}, {
|
|
260416
|
+
messages: historical,
|
|
260417
|
+
historical: true,
|
|
260418
|
+
select: selectHistoricalToolResultOffloads
|
|
260419
|
+
}].map((batch) => ({
|
|
260420
|
+
...batch,
|
|
260421
|
+
candidates: batch.messages.map((message) => {
|
|
260357
260422
|
if (message.toolCallId === void 0 || this.replacements.has(message.toolCallId) || isPersistedToolResultReference(message.content)) return;
|
|
260358
260423
|
const text = persistableToolResultText(message.content);
|
|
260359
260424
|
if (text === void 0) return;
|
|
@@ -260364,15 +260429,19 @@ var ToolResultBatchOffload = class {
|
|
|
260364
260429
|
toolName: toolCall?.name ?? "Tool",
|
|
260365
260430
|
toolArgs: this.toolArgsFor(toolCall)
|
|
260366
260431
|
};
|
|
260367
|
-
|
|
260368
|
-
|
|
260369
|
-
|
|
260432
|
+
}).filter((candidate) => candidate !== void 0)
|
|
260433
|
+
}));
|
|
260434
|
+
const selectedCandidates = candidateBatches.flatMap((batch) => batch.select(batch.candidates.map((candidate) => candidate.text.length)).map((index) => {
|
|
260435
|
+
const candidate = batch.candidates[index];
|
|
260436
|
+
return candidate === void 0 ? void 0 : { ...candidate, historical: batch.historical };
|
|
260437
|
+
}).filter((candidate) => candidate !== void 0));
|
|
260438
|
+
if (selectedCandidates.length === 0) return 0;
|
|
260370
260439
|
const replacements = [];
|
|
260371
260440
|
let charsBefore = 0;
|
|
260372
260441
|
let charsAfter = 0;
|
|
260373
|
-
|
|
260374
|
-
|
|
260375
|
-
|
|
260442
|
+
let historicalResultCount = 0;
|
|
260443
|
+
let tailResultCount = 0;
|
|
260444
|
+
for (const candidate of selectedCandidates) {
|
|
260376
260445
|
const original = {
|
|
260377
260446
|
output: candidate.message.content,
|
|
260378
260447
|
isError: candidate.message.isError
|
|
@@ -260386,7 +260455,7 @@ var ToolResultBatchOffload = class {
|
|
|
260386
260455
|
telemetry: this.agent.telemetry
|
|
260387
260456
|
};
|
|
260388
260457
|
const readSourcePath = reusableReadSourcePath(resultOptions);
|
|
260389
|
-
const persisted = readSourcePath === void 0 ? await persistToolResultForModel(resultOptions, candidate.text) : referenceReadSourceForModel(resultOptions, candidate.text, readSourcePath);
|
|
260458
|
+
const persisted = candidate.historical ? await persistHistoricalToolResultForModel(resultOptions, candidate.text) : readSourcePath === void 0 ? await persistToolResultForModel(resultOptions, candidate.text) : referenceReadSourceForModel(resultOptions, candidate.text, readSourcePath);
|
|
260390
260459
|
if (persisted === original || typeof persisted.output !== "string" || persisted.output.length >= candidate.text.length) continue;
|
|
260391
260460
|
const content = [{
|
|
260392
260461
|
type: "text",
|
|
@@ -260398,11 +260467,15 @@ var ToolResultBatchOffload = class {
|
|
|
260398
260467
|
});
|
|
260399
260468
|
charsBefore += candidate.text.length;
|
|
260400
260469
|
charsAfter += persisted.output.length;
|
|
260470
|
+
if (candidate.historical) historicalResultCount += 1;
|
|
260471
|
+
else tailResultCount += 1;
|
|
260401
260472
|
}
|
|
260402
260473
|
if (replacements.length === 0) return 0;
|
|
260403
260474
|
this.apply(replacements);
|
|
260404
260475
|
this.agent.telemetry.track("tool_result_batch_offloaded", {
|
|
260405
260476
|
result_count: replacements.length,
|
|
260477
|
+
historical_result_count: historicalResultCount,
|
|
260478
|
+
tail_result_count: tailResultCount,
|
|
260406
260479
|
chars_before: charsBefore,
|
|
260407
260480
|
chars_after: charsAfter,
|
|
260408
260481
|
chars_saved: charsBefore - charsAfter
|
|
@@ -260584,6 +260657,129 @@ var UserMessageOffload = class {
|
|
|
260584
260657
|
}
|
|
260585
260658
|
};
|
|
260586
260659
|
//#endregion
|
|
260660
|
+
//#region ../../packages/agent-core/src/agent/turn/assistant-message-offload.ts
|
|
260661
|
+
function persistableAssistantMessageText(message) {
|
|
260662
|
+
if (message?.role !== "assistant" || message.toolCalls.length > 0) return;
|
|
260663
|
+
const textParts = message.content.filter((part) => part.type === "text");
|
|
260664
|
+
if (textParts.length === 0) return;
|
|
260665
|
+
return textParts.map((part) => part.text).join("\n");
|
|
260666
|
+
}
|
|
260667
|
+
function assistantMessageOffloadHash(message, text) {
|
|
260668
|
+
return createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
260669
|
+
}
|
|
260670
|
+
async function saveAssistantMessage(homedir, text) {
|
|
260671
|
+
try {
|
|
260672
|
+
const dir = join$4(homedir, "conversation-history");
|
|
260673
|
+
await mkdir(dir, {
|
|
260674
|
+
recursive: true,
|
|
260675
|
+
mode: 448
|
|
260676
|
+
});
|
|
260677
|
+
const outputPath = join$4(dir, `assistant-message-${randomUUID()}.txt`);
|
|
260678
|
+
await writeFile(outputPath, text, {
|
|
260679
|
+
encoding: "utf8",
|
|
260680
|
+
flag: "wx",
|
|
260681
|
+
mode: 384
|
|
260682
|
+
});
|
|
260683
|
+
return outputPath;
|
|
260684
|
+
} catch {
|
|
260685
|
+
return;
|
|
260686
|
+
}
|
|
260687
|
+
}
|
|
260688
|
+
function renderPersistedAssistantMessage(text, outputPath) {
|
|
260689
|
+
return [
|
|
260690
|
+
ASSISTANT_MESSAGE_OFFLOAD_MARKER,
|
|
260691
|
+
"The complete original was archived to keep the active context fast and remains available on disk.",
|
|
260692
|
+
`message_size_chars: ${String(text.length)}`,
|
|
260693
|
+
`message_size_bytes: ${String(Buffer.byteLength(text, "utf8"))}`,
|
|
260694
|
+
`output_path: ${outputPath}`,
|
|
260695
|
+
"next_step: Use Read with output_path to inspect the complete message in pages.",
|
|
260696
|
+
"",
|
|
260697
|
+
"[preview: head and tail]",
|
|
260698
|
+
createAssistantMessagePreview(text)
|
|
260699
|
+
].join("\n");
|
|
260700
|
+
}
|
|
260701
|
+
var ASSISTANT_MESSAGE_MAX_CHARS, ASSISTANT_MESSAGE_OFFLOAD_MARKER, shouldOffloadHistoricalAssistantMessage, createAssistantMessagePreview;
|
|
260702
|
+
var init_assistant_message_offload = __esmMin((() => {
|
|
260703
|
+
const policy = createRequire(import.meta.url)("./bin/assistant-message-offload-policy.cjs");
|
|
260704
|
+
ASSISTANT_MESSAGE_MAX_CHARS = policy.ASSISTANT_MESSAGE_MAX_CHARS;
|
|
260705
|
+
ASSISTANT_MESSAGE_OFFLOAD_MARKER = policy.ASSISTANT_MESSAGE_OFFLOAD_MARKER;
|
|
260706
|
+
shouldOffloadHistoricalAssistantMessage = policy.shouldOffloadHistoricalAssistantMessage;
|
|
260707
|
+
createAssistantMessagePreview = policy.createAssistantMessagePreview;
|
|
260708
|
+
}));
|
|
260709
|
+
var AssistantMessageOffload = class {
|
|
260710
|
+
agent;
|
|
260711
|
+
replacements = /* @__PURE__ */ new Map();
|
|
260712
|
+
constructor(agent) {
|
|
260713
|
+
this.agent = agent;
|
|
260714
|
+
}
|
|
260715
|
+
async detect() {
|
|
260716
|
+
const history = this.agent.context.history;
|
|
260717
|
+
if (this.agent.homedir === void 0) return 0;
|
|
260718
|
+
let offloaded = 0;
|
|
260719
|
+
for (let historyIndex = 0; historyIndex < history.length; historyIndex++) {
|
|
260720
|
+
const message = history[historyIndex];
|
|
260721
|
+
const text = persistableAssistantMessageText(message);
|
|
260722
|
+
if (text === void 0 || !shouldOffloadHistoricalAssistantMessage({
|
|
260723
|
+
historyIndex,
|
|
260724
|
+
historyLength: history.length,
|
|
260725
|
+
textChars: text.length
|
|
260726
|
+
})) continue;
|
|
260727
|
+
const messageHash = assistantMessageOffloadHash(message, text);
|
|
260728
|
+
if (this.replacements.has(messageHash)) continue;
|
|
260729
|
+
const outputPath = await saveAssistantMessage(this.agent.homedir, text);
|
|
260730
|
+
if (outputPath === void 0) continue;
|
|
260731
|
+
const replacementText = renderPersistedAssistantMessage(text, outputPath);
|
|
260732
|
+
const content = [{
|
|
260733
|
+
type: "text",
|
|
260734
|
+
text: replacementText
|
|
260735
|
+
}, ...message.content.filter((part) => part.type !== "text")];
|
|
260736
|
+
this.apply({
|
|
260737
|
+
messageHash,
|
|
260738
|
+
content,
|
|
260739
|
+
outputPath
|
|
260740
|
+
});
|
|
260741
|
+
this.agent.telemetry.track("assistant_message_offloaded", {
|
|
260742
|
+
chars_before: text.length,
|
|
260743
|
+
chars_after: replacementText.length,
|
|
260744
|
+
chars_saved: text.length - replacementText.length,
|
|
260745
|
+
non_text_part_count: content.length - 1,
|
|
260746
|
+
reason: "historical_oversize"
|
|
260747
|
+
});
|
|
260748
|
+
offloaded++;
|
|
260749
|
+
}
|
|
260750
|
+
return offloaded;
|
|
260751
|
+
}
|
|
260752
|
+
apply(replacement) {
|
|
260753
|
+
this.replacements.set(replacement.messageHash, replacement);
|
|
260754
|
+
this.agent.records.logRecord({
|
|
260755
|
+
type: "assistant_message_offload.apply",
|
|
260756
|
+
replacement
|
|
260757
|
+
});
|
|
260758
|
+
}
|
|
260759
|
+
compact(messages) {
|
|
260760
|
+
if (this.replacements.size === 0) return messages;
|
|
260761
|
+
return messages.map((message) => {
|
|
260762
|
+
const text = persistableAssistantMessageText(message);
|
|
260763
|
+
if (text === void 0) return message;
|
|
260764
|
+
const replacement = this.replacements.get(assistantMessageOffloadHash(message, text));
|
|
260765
|
+
return replacement === void 0 ? message : {
|
|
260766
|
+
...message,
|
|
260767
|
+
content: replacement.content
|
|
260768
|
+
};
|
|
260769
|
+
});
|
|
260770
|
+
}
|
|
260771
|
+
reset(history = this.agent.context?.history ?? []) {
|
|
260772
|
+
const activeHashes = new Set(history.map((message) => {
|
|
260773
|
+
const text = persistableAssistantMessageText(message);
|
|
260774
|
+
return text === void 0 ? void 0 : assistantMessageOffloadHash(message, text);
|
|
260775
|
+
}).filter((hash) => hash !== void 0));
|
|
260776
|
+
for (const messageHash of this.replacements.keys()) if (!activeHashes.has(messageHash)) this.replacements.delete(messageHash);
|
|
260777
|
+
}
|
|
260778
|
+
clear() {
|
|
260779
|
+
this.replacements.clear();
|
|
260780
|
+
}
|
|
260781
|
+
};
|
|
260782
|
+
//#endregion
|
|
260587
260783
|
//#region ../../packages/agent-core/src/agent/turn/index.ts
|
|
260588
260784
|
function blunExtractText(content) {
|
|
260589
260785
|
if (typeof content === "string") return content;
|
|
@@ -260920,6 +261116,7 @@ var init_turn = __esmMin((() => {
|
|
|
260920
261116
|
init_tool_dedup();
|
|
260921
261117
|
init_tool_result_budget();
|
|
260922
261118
|
init_user_message_offload();
|
|
261119
|
+
init_assistant_message_offload();
|
|
260923
261120
|
({ CORE_TOOL_NAMES: BLUN_CORE_TOOL_NAMES, createDeferredToolLoader, toolSchemaBudgetTokens } = createRequire(import.meta.url)("./bin/turn-tool-performance-policy.cjs"));
|
|
260924
261121
|
BLUN_LEAN_TOOL_NAMES = new Set(BLUN_CORE_TOOL_NAMES);
|
|
260925
261122
|
BLUN_ATTACHMENT_MARKER_RE = /\b(?:attachment_file_id|telegram-anhang|telegram attachment)\b/i;
|
|
@@ -261555,6 +261752,7 @@ var init_turn = __esmMin((() => {
|
|
|
261555
261752
|
await this.agent.records.flush();
|
|
261556
261753
|
this.agent.microCompaction.detect();
|
|
261557
261754
|
await this.agent.userMessageOffload.detect();
|
|
261755
|
+
await this.agent.assistantMessageOffload.detect();
|
|
261558
261756
|
await this.agent.injection.inject();
|
|
261559
261757
|
stepSignal.throwIfAborted();
|
|
261560
261758
|
deduper.beginStep();
|
|
@@ -264332,6 +264530,7 @@ var init_agent = __esmMin((() => {
|
|
|
264332
264530
|
fullCompaction;
|
|
264333
264531
|
microCompaction;
|
|
264334
264532
|
toolResultBatchOffload;
|
|
264533
|
+
assistantMessageOffload;
|
|
264335
264534
|
context;
|
|
264336
264535
|
config;
|
|
264337
264536
|
turn;
|
|
@@ -264392,6 +264591,7 @@ var init_agent = __esmMin((() => {
|
|
|
264392
264591
|
this.microCompaction = new MicroCompaction(this, options.microCompaction);
|
|
264393
264592
|
this.toolResultBatchOffload = new ToolResultBatchOffload(this);
|
|
264394
264593
|
this.userMessageOffload = new UserMessageOffload(this);
|
|
264594
|
+
this.assistantMessageOffload = new AssistantMessageOffload(this);
|
|
264395
264595
|
this.context = new ContextMemory(this);
|
|
264396
264596
|
this.config = new ConfigState(this);
|
|
264397
264597
|
this.turn = new TurnFlow(this);
|