@arichiardi/pi-custom-compaction 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/NOTICE +14 -0
- package/package.json +44 -0
- package/src/custom-compaction.ts +198 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|
package/NOTICE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
This software is released into the public domain under The Unlicense.
|
|
2
|
+
See LICENSE for details.
|
|
3
|
+
|
|
4
|
+
NOTICE
|
|
5
|
+
------
|
|
6
|
+
The file src/custom-compaction.ts was originally derived from:
|
|
7
|
+
|
|
8
|
+
https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/custom-compaction.ts
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Mario Zechner
|
|
11
|
+
Licensed under the MIT License
|
|
12
|
+
|
|
13
|
+
The original source was substantially rewritten by Andrea Richiardi.
|
|
14
|
+
The MIT license and copyright notice above apply to the original portions.
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arichiardi/pi-custom-compaction",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pi extension that replaces default compaction with a full LLM-generated summary, with configurable model and per-provider request params.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Unlicense",
|
|
7
|
+
"private": false,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"pi-package",
|
|
10
|
+
"pi-extension",
|
|
11
|
+
"pi",
|
|
12
|
+
"compaction",
|
|
13
|
+
"summarization"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"NOTICE"
|
|
20
|
+
],
|
|
21
|
+
"pi": {
|
|
22
|
+
"extensions": [
|
|
23
|
+
"./src/custom-compaction.ts"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"typecheck": "tsc --noEmit"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@earendil-works/pi-ai": "*",
|
|
31
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@earendil-works/pi-coding-agent": "0.79.10",
|
|
35
|
+
"@types/node": "^25.6.0",
|
|
36
|
+
"typescript": "^6.0.3"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/arichiardi/ar-llm.git",
|
|
41
|
+
"directory": "extensions/pi-custom-compaction"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/arichiardi/ar-llm/tree/main/extensions/pi-custom-compaction#readme"
|
|
44
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Compaction Extension
|
|
3
|
+
*
|
|
4
|
+
* Original source: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/custom-compaction.ts
|
|
5
|
+
* Modified by Andrea Richiardi
|
|
6
|
+
*
|
|
7
|
+
* This is free and unencumbered software released into the public domain.
|
|
8
|
+
*
|
|
9
|
+
* Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
10
|
+
* distribute this software, either in source code form or as a compiled
|
|
11
|
+
* binary, for any purpose, commercial or non-commercial, and by any
|
|
12
|
+
* means.
|
|
13
|
+
*
|
|
14
|
+
* In jurisdictions that recognize copyright laws, the author or authors
|
|
15
|
+
* of this software dedicate any and all copyright interest in the
|
|
16
|
+
* software to the public domain. We make this dedication for the benefit
|
|
17
|
+
* of the public at large and to the detriment of our heirs and
|
|
18
|
+
* successors. We intend this dedication to be an overt act of
|
|
19
|
+
* relinquishment in favor of the public domain.
|
|
20
|
+
*
|
|
21
|
+
* The software is provided "as is", without warranty of any kind.
|
|
22
|
+
* See <https://unlicense.org> for details.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Replaces the default compaction behavior with a full summary of the entire context.
|
|
27
|
+
* Instead of keeping the last 20k tokens of conversation turns, this extension:
|
|
28
|
+
* 1. Summarizes ALL messages (messagesToSummarize + turnPrefixMessages)
|
|
29
|
+
* 2. Discards all old turns completely, keeping only the summary
|
|
30
|
+
*
|
|
31
|
+
* This example also demonstrates using a different model for summarization,
|
|
32
|
+
* which can be cheaper/faster than the main conversation model.
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* pi --extension examples/extensions/custom-compaction.ts
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import * as fs from "fs";
|
|
39
|
+
import { complete } from "@earendil-works/pi-ai";
|
|
40
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
41
|
+
import { convertToLlm, serializeConversation } from "@earendil-works/pi-coding-agent";
|
|
42
|
+
|
|
43
|
+
// ============================================================
|
|
44
|
+
// Configuration
|
|
45
|
+
// ============================================================
|
|
46
|
+
|
|
47
|
+
/** Compaction model provider and model ID — change these to use a different summarization model. */
|
|
48
|
+
const COMPACTION_PROVIDER = "github-copilot";
|
|
49
|
+
const COMPACTION_MODEL = "gpt-5.4-mini";
|
|
50
|
+
|
|
51
|
+
/** Set to false to disable debug logging to /tmp/custom-compaction-debug.log */
|
|
52
|
+
const DEBUG = false;
|
|
53
|
+
const DEBUG_LOG = "/tmp/custom-compaction-debug.log";
|
|
54
|
+
function log(msg: string) {
|
|
55
|
+
if (DEBUG) {
|
|
56
|
+
fs.appendFileSync(DEBUG_LOG, `${new Date().toISOString()} ${msg}\n`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function (pi: ExtensionAPI) {
|
|
61
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
62
|
+
ctx.ui.notify("Custom compaction extension triggered", "info");
|
|
63
|
+
log("session_before_compact triggered");
|
|
64
|
+
|
|
65
|
+
const { preparation, branchEntries: _, signal } = event;
|
|
66
|
+
const { messagesToSummarize, turnPrefixMessages, tokensBefore, firstKeptEntryId, previousSummary } = preparation;
|
|
67
|
+
log(`messagesToSummarize: ${messagesToSummarize.length}, turnPrefixMessages: ${turnPrefixMessages.length}, tokensBefore: ${tokensBefore}`);
|
|
68
|
+
log(`firstKeptEntryId: ${JSON.stringify(firstKeptEntryId)}, previousSummary length: ${previousSummary?.length ?? 'none'}`);
|
|
69
|
+
|
|
70
|
+
const model = ctx.modelRegistry.find(COMPACTION_PROVIDER, COMPACTION_MODEL);
|
|
71
|
+
if (!model) {
|
|
72
|
+
log(`Model not found: ${COMPACTION_PROVIDER}/${COMPACTION_MODEL}`);
|
|
73
|
+
ctx.ui.notify(`Could not find compaction model ${COMPACTION_PROVIDER}/${COMPACTION_MODEL}, using default compaction`, "warning");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
log(`Found model: ${model.provider}/${model.id}`);
|
|
77
|
+
|
|
78
|
+
// Resolve request auth for the summarization model
|
|
79
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
80
|
+
if (!auth.ok) {
|
|
81
|
+
log(`Auth failed: ${auth.error}`);
|
|
82
|
+
ctx.ui.notify(`Compaction auth failed: ${auth.error}`, "warning");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!auth.apiKey) {
|
|
86
|
+
log(`No API key for ${model.provider}`);
|
|
87
|
+
ctx.ui.notify(`No API key for ${model.provider}, using default compaction`, "warning");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
log(`Auth OK, apiKey present: ${!!auth.apiKey}, headers: ${JSON.stringify(Object.keys(auth.headers || {}))}`);
|
|
91
|
+
|
|
92
|
+
// Combine all messages for full summary
|
|
93
|
+
const allMessages = [...messagesToSummarize, ...turnPrefixMessages];
|
|
94
|
+
|
|
95
|
+
ctx.ui.notify(
|
|
96
|
+
`Custom compaction: summarizing ${allMessages.length} messages (${tokensBefore.toLocaleString()} tokens) with ${model.id}...`,
|
|
97
|
+
"info",
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Convert messages to readable text format
|
|
101
|
+
const conversationText = serializeConversation(convertToLlm(allMessages));
|
|
102
|
+
|
|
103
|
+
// Include previous summary context if available
|
|
104
|
+
const previousContext = previousSummary ? `\n\nPrevious session summary for context:\n${previousSummary}` : "";
|
|
105
|
+
|
|
106
|
+
// Build messages that ask for a comprehensive summary
|
|
107
|
+
const summaryMessages = [
|
|
108
|
+
{
|
|
109
|
+
role: "user" as const,
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text" as const,
|
|
113
|
+
text: `You are a conversation summarizer. Create a comprehensive summary of this conversation that captures:${previousContext}
|
|
114
|
+
|
|
115
|
+
1. The main goals and objectives discussed
|
|
116
|
+
2. Key decisions made and most importantly their rationale
|
|
117
|
+
3. Important code changes, file modifications, or technical details
|
|
118
|
+
4. Current state of any ongoing work
|
|
119
|
+
5. Any blockers, issues, or open questions
|
|
120
|
+
6. Next steps that were planned or suggested
|
|
121
|
+
|
|
122
|
+
Be thorough but concise. The summary will replace the ENTIRE conversation history, so include all information needed to continue the work effectively.
|
|
123
|
+
|
|
124
|
+
Format the summary as structured markdown with clear sections.
|
|
125
|
+
|
|
126
|
+
<conversation>
|
|
127
|
+
${conversationText}
|
|
128
|
+
</conversation>`,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
timestamp: Date.now(),
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
// Pass signal to honor abort requests (e.g., user cancels compaction)
|
|
137
|
+
const response = await complete(
|
|
138
|
+
model,
|
|
139
|
+
{ messages: summaryMessages },
|
|
140
|
+
{
|
|
141
|
+
apiKey: auth.apiKey,
|
|
142
|
+
headers: auth.headers,
|
|
143
|
+
maxTokens: 8192,
|
|
144
|
+
signal,
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
// Check for API-level errors (model not found, auth issues, etc.)
|
|
148
|
+
if ((response as any).stopReason === "error" || (response as any).errorMessage) {
|
|
149
|
+
const errMsg = (response as any).errorMessage ?? "Unknown error";
|
|
150
|
+
log(`Model returned error: ${errMsg}`);
|
|
151
|
+
ctx.ui.notify(`Compaction model error: ${errMsg}, using default compaction`, "warning");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Debug: inspect raw response structure
|
|
156
|
+
log(`Response keys: ${JSON.stringify(Object.keys(response))}`);
|
|
157
|
+
log(`Full response JSON: ${JSON.stringify(response, null, 2).substring(0, 5000)}`);
|
|
158
|
+
if (response.content) {
|
|
159
|
+
log(`Content array length: ${response.content.length}`);
|
|
160
|
+
response.content.forEach((c: any, i: number) => {
|
|
161
|
+
log(` content[${i}]: type=${c.type}, keys=${JSON.stringify(Object.keys(c))}, textLen=${c.text?.length ?? 'N/A'}`);
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
log(`response.content is ${response.content}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const summary = response.content
|
|
168
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
169
|
+
.map((c) => c.text)
|
|
170
|
+
.join("\n");
|
|
171
|
+
|
|
172
|
+
log(`Summary length: ${summary.length}, trimmed length: ${summary.trim().length}`);
|
|
173
|
+
log(`Summary preview: ${summary.substring(0, 500)}`);
|
|
174
|
+
|
|
175
|
+
if (!summary.trim()) {
|
|
176
|
+
if (!signal.aborted) ctx.ui.notify("Compaction summary was empty, using default compaction", "warning");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
log(`Returning compaction: summaryLen=${summary.length}, firstKeptEntryId=${JSON.stringify(firstKeptEntryId)}, tokensBefore=${tokensBefore}`);
|
|
181
|
+
|
|
182
|
+
// Return compaction content - SessionManager adds id/parentId
|
|
183
|
+
// Use firstKeptEntryId from preparation to keep recent messages
|
|
184
|
+
return {
|
|
185
|
+
compaction: {
|
|
186
|
+
summary,
|
|
187
|
+
firstKeptEntryId,
|
|
188
|
+
tokensBefore,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
} catch (error) {
|
|
192
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
193
|
+
ctx.ui.notify(`Compaction failed: ${message}`, "error");
|
|
194
|
+
// Fall back to default compaction on error
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|