@narumitw/pi-codex-compact 0.51.3 → 0.52.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/README.md +82 -70
- package/dist/chunks/chunk-JYXZMCXD.js +43 -0
- package/dist/chunks/chunk-JYXZMCXD.js.map +7 -0
- package/dist/chunks/{settings-menu-PEDLURUH.js → settings-menu-VYQ6ABCP.js} +54 -27
- package/dist/chunks/settings-menu-VYQ6ABCP.js.map +7 -0
- package/dist/index.ts +515 -72
- package/dist/index.ts.map +4 -4
- package/package.json +7 -10
- package/src/checkpoint.ts +48 -13
- package/src/codex-compact.ts +60 -33
- package/src/model-api.ts +43 -3
- package/src/protocol.ts +162 -4
- package/src/remote-compact.ts +298 -0
- package/src/remote-shared.ts +19 -0
- package/src/remote-types.ts +45 -0
- package/src/remote-v2.ts +70 -0
- package/src/remote.ts +12 -112
- package/src/settings-menu.ts +55 -20
- package/src/settings.ts +15 -0
- package/src/terminal.ts +6 -0
- package/dist/chunks/chunk-6TZ2L2BM.js +0 -13
- package/dist/chunks/chunk-6TZ2L2BM.js.map +0 -7
- package/dist/chunks/settings-menu-PEDLURUH.js.map +0 -7
package/dist/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
2
|
// @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
RESPONSES_COMPACTION_APIS,
|
|
5
|
+
resolveCompactionRoute,
|
|
6
|
+
terminalText,
|
|
7
|
+
usesResponsesCompactionApi
|
|
8
|
+
} from "./chunks/chunk-JYXZMCXD.js";
|
|
6
9
|
|
|
7
10
|
// src/codex-compact.ts
|
|
8
11
|
import {
|
|
@@ -17,6 +20,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
17
20
|
|
|
18
21
|
// src/protocol.ts
|
|
19
22
|
var MAX_SSE_BYTES = 8 * 1024 * 1024;
|
|
23
|
+
var MAX_COMPACT_JSON_BYTES = 8 * 1024 * 1024;
|
|
20
24
|
var MAX_COMPACTION_ITEM_BYTES = 2 * 1024 * 1024;
|
|
21
25
|
var CodexCompactionProtocolError = class extends Error {
|
|
22
26
|
constructor(message) {
|
|
@@ -141,6 +145,115 @@ async function collectCompactionSse(stream, options = {}) {
|
|
|
141
145
|
}
|
|
142
146
|
return { item: [...items.values()][0], completedResponse };
|
|
143
147
|
}
|
|
148
|
+
function isRetainedCompactContent(value) {
|
|
149
|
+
if (!isObject(value)) return false;
|
|
150
|
+
if (value.type === "input_text") return typeof value.text === "string";
|
|
151
|
+
if (value.type !== "input_image") return false;
|
|
152
|
+
if (value.detail !== void 0 && value.detail !== null && value.detail !== "auto" && value.detail !== "low" && value.detail !== "high" && value.detail !== "original") {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
if (value.file_id !== void 0 && value.file_id !== null && typeof value.file_id !== "string" || value.image_url !== void 0 && value.image_url !== null && typeof value.image_url !== "string") {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
return typeof value.file_id === "string" && value.file_id.length > 0 || typeof value.image_url === "string" && value.image_url.length > 0;
|
|
159
|
+
}
|
|
160
|
+
function isRetainedCompactMessage(value) {
|
|
161
|
+
return isObject(value) && value.role === "user" && (value.type === void 0 || value.type === "message") && Array.isArray(value.content) && value.content.length > 0 && value.content.every(isRetainedCompactContent);
|
|
162
|
+
}
|
|
163
|
+
function validateCompactedResponse(value, options = {}) {
|
|
164
|
+
if (!isObject(value) || !Array.isArray(value.output)) {
|
|
165
|
+
throw new CodexCompactionProtocolError("Responses Compact returned an invalid response object");
|
|
166
|
+
}
|
|
167
|
+
const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
|
|
168
|
+
const maxItemBytes = options.maxItemBytes ?? MAX_COMPACTION_ITEM_BYTES;
|
|
169
|
+
if (byteLength(value) > maxBytes) {
|
|
170
|
+
throw new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
|
|
171
|
+
}
|
|
172
|
+
if (value.output.length === 0) {
|
|
173
|
+
throw new CodexCompactionProtocolError("Responses Compact returned no output items");
|
|
174
|
+
}
|
|
175
|
+
const output = value.output.map((item2) => {
|
|
176
|
+
if (!isObject(item2)) {
|
|
177
|
+
throw new CodexCompactionProtocolError("Responses Compact returned a non-object output item");
|
|
178
|
+
}
|
|
179
|
+
if (byteLength(item2) > maxItemBytes) {
|
|
180
|
+
throw new CodexCompactionProtocolError(
|
|
181
|
+
"Responses Compact output item exceeded the size limit"
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return structuredClone(item2);
|
|
185
|
+
});
|
|
186
|
+
const compactionItems = output.filter((item2) => item2.type === "compaction");
|
|
187
|
+
if (compactionItems.length !== 1 || output.at(-1)?.type !== "compaction") {
|
|
188
|
+
throw new CodexCompactionProtocolError(
|
|
189
|
+
"Responses Compact must return retained messages followed by one compaction item"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
for (const item2 of output.slice(0, -1)) {
|
|
193
|
+
if (!isRetainedCompactMessage(item2)) {
|
|
194
|
+
throw new CodexCompactionProtocolError(
|
|
195
|
+
"Responses Compact returned an unsupported retained output item"
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const item = validateCompactionItem(output.at(-1), maxItemBytes);
|
|
200
|
+
return { item, output: [...output.slice(0, -1), item], response: structuredClone(value) };
|
|
201
|
+
}
|
|
202
|
+
async function collectCompactResponse(response, options = {}) {
|
|
203
|
+
const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
|
|
204
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
205
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
206
|
+
const error = new CodexCompactionProtocolError(
|
|
207
|
+
"Responses Compact response exceeded the size limit"
|
|
208
|
+
);
|
|
209
|
+
await response.body?.cancel(error).catch(() => void 0);
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
if (!response.body) {
|
|
213
|
+
throw new CodexCompactionProtocolError("Responses Compact response did not contain a body");
|
|
214
|
+
}
|
|
215
|
+
const reader = response.body.getReader();
|
|
216
|
+
const chunks = [];
|
|
217
|
+
let bytes = 0;
|
|
218
|
+
const onAbort = () => {
|
|
219
|
+
void reader.cancel(new DOMException("Compaction aborted", "AbortError")).catch(() => void 0);
|
|
220
|
+
};
|
|
221
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
222
|
+
try {
|
|
223
|
+
while (true) {
|
|
224
|
+
if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
|
|
225
|
+
const { done, value } = await reader.read();
|
|
226
|
+
if (done) break;
|
|
227
|
+
bytes += value.byteLength;
|
|
228
|
+
if (bytes > maxBytes) {
|
|
229
|
+
throw new CodexCompactionProtocolError(
|
|
230
|
+
"Responses Compact response exceeded the size limit"
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
chunks.push(value);
|
|
234
|
+
}
|
|
235
|
+
if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
|
|
236
|
+
} catch (error) {
|
|
237
|
+
await reader.cancel(error).catch(() => void 0);
|
|
238
|
+
throw error;
|
|
239
|
+
} finally {
|
|
240
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
241
|
+
reader.releaseLock();
|
|
242
|
+
}
|
|
243
|
+
const body = new Uint8Array(bytes);
|
|
244
|
+
let offset = 0;
|
|
245
|
+
for (const chunk of chunks) {
|
|
246
|
+
body.set(chunk, offset);
|
|
247
|
+
offset += chunk.byteLength;
|
|
248
|
+
}
|
|
249
|
+
let parsed;
|
|
250
|
+
try {
|
|
251
|
+
parsed = JSON.parse(new TextDecoder().decode(body));
|
|
252
|
+
} catch {
|
|
253
|
+
throw new CodexCompactionProtocolError("Responses Compact returned malformed JSON");
|
|
254
|
+
}
|
|
255
|
+
return validateCompactedResponse(parsed, options);
|
|
256
|
+
}
|
|
144
257
|
function markerTextFromItem(item) {
|
|
145
258
|
if (!isObject(item) || item.role !== "user" || !Array.isArray(item.content)) return void 0;
|
|
146
259
|
if (item.content.length !== 1) return void 0;
|
|
@@ -181,9 +294,17 @@ function appendCompactionTrigger(payload) {
|
|
|
181
294
|
}
|
|
182
295
|
return { ...payload, input: [...payload.input, { type: "compaction_trigger" }] };
|
|
183
296
|
}
|
|
297
|
+
function expandRemoteCompactionPayload(payload, checkpoint) {
|
|
298
|
+
if (checkpoint) {
|
|
299
|
+
return rewriteCheckpointMarker(payload, checkpoint.marker, checkpoint.replacementHistory);
|
|
300
|
+
}
|
|
301
|
+
if (!isObject(payload) || !Array.isArray(payload.input)) {
|
|
302
|
+
throw new CodexCompactionProtocolError("Responses payload is missing an input array");
|
|
303
|
+
}
|
|
304
|
+
return structuredClone(payload);
|
|
305
|
+
}
|
|
184
306
|
function prepareRemoteCompactionPayload(payload, checkpoint) {
|
|
185
|
-
|
|
186
|
-
return appendCompactionTrigger(expanded);
|
|
307
|
+
return appendCompactionTrigger(expandRemoteCompactionPayload(payload, checkpoint));
|
|
187
308
|
}
|
|
188
309
|
function hasCheckpointMarker(payload, marker) {
|
|
189
310
|
return isObject(payload) && Array.isArray(payload.input) && payload.input.some((item) => markerTextFromItem(item) === marker);
|
|
@@ -191,11 +312,15 @@ function hasCheckpointMarker(payload, marker) {
|
|
|
191
312
|
|
|
192
313
|
// src/checkpoint.ts
|
|
193
314
|
var CHECKPOINT_KIND = "pi-codex-remote-compaction";
|
|
194
|
-
var CHECKPOINT_VERSION =
|
|
315
|
+
var CHECKPOINT_VERSION = 2;
|
|
195
316
|
var REPLACEMENT_TOKEN_BUDGET = 64e3;
|
|
196
317
|
var REPLACEMENT_BYTE_BUDGET = 8 * 1024 * 1024;
|
|
197
318
|
var MAX_MEDIA_ITEM_BYTES = 2 * 1024 * 1024;
|
|
319
|
+
var MAX_CHECKPOINT_DETAILS_BYTES = 10 * 1024 * 1024;
|
|
320
|
+
var MAX_CHECKPOINT_ID_LENGTH = 128;
|
|
198
321
|
var MAX_PROVIDER_ID_LENGTH = 256;
|
|
322
|
+
var MAX_MODEL_ID_LENGTH = 512;
|
|
323
|
+
var MAX_KEPT_FINGERPRINTS = 1e5;
|
|
199
324
|
function isObject2(value) {
|
|
200
325
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
201
326
|
}
|
|
@@ -216,13 +341,13 @@ function checkpointMarker(checkpointId) {
|
|
|
216
341
|
return [
|
|
217
342
|
`[PI_CODEX_REMOTE_CHECKPOINT:${checkpointId}]`,
|
|
218
343
|
"Opaque checkpoint injection failed. Do not infer missing history; tell the user to re-enable",
|
|
219
|
-
"@narumitw/pi-codex-compact with
|
|
344
|
+
"@narumitw/pi-codex-compact with the same model and Responses API."
|
|
220
345
|
].join(" ");
|
|
221
346
|
}
|
|
222
347
|
function fallbackSummary(checkpointId) {
|
|
223
348
|
return [
|
|
224
|
-
`
|
|
225
|
-
"Full replay requires @narumitw/pi-codex-compact and the same model through a compatible
|
|
349
|
+
`Responses compaction checkpoint ${checkpointId} stores the older history opaquely.`,
|
|
350
|
+
"Full replay requires @narumitw/pi-codex-compact and the same model through a compatible Responses provider.",
|
|
226
351
|
"Without them, only Pi's retained recent messages remain available."
|
|
227
352
|
].join(" ");
|
|
228
353
|
}
|
|
@@ -235,7 +360,14 @@ function markerMessage(checkpointId, timestamp) {
|
|
|
235
360
|
}
|
|
236
361
|
function parseCheckpointDetails(value) {
|
|
237
362
|
if (!isObject2(value)) return void 0;
|
|
238
|
-
|
|
363
|
+
try {
|
|
364
|
+
if (serializedBytes(value) > MAX_CHECKPOINT_DETAILS_BYTES) return void 0;
|
|
365
|
+
} catch {
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
const isVersionOne = value.version === 1 && value.api === "openai-codex-responses" && value.protocol === "remote-compaction-v2";
|
|
369
|
+
const isVersionTwo = value.version === CHECKPOINT_VERSION && RESPONSES_COMPACTION_APIS.includes(value.api) && (value.protocol === "remote-v2" || value.protocol === "responses-compact");
|
|
370
|
+
if (value.kind !== CHECKPOINT_KIND || !isVersionOne && !isVersionTwo || typeof value.checkpointId !== "string" || value.checkpointId.length < 8 || value.checkpointId.length > MAX_CHECKPOINT_ID_LENGTH || typeof value.provider !== "string" || value.provider.length === 0 || value.provider.length > MAX_PROVIDER_ID_LENGTH || typeof value.modelId !== "string" || value.modelId.length === 0 || value.modelId.length > MAX_MODEL_ID_LENGTH || !Array.isArray(value.replacementHistory) || !Array.isArray(value.keptMessageFingerprints) || value.keptMessageFingerprints.length > MAX_KEPT_FINGERPRINTS || typeof value.createdAt !== "string" || value.createdAt.length > 64) {
|
|
239
371
|
return void 0;
|
|
240
372
|
}
|
|
241
373
|
if (value.replacementHistory.length === 0 || !value.replacementHistory.every(isObject2) || !value.keptMessageFingerprints.every(
|
|
@@ -249,7 +381,18 @@ function parseCheckpointDetails(value) {
|
|
|
249
381
|
} catch {
|
|
250
382
|
return void 0;
|
|
251
383
|
}
|
|
252
|
-
return
|
|
384
|
+
return {
|
|
385
|
+
kind: CHECKPOINT_KIND,
|
|
386
|
+
version: CHECKPOINT_VERSION,
|
|
387
|
+
checkpointId: value.checkpointId,
|
|
388
|
+
provider: value.provider,
|
|
389
|
+
api: isVersionOne ? "openai-codex-responses" : value.api,
|
|
390
|
+
modelId: value.modelId,
|
|
391
|
+
protocol: isVersionOne ? "remote-v2" : value.protocol,
|
|
392
|
+
replacementHistory: structuredClone(value.replacementHistory),
|
|
393
|
+
keptMessageFingerprints: [...value.keptMessageFingerprints],
|
|
394
|
+
createdAt: value.createdAt
|
|
395
|
+
};
|
|
253
396
|
}
|
|
254
397
|
function latestCheckpoint(entries) {
|
|
255
398
|
for (let index = entries.length - 1; index >= 0; index--) {
|
|
@@ -362,9 +505,9 @@ function createCheckpointDetails(input) {
|
|
|
362
505
|
version: CHECKPOINT_VERSION,
|
|
363
506
|
checkpointId: input.checkpointId ?? randomUUID(),
|
|
364
507
|
provider: input.provider,
|
|
365
|
-
api:
|
|
508
|
+
api: input.api,
|
|
366
509
|
modelId: input.modelId,
|
|
367
|
-
protocol:
|
|
510
|
+
protocol: input.protocol,
|
|
368
511
|
replacementHistory: structuredClone(input.replacementHistory),
|
|
369
512
|
keptMessageFingerprints: input.keptMessages.map(fingerprintMessage),
|
|
370
513
|
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -374,22 +517,298 @@ function createCheckpointDetails(input) {
|
|
|
374
517
|
return parsed;
|
|
375
518
|
}
|
|
376
519
|
|
|
377
|
-
// src/remote.ts
|
|
378
|
-
|
|
379
|
-
input: 0,
|
|
380
|
-
output: 0,
|
|
381
|
-
cacheRead: 0,
|
|
382
|
-
cacheWrite: 0,
|
|
383
|
-
totalTokens: 0,
|
|
384
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
385
|
-
};
|
|
386
|
-
function isObject3(value) {
|
|
520
|
+
// src/remote-types.ts
|
|
521
|
+
function isJsonObject(value) {
|
|
387
522
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
388
523
|
}
|
|
389
524
|
function abortError() {
|
|
390
525
|
return new DOMException("Compaction aborted", "AbortError");
|
|
391
526
|
}
|
|
392
|
-
|
|
527
|
+
function assertPreparedInput(payload) {
|
|
528
|
+
if (!Array.isArray(payload.input) || !payload.input.every(isJsonObject)) {
|
|
529
|
+
throw new Error("Prepared compaction payload has invalid input items");
|
|
530
|
+
}
|
|
531
|
+
return structuredClone(payload.input);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/remote-shared.ts
|
|
535
|
+
async function collectProviderUsage(stream, signal) {
|
|
536
|
+
let usage;
|
|
537
|
+
for await (const event of stream) {
|
|
538
|
+
if (signal.aborted) throw abortError();
|
|
539
|
+
if (event.type === "error") {
|
|
540
|
+
throw new Error(event.error.errorMessage ?? "Responses compaction request failed");
|
|
541
|
+
}
|
|
542
|
+
if (event.type === "done") usage = event.message.usage;
|
|
543
|
+
}
|
|
544
|
+
if (signal.aborted) throw abortError();
|
|
545
|
+
if (!usage) throw new Error("Responses provider stream ended without completion usage");
|
|
546
|
+
return usage;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/remote-compact.ts
|
|
550
|
+
var OFFICIAL_COMPACT_FIELDS = [
|
|
551
|
+
"model",
|
|
552
|
+
"input",
|
|
553
|
+
"instructions",
|
|
554
|
+
"previous_response_id",
|
|
555
|
+
"prompt_cache_key",
|
|
556
|
+
"prompt_cache_retention",
|
|
557
|
+
"service_tier"
|
|
558
|
+
];
|
|
559
|
+
var CODEX_COMPACT_FIELDS = [
|
|
560
|
+
"model",
|
|
561
|
+
"input",
|
|
562
|
+
"instructions",
|
|
563
|
+
"tools",
|
|
564
|
+
"parallel_tool_calls",
|
|
565
|
+
"reasoning",
|
|
566
|
+
"service_tier",
|
|
567
|
+
"prompt_cache_key",
|
|
568
|
+
"text",
|
|
569
|
+
"access_programs"
|
|
570
|
+
];
|
|
571
|
+
function compactPayload(payload, api) {
|
|
572
|
+
const fields = api === "openai-codex-responses" ? CODEX_COMPACT_FIELDS : OFFICIAL_COMPACT_FIELDS;
|
|
573
|
+
const result = {};
|
|
574
|
+
for (const field of fields) {
|
|
575
|
+
if (Object.hasOwn(payload, field) && payload[field] !== void 0) {
|
|
576
|
+
result[field] = structuredClone(payload[field]);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (typeof result.model !== "string" || result.model.length === 0) {
|
|
580
|
+
throw new CodexCompactionProtocolError("Responses payload is missing a model");
|
|
581
|
+
}
|
|
582
|
+
assertPreparedInput(result);
|
|
583
|
+
return result;
|
|
584
|
+
}
|
|
585
|
+
function requestUrl(input) {
|
|
586
|
+
return new URL(input instanceof Request ? input.url : String(input));
|
|
587
|
+
}
|
|
588
|
+
function responsesCompactUrl(input) {
|
|
589
|
+
const original = requestUrl(input);
|
|
590
|
+
if (!original.pathname.endsWith("/responses")) {
|
|
591
|
+
throw new CodexCompactionProtocolError(
|
|
592
|
+
"Provider request URL does not end with the Responses endpoint"
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
const compact = new URL(original);
|
|
596
|
+
compact.pathname = `${compact.pathname}/compact`;
|
|
597
|
+
if (compact.origin !== original.origin) {
|
|
598
|
+
throw new CodexCompactionProtocolError("Responses Compact URL changed origin");
|
|
599
|
+
}
|
|
600
|
+
return compact;
|
|
601
|
+
}
|
|
602
|
+
function mergedHeaders(input, init) {
|
|
603
|
+
const headers = new Headers(input instanceof Request ? input.headers : void 0);
|
|
604
|
+
new Headers(init?.headers).forEach((value, name) => {
|
|
605
|
+
headers.set(name, value);
|
|
606
|
+
});
|
|
607
|
+
headers.delete("content-encoding");
|
|
608
|
+
headers.delete("content-length");
|
|
609
|
+
headers.set("accept", "application/json");
|
|
610
|
+
headers.set("content-type", "application/json");
|
|
611
|
+
return headers;
|
|
612
|
+
}
|
|
613
|
+
function mergedSignal(input, init, ownerSignal) {
|
|
614
|
+
const signals = [ownerSignal];
|
|
615
|
+
if (input instanceof Request) signals.push(input.signal);
|
|
616
|
+
if (init?.signal) signals.push(init.signal);
|
|
617
|
+
return signals.length === 1 ? ownerSignal : AbortSignal.any(signals);
|
|
618
|
+
}
|
|
619
|
+
function nonRetryableBridgeFailure(error) {
|
|
620
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
621
|
+
return Response.json(
|
|
622
|
+
{
|
|
623
|
+
error: {
|
|
624
|
+
message,
|
|
625
|
+
type: "invalid_request_error",
|
|
626
|
+
code: "invalid_compact_response"
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
{ status: 400 }
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
function nonNegativeInteger(value) {
|
|
633
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
634
|
+
}
|
|
635
|
+
function optionalUsageDetail(details, field) {
|
|
636
|
+
if (details === void 0 || details === null) return 0;
|
|
637
|
+
if (!isJsonObject(details)) {
|
|
638
|
+
throw new CodexCompactionProtocolError("Responses Compact response has invalid usage details");
|
|
639
|
+
}
|
|
640
|
+
const value = details[field];
|
|
641
|
+
if (value === void 0 || value === null) return 0;
|
|
642
|
+
if (!nonNegativeInteger(value)) {
|
|
643
|
+
throw new CodexCompactionProtocolError(
|
|
644
|
+
`Responses Compact response has invalid usage detail ${field}`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
return value;
|
|
648
|
+
}
|
|
649
|
+
function validatedUsage(response) {
|
|
650
|
+
const usage = response.usage;
|
|
651
|
+
if (!isJsonObject(usage)) {
|
|
652
|
+
throw new CodexCompactionProtocolError("Responses Compact response is missing usage");
|
|
653
|
+
}
|
|
654
|
+
for (const field of ["input_tokens", "output_tokens", "total_tokens"]) {
|
|
655
|
+
if (!nonNegativeInteger(usage[field])) {
|
|
656
|
+
throw new CodexCompactionProtocolError(`Responses Compact response has invalid ${field}`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
const inputTokens = usage.input_tokens;
|
|
660
|
+
const outputTokens = usage.output_tokens;
|
|
661
|
+
const totalTokens = usage.total_tokens;
|
|
662
|
+
const cachedTokens = optionalUsageDetail(usage.input_tokens_details, "cached_tokens");
|
|
663
|
+
const cacheWriteTokens = optionalUsageDetail(usage.input_tokens_details, "cache_write_tokens");
|
|
664
|
+
const reasoningTokens = optionalUsageDetail(usage.output_tokens_details, "reasoning_tokens");
|
|
665
|
+
if (cachedTokens + cacheWriteTokens > inputTokens || reasoningTokens > outputTokens) {
|
|
666
|
+
throw new CodexCompactionProtocolError(
|
|
667
|
+
"Responses Compact response has inconsistent usage details"
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
if (totalTokens !== inputTokens + outputTokens) {
|
|
671
|
+
throw new CodexCompactionProtocolError(
|
|
672
|
+
"Responses Compact response has inconsistent total usage"
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
return structuredClone(usage);
|
|
676
|
+
}
|
|
677
|
+
function syntheticCompletion(result, payload) {
|
|
678
|
+
const completed = {
|
|
679
|
+
id: typeof result.response.id === "string" ? result.response.id : "resp_pi_compact_bridge",
|
|
680
|
+
object: "response",
|
|
681
|
+
created_at: typeof result.response.created_at === "number" ? result.response.created_at : Math.floor(Date.now() / 1e3),
|
|
682
|
+
status: "completed",
|
|
683
|
+
model: payload.model,
|
|
684
|
+
output: [],
|
|
685
|
+
parallel_tool_calls: false,
|
|
686
|
+
tool_choice: "auto",
|
|
687
|
+
tools: [],
|
|
688
|
+
usage: validatedUsage(result.response)
|
|
689
|
+
};
|
|
690
|
+
const events = [
|
|
691
|
+
{ type: "response.created", response: { ...completed, status: "in_progress" } },
|
|
692
|
+
{ type: "response.completed", response: completed }
|
|
693
|
+
];
|
|
694
|
+
return new Response(events.map((event) => `data: ${JSON.stringify(event)}
|
|
695
|
+
|
|
696
|
+
`).join(""), {
|
|
697
|
+
status: 200,
|
|
698
|
+
headers: { "content-type": "text/event-stream" }
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
async function requestResponsesCompact(request) {
|
|
702
|
+
if (request.signal.aborted) throw abortError();
|
|
703
|
+
let preparedPayload;
|
|
704
|
+
let sentInput;
|
|
705
|
+
let compactResult;
|
|
706
|
+
let bridgeError;
|
|
707
|
+
let dispatchInFlight = false;
|
|
708
|
+
let successfulResponses = 0;
|
|
709
|
+
const baseFetch = request.fetch ?? globalThis.fetch;
|
|
710
|
+
const bridgeFetch = async (input, init) => {
|
|
711
|
+
if (request.signal.aborted) throw abortError();
|
|
712
|
+
if (successfulResponses > 0) {
|
|
713
|
+
bridgeError = new CodexCompactionProtocolError(
|
|
714
|
+
"Provider dispatched again after Responses Compact succeeded"
|
|
715
|
+
);
|
|
716
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
717
|
+
}
|
|
718
|
+
if (dispatchInFlight) {
|
|
719
|
+
bridgeError = new CodexCompactionProtocolError(
|
|
720
|
+
"Provider dispatched overlapping Responses Compact requests"
|
|
721
|
+
);
|
|
722
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
723
|
+
}
|
|
724
|
+
if (!preparedPayload) {
|
|
725
|
+
bridgeError = new CodexCompactionProtocolError(
|
|
726
|
+
"Provider dispatched before exposing its request payload"
|
|
727
|
+
);
|
|
728
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
729
|
+
}
|
|
730
|
+
let compactUrl;
|
|
731
|
+
try {
|
|
732
|
+
compactUrl = responsesCompactUrl(input);
|
|
733
|
+
} catch (error) {
|
|
734
|
+
bridgeError = error;
|
|
735
|
+
return nonRetryableBridgeFailure(error);
|
|
736
|
+
}
|
|
737
|
+
const signal = mergedSignal(input, init, request.signal);
|
|
738
|
+
dispatchInFlight = true;
|
|
739
|
+
try {
|
|
740
|
+
const response = await baseFetch(compactUrl, {
|
|
741
|
+
...init,
|
|
742
|
+
method: "POST",
|
|
743
|
+
headers: mergedHeaders(input, init),
|
|
744
|
+
body: JSON.stringify(preparedPayload),
|
|
745
|
+
signal
|
|
746
|
+
});
|
|
747
|
+
if (!response.ok) return response;
|
|
748
|
+
try {
|
|
749
|
+
const result = await collectCompactResponse(response, { signal });
|
|
750
|
+
successfulResponses += 1;
|
|
751
|
+
if (successfulResponses !== 1) {
|
|
752
|
+
throw new CodexCompactionProtocolError(
|
|
753
|
+
"Provider returned more than one successful Responses Compact response"
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
compactResult = result;
|
|
757
|
+
return syntheticCompletion(result, preparedPayload);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
bridgeError = error;
|
|
760
|
+
return nonRetryableBridgeFailure(error);
|
|
761
|
+
}
|
|
762
|
+
} finally {
|
|
763
|
+
dispatchInFlight = false;
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
const stream = request.provider.stream(request.model, request.context, {
|
|
767
|
+
apiKey: request.apiKey,
|
|
768
|
+
headers: request.headers,
|
|
769
|
+
env: request.env,
|
|
770
|
+
signal: request.signal,
|
|
771
|
+
transport: "sse",
|
|
772
|
+
cacheRetention: "none",
|
|
773
|
+
timeoutMs: request.requestTimeoutMs ?? 5 * 60 * 1e3,
|
|
774
|
+
maxRetries: request.maxRetries ?? 2,
|
|
775
|
+
fetch: bridgeFetch,
|
|
776
|
+
onPayload: (payload) => {
|
|
777
|
+
if (preparedPayload) {
|
|
778
|
+
throw new CodexCompactionProtocolError(
|
|
779
|
+
"Provider exposed more than one compaction request payload"
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
const expanded = expandRemoteCompactionPayload(payload, request.priorCheckpoint);
|
|
783
|
+
preparedPayload = compactPayload(expanded, request.model.api);
|
|
784
|
+
sentInput = assertPreparedInput(preparedPayload);
|
|
785
|
+
return expanded;
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
let usage;
|
|
789
|
+
try {
|
|
790
|
+
usage = await collectProviderUsage(stream, request.signal);
|
|
791
|
+
} catch (error) {
|
|
792
|
+
if (bridgeError) throw bridgeError;
|
|
793
|
+
throw error;
|
|
794
|
+
}
|
|
795
|
+
if (request.signal.aborted) throw abortError();
|
|
796
|
+
if (bridgeError) throw bridgeError;
|
|
797
|
+
if (!preparedPayload || !sentInput || !compactResult || successfulResponses !== 1) {
|
|
798
|
+
throw new CodexCompactionProtocolError(
|
|
799
|
+
"Provider did not complete exactly one Responses Compact request"
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
item: compactResult.item,
|
|
804
|
+
promptInput: sentInput,
|
|
805
|
+
compactedOutput: compactResult.output,
|
|
806
|
+
usage
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/remote-v2.ts
|
|
811
|
+
async function requestRemoteCompactionV2(request) {
|
|
393
812
|
if (request.signal.aborted) throw abortError();
|
|
394
813
|
let sentInput;
|
|
395
814
|
const inspections = [];
|
|
@@ -421,35 +840,30 @@ async function requestRemoteCompaction(request) {
|
|
|
421
840
|
fetch: inspectedFetch,
|
|
422
841
|
onPayload: (payload) => {
|
|
423
842
|
const prepared = prepareRemoteCompactionPayload(payload, request.priorCheckpoint);
|
|
424
|
-
|
|
425
|
-
throw new CodexCompactionProtocolError(
|
|
426
|
-
"Prepared compaction payload has invalid input items"
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
sentInput = structuredClone(prepared.input.slice(0, -1));
|
|
843
|
+
sentInput = assertPreparedInput(prepared).slice(0, -1);
|
|
430
844
|
return prepared;
|
|
431
845
|
}
|
|
432
846
|
});
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (request.signal.aborted) throw abortError();
|
|
436
|
-
if (event.type === "error") {
|
|
437
|
-
throw new Error(event.error.errorMessage ?? "Codex remote compaction request failed");
|
|
438
|
-
}
|
|
439
|
-
if (event.type === "done") usage = event.message.usage;
|
|
440
|
-
}
|
|
441
|
-
if (request.signal.aborted) throw abortError();
|
|
442
|
-
if (!sentInput)
|
|
847
|
+
const usage = await collectProviderUsage(stream, request.signal);
|
|
848
|
+
if (!sentInput) {
|
|
443
849
|
throw new CodexCompactionProtocolError("Provider did not expose a request payload");
|
|
444
|
-
if (inspections.length === 0) {
|
|
445
|
-
throw new CodexCompactionProtocolError("Provider response did not expose an SSE body");
|
|
446
850
|
}
|
|
447
|
-
|
|
851
|
+
if (inspections.length !== 1) {
|
|
852
|
+
throw new CodexCompactionProtocolError(
|
|
853
|
+
`Provider exposed ${inspections.length} successful SSE responses; expected exactly one`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
const inspection = await inspections[0];
|
|
448
857
|
if (request.signal.aborted) throw abortError();
|
|
449
|
-
if (!inspection
|
|
858
|
+
if (!inspection.ok) throw inspection.error;
|
|
450
859
|
return { item: inspection.value.item, promptInput: sentInput, usage };
|
|
451
860
|
}
|
|
452
861
|
|
|
862
|
+
// src/remote.ts
|
|
863
|
+
function requestRemoteCompaction(request) {
|
|
864
|
+
return request.protocol === "responses-compact" ? requestResponsesCompact(request) : requestRemoteCompactionV2(request);
|
|
865
|
+
}
|
|
866
|
+
|
|
453
867
|
// src/settings.ts
|
|
454
868
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
455
869
|
import { constants } from "node:fs";
|
|
@@ -460,6 +874,7 @@ var CODEX_COMPACT_SETTINGS_FILE = "pi-codex-compact.json";
|
|
|
460
874
|
var MAX_SETTINGS_BYTES = 64 * 1024;
|
|
461
875
|
var DEFAULT_CODEX_COMPACT_SETTINGS = Object.freeze({
|
|
462
876
|
enabled: true,
|
|
877
|
+
protocol: "auto",
|
|
463
878
|
requestTimeoutMs: 3e5,
|
|
464
879
|
maxRetries: 2,
|
|
465
880
|
replacementTokenBudget: 64e3,
|
|
@@ -479,6 +894,9 @@ function validInteger(value, minimum, maximum) {
|
|
|
479
894
|
function normalizeCodexCompactSettings(value) {
|
|
480
895
|
if (!isRecord(value)) return void 0;
|
|
481
896
|
if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") return void 0;
|
|
897
|
+
if (Object.hasOwn(value, "protocol") && value.protocol !== "auto" && value.protocol !== "remote-v2" && value.protocol !== "responses-compact") {
|
|
898
|
+
return void 0;
|
|
899
|
+
}
|
|
482
900
|
if (Object.hasOwn(value, "notifyOnFallback") && typeof value.notifyOnFallback !== "boolean") {
|
|
483
901
|
return void 0;
|
|
484
902
|
}
|
|
@@ -489,6 +907,7 @@ function normalizeCodexCompactSettings(value) {
|
|
|
489
907
|
}
|
|
490
908
|
return {
|
|
491
909
|
enabled: typeof value.enabled === "boolean" ? value.enabled : DEFAULT_CODEX_COMPACT_SETTINGS.enabled,
|
|
910
|
+
protocol: value.protocol === "remote-v2" || value.protocol === "responses-compact" ? value.protocol : DEFAULT_CODEX_COMPACT_SETTINGS.protocol,
|
|
492
911
|
requestTimeoutMs: typeof value.requestTimeoutMs === "number" ? value.requestTimeoutMs : DEFAULT_CODEX_COMPACT_SETTINGS.requestTimeoutMs,
|
|
493
912
|
maxRetries: typeof value.maxRetries === "number" ? value.maxRetries : DEFAULT_CODEX_COMPACT_SETTINGS.maxRetries,
|
|
494
913
|
replacementTokenBudget: typeof value.replacementTokenBudget === "number" ? value.replacementTokenBudget : DEFAULT_CODEX_COMPACT_SETTINGS.replacementTokenBudget,
|
|
@@ -608,7 +1027,7 @@ function activeCheckpoint(ctx) {
|
|
|
608
1027
|
return latestCheckpoint(ctx.sessionManager.getBranch());
|
|
609
1028
|
}
|
|
610
1029
|
function isCheckpointCompatible(details, model) {
|
|
611
|
-
return
|
|
1030
|
+
return usesResponsesCompactionApi(model) && model.api === details.api && model.id === details.modelId;
|
|
612
1031
|
}
|
|
613
1032
|
function keptMessages(event) {
|
|
614
1033
|
const leafId = event.branchEntries.at(-1)?.id ?? null;
|
|
@@ -622,20 +1041,25 @@ function keptMessages(event) {
|
|
|
622
1041
|
return contextEntries.slice(keptIndex).flatMap(sessionEntryToContextMessages);
|
|
623
1042
|
}
|
|
624
1043
|
function activeTools(pi) {
|
|
625
|
-
const
|
|
626
|
-
return pi.
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1044
|
+
const available = new Map(pi.getAllTools().map((tool) => [tool.name, tool]));
|
|
1045
|
+
return pi.getActiveTools().flatMap((name) => {
|
|
1046
|
+
const tool = available.get(name);
|
|
1047
|
+
return tool ? [
|
|
1048
|
+
{
|
|
1049
|
+
name: tool.name,
|
|
1050
|
+
description: tool.description,
|
|
1051
|
+
parameters: tool.parameters
|
|
1052
|
+
}
|
|
1053
|
+
] : [];
|
|
1054
|
+
});
|
|
631
1055
|
}
|
|
632
1056
|
function projectedCurrentMessages(event, model) {
|
|
633
1057
|
const leafId = event.branchEntries.at(-1)?.id ?? null;
|
|
634
1058
|
const session = buildSessionContext(event.branchEntries, leafId);
|
|
635
1059
|
const prior = latestCheckpoint(event.branchEntries);
|
|
636
1060
|
if (!prior) return { messages: session.messages };
|
|
637
|
-
if (prior.details.modelId !== model.id) {
|
|
638
|
-
throw new Error("The active opaque checkpoint belongs to a different
|
|
1061
|
+
if (prior.details.api !== model.api || prior.details.modelId !== model.id) {
|
|
1062
|
+
throw new Error("The active opaque checkpoint belongs to a different Responses model");
|
|
639
1063
|
}
|
|
640
1064
|
const projected = projectCheckpointContext(session.messages, prior.details, prior.entry.summary);
|
|
641
1065
|
if (!projected) {
|
|
@@ -645,23 +1069,29 @@ function projectedCurrentMessages(event, model) {
|
|
|
645
1069
|
}
|
|
646
1070
|
function notifyFailure(ctx, error, settings) {
|
|
647
1071
|
if (!ctx.hasUI || !settings.notifyOnFallback) return;
|
|
648
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
649
|
-
ctx.ui.notify(`
|
|
1072
|
+
const message = terminalText(error instanceof Error ? error.message : String(error));
|
|
1073
|
+
ctx.ui.notify(`Responses compaction failed; using Pi compaction. ${message}`, "warning");
|
|
650
1074
|
}
|
|
651
1075
|
function sessionStillOwned(ctx, sessionId, signal) {
|
|
652
1076
|
return !signal.aborted && ctx.sessionManager.getSessionId() === sessionId;
|
|
653
1077
|
}
|
|
654
|
-
async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
1078
|
+
async function compactRemotely(pi, event, ctx, settings, ownerSignal, fetch) {
|
|
655
1079
|
const model = ctx.model;
|
|
656
|
-
|
|
1080
|
+
const route = resolveCompactionRoute(model, settings);
|
|
1081
|
+
if (route.kind === "native" || !usesResponsesCompactionApi(model)) return void 0;
|
|
1082
|
+
const signal = AbortSignal.any([event.signal, ownerSignal]);
|
|
1083
|
+
if (signal.aborted) return { cancel: true };
|
|
657
1084
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
658
|
-
ctx.ui.setStatus(
|
|
1085
|
+
ctx.ui.setStatus(
|
|
1086
|
+
STATUS_KEY,
|
|
1087
|
+
route.protocol === "remote-v2" ? "Responses Remote V2\u2026" : "Responses Compact API\u2026"
|
|
1088
|
+
);
|
|
659
1089
|
try {
|
|
660
1090
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
661
|
-
if (!sessionStillOwned(ctx, sessionId,
|
|
1091
|
+
if (!sessionStillOwned(ctx, sessionId, signal)) return { cancel: true };
|
|
662
1092
|
if (!auth.ok) throw new Error(auth.error);
|
|
663
1093
|
const provider = ctx.modelRegistry.getProvider(model.provider);
|
|
664
|
-
if (!provider) throw new Error("The active
|
|
1094
|
+
if (!provider) throw new Error("The active Responses provider is unavailable");
|
|
665
1095
|
const current = projectedCurrentMessages(event, model);
|
|
666
1096
|
const context = {
|
|
667
1097
|
systemPrompt: ctx.getSystemPrompt(),
|
|
@@ -672,10 +1102,11 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
|
672
1102
|
provider,
|
|
673
1103
|
model,
|
|
674
1104
|
context,
|
|
1105
|
+
protocol: route.protocol,
|
|
675
1106
|
apiKey: auth.apiKey,
|
|
676
1107
|
headers: auth.headers,
|
|
677
1108
|
env: auth.env,
|
|
678
|
-
signal
|
|
1109
|
+
signal,
|
|
679
1110
|
priorCheckpoint: current.prior ? {
|
|
680
1111
|
marker: checkpointMarker(current.prior.checkpointId),
|
|
681
1112
|
replacementHistory: current.prior.replacementHistory
|
|
@@ -684,13 +1115,17 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
|
684
1115
|
maxRetries: settings.maxRetries,
|
|
685
1116
|
fetch
|
|
686
1117
|
});
|
|
687
|
-
if (!sessionStillOwned(ctx, sessionId,
|
|
688
|
-
const replacementHistory = buildReplacementHistory(
|
|
689
|
-
|
|
690
|
-
|
|
1118
|
+
if (!sessionStillOwned(ctx, sessionId, signal)) return { cancel: true };
|
|
1119
|
+
const replacementHistory = buildReplacementHistory(
|
|
1120
|
+
response.compactedOutput?.slice(0, -1) ?? response.promptInput,
|
|
1121
|
+
response.item,
|
|
1122
|
+
{ tokenBudget: settings.replacementTokenBudget }
|
|
1123
|
+
);
|
|
691
1124
|
const details = createCheckpointDetails({
|
|
692
1125
|
provider: model.provider,
|
|
1126
|
+
api: route.api,
|
|
693
1127
|
modelId: model.id,
|
|
1128
|
+
protocol: route.protocol,
|
|
694
1129
|
replacementHistory,
|
|
695
1130
|
keptMessages: keptMessages(event)
|
|
696
1131
|
});
|
|
@@ -704,7 +1139,7 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
|
704
1139
|
}
|
|
705
1140
|
};
|
|
706
1141
|
} catch (error) {
|
|
707
|
-
if (
|
|
1142
|
+
if (signal.aborted || ctx.sessionManager.getSessionId() !== sessionId) {
|
|
708
1143
|
return { cancel: true };
|
|
709
1144
|
}
|
|
710
1145
|
notifyFailure(ctx, error, settings);
|
|
@@ -720,11 +1155,12 @@ function createCodexCompactExtension(options = {}) {
|
|
|
720
1155
|
let sessionController = new AbortController();
|
|
721
1156
|
let generation = 0;
|
|
722
1157
|
pi.registerCommand("codex-compact", {
|
|
723
|
-
description: "Compact now or configure
|
|
724
|
-
handler: async (
|
|
1158
|
+
description: "Compact now or configure Responses compaction",
|
|
1159
|
+
handler: async (args, ctx) => {
|
|
1160
|
+
if (args.trim()) throw new Error("Usage: /codex-compact");
|
|
725
1161
|
const ownerGeneration = generation;
|
|
726
1162
|
const controller = sessionController;
|
|
727
|
-
const { showCodexCompactMenu } = await import("./chunks/settings-menu-
|
|
1163
|
+
const { showCodexCompactMenu } = await import("./chunks/settings-menu-VYQ6ABCP.js");
|
|
728
1164
|
if (ownerGeneration !== generation || controller.signal.aborted) return;
|
|
729
1165
|
await showCodexCompactMenu(settingsRuntime, ctx, {
|
|
730
1166
|
signal: controller.signal,
|
|
@@ -746,7 +1182,7 @@ function createCodexCompactExtension(options = {}) {
|
|
|
746
1182
|
if (sessionController.signal.aborted || ownerGeneration !== generation) return;
|
|
747
1183
|
if (ctx.hasUI) {
|
|
748
1184
|
ctx.ui.notify(
|
|
749
|
-
`Could not load pi-codex-compact.json; using defaults. ${error instanceof Error ? error.message : String(error)}`,
|
|
1185
|
+
`Could not load pi-codex-compact.json; using defaults. ${terminalText(error instanceof Error ? error.message : String(error))}`,
|
|
750
1186
|
"warning"
|
|
751
1187
|
);
|
|
752
1188
|
}
|
|
@@ -757,14 +1193,21 @@ function createCodexCompactExtension(options = {}) {
|
|
|
757
1193
|
}
|
|
758
1194
|
if (ctx.hasUI && state.kind === "invalid") {
|
|
759
1195
|
ctx.ui.notify(
|
|
760
|
-
`Invalid pi-codex-compact.json; using defaults without overwriting it. ${state.issue}`,
|
|
1196
|
+
`Invalid pi-codex-compact.json; using defaults without overwriting it. ${terminalText(state.issue ?? "unknown validation error")}`,
|
|
761
1197
|
"warning"
|
|
762
1198
|
);
|
|
763
1199
|
}
|
|
764
1200
|
});
|
|
765
1201
|
pi.on(
|
|
766
1202
|
"session_before_compact",
|
|
767
|
-
(event, ctx) => compactRemotely(
|
|
1203
|
+
(event, ctx) => compactRemotely(
|
|
1204
|
+
pi,
|
|
1205
|
+
event,
|
|
1206
|
+
ctx,
|
|
1207
|
+
settingsRuntime.get().settings,
|
|
1208
|
+
sessionController.signal,
|
|
1209
|
+
options.fetch
|
|
1210
|
+
)
|
|
768
1211
|
);
|
|
769
1212
|
pi.on("context", (event, ctx) => {
|
|
770
1213
|
if (!settingsRuntime.get().settings.enabled) return void 0;
|
|
@@ -794,7 +1237,7 @@ function createCodexCompactExtension(options = {}) {
|
|
|
794
1237
|
providerWarnings.add(key);
|
|
795
1238
|
if (ctx.hasUI) {
|
|
796
1239
|
ctx.ui.notify(
|
|
797
|
-
"The active
|
|
1240
|
+
"The active Responses checkpoint cannot replay on this model; Pi will expose only its fallback marker and retained recent messages.",
|
|
798
1241
|
"warning"
|
|
799
1242
|
);
|
|
800
1243
|
}
|