@oh-my-pi/pi-agent-core 18.0.8 → 18.0.9
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 +6 -0
- package/dist/types/compaction/shake.d.ts +13 -9
- package/package.json +8 -8
- package/src/compaction/messages.ts +10 -1
- package/src/compaction/shake.ts +39 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Context-reducing surgical compaction ("shake").
|
|
3
3
|
*
|
|
4
|
-
* `shake` drops heavy content out of the live context mechanically:
|
|
5
|
-
* tool-
|
|
6
|
-
* placeholders
|
|
4
|
+
* `shake` drops heavy content out of the live context mechanically:
|
|
5
|
+
* tool-result text and large fenced/XML blocks are replaced with short
|
|
6
|
+
* placeholders while non-text tool-result content is preserved. This module
|
|
7
|
+
* is the pure layer — region detection and in-place
|
|
7
8
|
* mutation only. Artifact offload, persistence, and provider-session teardown
|
|
8
9
|
* are orchestrated by the caller (`AgentSession.shake`).
|
|
9
10
|
*
|
|
@@ -45,6 +46,7 @@ export declare const RESCUE_SHAKE_CONFIG: ShakeConfig;
|
|
|
45
46
|
export interface ToolResultShakeRegion {
|
|
46
47
|
kind: "toolResult";
|
|
47
48
|
entry: SessionMessageEntry;
|
|
49
|
+
/** Estimated tokens in the removable text only; retained images are excluded. */
|
|
48
50
|
tokens: number;
|
|
49
51
|
originalText: string;
|
|
50
52
|
/** Human label for the offload doc (tool name). */
|
|
@@ -68,10 +70,11 @@ export type ShakeRegion = ToolResultShakeRegion | BlockShakeRegion;
|
|
|
68
70
|
* Pure detection: locate every eligible shake region on a branch.
|
|
69
71
|
*
|
|
70
72
|
* Walks the protect-recent window (most recent `protectTokens` of context is
|
|
71
|
-
* kept intact), collects
|
|
72
|
-
* and skipping already-pruned results) and large
|
|
73
|
-
* user/developer/assistant/custom messages.
|
|
74
|
-
* useless
|
|
73
|
+
* kept intact), collects the text from eligible tool-result messages
|
|
74
|
+
* (honoring `protectedTools` and skipping already-pruned results) and large
|
|
75
|
+
* fenced/XML blocks inside user/developer/assistant/custom messages.
|
|
76
|
+
* Contextually useless tool results bypass the protect window — there is
|
|
77
|
+
* nothing recent
|
|
75
78
|
* worth keeping in them. Returns regions in document order.
|
|
76
79
|
*
|
|
77
80
|
* `toolCall` blocks are never touched (tool-call/result pairing is preserved)
|
|
@@ -82,8 +85,9 @@ export declare function collectShakeRegions(entries: SessionEntry[], tokenizer:
|
|
|
82
85
|
/**
|
|
83
86
|
* Pure mutation: replace a single region's content in place.
|
|
84
87
|
*
|
|
85
|
-
* Tool-result: replaces
|
|
86
|
-
*
|
|
88
|
+
* Tool-result: replaces its first non-empty text block with the placeholder,
|
|
89
|
+
* removes its other text blocks, preserves every non-text block, and stamps
|
|
90
|
+
* `prunedAt`. Block: splices `replacement` over `[start, end)` of the
|
|
87
91
|
* target text block. When several block regions share one text block they MUST
|
|
88
92
|
* be applied highest-start-first so earlier offsets stay valid — use
|
|
89
93
|
* {@link applyShakeRegions}, which orders them correctly.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "18.0.
|
|
4
|
+
"version": "18.0.9",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -35,16 +35,16 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "18.0.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "18.0.
|
|
40
|
-
"@oh-my-pi/pi-natives": "18.0.
|
|
41
|
-
"@oh-my-pi/pi-utils": "18.0.
|
|
42
|
-
"@oh-my-pi/pi-wire": "18.0.
|
|
43
|
-
"@oh-my-pi/snapcompact": "18.0.
|
|
38
|
+
"@oh-my-pi/pi-ai": "18.0.9",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "18.0.9",
|
|
40
|
+
"@oh-my-pi/pi-natives": "18.0.9",
|
|
41
|
+
"@oh-my-pi/pi-utils": "18.0.9",
|
|
42
|
+
"@oh-my-pi/pi-wire": "18.0.9",
|
|
43
|
+
"@oh-my-pi/snapcompact": "18.0.9",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@oh-my-pi/omptype": "18.0.
|
|
47
|
+
"@oh-my-pi/omptype": "18.0.9",
|
|
48
48
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
49
49
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
50
50
|
"@types/bun": "^1.3.14"
|
|
@@ -85,7 +85,16 @@ function getPrunedToolResultContent(message: ToolResultMessage): (TextContent |
|
|
|
85
85
|
}
|
|
86
86
|
const textBlocks = message.content.filter((content): content is TextContent => content.type === "text");
|
|
87
87
|
const text = textBlocks.map(block => block.text).join("") || "[Output truncated]";
|
|
88
|
-
|
|
88
|
+
const firstTextIndex = message.content.findIndex(content => content.type === "text");
|
|
89
|
+
if (firstTextIndex < 0) return [{ type: "text", text }, ...message.content];
|
|
90
|
+
|
|
91
|
+
const content: (TextContent | ImageContent)[] = [];
|
|
92
|
+
for (let index = 0; index < message.content.length; index++) {
|
|
93
|
+
const block = message.content[index];
|
|
94
|
+
if (block.type !== "text") content.push(block);
|
|
95
|
+
else if (index === firstTextIndex) content.push({ type: "text", text });
|
|
96
|
+
}
|
|
97
|
+
return content;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
export function renderBranchSummaryContext(summary: string): string {
|
package/src/compaction/shake.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Context-reducing surgical compaction ("shake").
|
|
3
3
|
*
|
|
4
|
-
* `shake` drops heavy content out of the live context mechanically:
|
|
5
|
-
* tool-
|
|
6
|
-
* placeholders
|
|
4
|
+
* `shake` drops heavy content out of the live context mechanically:
|
|
5
|
+
* tool-result text and large fenced/XML blocks are replaced with short
|
|
6
|
+
* placeholders while non-text tool-result content is preserved. This module
|
|
7
|
+
* is the pure layer — region detection and in-place
|
|
7
8
|
* mutation only. Artifact offload, persistence, and provider-session teardown
|
|
8
9
|
* are orchestrated by the caller (`AgentSession.shake`).
|
|
9
10
|
*
|
|
@@ -80,6 +81,7 @@ const PLACEHOLDER_TOKEN_ESTIMATE = 16;
|
|
|
80
81
|
export interface ToolResultShakeRegion {
|
|
81
82
|
kind: "toolResult";
|
|
82
83
|
entry: SessionMessageEntry;
|
|
84
|
+
/** Estimated tokens in the removable text only; retained images are excluded. */
|
|
83
85
|
tokens: number;
|
|
84
86
|
originalText: string;
|
|
85
87
|
/** Human label for the offload doc (tool name). */
|
|
@@ -114,11 +116,19 @@ function getToolResultMessage(entry: SessionEntry): ToolResultMessage | undefine
|
|
|
114
116
|
return message as ToolResultMessage;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
function toolResultText(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
function toolResultText(
|
|
120
|
+
message: ToolResultMessage,
|
|
121
|
+
tokenizer: Tokenizer,
|
|
122
|
+
): { originalText: string; tokens: number } | undefined {
|
|
123
|
+
const fragments: string[] = [];
|
|
124
|
+
for (const block of message.content) {
|
|
125
|
+
if (block.type === "text" && block.text.length > 0) fragments.push(block.text);
|
|
126
|
+
}
|
|
127
|
+
if (fragments.length === 0) return undefined;
|
|
128
|
+
return {
|
|
129
|
+
originalText: fragments.join("\n"),
|
|
130
|
+
tokens: tokenizer.countTokens(fragments),
|
|
131
|
+
};
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
/** Estimate the token contribution of an entry for the protect-recent window. */
|
|
@@ -292,10 +302,11 @@ function scanContentBlocks(
|
|
|
292
302
|
* Pure detection: locate every eligible shake region on a branch.
|
|
293
303
|
*
|
|
294
304
|
* Walks the protect-recent window (most recent `protectTokens` of context is
|
|
295
|
-
* kept intact), collects
|
|
296
|
-
* and skipping already-pruned results) and large
|
|
297
|
-
* user/developer/assistant/custom messages.
|
|
298
|
-
* useless
|
|
305
|
+
* kept intact), collects the text from eligible tool-result messages
|
|
306
|
+
* (honoring `protectedTools` and skipping already-pruned results) and large
|
|
307
|
+
* fenced/XML blocks inside user/developer/assistant/custom messages.
|
|
308
|
+
* Contextually useless tool results bypass the protect window — there is
|
|
309
|
+
* nothing recent
|
|
299
310
|
* worth keeping in them. Returns regions in document order.
|
|
300
311
|
*
|
|
301
312
|
* `toolCall` blocks are never touched (tool-call/result pairing is preserved)
|
|
@@ -339,13 +350,13 @@ export function collectShakeRegions(entries: SessionEntry[], tokenizer: Tokenize
|
|
|
339
350
|
if (toolResult.prunedAt !== undefined) continue;
|
|
340
351
|
if (isProtectedToolResult(toolResult, toolCallsById.get(toolResult.toolCallId), config.protectedTools))
|
|
341
352
|
continue;
|
|
342
|
-
const text = toolResultText(toolResult);
|
|
343
|
-
if (text
|
|
353
|
+
const text = toolResultText(toolResult, tokenizer);
|
|
354
|
+
if (!text) continue;
|
|
344
355
|
regions.push({
|
|
345
356
|
kind: "toolResult",
|
|
346
357
|
entry: entry as SessionMessageEntry,
|
|
347
|
-
tokens:
|
|
348
|
-
originalText: text,
|
|
358
|
+
tokens: text.tokens,
|
|
359
|
+
originalText: text.originalText,
|
|
349
360
|
label: toolResult.toolName,
|
|
350
361
|
});
|
|
351
362
|
continue;
|
|
@@ -414,8 +425,9 @@ function getBlockTextSlot(entry: SessionMessageEntry | CustomMessageEntry, block
|
|
|
414
425
|
/**
|
|
415
426
|
* Pure mutation: replace a single region's content in place.
|
|
416
427
|
*
|
|
417
|
-
* Tool-result: replaces
|
|
418
|
-
*
|
|
428
|
+
* Tool-result: replaces its first non-empty text block with the placeholder,
|
|
429
|
+
* removes its other text blocks, preserves every non-text block, and stamps
|
|
430
|
+
* `prunedAt`. Block: splices `replacement` over `[start, end)` of the
|
|
419
431
|
* target text block. When several block regions share one text block they MUST
|
|
420
432
|
* be applied highest-start-first so earlier offsets stay valid — use
|
|
421
433
|
* {@link applyShakeRegions}, which orders them correctly.
|
|
@@ -423,7 +435,15 @@ function getBlockTextSlot(entry: SessionMessageEntry | CustomMessageEntry, block
|
|
|
423
435
|
export function applyShakeRegion(region: ShakeRegion, replacement: string): void {
|
|
424
436
|
if (region.kind === "toolResult") {
|
|
425
437
|
const message = region.entry.message as ToolResultMessage;
|
|
426
|
-
message.content
|
|
438
|
+
const replacementIndex = message.content.findIndex(block => block.type === "text" && block.text.length > 0);
|
|
439
|
+
if (replacementIndex < 0) return;
|
|
440
|
+
const kept: typeof message.content = [];
|
|
441
|
+
for (let index = 0; index < message.content.length; index++) {
|
|
442
|
+
const block = message.content[index];
|
|
443
|
+
if (block.type !== "text") kept.push(block);
|
|
444
|
+
else if (index === replacementIndex) kept.push({ type: "text", text: replacement });
|
|
445
|
+
}
|
|
446
|
+
message.content = kept;
|
|
427
447
|
message.prunedAt = Date.now();
|
|
428
448
|
invalidateMessageCache(message as AgentMessage);
|
|
429
449
|
return;
|