@narumitw/pi-codex-compact 0.50.0 → 0.50.2
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 +87 -85
- package/dist/chunks/settings-menu-BLWCLVPZ.ts +187 -0
- package/dist/chunks/settings-menu-BLWCLVPZ.ts.map +7 -0
- package/dist/index.ts +813 -0
- package/dist/index.ts.map +7 -0
- package/package.json +13 -9
- package/src/checkpoint.ts +31 -10
- package/src/codex-compact.ts +5 -3
package/dist/index.ts
ADDED
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
|
|
4
|
+
// src/codex-compact.ts
|
|
5
|
+
import { hasApi } from "@earendil-works/pi-ai";
|
|
6
|
+
import {
|
|
7
|
+
buildContextEntries,
|
|
8
|
+
buildSessionContext,
|
|
9
|
+
convertToLlm,
|
|
10
|
+
sessionEntryToContextMessages
|
|
11
|
+
} from "@earendil-works/pi-coding-agent";
|
|
12
|
+
|
|
13
|
+
// src/checkpoint.ts
|
|
14
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
15
|
+
|
|
16
|
+
// src/protocol.ts
|
|
17
|
+
var MAX_SSE_BYTES = 8 * 1024 * 1024;
|
|
18
|
+
var MAX_COMPACTION_ITEM_BYTES = 2 * 1024 * 1024;
|
|
19
|
+
var CodexCompactionProtocolError = class extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "CodexCompactionProtocolError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function isObject(value) {
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function byteLength(value) {
|
|
29
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
30
|
+
}
|
|
31
|
+
function isCompactionItem(value) {
|
|
32
|
+
return isObject(value) && value.type === "compaction" && typeof value.encrypted_content === "string" && value.encrypted_content.length > 0;
|
|
33
|
+
}
|
|
34
|
+
function validateCompactionItem(value, maxBytes = MAX_COMPACTION_ITEM_BYTES) {
|
|
35
|
+
if (!isCompactionItem(value)) {
|
|
36
|
+
throw new CodexCompactionProtocolError(
|
|
37
|
+
"Remote response did not contain a valid compaction item"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (byteLength(value) > maxBytes) {
|
|
41
|
+
throw new CodexCompactionProtocolError("Remote compaction item exceeded the size limit");
|
|
42
|
+
}
|
|
43
|
+
return structuredClone(value);
|
|
44
|
+
}
|
|
45
|
+
function compactionItemsFromEvent(event) {
|
|
46
|
+
const items = [];
|
|
47
|
+
if (event.type === "response.output_item.done" && isObject(event.item)) {
|
|
48
|
+
items.push(event.item);
|
|
49
|
+
}
|
|
50
|
+
if (event.type === "response.completed" && isObject(event.response)) {
|
|
51
|
+
const output = event.response.output;
|
|
52
|
+
if (Array.isArray(output)) items.push(...output);
|
|
53
|
+
}
|
|
54
|
+
return items.filter((item) => isObject(item) && item.type === "compaction");
|
|
55
|
+
}
|
|
56
|
+
async function collectCompactionSse(stream, options = {}) {
|
|
57
|
+
const maxBytes = options.maxBytes ?? MAX_SSE_BYTES;
|
|
58
|
+
const reader = stream.getReader();
|
|
59
|
+
const onAbort = () => {
|
|
60
|
+
void reader.cancel(new DOMException("Compaction aborted", "AbortError")).catch(() => void 0);
|
|
61
|
+
};
|
|
62
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
63
|
+
const decoder = new TextDecoder();
|
|
64
|
+
let bytes = 0;
|
|
65
|
+
let pending = "";
|
|
66
|
+
let dataLines = [];
|
|
67
|
+
let completedResponse;
|
|
68
|
+
const items = /* @__PURE__ */ new Map();
|
|
69
|
+
const checkAbort = () => {
|
|
70
|
+
if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
|
|
71
|
+
};
|
|
72
|
+
const dispatch = () => {
|
|
73
|
+
if (dataLines.length === 0) return;
|
|
74
|
+
const data = dataLines.join("\n");
|
|
75
|
+
dataLines = [];
|
|
76
|
+
if (data === "[DONE]") return;
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = JSON.parse(data);
|
|
80
|
+
} catch {
|
|
81
|
+
throw new CodexCompactionProtocolError("Remote compaction returned malformed SSE JSON");
|
|
82
|
+
}
|
|
83
|
+
if (!isObject(parsed)) return;
|
|
84
|
+
if (parsed.type === "response.completed") {
|
|
85
|
+
completedResponse = isObject(parsed.response) ? parsed.response : {};
|
|
86
|
+
}
|
|
87
|
+
for (const candidate of compactionItemsFromEvent(parsed)) {
|
|
88
|
+
const item = validateCompactionItem(candidate, options.maxItemBytes);
|
|
89
|
+
items.set(JSON.stringify(item), item);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const processLine = (line) => {
|
|
93
|
+
if (line === "") {
|
|
94
|
+
dispatch();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (line.startsWith(":")) return;
|
|
98
|
+
if (line === "data") dataLines.push("");
|
|
99
|
+
else if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
while (true) {
|
|
103
|
+
checkAbort();
|
|
104
|
+
const { done, value } = await reader.read();
|
|
105
|
+
if (done) break;
|
|
106
|
+
bytes += value.byteLength;
|
|
107
|
+
if (bytes > maxBytes) {
|
|
108
|
+
throw new CodexCompactionProtocolError("Remote compaction stream exceeded the size limit");
|
|
109
|
+
}
|
|
110
|
+
pending += decoder.decode(value, { stream: true });
|
|
111
|
+
let newline = pending.indexOf("\n");
|
|
112
|
+
while (newline !== -1) {
|
|
113
|
+
const rawLine = pending.slice(0, newline);
|
|
114
|
+
pending = pending.slice(newline + 1);
|
|
115
|
+
processLine(rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine);
|
|
116
|
+
newline = pending.indexOf("\n");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
pending += decoder.decode();
|
|
120
|
+
if (pending.length > 0) processLine(pending.endsWith("\r") ? pending.slice(0, -1) : pending);
|
|
121
|
+
dispatch();
|
|
122
|
+
checkAbort();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
await reader.cancel(error).catch(() => void 0);
|
|
125
|
+
throw error;
|
|
126
|
+
} finally {
|
|
127
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
128
|
+
reader.releaseLock();
|
|
129
|
+
}
|
|
130
|
+
if (!completedResponse) {
|
|
131
|
+
throw new CodexCompactionProtocolError(
|
|
132
|
+
"Remote compaction stream ended without response.completed"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (items.size !== 1) {
|
|
136
|
+
throw new CodexCompactionProtocolError(
|
|
137
|
+
`Remote compaction returned ${items.size} distinct compaction items; expected exactly one`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return { item: [...items.values()][0], completedResponse };
|
|
141
|
+
}
|
|
142
|
+
function markerTextFromItem(item) {
|
|
143
|
+
if (!isObject(item) || item.role !== "user" || !Array.isArray(item.content)) return void 0;
|
|
144
|
+
if (item.content.length !== 1) return void 0;
|
|
145
|
+
const content = item.content[0];
|
|
146
|
+
if (!isObject(content) || content.type !== "input_text" || typeof content.text !== "string") {
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
return content.text;
|
|
150
|
+
}
|
|
151
|
+
function rewriteCheckpointMarker(payload, marker, replacementHistory) {
|
|
152
|
+
if (!isObject(payload) || !Array.isArray(payload.input)) {
|
|
153
|
+
throw new CodexCompactionProtocolError("OpenAI Codex payload is missing an input array");
|
|
154
|
+
}
|
|
155
|
+
const matches = payload.input.map((item, index2) => markerTextFromItem(item) === marker ? index2 : -1).filter((index2) => index2 >= 0);
|
|
156
|
+
if (matches.length !== 1) {
|
|
157
|
+
throw new CodexCompactionProtocolError(
|
|
158
|
+
`Provider payload contained ${matches.length} checkpoint markers; expected exactly one`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const index = matches[0];
|
|
162
|
+
return {
|
|
163
|
+
...payload,
|
|
164
|
+
input: [
|
|
165
|
+
...payload.input.slice(0, index),
|
|
166
|
+
...structuredClone(replacementHistory),
|
|
167
|
+
...payload.input.slice(index + 1)
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function appendCompactionTrigger(payload) {
|
|
172
|
+
if (!isObject(payload) || !Array.isArray(payload.input)) {
|
|
173
|
+
throw new CodexCompactionProtocolError("OpenAI Codex payload is missing an input array");
|
|
174
|
+
}
|
|
175
|
+
if (payload.input.some((item) => isObject(item) && item.type === "compaction_trigger")) {
|
|
176
|
+
throw new CodexCompactionProtocolError(
|
|
177
|
+
"Provider payload already contains a compaction trigger"
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return { ...payload, input: [...payload.input, { type: "compaction_trigger" }] };
|
|
181
|
+
}
|
|
182
|
+
function prepareRemoteCompactionPayload(payload, checkpoint) {
|
|
183
|
+
const expanded = checkpoint ? rewriteCheckpointMarker(payload, checkpoint.marker, checkpoint.replacementHistory) : payload;
|
|
184
|
+
return appendCompactionTrigger(expanded);
|
|
185
|
+
}
|
|
186
|
+
function hasCheckpointMarker(payload, marker) {
|
|
187
|
+
return isObject(payload) && Array.isArray(payload.input) && payload.input.some((item) => markerTextFromItem(item) === marker);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/checkpoint.ts
|
|
191
|
+
var CHECKPOINT_KIND = "pi-codex-remote-compaction";
|
|
192
|
+
var CHECKPOINT_VERSION = 1;
|
|
193
|
+
var REPLACEMENT_TOKEN_BUDGET = 64e3;
|
|
194
|
+
var REPLACEMENT_BYTE_BUDGET = 8 * 1024 * 1024;
|
|
195
|
+
var MAX_MEDIA_ITEM_BYTES = 2 * 1024 * 1024;
|
|
196
|
+
function isObject2(value) {
|
|
197
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
198
|
+
}
|
|
199
|
+
function stableValue(value) {
|
|
200
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
201
|
+
if (!isObject2(value)) return value;
|
|
202
|
+
return Object.fromEntries(
|
|
203
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, stableValue(child)])
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
function serializedBytes(value) {
|
|
207
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
208
|
+
}
|
|
209
|
+
function fingerprintMessage(message) {
|
|
210
|
+
return createHash("sha256").update(JSON.stringify(stableValue(message))).digest("hex");
|
|
211
|
+
}
|
|
212
|
+
function checkpointMarker(checkpointId) {
|
|
213
|
+
return [
|
|
214
|
+
`[PI_CODEX_REMOTE_CHECKPOINT:${checkpointId}]`,
|
|
215
|
+
"Opaque checkpoint injection failed. Do not infer missing history; tell the user to re-enable",
|
|
216
|
+
"@narumitw/pi-codex-compact with an openai-codex model."
|
|
217
|
+
].join(" ");
|
|
218
|
+
}
|
|
219
|
+
function fallbackSummary(checkpointId) {
|
|
220
|
+
return [
|
|
221
|
+
`OpenAI Codex Remote Compaction V2 checkpoint ${checkpointId} stores the older history opaquely.`,
|
|
222
|
+
"Full replay requires @narumitw/pi-codex-compact and an openai-codex model.",
|
|
223
|
+
"Without them, only Pi's retained recent messages remain available."
|
|
224
|
+
].join(" ");
|
|
225
|
+
}
|
|
226
|
+
function markerMessage(checkpointId, timestamp) {
|
|
227
|
+
return {
|
|
228
|
+
role: "user",
|
|
229
|
+
content: [{ type: "text", text: checkpointMarker(checkpointId) }],
|
|
230
|
+
timestamp
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function parseCheckpointDetails(value) {
|
|
234
|
+
if (!isObject2(value)) return void 0;
|
|
235
|
+
if (value.kind !== CHECKPOINT_KIND || value.version !== CHECKPOINT_VERSION || typeof value.checkpointId !== "string" || value.checkpointId.length < 8 || value.provider !== "openai-codex" || value.api !== "openai-codex-responses" || typeof value.modelId !== "string" || value.protocol !== "remote-compaction-v2" || !Array.isArray(value.replacementHistory) || !Array.isArray(value.keptMessageFingerprints) || typeof value.createdAt !== "string") {
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
if (value.replacementHistory.length === 0 || !value.replacementHistory.every(isObject2) || !value.keptMessageFingerprints.every(
|
|
239
|
+
(fingerprint) => typeof fingerprint === "string" && /^[a-f0-9]{64}$/.test(fingerprint)
|
|
240
|
+
) || serializedBytes(value.replacementHistory) > REPLACEMENT_BYTE_BUDGET) {
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
const last = value.replacementHistory.at(-1);
|
|
244
|
+
try {
|
|
245
|
+
validateCompactionItem(last);
|
|
246
|
+
} catch {
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
return structuredClone(value);
|
|
250
|
+
}
|
|
251
|
+
function latestCheckpoint(entries) {
|
|
252
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
253
|
+
const entry = entries[index];
|
|
254
|
+
if (entry.type !== "compaction") continue;
|
|
255
|
+
const details = parseCheckpointDetails(entry.details);
|
|
256
|
+
return details ? { entry, details } : void 0;
|
|
257
|
+
}
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
function isOlderCompactionSummary(message, timestamp) {
|
|
261
|
+
return message.role === "compactionSummary" && Number.isFinite(message.timestamp) && Number.isFinite(timestamp) && message.timestamp < timestamp;
|
|
262
|
+
}
|
|
263
|
+
function projectCheckpointContext(messages, details) {
|
|
264
|
+
const summary = fallbackSummary(details.checkpointId);
|
|
265
|
+
const summaryIndex = messages.findIndex(
|
|
266
|
+
(message) => message.role === "compactionSummary" && message.summary === summary
|
|
267
|
+
);
|
|
268
|
+
if (summaryIndex < 0) return void 0;
|
|
269
|
+
const timestamp = messages[summaryIndex].timestamp;
|
|
270
|
+
let messageIndex = summaryIndex + 1;
|
|
271
|
+
let fingerprintIndex = 0;
|
|
272
|
+
while (fingerprintIndex < details.keptMessageFingerprints.length) {
|
|
273
|
+
if (messageIndex >= messages.length) return void 0;
|
|
274
|
+
const message = messages[messageIndex];
|
|
275
|
+
if (fingerprintMessage(message) === details.keptMessageFingerprints[fingerprintIndex]) {
|
|
276
|
+
messageIndex += 1;
|
|
277
|
+
fingerprintIndex += 1;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (isOlderCompactionSummary(message, timestamp)) {
|
|
281
|
+
messageIndex += 1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
return void 0;
|
|
285
|
+
}
|
|
286
|
+
while (messageIndex < messages.length && isOlderCompactionSummary(messages[messageIndex], timestamp)) {
|
|
287
|
+
messageIndex += 1;
|
|
288
|
+
}
|
|
289
|
+
return [
|
|
290
|
+
...messages.slice(0, summaryIndex),
|
|
291
|
+
markerMessage(details.checkpointId, timestamp),
|
|
292
|
+
...messages.slice(messageIndex)
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
function rawText(item) {
|
|
296
|
+
if (!Array.isArray(item.content)) return "";
|
|
297
|
+
return item.content.flatMap(
|
|
298
|
+
(part) => isObject2(part) && typeof part.text === "string" && part.type === "input_text" ? [part.text] : []
|
|
299
|
+
).join("\n");
|
|
300
|
+
}
|
|
301
|
+
function hasMedia(item) {
|
|
302
|
+
return Array.isArray(item.content) && item.content.some((part) => isObject2(part) && part.type === "input_image");
|
|
303
|
+
}
|
|
304
|
+
function truncateTextItem(item, maxChars) {
|
|
305
|
+
if (!Array.isArray(item.content) || maxChars <= 32) return void 0;
|
|
306
|
+
let remaining = maxChars - 16;
|
|
307
|
+
const content = [...item.content].reverse().flatMap((part) => {
|
|
308
|
+
if (!isObject2(part) || part.type !== "input_text" || typeof part.text !== "string" || remaining <= 0) {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
const text = part.text.slice(-remaining);
|
|
312
|
+
remaining -= text.length;
|
|
313
|
+
return [{ ...part, text: `[truncated]
|
|
314
|
+
${text}` }];
|
|
315
|
+
});
|
|
316
|
+
if (content.length === 0) return void 0;
|
|
317
|
+
return { ...item, content: content.reverse() };
|
|
318
|
+
}
|
|
319
|
+
function buildReplacementHistory(input, compactionItem, options = {}) {
|
|
320
|
+
const tokenBudget = options.tokenBudget ?? REPLACEMENT_TOKEN_BUDGET;
|
|
321
|
+
const byteBudget = options.byteBudget ?? REPLACEMENT_BYTE_BUDGET;
|
|
322
|
+
const opaque = validateCompactionItem(compactionItem);
|
|
323
|
+
let remainingBytes = byteBudget - serializedBytes(opaque);
|
|
324
|
+
let remainingChars = tokenBudget * 4;
|
|
325
|
+
if (remainingBytes <= 0)
|
|
326
|
+
throw new Error("Opaque compaction item exceeds replacement history budget");
|
|
327
|
+
const retainedNewestFirst = [];
|
|
328
|
+
const candidates = input.filter(
|
|
329
|
+
(item) => isObject2(item) && item.role === "user" && item.type !== "compaction_trigger"
|
|
330
|
+
);
|
|
331
|
+
for (let index = candidates.length - 1; index >= 0; index--) {
|
|
332
|
+
const candidate = candidates[index];
|
|
333
|
+
const bytes = serializedBytes(candidate);
|
|
334
|
+
if (hasMedia(candidate) && bytes > MAX_MEDIA_ITEM_BYTES) continue;
|
|
335
|
+
const text = rawText(candidate);
|
|
336
|
+
let retained = candidate;
|
|
337
|
+
if (text.length > remainingChars) {
|
|
338
|
+
if (hasMedia(candidate)) continue;
|
|
339
|
+
const truncated = truncateTextItem(candidate, remainingChars);
|
|
340
|
+
if (!truncated) continue;
|
|
341
|
+
retained = truncated;
|
|
342
|
+
}
|
|
343
|
+
if (serializedBytes(retained) > remainingBytes) {
|
|
344
|
+
if (hasMedia(retained)) continue;
|
|
345
|
+
const maxCharsByBytes = Math.max(0, remainingBytes - 128);
|
|
346
|
+
const truncated = truncateTextItem(retained, Math.min(remainingChars, maxCharsByBytes));
|
|
347
|
+
if (!truncated || serializedBytes(truncated) > remainingBytes) continue;
|
|
348
|
+
retained = truncated;
|
|
349
|
+
}
|
|
350
|
+
retainedNewestFirst.push(structuredClone(retained));
|
|
351
|
+
remainingBytes -= serializedBytes(retained);
|
|
352
|
+
remainingChars -= Math.min(remainingChars, rawText(retained).length);
|
|
353
|
+
if (remainingBytes <= 128 || remainingChars <= 32) break;
|
|
354
|
+
}
|
|
355
|
+
return [...retainedNewestFirst.reverse(), opaque];
|
|
356
|
+
}
|
|
357
|
+
function createCheckpointDetails(input) {
|
|
358
|
+
const details = {
|
|
359
|
+
kind: CHECKPOINT_KIND,
|
|
360
|
+
version: CHECKPOINT_VERSION,
|
|
361
|
+
checkpointId: input.checkpointId ?? randomUUID(),
|
|
362
|
+
provider: "openai-codex",
|
|
363
|
+
api: "openai-codex-responses",
|
|
364
|
+
modelId: input.modelId,
|
|
365
|
+
protocol: "remote-compaction-v2",
|
|
366
|
+
replacementHistory: structuredClone(input.replacementHistory),
|
|
367
|
+
keptMessageFingerprints: input.keptMessages.map(fingerprintMessage),
|
|
368
|
+
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
369
|
+
};
|
|
370
|
+
const parsed = parseCheckpointDetails(details);
|
|
371
|
+
if (!parsed) throw new Error("Created an invalid Codex checkpoint");
|
|
372
|
+
return parsed;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/remote.ts
|
|
376
|
+
var EMPTY_USAGE = {
|
|
377
|
+
input: 0,
|
|
378
|
+
output: 0,
|
|
379
|
+
cacheRead: 0,
|
|
380
|
+
cacheWrite: 0,
|
|
381
|
+
totalTokens: 0,
|
|
382
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
383
|
+
};
|
|
384
|
+
function isObject3(value) {
|
|
385
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
386
|
+
}
|
|
387
|
+
function abortError() {
|
|
388
|
+
return new DOMException("Compaction aborted", "AbortError");
|
|
389
|
+
}
|
|
390
|
+
async function requestRemoteCompaction(request) {
|
|
391
|
+
if (request.signal.aborted) throw abortError();
|
|
392
|
+
let sentInput;
|
|
393
|
+
const inspections = [];
|
|
394
|
+
const baseFetch = request.fetch ?? globalThis.fetch;
|
|
395
|
+
const inspectedFetch = async (input, init) => {
|
|
396
|
+
const response = await baseFetch(input, init);
|
|
397
|
+
if (!response.ok || !response.body) return response;
|
|
398
|
+
const [providerBody, inspectionBody] = response.body.tee();
|
|
399
|
+
const inspection2 = collectCompactionSse(inspectionBody, { signal: request.signal }).then(
|
|
400
|
+
(value) => ({ ok: true, value }),
|
|
401
|
+
(error) => ({ ok: false, error })
|
|
402
|
+
);
|
|
403
|
+
inspections.push(inspection2);
|
|
404
|
+
return new Response(providerBody, {
|
|
405
|
+
status: response.status,
|
|
406
|
+
statusText: response.statusText,
|
|
407
|
+
headers: response.headers
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
const stream = request.provider.stream(request.model, request.context, {
|
|
411
|
+
apiKey: request.apiKey,
|
|
412
|
+
headers: request.headers,
|
|
413
|
+
env: request.env,
|
|
414
|
+
signal: request.signal,
|
|
415
|
+
transport: "sse",
|
|
416
|
+
cacheRetention: "none",
|
|
417
|
+
timeoutMs: request.requestTimeoutMs ?? 5 * 60 * 1e3,
|
|
418
|
+
maxRetries: request.maxRetries ?? 2,
|
|
419
|
+
fetch: inspectedFetch,
|
|
420
|
+
onPayload: (payload) => {
|
|
421
|
+
const prepared = prepareRemoteCompactionPayload(payload, request.priorCheckpoint);
|
|
422
|
+
if (!Array.isArray(prepared.input) || !prepared.input.every(isObject3)) {
|
|
423
|
+
throw new CodexCompactionProtocolError(
|
|
424
|
+
"Prepared compaction payload has invalid input items"
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
sentInput = structuredClone(prepared.input.slice(0, -1));
|
|
428
|
+
return prepared;
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
let usage = EMPTY_USAGE;
|
|
432
|
+
for await (const event of stream) {
|
|
433
|
+
if (request.signal.aborted) throw abortError();
|
|
434
|
+
if (event.type === "error") {
|
|
435
|
+
throw new Error(event.error.errorMessage ?? "OpenAI Codex compaction request failed");
|
|
436
|
+
}
|
|
437
|
+
if (event.type === "done") usage = event.message.usage;
|
|
438
|
+
}
|
|
439
|
+
if (request.signal.aborted) throw abortError();
|
|
440
|
+
if (!sentInput)
|
|
441
|
+
throw new CodexCompactionProtocolError("Provider did not expose a request payload");
|
|
442
|
+
if (inspections.length === 0) {
|
|
443
|
+
throw new CodexCompactionProtocolError("Provider response did not expose an SSE body");
|
|
444
|
+
}
|
|
445
|
+
const inspection = await inspections.at(-1);
|
|
446
|
+
if (request.signal.aborted) throw abortError();
|
|
447
|
+
if (!inspection?.ok) throw inspection?.error ?? new Error("Remote compaction inspection failed");
|
|
448
|
+
return { item: inspection.value.item, promptInput: sentInput, usage };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/settings.ts
|
|
452
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
453
|
+
import { constants } from "node:fs";
|
|
454
|
+
import { mkdir, open, rename, rm, writeFile } from "node:fs/promises";
|
|
455
|
+
import { basename, dirname, join } from "node:path";
|
|
456
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
457
|
+
var CODEX_COMPACT_SETTINGS_FILE = "pi-codex-compact.json";
|
|
458
|
+
var MAX_SETTINGS_BYTES = 64 * 1024;
|
|
459
|
+
var DEFAULT_CODEX_COMPACT_SETTINGS = Object.freeze({
|
|
460
|
+
enabled: true,
|
|
461
|
+
requestTimeoutMs: 3e5,
|
|
462
|
+
maxRetries: 2,
|
|
463
|
+
replacementTokenBudget: 64e3,
|
|
464
|
+
notifyOnFallback: true
|
|
465
|
+
});
|
|
466
|
+
var LIMITS = Object.freeze({
|
|
467
|
+
requestTimeoutMs: { minimum: 3e4, maximum: 6e5 },
|
|
468
|
+
maxRetries: { minimum: 0, maximum: 2 },
|
|
469
|
+
replacementTokenBudget: { minimum: 8e3, maximum: 128e3 }
|
|
470
|
+
});
|
|
471
|
+
function isRecord(value) {
|
|
472
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
473
|
+
}
|
|
474
|
+
function validInteger(value, minimum, maximum) {
|
|
475
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= minimum && value <= maximum;
|
|
476
|
+
}
|
|
477
|
+
function normalizeCodexCompactSettings(value) {
|
|
478
|
+
if (!isRecord(value)) return void 0;
|
|
479
|
+
if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") return void 0;
|
|
480
|
+
if (Object.hasOwn(value, "notifyOnFallback") && typeof value.notifyOnFallback !== "boolean") {
|
|
481
|
+
return void 0;
|
|
482
|
+
}
|
|
483
|
+
for (const [field, limits] of Object.entries(LIMITS)) {
|
|
484
|
+
if (Object.hasOwn(value, field) && !validInteger(value[field], limits.minimum, limits.maximum)) {
|
|
485
|
+
return void 0;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
enabled: typeof value.enabled === "boolean" ? value.enabled : DEFAULT_CODEX_COMPACT_SETTINGS.enabled,
|
|
490
|
+
requestTimeoutMs: typeof value.requestTimeoutMs === "number" ? value.requestTimeoutMs : DEFAULT_CODEX_COMPACT_SETTINGS.requestTimeoutMs,
|
|
491
|
+
maxRetries: typeof value.maxRetries === "number" ? value.maxRetries : DEFAULT_CODEX_COMPACT_SETTINGS.maxRetries,
|
|
492
|
+
replacementTokenBudget: typeof value.replacementTokenBudget === "number" ? value.replacementTokenBudget : DEFAULT_CODEX_COMPACT_SETTINGS.replacementTokenBudget,
|
|
493
|
+
notifyOnFallback: typeof value.notifyOnFallback === "boolean" ? value.notifyOnFallback : DEFAULT_CODEX_COMPACT_SETTINGS.notifyOnFallback
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function codexCompactSettingsPath() {
|
|
497
|
+
return join(getAgentDir(), CODEX_COMPACT_SETTINGS_FILE);
|
|
498
|
+
}
|
|
499
|
+
function aborted(signal) {
|
|
500
|
+
if (signal?.aborted) throw new DOMException("Settings operation aborted", "AbortError");
|
|
501
|
+
}
|
|
502
|
+
async function loadCodexCompactSettings(path = codexCompactSettingsPath(), signal) {
|
|
503
|
+
aborted(signal);
|
|
504
|
+
try {
|
|
505
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
506
|
+
let text;
|
|
507
|
+
try {
|
|
508
|
+
const stats = await handle.stat();
|
|
509
|
+
aborted(signal);
|
|
510
|
+
if (!stats.isFile()) throw new Error("settings path is not a regular file");
|
|
511
|
+
if (stats.size > MAX_SETTINGS_BYTES) throw new Error("settings file exceeds 64 KiB");
|
|
512
|
+
text = await handle.readFile("utf8");
|
|
513
|
+
} finally {
|
|
514
|
+
await handle.close();
|
|
515
|
+
}
|
|
516
|
+
aborted(signal);
|
|
517
|
+
const document = JSON.parse(text);
|
|
518
|
+
const settings = normalizeCodexCompactSettings(document);
|
|
519
|
+
if (!settings || !isRecord(document)) throw new Error("invalid settings shape or bounds");
|
|
520
|
+
return { kind: "loaded", path, settings, document };
|
|
521
|
+
} catch (error) {
|
|
522
|
+
if (signal?.aborted) throw error;
|
|
523
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
524
|
+
return {
|
|
525
|
+
kind: "missing",
|
|
526
|
+
path,
|
|
527
|
+
settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
|
|
528
|
+
document: {}
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
kind: "invalid",
|
|
533
|
+
path,
|
|
534
|
+
settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
|
|
535
|
+
issue: isNodeError(error) && error.code === "ELOOP" ? "symbolic links are not accepted" : error instanceof Error ? error.message : String(error)
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async function savePatch(path, patch, signal) {
|
|
540
|
+
const latest = await loadCodexCompactSettings(path, signal);
|
|
541
|
+
if (latest.kind === "invalid") {
|
|
542
|
+
throw new Error(
|
|
543
|
+
"Cannot overwrite an invalid pi-codex-compact.json; repair it and reload first"
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
const document = { ...latest.document, ...patch };
|
|
547
|
+
const settings = normalizeCodexCompactSettings(document);
|
|
548
|
+
if (!settings) throw new Error("Refusing to save invalid Codex compaction settings");
|
|
549
|
+
const temporaryPath = join(dirname(path), `.${basename(path)}.${randomUUID2()}.tmp`);
|
|
550
|
+
await mkdir(dirname(path), { recursive: true });
|
|
551
|
+
aborted(signal);
|
|
552
|
+
try {
|
|
553
|
+
await writeFile(temporaryPath, `${JSON.stringify(document, null, 2)}
|
|
554
|
+
`, {
|
|
555
|
+
encoding: "utf8",
|
|
556
|
+
flag: "wx",
|
|
557
|
+
mode: 384
|
|
558
|
+
});
|
|
559
|
+
aborted(signal);
|
|
560
|
+
const current = await loadCodexCompactSettings(path, signal);
|
|
561
|
+
if (current.kind === "invalid" || current.kind !== latest.kind || JSON.stringify(current.document) !== JSON.stringify(latest.document)) {
|
|
562
|
+
throw new Error("pi-codex-compact.json changed while saving; reopen settings and retry");
|
|
563
|
+
}
|
|
564
|
+
await rename(temporaryPath, path);
|
|
565
|
+
} finally {
|
|
566
|
+
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
567
|
+
}
|
|
568
|
+
return { kind: "loaded", path, settings, document };
|
|
569
|
+
}
|
|
570
|
+
function createCodexCompactSettingsRuntime(path = codexCompactSettingsPath()) {
|
|
571
|
+
let state = {
|
|
572
|
+
kind: "missing",
|
|
573
|
+
path,
|
|
574
|
+
settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
|
|
575
|
+
document: {}
|
|
576
|
+
};
|
|
577
|
+
let queue = Promise.resolve();
|
|
578
|
+
const enqueue = (operation) => {
|
|
579
|
+
const result = queue.then(operation, operation);
|
|
580
|
+
queue = result.then(
|
|
581
|
+
() => void 0,
|
|
582
|
+
() => void 0
|
|
583
|
+
);
|
|
584
|
+
return result;
|
|
585
|
+
};
|
|
586
|
+
return {
|
|
587
|
+
get: () => structuredClone(state),
|
|
588
|
+
reload: (signal) => enqueue(async () => {
|
|
589
|
+
state = await loadCodexCompactSettings(path, signal);
|
|
590
|
+
return structuredClone(state);
|
|
591
|
+
}),
|
|
592
|
+
update: (patch, signal) => enqueue(async () => {
|
|
593
|
+
state = await savePatch(path, patch, signal);
|
|
594
|
+
return structuredClone(state);
|
|
595
|
+
}),
|
|
596
|
+
flush: () => queue
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function isNodeError(error) {
|
|
600
|
+
return error instanceof Error && "code" in error;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/codex-compact.ts
|
|
604
|
+
var STATUS_KEY = "codex-compact";
|
|
605
|
+
function isSupportedModel(model) {
|
|
606
|
+
return model?.provider === "openai-codex" && hasApi(model, "openai-codex-responses");
|
|
607
|
+
}
|
|
608
|
+
function activeCheckpoint(ctx) {
|
|
609
|
+
return latestCheckpoint(ctx.sessionManager.getBranch());
|
|
610
|
+
}
|
|
611
|
+
function isCheckpointCompatible(details, model) {
|
|
612
|
+
return isSupportedModel(model) && model.id === details.modelId;
|
|
613
|
+
}
|
|
614
|
+
function keptMessages(event) {
|
|
615
|
+
const leafId = event.branchEntries.at(-1)?.id ?? null;
|
|
616
|
+
const contextEntries = buildContextEntries(event.branchEntries, leafId);
|
|
617
|
+
const keptIndex = contextEntries.findIndex(
|
|
618
|
+
(entry) => entry.id === event.preparation.firstKeptEntryId
|
|
619
|
+
);
|
|
620
|
+
if (keptIndex < 0) {
|
|
621
|
+
throw new Error("Pi compaction cut point is not present in the active context");
|
|
622
|
+
}
|
|
623
|
+
return contextEntries.slice(keptIndex).flatMap(sessionEntryToContextMessages);
|
|
624
|
+
}
|
|
625
|
+
function activeTools(pi) {
|
|
626
|
+
const enabled = new Set(pi.getActiveTools());
|
|
627
|
+
return pi.getAllTools().filter((tool) => enabled.has(tool.name)).map((tool) => ({
|
|
628
|
+
name: tool.name,
|
|
629
|
+
description: tool.description,
|
|
630
|
+
parameters: tool.parameters
|
|
631
|
+
}));
|
|
632
|
+
}
|
|
633
|
+
function projectedCurrentMessages(event, model) {
|
|
634
|
+
const leafId = event.branchEntries.at(-1)?.id ?? null;
|
|
635
|
+
const session = buildSessionContext(event.branchEntries, leafId);
|
|
636
|
+
const prior = latestCheckpoint(event.branchEntries)?.details;
|
|
637
|
+
if (!prior) return { messages: session.messages };
|
|
638
|
+
if (prior.modelId !== model.id) {
|
|
639
|
+
throw new Error("The active opaque checkpoint belongs to a different Codex model");
|
|
640
|
+
}
|
|
641
|
+
const projected = projectCheckpointContext(session.messages, prior);
|
|
642
|
+
if (!projected) {
|
|
643
|
+
throw new Error("The previous opaque checkpoint could not be projected safely");
|
|
644
|
+
}
|
|
645
|
+
return { messages: projected, prior };
|
|
646
|
+
}
|
|
647
|
+
function notifyFailure(ctx, error, settings) {
|
|
648
|
+
if (!ctx.hasUI || !settings.notifyOnFallback) return;
|
|
649
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
650
|
+
ctx.ui.notify(`Codex remote compaction failed; using Pi compaction. ${message}`, "warning");
|
|
651
|
+
}
|
|
652
|
+
function sessionStillOwned(ctx, sessionId, signal) {
|
|
653
|
+
return !signal.aborted && ctx.sessionManager.getSessionId() === sessionId;
|
|
654
|
+
}
|
|
655
|
+
async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
656
|
+
const model = ctx.model;
|
|
657
|
+
if (!settings.enabled || !isSupportedModel(model)) return void 0;
|
|
658
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
659
|
+
ctx.ui.setStatus(STATUS_KEY, "Codex remote compaction\u2026");
|
|
660
|
+
try {
|
|
661
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
662
|
+
if (!sessionStillOwned(ctx, sessionId, event.signal)) return { cancel: true };
|
|
663
|
+
if (!auth.ok || !auth.apiKey) {
|
|
664
|
+
throw new Error(auth.ok ? "OpenAI Codex OAuth token is unavailable" : auth.error);
|
|
665
|
+
}
|
|
666
|
+
const provider = ctx.modelRegistry.getProvider(model.provider);
|
|
667
|
+
if (!provider) throw new Error("OpenAI Codex provider is unavailable");
|
|
668
|
+
const current = projectedCurrentMessages(event, model);
|
|
669
|
+
const context = {
|
|
670
|
+
systemPrompt: ctx.getSystemPrompt(),
|
|
671
|
+
messages: convertToLlm(current.messages),
|
|
672
|
+
tools: activeTools(pi)
|
|
673
|
+
};
|
|
674
|
+
const response = await requestRemoteCompaction({
|
|
675
|
+
provider,
|
|
676
|
+
model,
|
|
677
|
+
context,
|
|
678
|
+
apiKey: auth.apiKey,
|
|
679
|
+
headers: auth.headers,
|
|
680
|
+
env: auth.env,
|
|
681
|
+
signal: event.signal,
|
|
682
|
+
priorCheckpoint: current.prior ? {
|
|
683
|
+
marker: checkpointMarker(current.prior.checkpointId),
|
|
684
|
+
replacementHistory: current.prior.replacementHistory
|
|
685
|
+
} : void 0,
|
|
686
|
+
requestTimeoutMs: settings.requestTimeoutMs,
|
|
687
|
+
maxRetries: settings.maxRetries,
|
|
688
|
+
fetch
|
|
689
|
+
});
|
|
690
|
+
if (!sessionStillOwned(ctx, sessionId, event.signal)) return { cancel: true };
|
|
691
|
+
const replacementHistory = buildReplacementHistory(response.promptInput, response.item, {
|
|
692
|
+
tokenBudget: settings.replacementTokenBudget
|
|
693
|
+
});
|
|
694
|
+
const details = createCheckpointDetails({
|
|
695
|
+
modelId: model.id,
|
|
696
|
+
replacementHistory,
|
|
697
|
+
keptMessages: keptMessages(event)
|
|
698
|
+
});
|
|
699
|
+
return {
|
|
700
|
+
compaction: {
|
|
701
|
+
summary: fallbackSummary(details.checkpointId),
|
|
702
|
+
firstKeptEntryId: event.preparation.firstKeptEntryId,
|
|
703
|
+
tokensBefore: event.preparation.tokensBefore,
|
|
704
|
+
usage: response.usage,
|
|
705
|
+
details
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
} catch (error) {
|
|
709
|
+
if (event.signal.aborted || ctx.sessionManager.getSessionId() !== sessionId) {
|
|
710
|
+
return { cancel: true };
|
|
711
|
+
}
|
|
712
|
+
notifyFailure(ctx, error, settings);
|
|
713
|
+
return void 0;
|
|
714
|
+
} finally {
|
|
715
|
+
if (ctx.sessionManager.getSessionId() === sessionId) ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function createCodexCompactExtension(options = {}) {
|
|
719
|
+
return (pi) => {
|
|
720
|
+
const providerWarnings = /* @__PURE__ */ new Set();
|
|
721
|
+
const settingsRuntime = options.settingsRuntime ?? createCodexCompactSettingsRuntime();
|
|
722
|
+
let sessionController = new AbortController();
|
|
723
|
+
let generation = 0;
|
|
724
|
+
pi.registerCommand("codex-compact", {
|
|
725
|
+
description: "Compact now or configure Codex Remote Compaction V2",
|
|
726
|
+
handler: async (_args, ctx) => {
|
|
727
|
+
const ownerGeneration = generation;
|
|
728
|
+
const controller = sessionController;
|
|
729
|
+
const { showCodexCompactMenu } = await import("./chunks/settings-menu-BLWCLVPZ.js");
|
|
730
|
+
if (ownerGeneration !== generation || controller.signal.aborted) return;
|
|
731
|
+
await showCodexCompactMenu(settingsRuntime, ctx, {
|
|
732
|
+
signal: controller.signal,
|
|
733
|
+
isCurrent: () => ownerGeneration === generation && !controller.signal.aborted
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
738
|
+
sessionController.abort();
|
|
739
|
+
sessionController = new AbortController();
|
|
740
|
+
generation += 1;
|
|
741
|
+
const ownerGeneration = generation;
|
|
742
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
743
|
+
providerWarnings.clear();
|
|
744
|
+
let state;
|
|
745
|
+
try {
|
|
746
|
+
state = await settingsRuntime.reload(sessionController.signal);
|
|
747
|
+
} catch (error) {
|
|
748
|
+
if (sessionController.signal.aborted || ownerGeneration !== generation) return;
|
|
749
|
+
if (ctx.hasUI) {
|
|
750
|
+
ctx.ui.notify(
|
|
751
|
+
`Could not load pi-codex-compact.json; using defaults. ${error instanceof Error ? error.message : String(error)}`,
|
|
752
|
+
"warning"
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (sessionController.signal.aborted || ownerGeneration !== generation || ctx.sessionManager.getSessionId() !== sessionId) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (ctx.hasUI && state.kind === "invalid") {
|
|
761
|
+
ctx.ui.notify(
|
|
762
|
+
`Invalid pi-codex-compact.json; using defaults without overwriting it. ${state.issue}`,
|
|
763
|
+
"warning"
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
pi.on(
|
|
768
|
+
"session_before_compact",
|
|
769
|
+
(event, ctx) => compactRemotely(pi, event, ctx, settingsRuntime.get().settings, options.fetch)
|
|
770
|
+
);
|
|
771
|
+
pi.on("context", (event, ctx) => {
|
|
772
|
+
if (!settingsRuntime.get().settings.enabled) return void 0;
|
|
773
|
+
const checkpoint = activeCheckpoint(ctx);
|
|
774
|
+
if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model)) return void 0;
|
|
775
|
+
const messages = projectCheckpointContext(event.messages, checkpoint.details);
|
|
776
|
+
return messages ? { messages } : void 0;
|
|
777
|
+
});
|
|
778
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
779
|
+
if (!settingsRuntime.get().settings.enabled) return void 0;
|
|
780
|
+
const checkpoint = activeCheckpoint(ctx);
|
|
781
|
+
if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model)) return void 0;
|
|
782
|
+
const marker = checkpointMarker(checkpoint.details.checkpointId);
|
|
783
|
+
if (!hasCheckpointMarker(event.payload, marker)) return void 0;
|
|
784
|
+
return rewriteCheckpointMarker(event.payload, marker, checkpoint.details.replacementHistory);
|
|
785
|
+
});
|
|
786
|
+
pi.on("model_select", (event, ctx) => {
|
|
787
|
+
if (!settingsRuntime.get().settings.enabled) return;
|
|
788
|
+
const checkpoint = activeCheckpoint(ctx);
|
|
789
|
+
if (!checkpoint || isCheckpointCompatible(checkpoint.details, event.model)) return;
|
|
790
|
+
const key = `${ctx.sessionManager.getSessionId()}:${event.model.provider}:${event.model.id}`;
|
|
791
|
+
if (providerWarnings.has(key)) return;
|
|
792
|
+
providerWarnings.add(key);
|
|
793
|
+
if (ctx.hasUI) {
|
|
794
|
+
ctx.ui.notify(
|
|
795
|
+
"The active Codex checkpoint cannot replay on this model; Pi will expose only its fallback marker and retained recent messages.",
|
|
796
|
+
"warning"
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
801
|
+
generation += 1;
|
|
802
|
+
sessionController.abort();
|
|
803
|
+
providerWarnings.clear();
|
|
804
|
+
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
805
|
+
await settingsRuntime.flush();
|
|
806
|
+
});
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
var codex_compact_default = createCodexCompactExtension();
|
|
810
|
+
export {
|
|
811
|
+
codex_compact_default as default
|
|
812
|
+
};
|
|
813
|
+
//# sourceMappingURL=index.ts.map
|