@code-yeongyu/senpi 2026.5.14 → 2026.5.15-3
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 +1110 -1175
- package/README.md +1 -2
- package/dist/core/agent-session.d.ts +9 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +109 -7
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/dynamic-prompt/verification.d.ts +31 -0
- package/dist/core/dynamic-prompt/verification.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/verification.js +41 -0
- package/dist/core/dynamic-prompt/verification.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/context-reduction.d.ts +97 -0
- package/dist/core/extensions/builtin/compaction/context-reduction.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/context-reduction.js +420 -0
- package/dist/core/extensions/builtin/compaction/context-reduction.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/index.js +168 -31
- package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +197 -0
- package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/openai-remote.js +690 -0
- package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/prompts.d.ts +3 -3
- package/dist/core/extensions/builtin/compaction/prompts.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/prompts.js +0 -22
- package/dist/core/extensions/builtin/compaction/prompts.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts +4 -0
- package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js +48 -0
- package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/speculative.d.ts +3 -1
- package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/speculative.js +82 -33
- package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts +8 -0
- package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/todo-bridge.js +12 -6
- package/dist/core/extensions/builtin/compaction/todo-bridge.js.map +1 -1
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +0 -2
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/openai-web-search/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/openai-web-search/index.js +26 -1
- package/dist/core/extensions/builtin/openai-web-search/index.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/prompt.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/prompt.js +0 -5
- package/dist/core/extensions/builtin/permission-system/prompt.js.map +1 -1
- package/dist/core/extensions/builtin/system-messages.d.ts +7 -7
- package/dist/core/extensions/builtin/system-messages.d.ts.map +1 -1
- package/dist/core/extensions/builtin/system-messages.js +10 -10
- package/dist/core/extensions/builtin/system-messages.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/continuation/prompt.d.ts +1 -1
- package/dist/core/extensions/builtin/todotools/continuation/prompt.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/continuation/prompt.js +1 -1
- package/dist/core/extensions/builtin/todotools/continuation/prompt.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/state.d.ts +1 -1
- package/dist/core/extensions/builtin/todotools/state.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/state.js +1 -1
- package/dist/core/extensions/builtin/todotools/state.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/system-messages.d.ts +3 -3
- package/dist/core/extensions/builtin/todotools/system-messages.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/system-messages.js +6 -6
- package/dist/core/extensions/builtin/todotools/system-messages.js.map +1 -1
- package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts +1 -1
- package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/tool-pair-guard/index.js +8 -4
- package/dist/core/extensions/builtin/tool-pair-guard/index.js.map +1 -1
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js +89 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js +122 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +2 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +18 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +22 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/messages.d.ts +3 -3
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +5 -10
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +1 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +7 -22
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +0 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/thinking-levels.d.ts +6 -0
- package/dist/core/thinking-levels.d.ts.map +1 -0
- package/dist/core/thinking-levels.js +36 -0
- package/dist/core/thinking-levels.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +15 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +20 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +3 -1
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +137 -49
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/working-status.d.ts +15 -0
- package/dist/modes/interactive/working-status.d.ts.map +1 -0
- package/dist/modes/interactive/working-status.js +60 -0
- package/dist/modes/interactive/working-status.js.map +1 -0
- package/docs/extensions.md +0 -1
- package/docs/index.md +0 -1
- package/docs/sdk.md +0 -1
- package/docs/settings.md +1 -29
- package/docs/termux.md +2 -2
- package/docs/usage.md +1 -1
- package/examples/README.md +1 -1
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/overlay-qa-tests.ts +1 -1
- package/package.json +4 -4
- package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts +0 -10
- package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/cancel-tool.js +0 -109
- package/dist/core/extensions/builtin/background-task/cancel-tool.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/index.d.ts +0 -3
- package/dist/core/extensions/builtin/background-task/index.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/index.js +0 -207
- package/dist/core/extensions/builtin/background-task/index.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/manager.d.ts +0 -17
- package/dist/core/extensions/builtin/background-task/manager.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/manager.js +0 -114
- package/dist/core/extensions/builtin/background-task/manager.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/notification.d.ts +0 -22
- package/dist/core/extensions/builtin/background-task/notification.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/notification.js +0 -105
- package/dist/core/extensions/builtin/background-task/notification.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/output-tool.d.ts +0 -11
- package/dist/core/extensions/builtin/background-task/output-tool.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/output-tool.js +0 -127
- package/dist/core/extensions/builtin/background-task/output-tool.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/spawner.d.ts +0 -8
- package/dist/core/extensions/builtin/background-task/spawner.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/spawner.js +0 -207
- package/dist/core/extensions/builtin/background-task/spawner.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/task-tool.d.ts +0 -20
- package/dist/core/extensions/builtin/background-task/task-tool.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/task-tool.js +0 -302
- package/dist/core/extensions/builtin/background-task/task-tool.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/types.d.ts +0 -72
- package/dist/core/extensions/builtin/background-task/types.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/types.js +0 -32
- package/dist/core/extensions/builtin/background-task/types.js.map +0 -1
- package/docs/agents.md +0 -348
- package/examples/extensions/subagent/README.md +0 -172
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/agents.ts +0 -126
- package/examples/extensions/subagent/index.ts +0 -987
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic, no-LLM context reductions applied before compaction summarization.
|
|
3
|
+
*
|
|
4
|
+
* Ported from plugsuits' `context-collapse` and `micro-compact` patterns and
|
|
5
|
+
* adapted to the senpi `AgentMessage` shape. Three independent transforms:
|
|
6
|
+
*
|
|
7
|
+
* 1. {@link collapseConsecutiveToolResults} — runs of same-kind read/grep/shell
|
|
8
|
+
* tool result payloads are replaced with a single one-line label so the
|
|
9
|
+
* summarizer pays for the shape, not for the bytes.
|
|
10
|
+
* 2. {@link microCompactAssistantText} — older long assistant text answers are
|
|
11
|
+
* truncated and tagged with a `[response shrunk]` marker.
|
|
12
|
+
* 3. {@link clearOldToolResults} — keep the last N tool results in full, replace
|
|
13
|
+
* older clearable tool result content with `[tool result cleared]`.
|
|
14
|
+
*
|
|
15
|
+
* Each transform is pure (`messages` in → new array out, no in-place mutation
|
|
16
|
+
* beyond freshly cloned messages) and returns aggregated token-savings stats.
|
|
17
|
+
*
|
|
18
|
+
* {@link reduceContextMessages} composes the three transforms in order.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_READ_TOOL_NAMES = ["read", "Read", "read_file"];
|
|
21
|
+
const DEFAULT_SEARCH_TOOL_NAMES = ["grep", "Grep", "glob", "Glob"];
|
|
22
|
+
const DEFAULT_SHELL_TOOL_NAMES = ["bash", "Bash", "shell", "shell_execute"];
|
|
23
|
+
const DEFAULT_CLEARABLE_TOOL_NAMES = [
|
|
24
|
+
"read",
|
|
25
|
+
"Read",
|
|
26
|
+
"read_file",
|
|
27
|
+
"write",
|
|
28
|
+
"Write",
|
|
29
|
+
"edit",
|
|
30
|
+
"Edit",
|
|
31
|
+
"grep",
|
|
32
|
+
"Grep",
|
|
33
|
+
"glob",
|
|
34
|
+
"Glob",
|
|
35
|
+
"bash",
|
|
36
|
+
"Bash",
|
|
37
|
+
"shell",
|
|
38
|
+
];
|
|
39
|
+
const DEFAULT_MIN_GROUP_SIZE = 2;
|
|
40
|
+
const DEFAULT_PROTECT_RECENT_MESSAGES = 5;
|
|
41
|
+
const DEFAULT_PROTECT_RECENT_TOKENS = 2000;
|
|
42
|
+
const DEFAULT_MAX_ASSISTANT_TEXT_TOKENS = 500;
|
|
43
|
+
const DEFAULT_MIN_SAVINGS_TOKENS = 100;
|
|
44
|
+
const DEFAULT_KEEP_RECENT_TOOL_RESULTS = 3;
|
|
45
|
+
const DEFAULT_CLEARED_PLACEHOLDER = "[tool result cleared]";
|
|
46
|
+
const DEFAULT_REPLACEMENT_TEMPLATE = "[response shrunk — {original_tokens} → {shrunk_tokens} tokens]";
|
|
47
|
+
const MAX_HINTS_IN_LABEL = 5;
|
|
48
|
+
const MAX_HINT_LENGTH = 80;
|
|
49
|
+
const SHRUNK_RESPONSE_RATIO = 0.3;
|
|
50
|
+
/**
|
|
51
|
+
* Default options passed to {@link reduceContextMessages} when the builtin
|
|
52
|
+
* compaction extension's `context` hook decides to run a reduction pass.
|
|
53
|
+
*
|
|
54
|
+
* Each value is chosen to be strictly more conservative than the corresponding
|
|
55
|
+
* plugsuits default — protect more of the recent tail, raise the per-message
|
|
56
|
+
* shrink threshold, and keep more recent tool results intact — so a single
|
|
57
|
+
* shared default is safe to apply across normal coding sessions without making
|
|
58
|
+
* targeted reductions less effective.
|
|
59
|
+
*/
|
|
60
|
+
export const BUILTIN_CONTEXT_REDUCTION_OPTIONS = {
|
|
61
|
+
collapse: {
|
|
62
|
+
minGroupSize: DEFAULT_MIN_GROUP_SIZE,
|
|
63
|
+
protectRecentMessages: DEFAULT_PROTECT_RECENT_MESSAGES,
|
|
64
|
+
},
|
|
65
|
+
shrinkAssistant: {
|
|
66
|
+
protectRecentTokens: 3000,
|
|
67
|
+
maxAssistantTextTokens: 800,
|
|
68
|
+
minSavingsTokens: DEFAULT_MIN_SAVINGS_TOKENS,
|
|
69
|
+
},
|
|
70
|
+
clearToolResults: {
|
|
71
|
+
keepRecent: 6,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
export const BUILTIN_CONTEXT_REDUCTION_GATE_RATIO = 0.5;
|
|
75
|
+
export function shouldApplyContextReduction(input) {
|
|
76
|
+
const gate = input.gateRatio ?? BUILTIN_CONTEXT_REDUCTION_GATE_RATIO;
|
|
77
|
+
if (input.isProviderNativeCompactionPath === true)
|
|
78
|
+
return false;
|
|
79
|
+
if (input.usageTokens === null)
|
|
80
|
+
return false;
|
|
81
|
+
if (input.contextWindow <= 0)
|
|
82
|
+
return false;
|
|
83
|
+
return input.usageTokens >= input.contextWindow * gate;
|
|
84
|
+
}
|
|
85
|
+
function approxTextTokens(text) {
|
|
86
|
+
if (!text)
|
|
87
|
+
return 0;
|
|
88
|
+
return Math.ceil(text.length / 4);
|
|
89
|
+
}
|
|
90
|
+
function extractContentText(content) {
|
|
91
|
+
if (!Array.isArray(content))
|
|
92
|
+
return "";
|
|
93
|
+
let out = "";
|
|
94
|
+
for (const part of content) {
|
|
95
|
+
if (part.type === "text")
|
|
96
|
+
out += part.text;
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
function extractMessageText(message) {
|
|
101
|
+
if (message.role === "user") {
|
|
102
|
+
if (typeof message.content === "string")
|
|
103
|
+
return message.content;
|
|
104
|
+
return extractContentText(message.content);
|
|
105
|
+
}
|
|
106
|
+
if (message.role === "assistant") {
|
|
107
|
+
let out = "";
|
|
108
|
+
for (const block of message.content) {
|
|
109
|
+
if (block.type === "text")
|
|
110
|
+
out += block.text;
|
|
111
|
+
else if (block.type === "toolCall")
|
|
112
|
+
out += `${block.name} ${JSON.stringify(block.arguments)}`;
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
if (message.role === "toolResult") {
|
|
117
|
+
return extractContentText(message.content);
|
|
118
|
+
}
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
function classifyTool(name, sets) {
|
|
122
|
+
if (sets.read.has(name))
|
|
123
|
+
return "read";
|
|
124
|
+
if (sets.search.has(name))
|
|
125
|
+
return "search";
|
|
126
|
+
if (sets.shell.has(name))
|
|
127
|
+
return "shell";
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
function getFirstToolCallFromAssistant(message) {
|
|
131
|
+
if (message.role !== "assistant")
|
|
132
|
+
return null;
|
|
133
|
+
for (const block of message.content) {
|
|
134
|
+
if (block.type === "toolCall") {
|
|
135
|
+
return { id: block.id, name: block.name, args: block.arguments };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function truncateHint(value) {
|
|
141
|
+
if (value.length <= MAX_HINT_LENGTH)
|
|
142
|
+
return value;
|
|
143
|
+
return `${value.slice(0, MAX_HINT_LENGTH - 1)}…`;
|
|
144
|
+
}
|
|
145
|
+
function extractHint(type, args) {
|
|
146
|
+
if (type === "read") {
|
|
147
|
+
const path = args.path ?? args.file_path ?? args.filePath;
|
|
148
|
+
if (typeof path === "string" && path.length > 0)
|
|
149
|
+
return truncateHint(path);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
if (type === "search") {
|
|
153
|
+
const path = typeof args.path === "string" ? args.path : undefined;
|
|
154
|
+
const pattern = (typeof args.pattern === "string" && args.pattern) ||
|
|
155
|
+
(typeof args.glob === "string" && args.glob) ||
|
|
156
|
+
(typeof args.query === "string" && args.query) ||
|
|
157
|
+
undefined;
|
|
158
|
+
if (path && pattern)
|
|
159
|
+
return truncateHint(`${path}:${pattern}`);
|
|
160
|
+
if (path)
|
|
161
|
+
return truncateHint(path);
|
|
162
|
+
if (pattern)
|
|
163
|
+
return truncateHint(pattern);
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const command = args.command ?? args.cmd;
|
|
167
|
+
if (typeof command === "string" && command.length > 0)
|
|
168
|
+
return truncateHint(command);
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
function buildGroupLabel(type, operations) {
|
|
172
|
+
const hints = [];
|
|
173
|
+
for (const op of operations) {
|
|
174
|
+
if (op.hint && hints.length < MAX_HINTS_IN_LABEL)
|
|
175
|
+
hints.push(op.hint);
|
|
176
|
+
}
|
|
177
|
+
const noun = type === "read" ? "read results" : type === "search" ? "search results" : "shell results";
|
|
178
|
+
if (hints.length === 0)
|
|
179
|
+
return `[${operations.length} ${noun}]`;
|
|
180
|
+
const more = operations.length - hints.length;
|
|
181
|
+
const moreSuffix = more > 0 ? `, and ${more} more` : "";
|
|
182
|
+
return `[${operations.length} ${noun}: ${hints.join(", ")}${moreSuffix}]`;
|
|
183
|
+
}
|
|
184
|
+
function collectCollapsibleOperations(messages, collapseLimit, sets) {
|
|
185
|
+
const operations = [];
|
|
186
|
+
let i = 0;
|
|
187
|
+
while (i < collapseLimit) {
|
|
188
|
+
const assistant = messages[i];
|
|
189
|
+
const call = getFirstToolCallFromAssistant(assistant);
|
|
190
|
+
if (!call) {
|
|
191
|
+
i += 1;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const next = messages[i + 1];
|
|
195
|
+
const resultInWindow = i + 1 < collapseLimit;
|
|
196
|
+
if (!next || next.role !== "toolResult" || next.toolCallId !== call.id || !resultInWindow) {
|
|
197
|
+
i += 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const type = classifyTool(call.name, sets);
|
|
201
|
+
if (!type) {
|
|
202
|
+
i += 1;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
operations.push({
|
|
206
|
+
type,
|
|
207
|
+
toolName: call.name,
|
|
208
|
+
assistantIndex: i,
|
|
209
|
+
resultIndex: i + 1,
|
|
210
|
+
hint: extractHint(type, call.args),
|
|
211
|
+
resultText: extractContentText(next.content),
|
|
212
|
+
});
|
|
213
|
+
i += 2;
|
|
214
|
+
}
|
|
215
|
+
return operations;
|
|
216
|
+
}
|
|
217
|
+
function groupConsecutiveOperations(operations) {
|
|
218
|
+
const groups = [];
|
|
219
|
+
let current = [];
|
|
220
|
+
for (const op of operations) {
|
|
221
|
+
if (current.length === 0) {
|
|
222
|
+
current.push(op);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const prev = current[current.length - 1];
|
|
226
|
+
if (op.type === prev.type && op.assistantIndex === prev.resultIndex + 1) {
|
|
227
|
+
current.push(op);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
groups.push(current);
|
|
231
|
+
current = [op];
|
|
232
|
+
}
|
|
233
|
+
if (current.length > 0)
|
|
234
|
+
groups.push(current);
|
|
235
|
+
return groups;
|
|
236
|
+
}
|
|
237
|
+
export function collapseConsecutiveToolResults(messages, options = {}) {
|
|
238
|
+
const minGroupSize = Math.max(1, options.minGroupSize ?? DEFAULT_MIN_GROUP_SIZE);
|
|
239
|
+
const protectRecentMessages = Math.max(0, options.protectRecentMessages ?? DEFAULT_PROTECT_RECENT_MESSAGES);
|
|
240
|
+
const sets = {
|
|
241
|
+
read: new Set(options.readToolNames ?? DEFAULT_READ_TOOL_NAMES),
|
|
242
|
+
search: new Set(options.searchToolNames ?? DEFAULT_SEARCH_TOOL_NAMES),
|
|
243
|
+
shell: new Set(options.shellToolNames ?? DEFAULT_SHELL_TOOL_NAMES),
|
|
244
|
+
};
|
|
245
|
+
const collapseLimit = Math.max(0, messages.length - protectRecentMessages);
|
|
246
|
+
const operations = collectCollapsibleOperations(messages, collapseLimit, sets);
|
|
247
|
+
const operationGroups = groupConsecutiveOperations(operations);
|
|
248
|
+
const collapsedGroups = [];
|
|
249
|
+
const nextMessages = messages.slice();
|
|
250
|
+
let tokensSaved = 0;
|
|
251
|
+
for (const group of operationGroups) {
|
|
252
|
+
if (group.length < minGroupSize)
|
|
253
|
+
continue;
|
|
254
|
+
const label = buildGroupLabel(group[0].type, group);
|
|
255
|
+
let originalTokens = 0;
|
|
256
|
+
let collapsedTokens = 0;
|
|
257
|
+
for (const op of group) {
|
|
258
|
+
const original = approxTextTokens(op.resultText);
|
|
259
|
+
const collapsed = approxTextTokens(label);
|
|
260
|
+
originalTokens += original;
|
|
261
|
+
collapsedTokens += collapsed;
|
|
262
|
+
const result = nextMessages[op.resultIndex];
|
|
263
|
+
if (result.role !== "toolResult")
|
|
264
|
+
continue;
|
|
265
|
+
const images = result.content.filter((c) => c.type === "image");
|
|
266
|
+
nextMessages[op.resultIndex] = {
|
|
267
|
+
...result,
|
|
268
|
+
content: [{ type: "text", text: label }, ...images],
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
tokensSaved += Math.max(0, originalTokens - collapsedTokens);
|
|
272
|
+
collapsedGroups.push({
|
|
273
|
+
type: group[0].type,
|
|
274
|
+
count: group.length,
|
|
275
|
+
label,
|
|
276
|
+
originalTokens,
|
|
277
|
+
collapsedTokens,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return { messages: nextMessages, groups: collapsedGroups, tokensSaved };
|
|
281
|
+
}
|
|
282
|
+
function resolveProtectedFromIndex(messages, protectRecentTokens) {
|
|
283
|
+
if (messages.length === 0)
|
|
284
|
+
return 0;
|
|
285
|
+
let recentTokens = 0;
|
|
286
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
287
|
+
const tokens = approxTextTokens(extractMessageText(messages[i]));
|
|
288
|
+
if (recentTokens + tokens > protectRecentTokens)
|
|
289
|
+
return i + 1;
|
|
290
|
+
recentTokens += tokens;
|
|
291
|
+
if (i === 0)
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
return messages.length;
|
|
295
|
+
}
|
|
296
|
+
function renderReplacementText(template, originalTokens, shrunkTokens) {
|
|
297
|
+
return template
|
|
298
|
+
.split("{original_tokens}")
|
|
299
|
+
.join(String(originalTokens))
|
|
300
|
+
.split("{shrunk_tokens}")
|
|
301
|
+
.join(String(shrunkTokens));
|
|
302
|
+
}
|
|
303
|
+
function buildShrunkText(originalText, originalTokens, maxAssistantTextTokens, template) {
|
|
304
|
+
const targetTextTokens = Math.max(1, Math.floor(maxAssistantTextTokens * SHRUNK_RESPONSE_RATIO));
|
|
305
|
+
const ratio = originalTokens > 0 ? targetTextTokens / originalTokens : 0;
|
|
306
|
+
const targetChars = Math.max(0, Math.floor(originalText.length * ratio));
|
|
307
|
+
const truncatedText = originalText.slice(0, targetChars);
|
|
308
|
+
let shrunkTokens = 0;
|
|
309
|
+
let shrunkText = "";
|
|
310
|
+
for (let iteration = 0; iteration < 5; iteration += 1) {
|
|
311
|
+
const replacement = renderReplacementText(template, originalTokens, shrunkTokens);
|
|
312
|
+
const candidate = truncatedText.length > 0 ? `${truncatedText}\n\n${replacement}` : replacement;
|
|
313
|
+
const tokens = approxTextTokens(candidate);
|
|
314
|
+
shrunkText = candidate;
|
|
315
|
+
if (tokens === shrunkTokens)
|
|
316
|
+
return { text: candidate, tokens };
|
|
317
|
+
shrunkTokens = tokens;
|
|
318
|
+
}
|
|
319
|
+
return { text: shrunkText, tokens: approxTextTokens(shrunkText) };
|
|
320
|
+
}
|
|
321
|
+
export function microCompactAssistantText(messages, options = {}) {
|
|
322
|
+
const protectRecentTokens = Math.max(0, options.protectRecentTokens ?? DEFAULT_PROTECT_RECENT_TOKENS);
|
|
323
|
+
const maxAssistantTextTokens = Math.max(0, options.maxAssistantTextTokens ?? DEFAULT_MAX_ASSISTANT_TEXT_TOKENS);
|
|
324
|
+
const minSavingsTokens = Math.max(0, options.minSavingsTokens ?? DEFAULT_MIN_SAVINGS_TOKENS);
|
|
325
|
+
const template = options.replacementTemplate ?? DEFAULT_REPLACEMENT_TEMPLATE;
|
|
326
|
+
const protectedFromIndex = resolveProtectedFromIndex(messages, protectRecentTokens);
|
|
327
|
+
const result = messages.slice();
|
|
328
|
+
let messagesModified = 0;
|
|
329
|
+
let tokensSaved = 0;
|
|
330
|
+
for (let i = 0; i < protectedFromIndex; i += 1) {
|
|
331
|
+
const msg = result[i];
|
|
332
|
+
if (msg.role !== "assistant")
|
|
333
|
+
continue;
|
|
334
|
+
const assistant = msg;
|
|
335
|
+
const allText = assistant.content.length > 0 && assistant.content.every((c) => c.type === "text");
|
|
336
|
+
if (!allText)
|
|
337
|
+
continue;
|
|
338
|
+
const originalText = assistant.content.map((c) => (c.type === "text" ? c.text : "")).join("\n");
|
|
339
|
+
const originalTokens = approxTextTokens(originalText);
|
|
340
|
+
if (originalTokens <= maxAssistantTextTokens)
|
|
341
|
+
continue;
|
|
342
|
+
const shrunk = buildShrunkText(originalText, originalTokens, maxAssistantTextTokens, template);
|
|
343
|
+
const saved = originalTokens - shrunk.tokens;
|
|
344
|
+
if (saved < minSavingsTokens)
|
|
345
|
+
continue;
|
|
346
|
+
result[i] = {
|
|
347
|
+
...assistant,
|
|
348
|
+
content: [{ type: "text", text: shrunk.text }],
|
|
349
|
+
};
|
|
350
|
+
tokensSaved += saved;
|
|
351
|
+
messagesModified += 1;
|
|
352
|
+
}
|
|
353
|
+
return { messages: result, tokensSaved, messagesModified };
|
|
354
|
+
}
|
|
355
|
+
export function clearOldToolResults(messages, options = {}) {
|
|
356
|
+
const keepRecent = Math.max(0, options.keepRecent ?? DEFAULT_KEEP_RECENT_TOOL_RESULTS);
|
|
357
|
+
const clearable = new Set(options.clearableToolNames ?? DEFAULT_CLEARABLE_TOOL_NAMES);
|
|
358
|
+
const replacementText = options.replacementText ?? DEFAULT_CLEARED_PLACEHOLDER;
|
|
359
|
+
const clearableIndices = [];
|
|
360
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
361
|
+
const msg = messages[i];
|
|
362
|
+
if (msg.role === "toolResult" && clearable.has(msg.toolName)) {
|
|
363
|
+
clearableIndices.push(i);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const clearUntil = Math.max(0, clearableIndices.length - keepRecent);
|
|
367
|
+
if (clearUntil === 0) {
|
|
368
|
+
return { messages: messages.slice(), tokensSaved: 0, toolResultsCleared: 0 };
|
|
369
|
+
}
|
|
370
|
+
const result = messages.slice();
|
|
371
|
+
let tokensSaved = 0;
|
|
372
|
+
let toolResultsCleared = 0;
|
|
373
|
+
const replacementTokens = approxTextTokens(replacementText);
|
|
374
|
+
for (let k = 0; k < clearUntil; k += 1) {
|
|
375
|
+
const idx = clearableIndices[k];
|
|
376
|
+
const msg = result[idx];
|
|
377
|
+
if (msg.role !== "toolResult")
|
|
378
|
+
continue;
|
|
379
|
+
const original = msg;
|
|
380
|
+
const originalText = extractContentText(original.content);
|
|
381
|
+
const originalTokens = approxTextTokens(originalText);
|
|
382
|
+
const images = original.content.filter((c) => c.type === "image");
|
|
383
|
+
result[idx] = {
|
|
384
|
+
...original,
|
|
385
|
+
content: [{ type: "text", text: replacementText }, ...images],
|
|
386
|
+
};
|
|
387
|
+
const savings = originalTokens - replacementTokens;
|
|
388
|
+
if (savings > 0)
|
|
389
|
+
tokensSaved += savings;
|
|
390
|
+
toolResultsCleared += 1;
|
|
391
|
+
}
|
|
392
|
+
return { messages: result, tokensSaved, toolResultsCleared };
|
|
393
|
+
}
|
|
394
|
+
export function reduceContextMessages(messages, options = {}) {
|
|
395
|
+
let current = messages;
|
|
396
|
+
let tokensSaved = 0;
|
|
397
|
+
let groupsCollapsed = 0;
|
|
398
|
+
let messagesShrunk = 0;
|
|
399
|
+
let toolResultsCleared = 0;
|
|
400
|
+
if (options.collapse !== false) {
|
|
401
|
+
const collapsed = collapseConsecutiveToolResults(current, options.collapse ?? undefined);
|
|
402
|
+
current = collapsed.messages;
|
|
403
|
+
tokensSaved += collapsed.tokensSaved;
|
|
404
|
+
groupsCollapsed += collapsed.groups.length;
|
|
405
|
+
}
|
|
406
|
+
if (options.shrinkAssistant !== false) {
|
|
407
|
+
const shrunk = microCompactAssistantText(current, options.shrinkAssistant ?? undefined);
|
|
408
|
+
current = shrunk.messages;
|
|
409
|
+
tokensSaved += shrunk.tokensSaved;
|
|
410
|
+
messagesShrunk += shrunk.messagesModified;
|
|
411
|
+
}
|
|
412
|
+
if (options.clearToolResults !== false) {
|
|
413
|
+
const cleared = clearOldToolResults(current, options.clearToolResults ?? undefined);
|
|
414
|
+
current = cleared.messages;
|
|
415
|
+
tokensSaved += cleared.tokensSaved;
|
|
416
|
+
toolResultsCleared += cleared.toolResultsCleared;
|
|
417
|
+
}
|
|
418
|
+
return { messages: current, tokensSaved, groupsCollapsed, messagesShrunk, toolResultsCleared };
|
|
419
|
+
}
|
|
420
|
+
//# sourceMappingURL=context-reduction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-reduction.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/context-reduction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Deterministic, no-LLM context reductions applied before compaction summarization.\n *\n * Ported from plugsuits' `context-collapse` and `micro-compact` patterns and\n * adapted to the senpi `AgentMessage` shape. Three independent transforms:\n *\n * 1. {@link collapseConsecutiveToolResults} — runs of same-kind read/grep/shell\n * tool result payloads are replaced with a single one-line label so the\n * summarizer pays for the shape, not for the bytes.\n * 2. {@link microCompactAssistantText} — older long assistant text answers are\n * truncated and tagged with a `[response shrunk]` marker.\n * 3. {@link clearOldToolResults} — keep the last N tool results in full, replace\n * older clearable tool result content with `[tool result cleared]`.\n *\n * Each transform is pure (`messages` in → new array out, no in-place mutation\n * beyond freshly cloned messages) and returns aggregated token-savings stats.\n *\n * {@link reduceContextMessages} composes the three transforms in order.\n */\n\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { AssistantMessage, ImageContent, TextContent, ToolResultMessage } from \"@earendil-works/pi-ai\";\n\nconst DEFAULT_READ_TOOL_NAMES = [\"read\", \"Read\", \"read_file\"];\nconst DEFAULT_SEARCH_TOOL_NAMES = [\"grep\", \"Grep\", \"glob\", \"Glob\"];\nconst DEFAULT_SHELL_TOOL_NAMES = [\"bash\", \"Bash\", \"shell\", \"shell_execute\"];\nconst DEFAULT_CLEARABLE_TOOL_NAMES = [\n\t\"read\",\n\t\"Read\",\n\t\"read_file\",\n\t\"write\",\n\t\"Write\",\n\t\"edit\",\n\t\"Edit\",\n\t\"grep\",\n\t\"Grep\",\n\t\"glob\",\n\t\"Glob\",\n\t\"bash\",\n\t\"Bash\",\n\t\"shell\",\n];\n\nconst DEFAULT_MIN_GROUP_SIZE = 2;\nconst DEFAULT_PROTECT_RECENT_MESSAGES = 5;\nconst DEFAULT_PROTECT_RECENT_TOKENS = 2000;\nconst DEFAULT_MAX_ASSISTANT_TEXT_TOKENS = 500;\nconst DEFAULT_MIN_SAVINGS_TOKENS = 100;\nconst DEFAULT_KEEP_RECENT_TOOL_RESULTS = 3;\nconst DEFAULT_CLEARED_PLACEHOLDER = \"[tool result cleared]\";\nconst DEFAULT_REPLACEMENT_TEMPLATE = \"[response shrunk — {original_tokens} → {shrunk_tokens} tokens]\";\n\nconst MAX_HINTS_IN_LABEL = 5;\nconst MAX_HINT_LENGTH = 80;\nconst SHRUNK_RESPONSE_RATIO = 0.3;\n\nexport type CollapsedGroupKind = \"read\" | \"search\" | \"shell\";\n\nexport interface CollapsedGroup {\n\ttype: CollapsedGroupKind;\n\tcount: number;\n\tlabel: string;\n\toriginalTokens: number;\n\tcollapsedTokens: number;\n}\n\nexport interface CollapseConsecutiveOptions {\n\tminGroupSize?: number;\n\tprotectRecentMessages?: number;\n\treadToolNames?: string[];\n\tsearchToolNames?: string[];\n\tshellToolNames?: string[];\n}\n\nexport interface CollapseConsecutiveResult {\n\tmessages: AgentMessage[];\n\tgroups: CollapsedGroup[];\n\ttokensSaved: number;\n}\n\nexport interface MicroCompactAssistantOptions {\n\tprotectRecentTokens?: number;\n\tmaxAssistantTextTokens?: number;\n\tminSavingsTokens?: number;\n\treplacementTemplate?: string;\n}\n\nexport interface MicroCompactAssistantResult {\n\tmessages: AgentMessage[];\n\ttokensSaved: number;\n\tmessagesModified: number;\n}\n\nexport interface ClearOldToolResultsOptions {\n\tkeepRecent?: number;\n\tclearableToolNames?: string[];\n\treplacementText?: string;\n}\n\nexport interface ClearOldToolResultsResult {\n\tmessages: AgentMessage[];\n\ttokensSaved: number;\n\ttoolResultsCleared: number;\n}\n\nexport interface ReduceContextOptions {\n\tcollapse?: false | CollapseConsecutiveOptions;\n\tshrinkAssistant?: false | MicroCompactAssistantOptions;\n\tclearToolResults?: false | ClearOldToolResultsOptions;\n}\n\nexport interface ReduceContextResult {\n\tmessages: AgentMessage[];\n\ttokensSaved: number;\n\tgroupsCollapsed: number;\n\tmessagesShrunk: number;\n\ttoolResultsCleared: number;\n}\n\n/**\n * Default options passed to {@link reduceContextMessages} when the builtin\n * compaction extension's `context` hook decides to run a reduction pass.\n *\n * Each value is chosen to be strictly more conservative than the corresponding\n * plugsuits default — protect more of the recent tail, raise the per-message\n * shrink threshold, and keep more recent tool results intact — so a single\n * shared default is safe to apply across normal coding sessions without making\n * targeted reductions less effective.\n */\nexport const BUILTIN_CONTEXT_REDUCTION_OPTIONS: ReduceContextOptions = {\n\tcollapse: {\n\t\tminGroupSize: DEFAULT_MIN_GROUP_SIZE,\n\t\tprotectRecentMessages: DEFAULT_PROTECT_RECENT_MESSAGES,\n\t},\n\tshrinkAssistant: {\n\t\tprotectRecentTokens: 3000,\n\t\tmaxAssistantTextTokens: 800,\n\t\tminSavingsTokens: DEFAULT_MIN_SAVINGS_TOKENS,\n\t},\n\tclearToolResults: {\n\t\tkeepRecent: 6,\n\t},\n};\n\nexport const BUILTIN_CONTEXT_REDUCTION_GATE_RATIO = 0.5;\n\nexport interface ShouldApplyContextReductionInput {\n\tusageTokens: number | null;\n\tcontextWindow: number;\n\tgateRatio?: number;\n\tisProviderNativeCompactionPath?: boolean;\n}\n\nexport function shouldApplyContextReduction(input: ShouldApplyContextReductionInput): boolean {\n\tconst gate = input.gateRatio ?? BUILTIN_CONTEXT_REDUCTION_GATE_RATIO;\n\tif (input.isProviderNativeCompactionPath === true) return false;\n\tif (input.usageTokens === null) return false;\n\tif (input.contextWindow <= 0) return false;\n\treturn input.usageTokens >= input.contextWindow * gate;\n}\n\nfunction approxTextTokens(text: string): number {\n\tif (!text) return 0;\n\treturn Math.ceil(text.length / 4);\n}\n\nfunction extractContentText(content: (TextContent | ImageContent)[] | undefined): string {\n\tif (!Array.isArray(content)) return \"\";\n\tlet out = \"\";\n\tfor (const part of content) {\n\t\tif (part.type === \"text\") out += part.text;\n\t}\n\treturn out;\n}\n\nfunction extractMessageText(message: AgentMessage): string {\n\tif (message.role === \"user\") {\n\t\tif (typeof message.content === \"string\") return message.content;\n\t\treturn extractContentText(message.content as (TextContent | ImageContent)[]);\n\t}\n\tif (message.role === \"assistant\") {\n\t\tlet out = \"\";\n\t\tfor (const block of message.content) {\n\t\t\tif (block.type === \"text\") out += block.text;\n\t\t\telse if (block.type === \"toolCall\") out += `${block.name} ${JSON.stringify(block.arguments)}`;\n\t\t}\n\t\treturn out;\n\t}\n\tif (message.role === \"toolResult\") {\n\t\treturn extractContentText(message.content);\n\t}\n\treturn \"\";\n}\n\ninterface ToolNameSets {\n\tread: Set<string>;\n\tsearch: Set<string>;\n\tshell: Set<string>;\n}\n\nfunction classifyTool(name: string, sets: ToolNameSets): CollapsedGroupKind | null {\n\tif (sets.read.has(name)) return \"read\";\n\tif (sets.search.has(name)) return \"search\";\n\tif (sets.shell.has(name)) return \"shell\";\n\treturn null;\n}\n\ninterface FirstToolCall {\n\tid: string;\n\tname: string;\n\targs: Record<string, unknown>;\n}\n\nfunction getFirstToolCallFromAssistant(message: AgentMessage): FirstToolCall | null {\n\tif (message.role !== \"assistant\") return null;\n\tfor (const block of message.content) {\n\t\tif (block.type === \"toolCall\") {\n\t\t\treturn { id: block.id, name: block.name, args: block.arguments as Record<string, unknown> };\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface CollapsibleOperation {\n\ttype: CollapsedGroupKind;\n\ttoolName: string;\n\tassistantIndex: number;\n\tresultIndex: number;\n\thint?: string;\n\tresultText: string;\n}\n\nfunction truncateHint(value: string): string {\n\tif (value.length <= MAX_HINT_LENGTH) return value;\n\treturn `${value.slice(0, MAX_HINT_LENGTH - 1)}…`;\n}\n\nfunction extractHint(type: CollapsedGroupKind, args: Record<string, unknown>): string | undefined {\n\tif (type === \"read\") {\n\t\tconst path = args.path ?? args.file_path ?? args.filePath;\n\t\tif (typeof path === \"string\" && path.length > 0) return truncateHint(path);\n\t\treturn undefined;\n\t}\n\tif (type === \"search\") {\n\t\tconst path = typeof args.path === \"string\" ? args.path : undefined;\n\t\tconst pattern =\n\t\t\t(typeof args.pattern === \"string\" && args.pattern) ||\n\t\t\t(typeof args.glob === \"string\" && args.glob) ||\n\t\t\t(typeof args.query === \"string\" && args.query) ||\n\t\t\tundefined;\n\t\tif (path && pattern) return truncateHint(`${path}:${pattern}`);\n\t\tif (path) return truncateHint(path);\n\t\tif (pattern) return truncateHint(pattern as string);\n\t\treturn undefined;\n\t}\n\tconst command = args.command ?? args.cmd;\n\tif (typeof command === \"string\" && command.length > 0) return truncateHint(command);\n\treturn undefined;\n}\n\nfunction buildGroupLabel(type: CollapsedGroupKind, operations: CollapsibleOperation[]): string {\n\tconst hints: string[] = [];\n\tfor (const op of operations) {\n\t\tif (op.hint && hints.length < MAX_HINTS_IN_LABEL) hints.push(op.hint);\n\t}\n\tconst noun = type === \"read\" ? \"read results\" : type === \"search\" ? \"search results\" : \"shell results\";\n\tif (hints.length === 0) return `[${operations.length} ${noun}]`;\n\tconst more = operations.length - hints.length;\n\tconst moreSuffix = more > 0 ? `, and ${more} more` : \"\";\n\treturn `[${operations.length} ${noun}: ${hints.join(\", \")}${moreSuffix}]`;\n}\n\nfunction collectCollapsibleOperations(\n\tmessages: AgentMessage[],\n\tcollapseLimit: number,\n\tsets: ToolNameSets,\n): CollapsibleOperation[] {\n\tconst operations: CollapsibleOperation[] = [];\n\tlet i = 0;\n\twhile (i < collapseLimit) {\n\t\tconst assistant = messages[i];\n\t\tconst call = getFirstToolCallFromAssistant(assistant);\n\t\tif (!call) {\n\t\t\ti += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tconst next = messages[i + 1];\n\t\tconst resultInWindow = i + 1 < collapseLimit;\n\t\tif (!next || next.role !== \"toolResult\" || next.toolCallId !== call.id || !resultInWindow) {\n\t\t\ti += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = classifyTool(call.name, sets);\n\t\tif (!type) {\n\t\t\ti += 1;\n\t\t\tcontinue;\n\t\t}\n\t\toperations.push({\n\t\t\ttype,\n\t\t\ttoolName: call.name,\n\t\t\tassistantIndex: i,\n\t\t\tresultIndex: i + 1,\n\t\t\thint: extractHint(type, call.args),\n\t\t\tresultText: extractContentText((next as ToolResultMessage).content),\n\t\t});\n\t\ti += 2;\n\t}\n\treturn operations;\n}\n\nfunction groupConsecutiveOperations(operations: CollapsibleOperation[]): CollapsibleOperation[][] {\n\tconst groups: CollapsibleOperation[][] = [];\n\tlet current: CollapsibleOperation[] = [];\n\tfor (const op of operations) {\n\t\tif (current.length === 0) {\n\t\t\tcurrent.push(op);\n\t\t\tcontinue;\n\t\t}\n\t\tconst prev = current[current.length - 1];\n\t\tif (op.type === prev.type && op.assistantIndex === prev.resultIndex + 1) {\n\t\t\tcurrent.push(op);\n\t\t\tcontinue;\n\t\t}\n\t\tgroups.push(current);\n\t\tcurrent = [op];\n\t}\n\tif (current.length > 0) groups.push(current);\n\treturn groups;\n}\n\nexport function collapseConsecutiveToolResults(\n\tmessages: AgentMessage[],\n\toptions: CollapseConsecutiveOptions = {},\n): CollapseConsecutiveResult {\n\tconst minGroupSize = Math.max(1, options.minGroupSize ?? DEFAULT_MIN_GROUP_SIZE);\n\tconst protectRecentMessages = Math.max(0, options.protectRecentMessages ?? DEFAULT_PROTECT_RECENT_MESSAGES);\n\tconst sets: ToolNameSets = {\n\t\tread: new Set(options.readToolNames ?? DEFAULT_READ_TOOL_NAMES),\n\t\tsearch: new Set(options.searchToolNames ?? DEFAULT_SEARCH_TOOL_NAMES),\n\t\tshell: new Set(options.shellToolNames ?? DEFAULT_SHELL_TOOL_NAMES),\n\t};\n\n\tconst collapseLimit = Math.max(0, messages.length - protectRecentMessages);\n\tconst operations = collectCollapsibleOperations(messages, collapseLimit, sets);\n\tconst operationGroups = groupConsecutiveOperations(operations);\n\n\tconst collapsedGroups: CollapsedGroup[] = [];\n\tconst nextMessages = messages.slice();\n\tlet tokensSaved = 0;\n\n\tfor (const group of operationGroups) {\n\t\tif (group.length < minGroupSize) continue;\n\t\tconst label = buildGroupLabel(group[0].type, group);\n\t\tlet originalTokens = 0;\n\t\tlet collapsedTokens = 0;\n\t\tfor (const op of group) {\n\t\t\tconst original = approxTextTokens(op.resultText);\n\t\t\tconst collapsed = approxTextTokens(label);\n\t\t\toriginalTokens += original;\n\t\t\tcollapsedTokens += collapsed;\n\t\t\tconst result = nextMessages[op.resultIndex];\n\t\t\tif (result.role !== \"toolResult\") continue;\n\t\t\tconst images = result.content.filter((c): c is ImageContent => c.type === \"image\");\n\t\t\tnextMessages[op.resultIndex] = {\n\t\t\t\t...result,\n\t\t\t\tcontent: [{ type: \"text\", text: label } as TextContent, ...images],\n\t\t\t};\n\t\t}\n\t\ttokensSaved += Math.max(0, originalTokens - collapsedTokens);\n\t\tcollapsedGroups.push({\n\t\t\ttype: group[0].type,\n\t\t\tcount: group.length,\n\t\t\tlabel,\n\t\t\toriginalTokens,\n\t\t\tcollapsedTokens,\n\t\t});\n\t}\n\n\treturn { messages: nextMessages, groups: collapsedGroups, tokensSaved };\n}\n\nfunction resolveProtectedFromIndex(messages: AgentMessage[], protectRecentTokens: number): number {\n\tif (messages.length === 0) return 0;\n\tlet recentTokens = 0;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst tokens = approxTextTokens(extractMessageText(messages[i]));\n\t\tif (recentTokens + tokens > protectRecentTokens) return i + 1;\n\t\trecentTokens += tokens;\n\t\tif (i === 0) return 0;\n\t}\n\treturn messages.length;\n}\n\nfunction renderReplacementText(template: string, originalTokens: number, shrunkTokens: number): string {\n\treturn template\n\t\t.split(\"{original_tokens}\")\n\t\t.join(String(originalTokens))\n\t\t.split(\"{shrunk_tokens}\")\n\t\t.join(String(shrunkTokens));\n}\n\nfunction buildShrunkText(\n\toriginalText: string,\n\toriginalTokens: number,\n\tmaxAssistantTextTokens: number,\n\ttemplate: string,\n): { text: string; tokens: number } {\n\tconst targetTextTokens = Math.max(1, Math.floor(maxAssistantTextTokens * SHRUNK_RESPONSE_RATIO));\n\tconst ratio = originalTokens > 0 ? targetTextTokens / originalTokens : 0;\n\tconst targetChars = Math.max(0, Math.floor(originalText.length * ratio));\n\tconst truncatedText = originalText.slice(0, targetChars);\n\tlet shrunkTokens = 0;\n\tlet shrunkText = \"\";\n\tfor (let iteration = 0; iteration < 5; iteration += 1) {\n\t\tconst replacement = renderReplacementText(template, originalTokens, shrunkTokens);\n\t\tconst candidate = truncatedText.length > 0 ? `${truncatedText}\\n\\n${replacement}` : replacement;\n\t\tconst tokens = approxTextTokens(candidate);\n\t\tshrunkText = candidate;\n\t\tif (tokens === shrunkTokens) return { text: candidate, tokens };\n\t\tshrunkTokens = tokens;\n\t}\n\treturn { text: shrunkText, tokens: approxTextTokens(shrunkText) };\n}\n\nexport function microCompactAssistantText(\n\tmessages: AgentMessage[],\n\toptions: MicroCompactAssistantOptions = {},\n): MicroCompactAssistantResult {\n\tconst protectRecentTokens = Math.max(0, options.protectRecentTokens ?? DEFAULT_PROTECT_RECENT_TOKENS);\n\tconst maxAssistantTextTokens = Math.max(0, options.maxAssistantTextTokens ?? DEFAULT_MAX_ASSISTANT_TEXT_TOKENS);\n\tconst minSavingsTokens = Math.max(0, options.minSavingsTokens ?? DEFAULT_MIN_SAVINGS_TOKENS);\n\tconst template = options.replacementTemplate ?? DEFAULT_REPLACEMENT_TEMPLATE;\n\n\tconst protectedFromIndex = resolveProtectedFromIndex(messages, protectRecentTokens);\n\tconst result = messages.slice();\n\tlet messagesModified = 0;\n\tlet tokensSaved = 0;\n\n\tfor (let i = 0; i < protectedFromIndex; i += 1) {\n\t\tconst msg = result[i];\n\t\tif (msg.role !== \"assistant\") continue;\n\t\tconst assistant = msg as AssistantMessage;\n\t\tconst allText = assistant.content.length > 0 && assistant.content.every((c) => c.type === \"text\");\n\t\tif (!allText) continue;\n\t\tconst originalText = assistant.content.map((c) => (c.type === \"text\" ? c.text : \"\")).join(\"\\n\");\n\t\tconst originalTokens = approxTextTokens(originalText);\n\t\tif (originalTokens <= maxAssistantTextTokens) continue;\n\t\tconst shrunk = buildShrunkText(originalText, originalTokens, maxAssistantTextTokens, template);\n\t\tconst saved = originalTokens - shrunk.tokens;\n\t\tif (saved < minSavingsTokens) continue;\n\t\tresult[i] = {\n\t\t\t...assistant,\n\t\t\tcontent: [{ type: \"text\", text: shrunk.text } as TextContent],\n\t\t};\n\t\ttokensSaved += saved;\n\t\tmessagesModified += 1;\n\t}\n\n\treturn { messages: result, tokensSaved, messagesModified };\n}\n\nexport function clearOldToolResults(\n\tmessages: AgentMessage[],\n\toptions: ClearOldToolResultsOptions = {},\n): ClearOldToolResultsResult {\n\tconst keepRecent = Math.max(0, options.keepRecent ?? DEFAULT_KEEP_RECENT_TOOL_RESULTS);\n\tconst clearable = new Set(options.clearableToolNames ?? DEFAULT_CLEARABLE_TOOL_NAMES);\n\tconst replacementText = options.replacementText ?? DEFAULT_CLEARED_PLACEHOLDER;\n\n\tconst clearableIndices: number[] = [];\n\tfor (let i = 0; i < messages.length; i += 1) {\n\t\tconst msg = messages[i];\n\t\tif (msg.role === \"toolResult\" && clearable.has(msg.toolName)) {\n\t\t\tclearableIndices.push(i);\n\t\t}\n\t}\n\n\tconst clearUntil = Math.max(0, clearableIndices.length - keepRecent);\n\tif (clearUntil === 0) {\n\t\treturn { messages: messages.slice(), tokensSaved: 0, toolResultsCleared: 0 };\n\t}\n\n\tconst result = messages.slice();\n\tlet tokensSaved = 0;\n\tlet toolResultsCleared = 0;\n\tconst replacementTokens = approxTextTokens(replacementText);\n\n\tfor (let k = 0; k < clearUntil; k += 1) {\n\t\tconst idx = clearableIndices[k];\n\t\tconst msg = result[idx];\n\t\tif (msg.role !== \"toolResult\") continue;\n\t\tconst original = msg as ToolResultMessage;\n\t\tconst originalText = extractContentText(original.content);\n\t\tconst originalTokens = approxTextTokens(originalText);\n\t\tconst images = original.content.filter((c): c is ImageContent => c.type === \"image\");\n\t\tresult[idx] = {\n\t\t\t...original,\n\t\t\tcontent: [{ type: \"text\", text: replacementText } as TextContent, ...images],\n\t\t};\n\t\tconst savings = originalTokens - replacementTokens;\n\t\tif (savings > 0) tokensSaved += savings;\n\t\ttoolResultsCleared += 1;\n\t}\n\n\treturn { messages: result, tokensSaved, toolResultsCleared };\n}\n\nexport function reduceContextMessages(\n\tmessages: AgentMessage[],\n\toptions: ReduceContextOptions = {},\n): ReduceContextResult {\n\tlet current = messages;\n\tlet tokensSaved = 0;\n\tlet groupsCollapsed = 0;\n\tlet messagesShrunk = 0;\n\tlet toolResultsCleared = 0;\n\n\tif (options.collapse !== false) {\n\t\tconst collapsed = collapseConsecutiveToolResults(current, options.collapse ?? undefined);\n\t\tcurrent = collapsed.messages;\n\t\ttokensSaved += collapsed.tokensSaved;\n\t\tgroupsCollapsed += collapsed.groups.length;\n\t}\n\tif (options.shrinkAssistant !== false) {\n\t\tconst shrunk = microCompactAssistantText(current, options.shrinkAssistant ?? undefined);\n\t\tcurrent = shrunk.messages;\n\t\ttokensSaved += shrunk.tokensSaved;\n\t\tmessagesShrunk += shrunk.messagesModified;\n\t}\n\tif (options.clearToolResults !== false) {\n\t\tconst cleared = clearOldToolResults(current, options.clearToolResults ?? undefined);\n\t\tcurrent = cleared.messages;\n\t\ttokensSaved += cleared.tokensSaved;\n\t\ttoolResultsCleared += cleared.toolResultsCleared;\n\t}\n\n\treturn { messages: current, tokensSaved, groupsCollapsed, messagesShrunk, toolResultsCleared };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAqDrE,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA8LlE","sourcesContent":["import type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport { repairOrphanedToolResults } from \"@earendil-works/pi-ai\";\nimport { type CompactionResult, DEFAULT_COMPACTION_SETTINGS } from \"../../../compaction/index.js\";\nimport { convertToLlm } from \"../../../messages.js\";\nimport type { CompactionEntry } from \"../../../session-manager.js\";\nimport type { ExtensionAPI, ExtensionContext } from \"../../types.js\";\nimport * as checkpointState from \"./checkpoint-state.js\";\nimport * as breaker from \"./circuit-breaker.js\";\nimport {\n\tcreateDegradationMonitorState,\n\thandleMessageEnd,\n\thandleTurnEnd,\n\tRECOVERY_INSTRUCTIONS,\n\tresetOnSessionCompact,\n} from \"./degradation-monitor.js\";\nimport * as overflow from \"./overflow-detection.js\";\nimport * as cap from \"./per-turn-cap.js\";\nimport * as policy from \"./policy.js\";\nimport * as restoration from \"./restoration-tracker.js\";\nimport {\n\tapplyGeneratedCompaction,\n\tcreateSpeculativeCompactionSnapshot,\n\tgetPromptVariant,\n\thardLimitEmergencyPrune,\n\trunExtensionCompaction,\n\ttype SpeculativeCompactionResult,\n\ttype SpeculativeCompactionSnapshot,\n} from \"./speculative.js\";\nimport { type CompactionExtensionState, createInitialState, resetTurnCounter } from \"./state.js\";\nimport * as todoBridge from \"./todo-bridge.js\";\nimport * as truncation from \"./tool-truncation.js\";\n\nconst DEFAULT_CONTEXT_WINDOW = 200_000;\nconst EMERGENCY_COMPACTION_INSTRUCTIONS =\n\t\"EMERGENCY: hard context limit reached. Produce an aggressive recovery summary that preserves current goal, constraints, files touched, tool outcomes, and exact next steps. Prefer concise factual state over transcript detail.\";\nconst PROACTIVE_COMPACTION_INSTRUCTIONS = \"Proactively compact before the next agent turn.\";\n\nfunction approxTokens(text: string): number {\n\treturn Math.ceil(text.length / 4);\n}\n\nfunction isMonitorableMessageEvent(event: { message: AgentMessage }): event is {\n\tmessage: AgentMessage & { content: Array<{ type: string; text?: string }> };\n} {\n\treturn \"content\" in event.message && Array.isArray(event.message.content);\n}\n\nfunction updateLastYield(state: CompactionExtensionState, entry: CompactionEntry): CompactionExtensionState {\n\tconst savedTokens = Math.max(0, entry.tokensBefore - approxTokens(entry.summary));\n\treturn { ...state, lastYield: { savedTokens, tokensBefore: entry.tokensBefore } };\n}\n\nfunction recentCheckpoint(ctx: ExtensionContext): checkpointState.AgentCheckpoint | null {\n\tconst checkpoint = checkpointState.getLatestCheckpoint(ctx);\n\tif (!checkpoint?.timestamp) return null;\n\treturn Date.now() - checkpoint.timestamp <= 60_000 ? checkpoint : null;\n}\n\nexport default function compactionExtension(pi: ExtensionAPI): void {\n\tlet state: CompactionExtensionState = createInitialState();\n\tconst degradationState = createDegradationMonitorState();\n\tconst restorationState = state.restoration ?? restoration.createRestorationTrackerState();\n\tstate = { ...state, restoration: restorationState };\n\tlet speculativeGeneration = 0;\n\tlet speculativeJob:\n\t\t| {\n\t\t\t\tgeneration: number;\n\t\t\t\tsnapshot: SpeculativeCompactionSnapshot;\n\t\t\t\tcontroller: AbortController;\n\t\t\t\tpromise: Promise<CompactionResult | undefined>;\n\t\t }\n\t\t| undefined;\n\n\tfunction invalidateSpeculativeCompaction(): void {\n\t\tspeculativeGeneration++;\n\t\tspeculativeJob?.controller.abort();\n\t\tspeculativeJob = undefined;\n\t}\n\n\tfunction startSpeculativeCompaction(ctx: ExtensionContext, customInstructions: string): void {\n\t\tif (speculativeJob) return;\n\t\tconst generation = ++speculativeGeneration;\n\t\tconst snapshot = createSpeculativeCompactionSnapshot(ctx, { generation, customInstructions });\n\t\tif (!snapshot) return;\n\n\t\tconst controller = new AbortController();\n\t\tconst promise = runExtensionCompaction(ctx, snapshot, controller.signal).catch(() => undefined);\n\t\tspeculativeJob = { generation, snapshot, controller, promise };\n\t}\n\n\tasync function applyBlockingCompaction(\n\t\tctx: ExtensionContext,\n\t\tcustomInstructions: string,\n\t): Promise<SpeculativeCompactionResult> {\n\t\tconst pendingJob = speculativeJob;\n\t\tif (pendingJob) {\n\t\t\tconst compaction = await pendingJob.promise;\n\t\t\tconst result = await applyGeneratedCompaction(\n\t\t\t\tctx,\n\t\t\t\tpendingJob.snapshot,\n\t\t\t\t() => speculativeGeneration,\n\t\t\t\tcompaction,\n\t\t\t);\n\t\t\tif (result.applied || result.reason === \"stale\") {\n\t\t\t\tspeculativeJob = undefined;\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tspeculativeJob = undefined;\n\t\t}\n\n\t\tconst generation = ++speculativeGeneration;\n\t\tconst snapshot = createSpeculativeCompactionSnapshot(ctx, { generation, customInstructions });\n\t\tif (!snapshot) return { applied: false, reason: \"unavailable\" };\n\t\tconst compaction = await runExtensionCompaction(ctx, snapshot);\n\t\treturn await applyGeneratedCompaction(ctx, snapshot, () => speculativeGeneration, compaction);\n\t}\n\n\tpi.on(\"session_before_compact\", async (event, ctx) => {\n\t\tinvalidateSpeculativeCompaction();\n\t\tif (cap.shouldRejectByCap(state, { reason: event.reason }).cancel) return { cancel: true };\n\t\tif (breaker.isTripped(state, Date.now()) && !breaker.shouldBypass(state, { reason: event.reason }))\n\t\t\treturn { cancel: true };\n\n\t\tcheckpointState.persistCheckpoint(pi, checkpointState.captureAgentCheckpoint(pi, ctx));\n\t\ttodoBridge.captureTodoSnapshot(pi, ctx);\n\n\t\tconst model = ctx.model;\n\t\tif (!model) return undefined;\n\t\tconst snapshot = {\n\t\t\tgeneration: ++speculativeGeneration,\n\t\t\texpectedRevision: ctx.getMessageRevision(),\n\t\t\tmodel,\n\t\t\tcontextWindow: ctx.getContextUsage()?.contextWindow ?? model.contextWindow ?? DEFAULT_CONTEXT_WINDOW,\n\t\t\tpreparation: event.preparation,\n\t\t\tpromptVariant: getPromptVariant(event),\n\t\t\tcustomInstructions: event.customInstructions,\n\t\t};\n\t\tconst compaction = await runExtensionCompaction(ctx, snapshot, event.signal);\n\t\tif (!compaction) return { cancel: true };\n\n\t\treturn {\n\t\t\tcompaction,\n\t\t};\n\t});\n\n\tpi.on(\"session_compact\", async (event, ctx) => {\n\t\tinvalidateSpeculativeCompaction();\n\t\tif (event.accepted) {\n\t\t\tconst branchEntries = ctx.sessionManager.getBranch();\n\t\t\tconst firstKeptIndex = branchEntries.findIndex((entry) => entry.id === event.compactionEntry.firstKeptEntryId);\n\t\t\tconst keptEntries = firstKeptIndex === -1 ? [] : branchEntries.slice(firstKeptIndex);\n\t\t\tstate = cap.incrementAccepted(state);\n\t\t\tstate = breaker.recordSuccess(state);\n\t\t\tstate = updateLastYield(state, event.compactionEntry);\n\t\t\tresetOnSessionCompact(degradationState);\n\t\t\ttodoBridge.restoreTodosIfMissing(pi, ctx);\n\t\t\tconst usage = ctx.getContextUsage();\n\t\t\tif (DEFAULT_COMPACTION_SETTINGS.restorationEnabled) {\n\t\t\t\trestoration.preparePendingPayload(restorationState, {\n\t\t\t\t\taccepted: true,\n\t\t\t\t\treason: event.reason,\n\t\t\t\t\tcompactionEntryId: event.compactionEntry.id,\n\t\t\t\t\tcontextWindow: usage?.contextWindow ?? ctx.model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW,\n\t\t\t\t\tusageTokens: usage?.tokens ?? null,\n\t\t\t\t\treserveTokens: DEFAULT_COMPACTION_SETTINGS.reserveTokens,\n\t\t\t\t\tsettings: DEFAULT_COMPACTION_SETTINGS,\n\t\t\t\t\tkeptMessages: keptEntries.flatMap((entry) => {\n\t\t\t\t\t\tif (entry.type !== \"message\") return [];\n\t\t\t\t\t\treturn [entry.message];\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tstate = breaker.recordFailure(state, Date.now(), { route: event.reason });\n\t\tctx.ui.notify(`Compaction rejected: ${event.rejectionCause ?? \"unknown\"}`, \"warning\");\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\tlet systemPrompt = event.systemPrompt;\n\t\tconst message = restoration.consumePendingPayload(restorationState);\n\t\tconst checkpoint = recentCheckpoint(ctx);\n\t\tif (checkpoint) systemPrompt = checkpointState.injectRestorationDirective(systemPrompt, checkpoint);\n\n\t\tconst usage = ctx.getContextUsage();\n\t\tconst contextWindow = usage?.contextWindow ?? ctx.model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;\n\t\tconst settings = ctx.getCompactionSettings();\n\t\tif (usage && policy.isAtHardLimit(usage, contextWindow, settings.reserveTokens)) {\n\t\t\tawait applyBlockingCompaction(ctx, EMERGENCY_COMPACTION_INSTRUCTIONS);\n\t\t} else if (\n\t\t\tusage &&\n\t\t\tpolicy.shouldTriggerCompaction(usage, contextWindow, settings, state.lastYield ?? undefined)\n\t\t) {\n\t\t\tawait applyBlockingCompaction(ctx, PROACTIVE_COMPACTION_INSTRUCTIONS);\n\t\t} else if (\n\t\t\tusage &&\n\t\t\tpolicy.shouldStartSpeculativeCompaction(usage, contextWindow, settings, state.lastYield ?? undefined)\n\t\t) {\n\t\t\tstartSpeculativeCompaction(ctx, PROACTIVE_COMPACTION_INSTRUCTIONS);\n\t\t}\n\n\t\tif (systemPrompt === event.systemPrompt && !message) return undefined;\n\t\treturn message ? { systemPrompt, message } : { systemPrompt };\n\t});\n\n\tpi.on(\"context\", (event, ctx) => {\n\t\tconst contextWindow = ctx.getContextUsage()?.contextWindow ?? ctx.model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;\n\t\tconst emergency = hardLimitEmergencyPrune(event.messages, contextWindow);\n\t\treturn { messages: repairOrphanedToolResults(convertToLlm(emergency.messages)) };\n\t});\n\n\tpi.on(\"turn_end\", async (_event, ctx) => {\n\t\thandleTurnEnd(degradationState);\n\t\tif (degradationState.recoveryTriggeredThisCycle) return;\n\t\tif (state.lastYield && state.lastYield.savedTokens <= 0) {\n\t\t\tvoid applyBlockingCompaction(ctx, RECOVERY_INSTRUCTIONS);\n\t\t}\n\t});\n\n\tpi.on(\"agent_end\", () => {\n\t\tstate = resetTurnCounter(state, \"\");\n\t});\n\n\tpi.on(\"message_end\", async (event, ctx) => {\n\t\tif (isMonitorableMessageEvent(event)) {\n\t\t\tawait handleMessageEnd(degradationState, event, {\n\t\t\t\tapplyCompaction: async (options) => {\n\t\t\t\t\treturn await applyBlockingCompaction(ctx, options.customInstructions);\n\t\t\t\t},\n\t\t\t\tnotify: (message) => ctx.ui.notify(message, \"warning\"),\n\t\t\t});\n\t\t}\n\t\tif (event.message.role === \"assistant\" && event.message.stopReason === \"error\") {\n\t\t\tconst detected = overflow.isContextOverflowError(new Error(event.message.errorMessage ?? \"\"));\n\t\t\tif (detected.detected) {\n\t\t\t\tvoid applyBlockingCompaction(ctx, `RECOVERY: context overflow detected (${detected.confidence})`);\n\t\t\t}\n\t\t}\n\t});\n\n\tpi.on(\"tool_result\", (event) => {\n\t\tconst [truncated] = truncation.truncateOversizedToolResults([{ content: event.content, details: event.details }]);\n\t\treturn truncated ? { content: truncated.content, details: event.details, isError: event.isError } : undefined;\n\t});\n\n\tpi.on(\"tool_call\", (event) => {\n\t\trestoration.trackToolCall(restorationState, event);\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,YAAY,EAA+C,MAAM,gBAAgB,CAAC;AAmI9G,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA+SlE","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport { type CompactionResult, DEFAULT_COMPACTION_SETTINGS } from \"../../../compaction/index.js\";\nimport { convertToLlm } from \"../../../messages.js\";\nimport type { CompactionEntry } from \"../../../session-manager.js\";\nimport type { ContextUsage, ExtensionAPI, ExtensionContext, SessionBeforeCompactEvent } from \"../../types.js\";\nimport * as checkpointState from \"./checkpoint-state.js\";\nimport * as breaker from \"./circuit-breaker.js\";\nimport {\n\tBUILTIN_CONTEXT_REDUCTION_OPTIONS,\n\treduceContextMessages,\n\tshouldApplyContextReduction,\n} from \"./context-reduction.js\";\nimport {\n\tcreateDegradationMonitorState,\n\thandleMessageEnd,\n\thandleTurnEnd,\n\tRECOVERY_INSTRUCTIONS,\n\tresetOnSessionCompact,\n} from \"./degradation-monitor.js\";\nimport {\n\trewriteOpenAiPayloadWithRemoteCompaction,\n\trunOpenAiRemoteCompaction,\n\tSENPI_COMPACTION_EVENT,\n} from \"./openai-remote.js\";\nimport * as cap from \"./per-turn-cap.js\";\nimport * as policy from \"./policy.js\";\nimport { repairOrphanedToolResults } from \"./repair-tool-pairs.js\";\nimport * as restoration from \"./restoration-tracker.js\";\nimport {\n\tapplyGeneratedCompaction,\n\tcreateSpeculativeCompactionSnapshot,\n\tgetPromptVariant,\n\thardLimitEmergencyPrune,\n\trunExtensionCompaction,\n\ttype SpeculativeCompactionResult,\n\ttype SpeculativeCompactionSnapshot,\n} from \"./speculative.js\";\nimport { type CompactionExtensionState, createInitialState, resetTurnCounter } from \"./state.js\";\nimport * as todoBridge from \"./todo-bridge.js\";\nimport * as truncation from \"./tool-truncation.js\";\n\nconst DEFAULT_CONTEXT_WINDOW = 200_000;\nconst EMERGENCY_COMPACTION_INSTRUCTIONS =\n\t\"EMERGENCY: hard context limit reached. Produce an aggressive recovery summary that preserves current goal, constraints, files touched, tool outcomes, and exact next steps. Prefer concise factual state over transcript detail.\";\nconst PROACTIVE_COMPACTION_INSTRUCTIONS = \"Proactively compact before the next agent turn.\";\nconst MAX_PENDING_METADATA = 8;\nconst IMAGE_PROMPT_TOKEN_ESTIMATE = 1_200;\n\ninterface PendingCompactionMetadata {\n\tcheckpoint: checkpointState.AgentCheckpoint;\n\ttodoSnapshot: todoBridge.TodoSnapshotPayload;\n}\n\nfunction approxTokens(text: string): number {\n\treturn Math.ceil(text.length / 4);\n}\n\nfunction isOpenAiResponsesModel(model: ExtensionContext[\"model\"]): boolean {\n\treturn model?.provider === \"openai\" && model.api === \"openai-responses\";\n}\n\nfunction estimatePendingPromptTokens(event: { prompt?: string; images?: readonly unknown[] }): number {\n\treturn approxTokens(event.prompt ?? \"\") + (event.images?.length ?? 0) * IMAGE_PROMPT_TOKEN_ESTIMATE;\n}\n\nfunction withAdditionalTokens(usage: ContextUsage, additionalTokens: number): ContextUsage {\n\tif (usage.tokens === null || additionalTokens <= 0) return usage;\n\tconst tokens = usage.tokens + additionalTokens;\n\treturn {\n\t\t...usage,\n\t\ttokens,\n\t\tpercent: usage.contextWindow > 0 ? (tokens / usage.contextWindow) * 100 : usage.percent,\n\t};\n}\n\nfunction isMonitorableMessageEvent(event: { message: AgentMessage }): event is {\n\tmessage: AgentMessage & { content: Array<{ type: string; text?: string }> };\n} {\n\treturn \"content\" in event.message && Array.isArray(event.message.content);\n}\n\nfunction updateLastYield(state: CompactionExtensionState, entry: CompactionEntry): CompactionExtensionState {\n\tconst savedTokens = Math.max(0, entry.tokensBefore - approxTokens(entry.summary));\n\treturn { ...state, lastYield: { savedTokens, tokensBefore: entry.tokensBefore } };\n}\n\nfunction recentCheckpoint(ctx: ExtensionContext): checkpointState.AgentCheckpoint | null {\n\tconst checkpoint = checkpointState.getLatestCheckpoint(ctx);\n\tif (!checkpoint?.timestamp) return null;\n\treturn Date.now() - checkpoint.timestamp <= 60_000 ? checkpoint : null;\n}\n\nfunction shouldEndFeedback(result: SpeculativeCompactionResult): boolean {\n\treturn !result.applied && result.reason !== \"rejected\";\n}\n\nfunction endCompactionFeedback(\n\tctx: ExtensionContext,\n\tsignal: AbortSignal | undefined,\n\tresult: SpeculativeCompactionResult,\n): void {\n\tif (shouldEndFeedback(result)) {\n\t\tctx.endCompaction?.({ reason: \"extension\", aborted: signal?.aborted });\n\t}\n}\n\nfunction linkAbortSignal(source: AbortSignal | undefined, target: AbortController): () => void {\n\tif (!source) return () => {};\n\tif (source.aborted) {\n\t\ttarget.abort();\n\t\treturn () => {};\n\t}\n\tconst abort = () => target.abort();\n\tsource.addEventListener(\"abort\", abort, { once: true });\n\treturn () => source.removeEventListener(\"abort\", abort);\n}\n\nfunction createBlockingRemoteCompactionEvent(\n\tctx: ExtensionContext,\n\tsnapshot: SpeculativeCompactionSnapshot,\n\tcustomInstructions: string,\n\tsignal: AbortSignal,\n): SessionBeforeCompactEvent {\n\treturn {\n\t\ttype: \"session_before_compact\",\n\t\treason: \"extension\",\n\t\twillRetry: false,\n\t\trequestId: randomUUID(),\n\t\tpreparation: snapshot.preparation,\n\t\tbranchEntries: ctx.sessionManager.getBranch(),\n\t\tcustomInstructions,\n\t\tsignal,\n\t};\n}\n\nexport default function compactionExtension(pi: ExtensionAPI): void {\n\tlet state: CompactionExtensionState = createInitialState();\n\tconst degradationState = createDegradationMonitorState();\n\tconst restorationState = state.restoration ?? restoration.createRestorationTrackerState();\n\tstate = { ...state, restoration: restorationState };\n\tlet speculativeGeneration = 0;\n\tlet speculativeJob:\n\t\t| {\n\t\t\t\tgeneration: number;\n\t\t\t\tsnapshot: SpeculativeCompactionSnapshot;\n\t\t\t\tcontroller: AbortController;\n\t\t\t\tpromise: Promise<CompactionResult | undefined>;\n\t\t }\n\t\t| undefined;\n\tconst pendingMetadata = new Map<string, PendingCompactionMetadata>();\n\n\tfunction invalidateSpeculativeCompaction(): void {\n\t\tspeculativeGeneration++;\n\t\tspeculativeJob?.controller.abort();\n\t\tspeculativeJob = undefined;\n\t}\n\n\tfunction startSpeculativeCompaction(ctx: ExtensionContext, customInstructions: string): void {\n\t\tif (speculativeJob) return;\n\t\tconst generation = ++speculativeGeneration;\n\t\tconst snapshot = createSpeculativeCompactionSnapshot(ctx, { generation, customInstructions });\n\t\tif (!snapshot) return;\n\n\t\tconst controller = new AbortController();\n\t\tconst promise = runExtensionCompaction(ctx, snapshot, controller.signal).catch(() => undefined);\n\t\tspeculativeJob = { generation, snapshot, controller, promise };\n\t}\n\n\tfunction capturePendingMetadata(requestId: string, ctx: ExtensionContext): void {\n\t\tpendingMetadata.set(requestId, {\n\t\t\tcheckpoint: checkpointState.captureAgentCheckpoint(pi, ctx),\n\t\t\ttodoSnapshot: todoBridge.createTodoSnapshot(ctx),\n\t\t});\n\t\twhile (pendingMetadata.size > MAX_PENDING_METADATA) {\n\t\t\tconst oldestRequestId = pendingMetadata.keys().next().value;\n\t\t\tif (oldestRequestId === undefined) break;\n\t\t\tpendingMetadata.delete(oldestRequestId);\n\t\t}\n\t}\n\n\tfunction persistAcceptedMetadata(requestId: string): void {\n\t\tconst metadata = pendingMetadata.get(requestId);\n\t\tif (!metadata) return;\n\t\tpendingMetadata.delete(requestId);\n\t\tcheckpointState.persistCheckpoint(pi, metadata.checkpoint);\n\t\ttodoBridge.persistTodoSnapshot(pi, metadata.todoSnapshot);\n\t}\n\n\tasync function applyBlockingCompaction(\n\t\tctx: ExtensionContext,\n\t\tcustomInstructions: string,\n\t): Promise<SpeculativeCompactionResult> {\n\t\tlet feedbackSignal = ctx.beginCompaction?.({ reason: \"extension\" });\n\t\ttry {\n\t\t\tif (isOpenAiResponsesModel(ctx.model)) {\n\t\t\t\tconst remoteGeneration = speculativeGeneration + 1;\n\t\t\t\tconst remoteSnapshot = createSpeculativeCompactionSnapshot(ctx, {\n\t\t\t\t\tgeneration: remoteGeneration,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t});\n\t\t\t\tif (remoteSnapshot) {\n\t\t\t\t\tconst remoteSignal = feedbackSignal ?? new AbortController().signal;\n\t\t\t\t\tconst remoteCompaction = await runOpenAiRemoteCompaction(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tcreateBlockingRemoteCompactionEvent(ctx, remoteSnapshot, customInstructions, remoteSignal),\n\t\t\t\t\t\t(data) => pi.events.emit(SENPI_COMPACTION_EVENT, data),\n\t\t\t\t\t);\n\t\t\t\t\tif (remoteCompaction) {\n\t\t\t\t\t\tif (speculativeGeneration !== remoteGeneration - 1) {\n\t\t\t\t\t\t\tconst result = { applied: false, reason: \"stale\" } as const;\n\t\t\t\t\t\t\tendCompactionFeedback(ctx, feedbackSignal, result);\n\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspeculativeGeneration = remoteGeneration;\n\t\t\t\t\t\tspeculativeJob?.controller.abort();\n\t\t\t\t\t\tspeculativeJob = undefined;\n\t\t\t\t\t\tconst result = await applyGeneratedCompaction(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\tremoteSnapshot,\n\t\t\t\t\t\t\t() => speculativeGeneration,\n\t\t\t\t\t\t\tremoteCompaction,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tendCompactionFeedback(ctx, feedbackSignal, result);\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst pendingJob = speculativeJob;\n\t\t\tif (pendingJob) {\n\t\t\t\tconst unlinkAbort = linkAbortSignal(feedbackSignal, pendingJob.controller);\n\t\t\t\tlet compaction: CompactionResult | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tcompaction = await pendingJob.promise;\n\t\t\t\t} finally {\n\t\t\t\t\tunlinkAbort();\n\t\t\t\t}\n\t\t\t\tconst result = await applyGeneratedCompaction(\n\t\t\t\t\tctx,\n\t\t\t\t\tpendingJob.snapshot,\n\t\t\t\t\t() => speculativeGeneration,\n\t\t\t\t\tcompaction,\n\t\t\t\t);\n\t\t\t\tif (result.applied || result.reason === \"stale\") {\n\t\t\t\t\tspeculativeJob = undefined;\n\t\t\t\t\tendCompactionFeedback(ctx, feedbackSignal, result);\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\tif (result.reason === \"rejected\") {\n\t\t\t\t\tfeedbackSignal = ctx.beginCompaction?.({ reason: \"extension\" });\n\t\t\t\t}\n\t\t\t\tspeculativeJob = undefined;\n\t\t\t}\n\n\t\t\tconst generation = ++speculativeGeneration;\n\t\t\tconst snapshot = createSpeculativeCompactionSnapshot(ctx, { generation, customInstructions });\n\t\t\tif (!snapshot) {\n\t\t\t\tconst result = { applied: false, reason: \"unavailable\" } as const;\n\t\t\t\tendCompactionFeedback(ctx, feedbackSignal, result);\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tconst compaction = await runExtensionCompaction(ctx, snapshot, feedbackSignal, (delta) =>\n\t\t\t\tctx.updateCompaction?.({ reason: \"extension\", delta }),\n\t\t\t);\n\t\t\tconst result = await applyGeneratedCompaction(ctx, snapshot, () => speculativeGeneration, compaction);\n\t\t\tendCompactionFeedback(ctx, feedbackSignal, result);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tctx.endCompaction?.({\n\t\t\t\treason: \"extension\",\n\t\t\t\taborted: feedbackSignal?.aborted,\n\t\t\t\terrorMessage: `Compaction failed: ${message}`,\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tpi.on(\"session_before_compact\", async (event, ctx) => {\n\t\tinvalidateSpeculativeCompaction();\n\t\tif (cap.shouldRejectByCap(state, { reason: event.reason }).cancel) return { cancel: true };\n\t\tif (breaker.isTripped(state, Date.now()) && !breaker.shouldBypass(state, { reason: event.reason }))\n\t\t\treturn { cancel: true };\n\n\t\tcapturePendingMetadata(event.requestId, ctx);\n\n\t\tconst model = ctx.model;\n\t\tif (!model) return undefined;\n\t\tconst remoteCompaction = await runOpenAiRemoteCompaction(ctx, event, (data) =>\n\t\t\tpi.events.emit(SENPI_COMPACTION_EVENT, data),\n\t\t);\n\t\tif (remoteCompaction) {\n\t\t\treturn { compaction: remoteCompaction };\n\t\t}\n\n\t\tconst snapshot = {\n\t\t\tgeneration: ++speculativeGeneration,\n\t\t\texpectedRevision: ctx.getMessageRevision(),\n\t\t\tmodel,\n\t\t\tcontextWindow: ctx.getContextUsage()?.contextWindow ?? model.contextWindow ?? DEFAULT_CONTEXT_WINDOW,\n\t\t\tpreparation: event.preparation,\n\t\t\tpromptVariant: getPromptVariant(event),\n\t\t\tcustomInstructions: event.customInstructions,\n\t\t};\n\t\tconst compaction = await runExtensionCompaction(ctx, snapshot, event.signal, (delta) =>\n\t\t\tctx.updateCompaction?.({ reason: event.reason, delta }),\n\t\t);\n\t\tif (!compaction) {\n\t\t\tpendingMetadata.delete(event.requestId);\n\t\t\treturn { cancel: true };\n\t\t}\n\n\t\treturn {\n\t\t\tcompaction,\n\t\t};\n\t});\n\n\tpi.on(\"session_compact\", async (event, ctx) => {\n\t\tinvalidateSpeculativeCompaction();\n\t\tif (event.accepted) {\n\t\t\tpersistAcceptedMetadata(event.requestId);\n\t\t\tconst branchEntries = ctx.sessionManager.getBranch();\n\t\t\tconst firstKeptIndex = branchEntries.findIndex((entry) => entry.id === event.compactionEntry.firstKeptEntryId);\n\t\t\tconst keptEntries = firstKeptIndex === -1 ? [] : branchEntries.slice(firstKeptIndex);\n\t\t\tstate = cap.incrementAccepted(state);\n\t\t\tstate = breaker.recordSuccess(state);\n\t\t\tstate = updateLastYield(state, event.compactionEntry);\n\t\t\tresetOnSessionCompact(degradationState);\n\t\t\ttodoBridge.restoreTodosIfMissing(pi, ctx);\n\t\t\tconst usage = ctx.getContextUsage();\n\t\t\tif (DEFAULT_COMPACTION_SETTINGS.restorationEnabled) {\n\t\t\t\trestoration.preparePendingPayload(restorationState, {\n\t\t\t\t\taccepted: true,\n\t\t\t\t\treason: event.reason,\n\t\t\t\t\tcompactionEntryId: event.compactionEntry.id,\n\t\t\t\t\tcontextWindow: usage?.contextWindow ?? ctx.model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW,\n\t\t\t\t\tusageTokens: usage?.tokens ?? null,\n\t\t\t\t\treserveTokens: DEFAULT_COMPACTION_SETTINGS.reserveTokens,\n\t\t\t\t\tsettings: DEFAULT_COMPACTION_SETTINGS,\n\t\t\t\t\tkeptMessages: keptEntries.flatMap((entry) => {\n\t\t\t\t\t\tif (entry.type !== \"message\") return [];\n\t\t\t\t\t\treturn [entry.message];\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tstate = breaker.recordFailure(state, Date.now(), { route: event.reason });\n\t\tctx.ui.notify(`Compaction rejected: ${event.rejectionCause ?? \"unknown\"}`, \"warning\");\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\tlet systemPrompt = event.systemPrompt;\n\t\tconst message = restoration.consumePendingPayload(restorationState);\n\t\tconst checkpoint = recentCheckpoint(ctx);\n\t\tif (checkpoint) systemPrompt = checkpointState.injectRestorationDirective(systemPrompt, checkpoint);\n\n\t\tconst usage = ctx.getContextUsage();\n\t\tconst contextWindow = usage?.contextWindow ?? ctx.model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;\n\t\tconst settings = ctx.getCompactionSettings();\n\t\tconst pendingPromptTokens = estimatePendingPromptTokens(event);\n\t\tconst usageWithPendingPrompt = usage ? withAdditionalTokens(usage, pendingPromptTokens) : undefined;\n\t\tif (usage && policy.isAtHardLimit(usage, contextWindow, settings.reserveTokens, pendingPromptTokens)) {\n\t\t\tawait applyBlockingCompaction(ctx, EMERGENCY_COMPACTION_INSTRUCTIONS);\n\t\t} else if (\n\t\t\tusageWithPendingPrompt &&\n\t\t\tpolicy.shouldTriggerCompaction(usageWithPendingPrompt, contextWindow, settings, state.lastYield ?? undefined)\n\t\t) {\n\t\t\tawait applyBlockingCompaction(ctx, PROACTIVE_COMPACTION_INSTRUCTIONS);\n\t\t} else if (\n\t\t\tusageWithPendingPrompt &&\n\t\t\tpolicy.shouldStartSpeculativeCompaction(\n\t\t\t\tusageWithPendingPrompt,\n\t\t\t\tcontextWindow,\n\t\t\t\tsettings,\n\t\t\t\tstate.lastYield ?? undefined,\n\t\t\t)\n\t\t) {\n\t\t\tstartSpeculativeCompaction(ctx, PROACTIVE_COMPACTION_INSTRUCTIONS);\n\t\t}\n\n\t\tif (systemPrompt === event.systemPrompt && !message) return undefined;\n\t\treturn message ? { systemPrompt, message } : { systemPrompt };\n\t});\n\n\tpi.on(\"context\", (event, ctx) => {\n\t\tconst usage = ctx.getContextUsage();\n\t\tconst contextWindow = usage?.contextWindow ?? ctx.model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;\n\t\tconst sourceMessages = shouldApplyContextReduction({\n\t\t\tusageTokens: usage?.tokens ?? null,\n\t\t\tcontextWindow,\n\t\t\tisProviderNativeCompactionPath: isOpenAiResponsesModel(ctx.model),\n\t\t})\n\t\t\t? reduceContextMessages(event.messages, BUILTIN_CONTEXT_REDUCTION_OPTIONS).messages\n\t\t\t: event.messages;\n\t\tconst emergency = hardLimitEmergencyPrune(sourceMessages, contextWindow);\n\t\treturn { messages: repairOrphanedToolResults(convertToLlm(emergency.messages)) };\n\t});\n\n\tpi.on(\"before_provider_request\", (event, ctx) => {\n\t\treturn rewriteOpenAiPayloadWithRemoteCompaction(\n\t\t\tevent.payload,\n\t\t\t{ model: ctx.model, branchEntries: ctx.sessionManager.getBranch() },\n\t\t\t(data) => pi.events.emit(SENPI_COMPACTION_EVENT, data),\n\t\t);\n\t});\n\n\tpi.on(\"turn_end\", async (_event, ctx) => {\n\t\thandleTurnEnd(degradationState);\n\t\tif (degradationState.recoveryTriggeredThisCycle) return;\n\t\tif (state.lastYield && state.lastYield.savedTokens <= 0) {\n\t\t\tvoid applyBlockingCompaction(ctx, RECOVERY_INSTRUCTIONS);\n\t\t}\n\t});\n\n\tpi.on(\"agent_end\", () => {\n\t\tstate = resetTurnCounter(state, \"\");\n\t});\n\n\tpi.on(\"message_end\", async (event, ctx) => {\n\t\tif (isMonitorableMessageEvent(event)) {\n\t\t\tawait handleMessageEnd(degradationState, event, {\n\t\t\t\tapplyCompaction: async (options) => {\n\t\t\t\t\treturn await applyBlockingCompaction(ctx, options.customInstructions);\n\t\t\t\t},\n\t\t\t\tnotify: (message) => ctx.ui.notify(message, \"warning\"),\n\t\t\t});\n\t\t}\n\t});\n\n\tpi.on(\"tool_result\", (event) => {\n\t\tconst [truncated] = truncation.truncateOversizedToolResults([{ content: event.content, details: event.details }]);\n\t\treturn truncated ? { content: truncated.content, details: event.details, isError: event.isError } : undefined;\n\t});\n\n\tpi.on(\"tool_call\", (event) => {\n\t\trestoration.trackToolCall(restorationState, event);\n\t});\n}\n"]}
|