@lll9p/pi-better-compaction 0.6.0 → 0.6.1
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/README.md +0 -6
- package/README.zh-CN.md +0 -6
- package/package.json +1 -2
- package/src/compact-client-v2.ts +1 -0
- package/src/config.ts +0 -13
- package/src/extension-runtime.ts +0 -1
- package/src/runtime.ts +10 -1
- package/src/types.ts +0 -10
- package/src/midrun.ts +0 -229
package/README.md
CHANGED
|
@@ -44,10 +44,6 @@ If the file doesn't exist, all defaults apply. The extension never creates this
|
|
|
44
44
|
```jsonc
|
|
45
45
|
{
|
|
46
46
|
"enabled": true,
|
|
47
|
-
"midRun": {
|
|
48
|
-
"enabled": false,
|
|
49
|
-
"thresholdPercent": 80
|
|
50
|
-
},
|
|
51
47
|
"compactionVersion": "v2",
|
|
52
48
|
"compactionModel": null,
|
|
53
49
|
"compactionThinkingLevel": "off",
|
|
@@ -69,8 +65,6 @@ If the file doesn't exist, all defaults apply. The extension never creates this
|
|
|
69
65
|
| Option | Type | Default | Description |
|
|
70
66
|
|--------|------|---------|-------------|
|
|
71
67
|
| `enabled` | `boolean` | `true` | Master switch. Set `false` to disable the extension entirely. |
|
|
72
|
-
| `midRun.enabled` | `boolean` | `false` | Reserved and currently ignored. The mid-run guard remains disabled even when set to `true`. |
|
|
73
|
-
| `midRun.thresholdPercent` | `number` | `80` | Reserved threshold for the disabled mid-run guard. Must be greater than 0 and at most 100. |
|
|
74
68
|
| `compactionVersion` | `"v1" \| "v2"` | `"v2"` | Protocol for Responses-family APIs. **V2** (streaming, encrypted blob) is the current OpenAI default. **V1** uses the legacy `/responses/compact` endpoint. |
|
|
75
69
|
| `compactionModel` | `string \| null` | `null` | Model for fallback compaction (non-Responses APIs, or when native compact fails). Format: `"provider/model-id"`, e.g. `"openai/gpt-5.1-mini"`. `null` = let pi use the current chat model. |
|
|
76
70
|
| `compactionThinkingLevel` | `string` | `"off"` | Thinking level for the fallback compaction model. One of: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. |
|
package/README.zh-CN.md
CHANGED
|
@@ -44,10 +44,6 @@ cd pi-better-compaction && pi install .
|
|
|
44
44
|
```jsonc
|
|
45
45
|
{
|
|
46
46
|
"enabled": true,
|
|
47
|
-
"midRun": {
|
|
48
|
-
"enabled": false,
|
|
49
|
-
"thresholdPercent": 80
|
|
50
|
-
},
|
|
51
47
|
"compactionVersion": "v2",
|
|
52
48
|
"compactionModel": null,
|
|
53
49
|
"compactionThinkingLevel": "off",
|
|
@@ -69,8 +65,6 @@ cd pi-better-compaction && pi install .
|
|
|
69
65
|
| 选项 | 类型 | 默认值 | 说明 |
|
|
70
66
|
|------|------|--------|------|
|
|
71
67
|
| `enabled` | `boolean` | `true` | 总开关。设为 `false` 完全禁用扩展。 |
|
|
72
|
-
| `midRun.enabled` | `boolean` | `false` | 保留配置,当前会被忽略。即使设为 `true`,mid-run guard 仍保持禁用。 |
|
|
73
|
-
| `midRun.thresholdPercent` | `number` | `80` | 已禁用的 mid-run guard 的保留阈值。必须大于 0 且不超过 100。 |
|
|
74
68
|
| `compactionVersion` | `"v1" \| "v2"` | `"v2"` | Responses 系列 API 的压缩协议。**V2**(流式,加密 blob)是 OpenAI 当前默认协议;**V1** 使用旧版 `/responses/compact` 端点。 |
|
|
75
69
|
| `compactionModel` | `string \| null` | `null` | 回退压缩使用的模型(用于非 Responses API,或原生压缩失败时)。格式:`"provider/model-id"`,如 `"openai/gpt-5.1-mini"`。`null` = 由 pi 使用当前对话模型。 |
|
|
76
70
|
| `compactionThinkingLevel` | `string` | `"off"` | 回退压缩模型的思考级别。可选:`off`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`。 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lll9p/pi-better-compaction",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Better compaction for pi: native /responses/compact replay for OpenAI Responses APIs, plus a configurable compaction model driving pi's native summarization everywhere else.",
|
|
6
6
|
"author": "Lilin Lao",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"src/debug.ts",
|
|
39
39
|
"src/details-store.ts",
|
|
40
40
|
"src/extension-runtime.ts",
|
|
41
|
-
"src/midrun.ts",
|
|
42
41
|
"src/native-fallback.ts",
|
|
43
42
|
"src/payload-rewrite.ts",
|
|
44
43
|
"src/request-context-cache.ts",
|
package/src/compact-client-v2.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -128,7 +128,6 @@ export function loadExtensionConfig(configPath: string = CONFIG_PATH): LoadedExt
|
|
|
128
128
|
const warnings: string[] = [];
|
|
129
129
|
const resolved: ExtensionConfig = {
|
|
130
130
|
...DEFAULT_EXTENSION_CONFIG,
|
|
131
|
-
midRun: { ...DEFAULT_EXTENSION_CONFIG.midRun },
|
|
132
131
|
responsesCompactApis: [...DEFAULT_EXTENSION_CONFIG.responsesCompactApis],
|
|
133
132
|
};
|
|
134
133
|
let source: string | undefined;
|
|
@@ -139,18 +138,6 @@ export function loadExtensionConfig(configPath: string = CONFIG_PATH): LoadedExt
|
|
|
139
138
|
|
|
140
139
|
resolved.enabled = toBoolean(raw.enabled, "enabled", warnings) ?? resolved.enabled;
|
|
141
140
|
|
|
142
|
-
if (raw.midRun === undefined) {
|
|
143
|
-
// Keep defaults.
|
|
144
|
-
} else if (isRecord(raw.midRun)) {
|
|
145
|
-
resolved.midRun.enabled =
|
|
146
|
-
toBoolean(raw.midRun.enabled, "midRun.enabled", warnings) ?? resolved.midRun.enabled;
|
|
147
|
-
resolved.midRun.thresholdPercent =
|
|
148
|
-
toThresholdPercent(raw.midRun.thresholdPercent, "midRun.thresholdPercent", warnings) ??
|
|
149
|
-
resolved.midRun.thresholdPercent;
|
|
150
|
-
} else {
|
|
151
|
-
warnings.push("Ignoring midRun: expected a JSON object.");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
141
|
resolved.allowCompactionContinuityBreak =
|
|
155
142
|
toBoolean(raw.allowCompactionContinuityBreak, "allowCompactionContinuityBreak", warnings) ??
|
|
156
143
|
resolved.allowCompactionContinuityBreak;
|
package/src/extension-runtime.ts
CHANGED
|
@@ -671,7 +671,6 @@ export function registerExtensionRuntime(
|
|
|
671
671
|
pi: ExtensionAPI,
|
|
672
672
|
dependencies: ExtensionRuntimeDependencies = DEFAULT_DEPENDENCIES,
|
|
673
673
|
): void {
|
|
674
|
-
// Mid-run compaction is intentionally disabled regardless of config.
|
|
675
674
|
pi.on("session_start", (_event, ctx) => {
|
|
676
675
|
const { config, source, warnings } = dependencies.loadExtensionConfig();
|
|
677
676
|
if (!config.enabled) return;
|
package/src/runtime.ts
CHANGED
|
@@ -201,7 +201,16 @@ export async function resolveNativeCompactionEnvironment(
|
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
let sessionModel: RuntimeModel | undefined;
|
|
205
|
+
const branch = ctx.sessionManager.getBranch();
|
|
206
|
+
for (let index = branch.length - 1; index >= 0; index -= 1) {
|
|
207
|
+
const entry = branch[index];
|
|
208
|
+
if (entry?.type === "model_change") {
|
|
209
|
+
sessionModel = ctx.modelRegistry.find(entry.provider, entry.modelId);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const currentModel = ctx.model ?? sessionModel;
|
|
205
214
|
const descriptor = getRuntimeModelDescriptor(currentModel);
|
|
206
215
|
if (!currentModel || !descriptor.provider || !descriptor.api || !descriptor.model) {
|
|
207
216
|
return {
|
package/src/types.ts
CHANGED
|
@@ -33,14 +33,8 @@ export type DebugArtifactKind =
|
|
|
33
33
|
| "compaction-event"
|
|
34
34
|
| "lifecycle";
|
|
35
35
|
|
|
36
|
-
export type MidRunConfig = {
|
|
37
|
-
enabled: boolean;
|
|
38
|
-
thresholdPercent: number;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
36
|
export type ExtensionConfig = {
|
|
42
37
|
enabled: boolean;
|
|
43
|
-
midRun: MidRunConfig;
|
|
44
38
|
/**
|
|
45
39
|
* Allow a Responses session whose latest compaction was not created by this extension
|
|
46
40
|
* to restart native compaction from Pi's current serialized session context.
|
|
@@ -310,10 +304,6 @@ export function createNativeCompactionResult(
|
|
|
310
304
|
|
|
311
305
|
export const DEFAULT_EXTENSION_CONFIG: ExtensionConfig = {
|
|
312
306
|
enabled: true,
|
|
313
|
-
midRun: {
|
|
314
|
-
enabled: false,
|
|
315
|
-
thresholdPercent: 80,
|
|
316
|
-
},
|
|
317
307
|
allowCompactionContinuityBreak: false,
|
|
318
308
|
compactionModel: undefined,
|
|
319
309
|
compactionThinkingLevel: "off",
|
package/src/midrun.ts
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { loadExtensionConfig } from "./config";
|
|
3
|
-
import { writeDebugArtifact } from "./debug";
|
|
4
|
-
import { EXTENSION_ID, type LoadedExtensionConfig } from "./types";
|
|
5
|
-
|
|
6
|
-
type MidRunPhase = "idle" | "abort-pending" | "compacting" | "resume-pending" | "failed";
|
|
7
|
-
|
|
8
|
-
type MidRunState = {
|
|
9
|
-
phase: MidRunPhase;
|
|
10
|
-
generation: number;
|
|
11
|
-
sessionId?: string;
|
|
12
|
-
baselineCompactionId?: string;
|
|
13
|
-
triggerTokens?: number;
|
|
14
|
-
triggerPercent?: number;
|
|
15
|
-
triggerContextWindow?: number;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type ConfigLoader = () => LoadedExtensionConfig;
|
|
19
|
-
|
|
20
|
-
const RESUME_CUSTOM_TYPE = "pi-better-compaction-midrun-resume";
|
|
21
|
-
const RESUME_PROMPT = `[pi-better-compaction/midrun]
|
|
22
|
-
Context compaction completed. Continue the interrupted task from the exact point where execution stopped.`;
|
|
23
|
-
const MIDRUN_COMPACTION_INSTRUCTIONS =
|
|
24
|
-
"Preserve the active task, completed work, decisions, changed files, failures, current tool-loop state, and exact next steps so execution can resume immediately after compaction.";
|
|
25
|
-
|
|
26
|
-
function getSessionId(ctx: ExtensionContext): string | undefined {
|
|
27
|
-
try {
|
|
28
|
-
return ctx.sessionManager.getSessionId();
|
|
29
|
-
} catch {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function getLatestCompactionId(ctx: ExtensionContext): string | undefined {
|
|
35
|
-
const branch = ctx.sessionManager.getBranch();
|
|
36
|
-
for (let index = branch.length - 1; index >= 0; index--) {
|
|
37
|
-
const entry = branch[index];
|
|
38
|
-
if (entry?.type === "compaction") return entry.id;
|
|
39
|
-
}
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function resetState(state: MidRunState): void {
|
|
44
|
-
state.phase = "idle";
|
|
45
|
-
state.generation += 1;
|
|
46
|
-
state.sessionId = undefined;
|
|
47
|
-
state.baselineCompactionId = undefined;
|
|
48
|
-
state.triggerTokens = undefined;
|
|
49
|
-
state.triggerPercent = undefined;
|
|
50
|
-
state.triggerContextWindow = undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function sameSession(state: MidRunState, ctx: ExtensionContext): boolean {
|
|
54
|
-
return state.sessionId !== undefined && state.sessionId === getSessionId(ctx);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function notifyFailure(ctx: ExtensionContext, message: string): void {
|
|
58
|
-
if (ctx.hasUI) {
|
|
59
|
-
ctx.ui.notify(`${EXTENSION_ID}: ${message}`, "error");
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function scheduleResume(
|
|
64
|
-
pi: ExtensionAPI,
|
|
65
|
-
ctx: ExtensionContext,
|
|
66
|
-
state: MidRunState,
|
|
67
|
-
generation: number,
|
|
68
|
-
): void {
|
|
69
|
-
state.phase = "resume-pending";
|
|
70
|
-
|
|
71
|
-
setImmediate(() => {
|
|
72
|
-
if (
|
|
73
|
-
state.generation !== generation ||
|
|
74
|
-
state.phase !== "resume-pending" ||
|
|
75
|
-
!sameSession(state, ctx)
|
|
76
|
-
) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!ctx.isIdle() || ctx.hasPendingMessages()) {
|
|
81
|
-
resetState(state);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
resetState(state);
|
|
86
|
-
pi.sendMessage(
|
|
87
|
-
{
|
|
88
|
-
customType: RESUME_CUSTOM_TYPE,
|
|
89
|
-
content: RESUME_PROMPT,
|
|
90
|
-
display: false,
|
|
91
|
-
details: { source: "midrun-compaction" },
|
|
92
|
-
},
|
|
93
|
-
{ triggerTurn: true },
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function registerMidRunGuard(
|
|
99
|
-
pi: ExtensionAPI,
|
|
100
|
-
loadConfig: ConfigLoader = loadExtensionConfig,
|
|
101
|
-
): void {
|
|
102
|
-
const state: MidRunState = { phase: "idle", generation: 0 };
|
|
103
|
-
|
|
104
|
-
pi.on("turn_end", (event, ctx) => {
|
|
105
|
-
const { config } = loadConfig();
|
|
106
|
-
if (!config.enabled || !config.midRun.enabled || state.phase !== "idle") return;
|
|
107
|
-
if (event.toolResults.length === 0 || ctx.hasPendingMessages()) return;
|
|
108
|
-
|
|
109
|
-
const usage = ctx.getContextUsage();
|
|
110
|
-
if (!usage || usage.tokens == null || usage.percent == null) return;
|
|
111
|
-
if (usage.percent < config.midRun.thresholdPercent) return;
|
|
112
|
-
|
|
113
|
-
const sessionId = getSessionId(ctx);
|
|
114
|
-
if (!sessionId) return;
|
|
115
|
-
|
|
116
|
-
state.phase = "abort-pending";
|
|
117
|
-
state.generation += 1;
|
|
118
|
-
state.sessionId = sessionId;
|
|
119
|
-
state.baselineCompactionId = getLatestCompactionId(ctx);
|
|
120
|
-
state.triggerTokens = usage.tokens;
|
|
121
|
-
state.triggerPercent = usage.percent;
|
|
122
|
-
state.triggerContextWindow = usage.contextWindow;
|
|
123
|
-
|
|
124
|
-
writeDebugArtifact(
|
|
125
|
-
"lifecycle",
|
|
126
|
-
{
|
|
127
|
-
event: "midrun.threshold",
|
|
128
|
-
turnIndex: event.turnIndex,
|
|
129
|
-
tokens: usage.tokens,
|
|
130
|
-
contextWindow: usage.contextWindow,
|
|
131
|
-
percent: usage.percent,
|
|
132
|
-
thresholdPercent: config.midRun.thresholdPercent,
|
|
133
|
-
baselineCompactionId: state.baselineCompactionId,
|
|
134
|
-
},
|
|
135
|
-
config,
|
|
136
|
-
ctx,
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
// Never compact while the agent run is active; let Pi settle first.
|
|
140
|
-
ctx.abort();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
pi.on("agent_settled", (_event, ctx) => {
|
|
144
|
-
if (state.phase !== "abort-pending") return;
|
|
145
|
-
if (!sameSession(state, ctx)) {
|
|
146
|
-
resetState(state);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// An earlier agent_settled handler may already have started or queued another run.
|
|
151
|
-
if (!ctx.isIdle() || ctx.hasPendingMessages()) {
|
|
152
|
-
resetState(state);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const generation = state.generation;
|
|
157
|
-
const latestCompactionId = getLatestCompactionId(ctx);
|
|
158
|
-
if (latestCompactionId !== state.baselineCompactionId) {
|
|
159
|
-
const { config } = loadConfig();
|
|
160
|
-
writeDebugArtifact(
|
|
161
|
-
"lifecycle",
|
|
162
|
-
{
|
|
163
|
-
event: "midrun.coalesced",
|
|
164
|
-
reason: "compaction-already-occurred-during-abort",
|
|
165
|
-
baselineCompactionId: state.baselineCompactionId,
|
|
166
|
-
latestCompactionId,
|
|
167
|
-
},
|
|
168
|
-
config,
|
|
169
|
-
ctx,
|
|
170
|
-
);
|
|
171
|
-
scheduleResume(pi, ctx, state, generation);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const { config } = loadConfig();
|
|
176
|
-
if (!config.enabled || !config.midRun.enabled) {
|
|
177
|
-
scheduleResume(pi, ctx, state, generation);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const usage = ctx.getContextUsage();
|
|
182
|
-
if (usage?.percent != null && usage.percent < config.midRun.thresholdPercent) {
|
|
183
|
-
scheduleResume(pi, ctx, state, generation);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
state.phase = "compacting";
|
|
188
|
-
ctx.compact({
|
|
189
|
-
customInstructions: MIDRUN_COMPACTION_INSTRUCTIONS,
|
|
190
|
-
onComplete: () => {
|
|
191
|
-
if (
|
|
192
|
-
state.generation !== generation ||
|
|
193
|
-
state.phase !== "compacting" ||
|
|
194
|
-
!sameSession(state, ctx)
|
|
195
|
-
) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
scheduleResume(pi, ctx, state, generation);
|
|
199
|
-
},
|
|
200
|
-
onError: (error) => {
|
|
201
|
-
if (state.generation !== generation || !sameSession(state, ctx)) return;
|
|
202
|
-
|
|
203
|
-
const latest = getLatestCompactionId(ctx);
|
|
204
|
-
if (/Already compacted/i.test(error.message) && latest !== state.baselineCompactionId) {
|
|
205
|
-
scheduleResume(pi, ctx, state, generation);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
state.phase = "failed";
|
|
210
|
-
writeDebugArtifact(
|
|
211
|
-
"lifecycle",
|
|
212
|
-
{
|
|
213
|
-
event: "midrun.failed",
|
|
214
|
-
errorMessage: error.message,
|
|
215
|
-
triggerTokens: state.triggerTokens,
|
|
216
|
-
triggerPercent: state.triggerPercent,
|
|
217
|
-
triggerContextWindow: state.triggerContextWindow,
|
|
218
|
-
},
|
|
219
|
-
config,
|
|
220
|
-
ctx,
|
|
221
|
-
);
|
|
222
|
-
notifyFailure(ctx, `mid-run compaction failed: ${error.message}`);
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
pi.on("session_start", () => resetState(state));
|
|
228
|
-
pi.on("session_shutdown", () => resetState(state));
|
|
229
|
-
}
|