@narumitw/pi-codex-compact 0.51.3 → 0.53.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 +90 -90
- package/dist/chunks/chunk-7H7JJ7ZV.js +37 -0
- package/dist/chunks/chunk-7H7JJ7ZV.js.map +7 -0
- package/dist/chunks/{settings-menu-PEDLURUH.js → settings-menu-TVWWDPBF.js} +52 -35
- package/dist/chunks/settings-menu-TVWWDPBF.js.map +7 -0
- package/dist/index.ts +541 -101
- package/dist/index.ts.map +4 -4
- package/package.json +53 -57
- package/src/checkpoint.ts +265 -218
- package/src/codex-compact.ts +252 -229
- package/src/model-api.ts +56 -5
- package/src/protocol.ts +318 -179
- package/src/remote-compact.ts +276 -0
- package/src/remote-shared.ts +16 -0
- package/src/remote-types.ts +46 -0
- package/src/remote-v2.ts +66 -0
- package/src/remote.ts +14 -116
- package/src/settings-menu.ts +213 -201
- package/src/settings.ts +228 -176
- 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,9 @@
|
|
|
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
|
+
resolveCompactionRoute,
|
|
5
|
+
terminalText
|
|
6
|
+
} from "./chunks/chunk-7H7JJ7ZV.js";
|
|
6
7
|
|
|
7
8
|
// src/codex-compact.ts
|
|
8
9
|
import {
|
|
@@ -17,6 +18,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
17
18
|
|
|
18
19
|
// src/protocol.ts
|
|
19
20
|
var MAX_SSE_BYTES = 8 * 1024 * 1024;
|
|
21
|
+
var MAX_COMPACT_JSON_BYTES = 8 * 1024 * 1024;
|
|
20
22
|
var MAX_COMPACTION_ITEM_BYTES = 2 * 1024 * 1024;
|
|
21
23
|
var CodexCompactionProtocolError = class extends Error {
|
|
22
24
|
constructor(message) {
|
|
@@ -35,9 +37,7 @@ function isCompactionItem(value) {
|
|
|
35
37
|
}
|
|
36
38
|
function validateCompactionItem(value, maxBytes = MAX_COMPACTION_ITEM_BYTES) {
|
|
37
39
|
if (!isCompactionItem(value)) {
|
|
38
|
-
throw new CodexCompactionProtocolError(
|
|
39
|
-
"Remote response did not contain a valid compaction item"
|
|
40
|
-
);
|
|
40
|
+
throw new CodexCompactionProtocolError("Remote response did not contain a valid compaction item");
|
|
41
41
|
}
|
|
42
42
|
if (byteLength(value) > maxBytes) {
|
|
43
43
|
throw new CodexCompactionProtocolError("Remote compaction item exceeded the size limit");
|
|
@@ -130,9 +130,7 @@ async function collectCompactionSse(stream, options = {}) {
|
|
|
130
130
|
reader.releaseLock();
|
|
131
131
|
}
|
|
132
132
|
if (!completedResponse) {
|
|
133
|
-
throw new CodexCompactionProtocolError(
|
|
134
|
-
"Remote compaction stream ended without response.completed"
|
|
135
|
-
);
|
|
133
|
+
throw new CodexCompactionProtocolError("Remote compaction stream ended without response.completed");
|
|
136
134
|
}
|
|
137
135
|
if (items.size !== 1) {
|
|
138
136
|
throw new CodexCompactionProtocolError(
|
|
@@ -141,6 +139,107 @@ async function collectCompactionSse(stream, options = {}) {
|
|
|
141
139
|
}
|
|
142
140
|
return { item: [...items.values()][0], completedResponse };
|
|
143
141
|
}
|
|
142
|
+
function isRetainedCompactContent(value) {
|
|
143
|
+
if (!isObject(value)) return false;
|
|
144
|
+
if (value.type === "input_text") return typeof value.text === "string";
|
|
145
|
+
if (value.type !== "input_image") return false;
|
|
146
|
+
if (value.detail !== void 0 && value.detail !== null && value.detail !== "auto" && value.detail !== "low" && value.detail !== "high" && value.detail !== "original") {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
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") {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return typeof value.file_id === "string" && value.file_id.length > 0 || typeof value.image_url === "string" && value.image_url.length > 0;
|
|
153
|
+
}
|
|
154
|
+
function isRetainedCompactMessage(value) {
|
|
155
|
+
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);
|
|
156
|
+
}
|
|
157
|
+
function validateCompactedResponse(value, options = {}) {
|
|
158
|
+
if (!isObject(value) || !Array.isArray(value.output)) {
|
|
159
|
+
throw new CodexCompactionProtocolError("Responses Compact returned an invalid response object");
|
|
160
|
+
}
|
|
161
|
+
const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
|
|
162
|
+
const maxItemBytes = options.maxItemBytes ?? MAX_COMPACTION_ITEM_BYTES;
|
|
163
|
+
if (byteLength(value) > maxBytes) {
|
|
164
|
+
throw new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
|
|
165
|
+
}
|
|
166
|
+
if (value.output.length === 0) {
|
|
167
|
+
throw new CodexCompactionProtocolError("Responses Compact returned no output items");
|
|
168
|
+
}
|
|
169
|
+
const output = value.output.map((item2) => {
|
|
170
|
+
if (!isObject(item2)) {
|
|
171
|
+
throw new CodexCompactionProtocolError("Responses Compact returned a non-object output item");
|
|
172
|
+
}
|
|
173
|
+
if (byteLength(item2) > maxItemBytes) {
|
|
174
|
+
throw new CodexCompactionProtocolError("Responses Compact output item exceeded the size limit");
|
|
175
|
+
}
|
|
176
|
+
return structuredClone(item2);
|
|
177
|
+
});
|
|
178
|
+
const compactionItems = output.filter((item2) => item2.type === "compaction");
|
|
179
|
+
if (compactionItems.length !== 1 || output.at(-1)?.type !== "compaction") {
|
|
180
|
+
throw new CodexCompactionProtocolError(
|
|
181
|
+
"Responses Compact must return retained messages followed by one compaction item"
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
for (const item2 of output.slice(0, -1)) {
|
|
185
|
+
if (!isRetainedCompactMessage(item2)) {
|
|
186
|
+
throw new CodexCompactionProtocolError("Responses Compact returned an unsupported retained output item");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const item = validateCompactionItem(output.at(-1), maxItemBytes);
|
|
190
|
+
return { item, output: [...output.slice(0, -1), item], response: structuredClone(value) };
|
|
191
|
+
}
|
|
192
|
+
async function collectCompactResponse(response, options = {}) {
|
|
193
|
+
const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
|
|
194
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
195
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
196
|
+
const error = new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
|
|
197
|
+
await response.body?.cancel(error).catch(() => void 0);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
if (!response.body) {
|
|
201
|
+
throw new CodexCompactionProtocolError("Responses Compact response did not contain a body");
|
|
202
|
+
}
|
|
203
|
+
const reader = response.body.getReader();
|
|
204
|
+
const chunks = [];
|
|
205
|
+
let bytes = 0;
|
|
206
|
+
const onAbort = () => {
|
|
207
|
+
void reader.cancel(new DOMException("Compaction aborted", "AbortError")).catch(() => void 0);
|
|
208
|
+
};
|
|
209
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
210
|
+
try {
|
|
211
|
+
while (true) {
|
|
212
|
+
if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
|
|
213
|
+
const { done, value } = await reader.read();
|
|
214
|
+
if (done) break;
|
|
215
|
+
bytes += value.byteLength;
|
|
216
|
+
if (bytes > maxBytes) {
|
|
217
|
+
throw new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
|
|
218
|
+
}
|
|
219
|
+
chunks.push(value);
|
|
220
|
+
}
|
|
221
|
+
if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
|
|
222
|
+
} catch (error) {
|
|
223
|
+
await reader.cancel(error).catch(() => void 0);
|
|
224
|
+
throw error;
|
|
225
|
+
} finally {
|
|
226
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
227
|
+
reader.releaseLock();
|
|
228
|
+
}
|
|
229
|
+
const body = new Uint8Array(bytes);
|
|
230
|
+
let offset = 0;
|
|
231
|
+
for (const chunk of chunks) {
|
|
232
|
+
body.set(chunk, offset);
|
|
233
|
+
offset += chunk.byteLength;
|
|
234
|
+
}
|
|
235
|
+
let parsed;
|
|
236
|
+
try {
|
|
237
|
+
parsed = JSON.parse(new TextDecoder().decode(body));
|
|
238
|
+
} catch {
|
|
239
|
+
throw new CodexCompactionProtocolError("Responses Compact returned malformed JSON");
|
|
240
|
+
}
|
|
241
|
+
return validateCompactedResponse(parsed, options);
|
|
242
|
+
}
|
|
144
243
|
function markerTextFromItem(item) {
|
|
145
244
|
if (!isObject(item) || item.role !== "user" || !Array.isArray(item.content)) return void 0;
|
|
146
245
|
if (item.content.length !== 1) return void 0;
|
|
@@ -175,15 +274,21 @@ function appendCompactionTrigger(payload) {
|
|
|
175
274
|
throw new CodexCompactionProtocolError("Codex Responses payload is missing an input array");
|
|
176
275
|
}
|
|
177
276
|
if (payload.input.some((item) => isObject(item) && item.type === "compaction_trigger")) {
|
|
178
|
-
throw new CodexCompactionProtocolError(
|
|
179
|
-
"Provider payload already contains a compaction trigger"
|
|
180
|
-
);
|
|
277
|
+
throw new CodexCompactionProtocolError("Provider payload already contains a compaction trigger");
|
|
181
278
|
}
|
|
182
279
|
return { ...payload, input: [...payload.input, { type: "compaction_trigger" }] };
|
|
183
280
|
}
|
|
281
|
+
function expandRemoteCompactionPayload(payload, checkpoint) {
|
|
282
|
+
if (checkpoint) {
|
|
283
|
+
return rewriteCheckpointMarker(payload, checkpoint.marker, checkpoint.replacementHistory);
|
|
284
|
+
}
|
|
285
|
+
if (!isObject(payload) || !Array.isArray(payload.input)) {
|
|
286
|
+
throw new CodexCompactionProtocolError("Responses payload is missing an input array");
|
|
287
|
+
}
|
|
288
|
+
return structuredClone(payload);
|
|
289
|
+
}
|
|
184
290
|
function prepareRemoteCompactionPayload(payload, checkpoint) {
|
|
185
|
-
|
|
186
|
-
return appendCompactionTrigger(expanded);
|
|
291
|
+
return appendCompactionTrigger(expandRemoteCompactionPayload(payload, checkpoint));
|
|
187
292
|
}
|
|
188
293
|
function hasCheckpointMarker(payload, marker) {
|
|
189
294
|
return isObject(payload) && Array.isArray(payload.input) && payload.input.some((item) => markerTextFromItem(item) === marker);
|
|
@@ -191,11 +296,16 @@ function hasCheckpointMarker(payload, marker) {
|
|
|
191
296
|
|
|
192
297
|
// src/checkpoint.ts
|
|
193
298
|
var CHECKPOINT_KIND = "pi-codex-remote-compaction";
|
|
194
|
-
var CHECKPOINT_VERSION =
|
|
299
|
+
var CHECKPOINT_VERSION = 3;
|
|
195
300
|
var REPLACEMENT_TOKEN_BUDGET = 64e3;
|
|
196
301
|
var REPLACEMENT_BYTE_BUDGET = 8 * 1024 * 1024;
|
|
197
302
|
var MAX_MEDIA_ITEM_BYTES = 2 * 1024 * 1024;
|
|
303
|
+
var MAX_CHECKPOINT_DETAILS_BYTES = 10 * 1024 * 1024;
|
|
304
|
+
var MAX_CHECKPOINT_ID_LENGTH = 128;
|
|
198
305
|
var MAX_PROVIDER_ID_LENGTH = 256;
|
|
306
|
+
var MAX_API_ID_LENGTH = 256;
|
|
307
|
+
var MAX_MODEL_ID_LENGTH = 512;
|
|
308
|
+
var MAX_KEPT_FINGERPRINTS = 1e5;
|
|
199
309
|
function isObject2(value) {
|
|
200
310
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
201
311
|
}
|
|
@@ -216,13 +326,13 @@ function checkpointMarker(checkpointId) {
|
|
|
216
326
|
return [
|
|
217
327
|
`[PI_CODEX_REMOTE_CHECKPOINT:${checkpointId}]`,
|
|
218
328
|
"Opaque checkpoint injection failed. Do not infer missing history; tell the user to re-enable",
|
|
219
|
-
"@narumitw/pi-codex-compact with
|
|
329
|
+
"@narumitw/pi-codex-compact with the same model and Responses API."
|
|
220
330
|
].join(" ");
|
|
221
331
|
}
|
|
222
332
|
function fallbackSummary(checkpointId) {
|
|
223
333
|
return [
|
|
224
|
-
`
|
|
225
|
-
"Full replay requires @narumitw/pi-codex-compact and the same model through a compatible
|
|
334
|
+
`Responses compaction checkpoint ${checkpointId} stores the older history opaquely.`,
|
|
335
|
+
"Full replay requires @narumitw/pi-codex-compact and the same model through a compatible Responses provider.",
|
|
226
336
|
"Without them, only Pi's retained recent messages remain available."
|
|
227
337
|
].join(" ");
|
|
228
338
|
}
|
|
@@ -235,7 +345,20 @@ function markerMessage(checkpointId, timestamp) {
|
|
|
235
345
|
}
|
|
236
346
|
function parseCheckpointDetails(value) {
|
|
237
347
|
if (!isObject2(value)) return void 0;
|
|
238
|
-
|
|
348
|
+
try {
|
|
349
|
+
if (serializedBytes(value) > MAX_CHECKPOINT_DETAILS_BYTES) return void 0;
|
|
350
|
+
} catch {
|
|
351
|
+
return void 0;
|
|
352
|
+
}
|
|
353
|
+
const isVersionOne = value.version === 1 && value.api === "openai-codex-responses" && value.protocol === "remote-compaction-v2";
|
|
354
|
+
const isVersionTwo = value.version === 2 && (value.api === "openai-codex-responses" || value.api === "openai-responses" || value.api === "azure-openai-responses") && (value.protocol === "remote-v2" || value.protocol === "responses-compact");
|
|
355
|
+
const isVersionThree = value.version === CHECKPOINT_VERSION && typeof value.api === "string" && value.api.length > 0 && value.api.length <= MAX_API_ID_LENGTH && (value.profile === "codex-responses-v1" || value.profile === "openai-responses-v1") && (value.protocol === "remote-v2" || value.protocol === "responses-compact");
|
|
356
|
+
if (value.kind !== CHECKPOINT_KIND || !isVersionOne && !isVersionTwo && !isVersionThree || 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) {
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
const api = value.api;
|
|
360
|
+
const profile = api === "openai-codex-responses" ? "codex-responses-v1" : api === "openai-responses" || api === "azure-openai-responses" ? "openai-responses-v1" : value.profile;
|
|
361
|
+
if (profile !== "codex-responses-v1" && profile !== "openai-responses-v1" || api !== "openai-codex-responses" && api !== "openai-responses" && api !== "azure-openai-responses" && profile !== "codex-responses-v1" || isVersionThree && value.profile !== profile) {
|
|
239
362
|
return void 0;
|
|
240
363
|
}
|
|
241
364
|
if (value.replacementHistory.length === 0 || !value.replacementHistory.every(isObject2) || !value.keptMessageFingerprints.every(
|
|
@@ -249,7 +372,19 @@ function parseCheckpointDetails(value) {
|
|
|
249
372
|
} catch {
|
|
250
373
|
return void 0;
|
|
251
374
|
}
|
|
252
|
-
return
|
|
375
|
+
return {
|
|
376
|
+
kind: CHECKPOINT_KIND,
|
|
377
|
+
version: CHECKPOINT_VERSION,
|
|
378
|
+
checkpointId: value.checkpointId,
|
|
379
|
+
provider: value.provider,
|
|
380
|
+
api,
|
|
381
|
+
profile,
|
|
382
|
+
modelId: value.modelId,
|
|
383
|
+
protocol: isVersionOne ? "remote-v2" : value.protocol,
|
|
384
|
+
replacementHistory: structuredClone(value.replacementHistory),
|
|
385
|
+
keptMessageFingerprints: [...value.keptMessageFingerprints],
|
|
386
|
+
createdAt: value.createdAt
|
|
387
|
+
};
|
|
253
388
|
}
|
|
254
389
|
function latestCheckpoint(entries) {
|
|
255
390
|
for (let index = entries.length - 1; index >= 0; index--) {
|
|
@@ -324,8 +459,7 @@ function buildReplacementHistory(input, compactionItem, options = {}) {
|
|
|
324
459
|
const opaque = validateCompactionItem(compactionItem);
|
|
325
460
|
let remainingBytes = byteBudget - serializedBytes(opaque);
|
|
326
461
|
let remainingChars = tokenBudget * 4;
|
|
327
|
-
if (remainingBytes <= 0)
|
|
328
|
-
throw new Error("Opaque compaction item exceeds replacement history budget");
|
|
462
|
+
if (remainingBytes <= 0) throw new Error("Opaque compaction item exceeds replacement history budget");
|
|
329
463
|
const retainedNewestFirst = [];
|
|
330
464
|
const candidates = input.filter(
|
|
331
465
|
(item) => isObject2(item) && item.role === "user" && item.type !== "compaction_trigger"
|
|
@@ -362,9 +496,10 @@ function createCheckpointDetails(input) {
|
|
|
362
496
|
version: CHECKPOINT_VERSION,
|
|
363
497
|
checkpointId: input.checkpointId ?? randomUUID(),
|
|
364
498
|
provider: input.provider,
|
|
365
|
-
api:
|
|
499
|
+
api: input.api,
|
|
500
|
+
profile: input.profile,
|
|
366
501
|
modelId: input.modelId,
|
|
367
|
-
protocol:
|
|
502
|
+
protocol: input.protocol,
|
|
368
503
|
replacementHistory: structuredClone(input.replacementHistory),
|
|
369
504
|
keptMessageFingerprints: input.keptMessages.map(fingerprintMessage),
|
|
370
505
|
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -374,22 +509,280 @@ function createCheckpointDetails(input) {
|
|
|
374
509
|
return parsed;
|
|
375
510
|
}
|
|
376
511
|
|
|
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) {
|
|
512
|
+
// src/remote-types.ts
|
|
513
|
+
function isJsonObject(value) {
|
|
387
514
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
388
515
|
}
|
|
389
516
|
function abortError() {
|
|
390
517
|
return new DOMException("Compaction aborted", "AbortError");
|
|
391
518
|
}
|
|
392
|
-
|
|
519
|
+
function assertPreparedInput(payload) {
|
|
520
|
+
if (!Array.isArray(payload.input) || !payload.input.every(isJsonObject)) {
|
|
521
|
+
throw new Error("Prepared compaction payload has invalid input items");
|
|
522
|
+
}
|
|
523
|
+
return structuredClone(payload.input);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/remote-shared.ts
|
|
527
|
+
async function collectProviderUsage(stream, signal) {
|
|
528
|
+
let usage;
|
|
529
|
+
for await (const event of stream) {
|
|
530
|
+
if (signal.aborted) throw abortError();
|
|
531
|
+
if (event.type === "error") {
|
|
532
|
+
throw new Error(event.error.errorMessage ?? "Responses compaction request failed");
|
|
533
|
+
}
|
|
534
|
+
if (event.type === "done") usage = event.message.usage;
|
|
535
|
+
}
|
|
536
|
+
if (signal.aborted) throw abortError();
|
|
537
|
+
if (!usage) throw new Error("Responses provider stream ended without completion usage");
|
|
538
|
+
return usage;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/remote-compact.ts
|
|
542
|
+
var OFFICIAL_COMPACT_FIELDS = [
|
|
543
|
+
"model",
|
|
544
|
+
"input",
|
|
545
|
+
"instructions",
|
|
546
|
+
"previous_response_id",
|
|
547
|
+
"prompt_cache_key",
|
|
548
|
+
"prompt_cache_retention",
|
|
549
|
+
"service_tier"
|
|
550
|
+
];
|
|
551
|
+
var CODEX_COMPACT_FIELDS = [
|
|
552
|
+
"model",
|
|
553
|
+
"input",
|
|
554
|
+
"instructions",
|
|
555
|
+
"tools",
|
|
556
|
+
"parallel_tool_calls",
|
|
557
|
+
"reasoning",
|
|
558
|
+
"service_tier",
|
|
559
|
+
"prompt_cache_key",
|
|
560
|
+
"text",
|
|
561
|
+
"access_programs"
|
|
562
|
+
];
|
|
563
|
+
function compactPayload(payload, profile) {
|
|
564
|
+
const fields = profile === "codex-responses-v1" ? CODEX_COMPACT_FIELDS : OFFICIAL_COMPACT_FIELDS;
|
|
565
|
+
const result = {};
|
|
566
|
+
for (const field of fields) {
|
|
567
|
+
if (Object.hasOwn(payload, field) && payload[field] !== void 0) {
|
|
568
|
+
result[field] = structuredClone(payload[field]);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (typeof result.model !== "string" || result.model.length === 0) {
|
|
572
|
+
throw new CodexCompactionProtocolError("Responses payload is missing a model");
|
|
573
|
+
}
|
|
574
|
+
assertPreparedInput(result);
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
function requestUrl(input) {
|
|
578
|
+
return new URL(input instanceof Request ? input.url : String(input));
|
|
579
|
+
}
|
|
580
|
+
function responsesCompactUrl(input) {
|
|
581
|
+
const original = requestUrl(input);
|
|
582
|
+
if (!original.pathname.endsWith("/responses")) {
|
|
583
|
+
throw new CodexCompactionProtocolError("Provider request URL does not end with the Responses endpoint");
|
|
584
|
+
}
|
|
585
|
+
const compact = new URL(original);
|
|
586
|
+
compact.pathname = `${compact.pathname}/compact`;
|
|
587
|
+
if (compact.origin !== original.origin) {
|
|
588
|
+
throw new CodexCompactionProtocolError("Responses Compact URL changed origin");
|
|
589
|
+
}
|
|
590
|
+
return compact;
|
|
591
|
+
}
|
|
592
|
+
function mergedHeaders(input, init) {
|
|
593
|
+
const headers = new Headers(input instanceof Request ? input.headers : void 0);
|
|
594
|
+
new Headers(init?.headers).forEach((value, name) => {
|
|
595
|
+
headers.set(name, value);
|
|
596
|
+
});
|
|
597
|
+
headers.delete("content-encoding");
|
|
598
|
+
headers.delete("content-length");
|
|
599
|
+
headers.set("accept", "application/json");
|
|
600
|
+
headers.set("content-type", "application/json");
|
|
601
|
+
return headers;
|
|
602
|
+
}
|
|
603
|
+
function mergedSignal(input, init, ownerSignal) {
|
|
604
|
+
const signals = [ownerSignal];
|
|
605
|
+
if (input instanceof Request) signals.push(input.signal);
|
|
606
|
+
if (init?.signal) signals.push(init.signal);
|
|
607
|
+
return signals.length === 1 ? ownerSignal : AbortSignal.any(signals);
|
|
608
|
+
}
|
|
609
|
+
function nonRetryableBridgeFailure(error) {
|
|
610
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
611
|
+
return Response.json(
|
|
612
|
+
{
|
|
613
|
+
error: {
|
|
614
|
+
message,
|
|
615
|
+
type: "invalid_request_error",
|
|
616
|
+
code: "invalid_compact_response"
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
{ status: 400 }
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
function nonNegativeInteger(value) {
|
|
623
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
624
|
+
}
|
|
625
|
+
function optionalUsageDetail(details, field) {
|
|
626
|
+
if (details === void 0 || details === null) return 0;
|
|
627
|
+
if (!isJsonObject(details)) {
|
|
628
|
+
throw new CodexCompactionProtocolError("Responses Compact response has invalid usage details");
|
|
629
|
+
}
|
|
630
|
+
const value = details[field];
|
|
631
|
+
if (value === void 0 || value === null) return 0;
|
|
632
|
+
if (!nonNegativeInteger(value)) {
|
|
633
|
+
throw new CodexCompactionProtocolError(`Responses Compact response has invalid usage detail ${field}`);
|
|
634
|
+
}
|
|
635
|
+
return value;
|
|
636
|
+
}
|
|
637
|
+
function validatedUsage(response) {
|
|
638
|
+
const usage = response.usage;
|
|
639
|
+
if (!isJsonObject(usage)) {
|
|
640
|
+
throw new CodexCompactionProtocolError("Responses Compact response is missing usage");
|
|
641
|
+
}
|
|
642
|
+
for (const field of ["input_tokens", "output_tokens", "total_tokens"]) {
|
|
643
|
+
if (!nonNegativeInteger(usage[field])) {
|
|
644
|
+
throw new CodexCompactionProtocolError(`Responses Compact response has invalid ${field}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const inputTokens = usage.input_tokens;
|
|
648
|
+
const outputTokens = usage.output_tokens;
|
|
649
|
+
const totalTokens = usage.total_tokens;
|
|
650
|
+
const cachedTokens = optionalUsageDetail(usage.input_tokens_details, "cached_tokens");
|
|
651
|
+
const cacheWriteTokens = optionalUsageDetail(usage.input_tokens_details, "cache_write_tokens");
|
|
652
|
+
const reasoningTokens = optionalUsageDetail(usage.output_tokens_details, "reasoning_tokens");
|
|
653
|
+
if (cachedTokens + cacheWriteTokens > inputTokens || reasoningTokens > outputTokens) {
|
|
654
|
+
throw new CodexCompactionProtocolError("Responses Compact response has inconsistent usage details");
|
|
655
|
+
}
|
|
656
|
+
if (totalTokens !== inputTokens + outputTokens) {
|
|
657
|
+
throw new CodexCompactionProtocolError("Responses Compact response has inconsistent total usage");
|
|
658
|
+
}
|
|
659
|
+
return structuredClone(usage);
|
|
660
|
+
}
|
|
661
|
+
function syntheticCompletion(result, payload) {
|
|
662
|
+
const completed = {
|
|
663
|
+
id: typeof result.response.id === "string" ? result.response.id : "resp_pi_compact_bridge",
|
|
664
|
+
object: "response",
|
|
665
|
+
created_at: typeof result.response.created_at === "number" ? result.response.created_at : Math.floor(Date.now() / 1e3),
|
|
666
|
+
status: "completed",
|
|
667
|
+
model: payload.model,
|
|
668
|
+
output: [],
|
|
669
|
+
parallel_tool_calls: false,
|
|
670
|
+
tool_choice: "auto",
|
|
671
|
+
tools: [],
|
|
672
|
+
usage: validatedUsage(result.response)
|
|
673
|
+
};
|
|
674
|
+
const events = [
|
|
675
|
+
{ type: "response.created", response: { ...completed, status: "in_progress" } },
|
|
676
|
+
{ type: "response.completed", response: completed }
|
|
677
|
+
];
|
|
678
|
+
return new Response(events.map((event) => `data: ${JSON.stringify(event)}
|
|
679
|
+
|
|
680
|
+
`).join(""), {
|
|
681
|
+
status: 200,
|
|
682
|
+
headers: { "content-type": "text/event-stream" }
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
async function requestResponsesCompact(request) {
|
|
686
|
+
if (request.signal.aborted) throw abortError();
|
|
687
|
+
let preparedPayload;
|
|
688
|
+
let sentInput;
|
|
689
|
+
let compactResult;
|
|
690
|
+
let bridgeError;
|
|
691
|
+
let dispatchInFlight = false;
|
|
692
|
+
let successfulResponses = 0;
|
|
693
|
+
const baseFetch = request.fetch ?? globalThis.fetch;
|
|
694
|
+
const bridgeFetch = async (input, init) => {
|
|
695
|
+
if (request.signal.aborted) throw abortError();
|
|
696
|
+
if (successfulResponses > 0) {
|
|
697
|
+
bridgeError = new CodexCompactionProtocolError("Provider dispatched again after Responses Compact succeeded");
|
|
698
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
699
|
+
}
|
|
700
|
+
if (dispatchInFlight) {
|
|
701
|
+
bridgeError = new CodexCompactionProtocolError("Provider dispatched overlapping Responses Compact requests");
|
|
702
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
703
|
+
}
|
|
704
|
+
if (!preparedPayload) {
|
|
705
|
+
bridgeError = new CodexCompactionProtocolError("Provider dispatched before exposing its request payload");
|
|
706
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
707
|
+
}
|
|
708
|
+
let compactUrl;
|
|
709
|
+
try {
|
|
710
|
+
compactUrl = responsesCompactUrl(input);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
bridgeError = error;
|
|
713
|
+
return nonRetryableBridgeFailure(error);
|
|
714
|
+
}
|
|
715
|
+
const signal = mergedSignal(input, init, request.signal);
|
|
716
|
+
dispatchInFlight = true;
|
|
717
|
+
try {
|
|
718
|
+
const response = await baseFetch(compactUrl, {
|
|
719
|
+
...init,
|
|
720
|
+
method: "POST",
|
|
721
|
+
headers: mergedHeaders(input, init),
|
|
722
|
+
body: JSON.stringify(preparedPayload),
|
|
723
|
+
signal
|
|
724
|
+
});
|
|
725
|
+
if (!response.ok) return response;
|
|
726
|
+
try {
|
|
727
|
+
const result = await collectCompactResponse(response, { signal });
|
|
728
|
+
successfulResponses += 1;
|
|
729
|
+
if (successfulResponses !== 1) {
|
|
730
|
+
throw new CodexCompactionProtocolError(
|
|
731
|
+
"Provider returned more than one successful Responses Compact response"
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
compactResult = result;
|
|
735
|
+
return syntheticCompletion(result, preparedPayload);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
bridgeError = error;
|
|
738
|
+
return nonRetryableBridgeFailure(error);
|
|
739
|
+
}
|
|
740
|
+
} finally {
|
|
741
|
+
dispatchInFlight = false;
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
const stream = request.provider.stream(request.model, request.context, {
|
|
745
|
+
apiKey: request.apiKey,
|
|
746
|
+
headers: request.headers,
|
|
747
|
+
env: request.env,
|
|
748
|
+
signal: request.signal,
|
|
749
|
+
transport: "sse",
|
|
750
|
+
cacheRetention: "none",
|
|
751
|
+
timeoutMs: request.requestTimeoutMs ?? 5 * 60 * 1e3,
|
|
752
|
+
maxRetries: request.maxRetries ?? 2,
|
|
753
|
+
fetch: bridgeFetch,
|
|
754
|
+
onPayload: (payload) => {
|
|
755
|
+
if (preparedPayload) {
|
|
756
|
+
throw new CodexCompactionProtocolError("Provider exposed more than one compaction request payload");
|
|
757
|
+
}
|
|
758
|
+
const expanded = expandRemoteCompactionPayload(payload, request.priorCheckpoint);
|
|
759
|
+
preparedPayload = compactPayload(expanded, request.profile);
|
|
760
|
+
sentInput = assertPreparedInput(preparedPayload);
|
|
761
|
+
return expanded;
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
let usage;
|
|
765
|
+
try {
|
|
766
|
+
usage = await collectProviderUsage(stream, request.signal);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
if (bridgeError) throw bridgeError;
|
|
769
|
+
throw error;
|
|
770
|
+
}
|
|
771
|
+
if (request.signal.aborted) throw abortError();
|
|
772
|
+
if (bridgeError) throw bridgeError;
|
|
773
|
+
if (!preparedPayload || !sentInput || !compactResult || successfulResponses !== 1) {
|
|
774
|
+
throw new CodexCompactionProtocolError("Provider did not complete exactly one Responses Compact request");
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
item: compactResult.item,
|
|
778
|
+
promptInput: sentInput,
|
|
779
|
+
compactedOutput: compactResult.output,
|
|
780
|
+
usage
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/remote-v2.ts
|
|
785
|
+
async function requestRemoteCompactionV2(request) {
|
|
393
786
|
if (request.signal.aborted) throw abortError();
|
|
394
787
|
let sentInput;
|
|
395
788
|
const inspections = [];
|
|
@@ -421,35 +814,30 @@ async function requestRemoteCompaction(request) {
|
|
|
421
814
|
fetch: inspectedFetch,
|
|
422
815
|
onPayload: (payload) => {
|
|
423
816
|
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));
|
|
817
|
+
sentInput = assertPreparedInput(prepared).slice(0, -1);
|
|
430
818
|
return prepared;
|
|
431
819
|
}
|
|
432
820
|
});
|
|
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)
|
|
821
|
+
const usage = await collectProviderUsage(stream, request.signal);
|
|
822
|
+
if (!sentInput) {
|
|
443
823
|
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
824
|
}
|
|
447
|
-
|
|
825
|
+
if (inspections.length !== 1) {
|
|
826
|
+
throw new CodexCompactionProtocolError(
|
|
827
|
+
`Provider exposed ${inspections.length} successful SSE responses; expected exactly one`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
const inspection = await inspections[0];
|
|
448
831
|
if (request.signal.aborted) throw abortError();
|
|
449
|
-
if (!inspection
|
|
832
|
+
if (!inspection.ok) throw inspection.error;
|
|
450
833
|
return { item: inspection.value.item, promptInput: sentInput, usage };
|
|
451
834
|
}
|
|
452
835
|
|
|
836
|
+
// src/remote.ts
|
|
837
|
+
function requestRemoteCompaction(request) {
|
|
838
|
+
return request.protocol === "responses-compact" ? requestResponsesCompact(request) : requestRemoteCompactionV2(request);
|
|
839
|
+
}
|
|
840
|
+
|
|
453
841
|
// src/settings.ts
|
|
454
842
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
455
843
|
import { constants } from "node:fs";
|
|
@@ -460,6 +848,8 @@ var CODEX_COMPACT_SETTINGS_FILE = "pi-codex-compact.json";
|
|
|
460
848
|
var MAX_SETTINGS_BYTES = 64 * 1024;
|
|
461
849
|
var DEFAULT_CODEX_COMPACT_SETTINGS = Object.freeze({
|
|
462
850
|
enabled: true,
|
|
851
|
+
protocol: "auto",
|
|
852
|
+
apiProfiles: {},
|
|
463
853
|
requestTimeoutMs: 3e5,
|
|
464
854
|
maxRetries: 2,
|
|
465
855
|
replacementTokenBudget: 64e3,
|
|
@@ -470,18 +860,54 @@ var LIMITS = Object.freeze({
|
|
|
470
860
|
maxRetries: { minimum: 0, maximum: 2 },
|
|
471
861
|
replacementTokenBudget: { minimum: 8e3, maximum: 128e3 }
|
|
472
862
|
});
|
|
863
|
+
var BUILT_IN_APIS = /* @__PURE__ */ new Set([
|
|
864
|
+
"openai-completions",
|
|
865
|
+
"mistral-conversations",
|
|
866
|
+
"openai-codex-responses",
|
|
867
|
+
"openai-responses",
|
|
868
|
+
"azure-openai-responses",
|
|
869
|
+
"anthropic-messages",
|
|
870
|
+
"bedrock-converse-stream",
|
|
871
|
+
"google-generative-ai",
|
|
872
|
+
"google-vertex",
|
|
873
|
+
"pi-messages"
|
|
874
|
+
]);
|
|
875
|
+
var MAX_API_PROFILE_ID_LENGTH = 256;
|
|
473
876
|
function isRecord(value) {
|
|
474
877
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
475
878
|
}
|
|
476
879
|
function validInteger(value, minimum, maximum) {
|
|
477
880
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= minimum && value <= maximum;
|
|
478
881
|
}
|
|
882
|
+
function hasWhitespaceOrControl(value) {
|
|
883
|
+
return [...value].some((character) => {
|
|
884
|
+
const code = character.codePointAt(0) ?? 0;
|
|
885
|
+
return code <= 32 || code === 127;
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
function normalizeApiProfiles(value) {
|
|
889
|
+
if (!isRecord(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
890
|
+
return void 0;
|
|
891
|
+
}
|
|
892
|
+
const profiles = {};
|
|
893
|
+
for (const [api, profile] of Object.entries(value)) {
|
|
894
|
+
if (api.length === 0 || api.length > MAX_API_PROFILE_ID_LENGTH || api.trim() !== api || hasWhitespaceOrControl(api) || BUILT_IN_APIS.has(api) || api === "__proto__" || api === "constructor" || api === "prototype" || profile !== "codex-responses-v1") {
|
|
895
|
+
return void 0;
|
|
896
|
+
}
|
|
897
|
+
profiles[api] = profile;
|
|
898
|
+
}
|
|
899
|
+
return profiles;
|
|
900
|
+
}
|
|
479
901
|
function normalizeCodexCompactSettings(value) {
|
|
480
902
|
if (!isRecord(value)) return void 0;
|
|
481
903
|
if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") return void 0;
|
|
904
|
+
if (Object.hasOwn(value, "protocol") && value.protocol !== "auto" && value.protocol !== "remote-v2" && value.protocol !== "responses-compact") {
|
|
905
|
+
return void 0;
|
|
906
|
+
}
|
|
482
907
|
if (Object.hasOwn(value, "notifyOnFallback") && typeof value.notifyOnFallback !== "boolean") {
|
|
483
908
|
return void 0;
|
|
484
909
|
}
|
|
910
|
+
if (Object.hasOwn(value, "apiProfiles") && normalizeApiProfiles(value.apiProfiles) === void 0) return void 0;
|
|
485
911
|
for (const [field, limits] of Object.entries(LIMITS)) {
|
|
486
912
|
if (Object.hasOwn(value, field) && !validInteger(value[field], limits.minimum, limits.maximum)) {
|
|
487
913
|
return void 0;
|
|
@@ -489,6 +915,8 @@ function normalizeCodexCompactSettings(value) {
|
|
|
489
915
|
}
|
|
490
916
|
return {
|
|
491
917
|
enabled: typeof value.enabled === "boolean" ? value.enabled : DEFAULT_CODEX_COMPACT_SETTINGS.enabled,
|
|
918
|
+
protocol: value.protocol === "remote-v2" || value.protocol === "responses-compact" ? value.protocol : DEFAULT_CODEX_COMPACT_SETTINGS.protocol,
|
|
919
|
+
apiProfiles: normalizeApiProfiles(value.apiProfiles) ?? structuredClone(DEFAULT_CODEX_COMPACT_SETTINGS.apiProfiles),
|
|
492
920
|
requestTimeoutMs: typeof value.requestTimeoutMs === "number" ? value.requestTimeoutMs : DEFAULT_CODEX_COMPACT_SETTINGS.requestTimeoutMs,
|
|
493
921
|
maxRetries: typeof value.maxRetries === "number" ? value.maxRetries : DEFAULT_CODEX_COMPACT_SETTINGS.maxRetries,
|
|
494
922
|
replacementTokenBudget: typeof value.replacementTokenBudget === "number" ? value.replacementTokenBudget : DEFAULT_CODEX_COMPACT_SETTINGS.replacementTokenBudget,
|
|
@@ -541,9 +969,7 @@ async function loadCodexCompactSettings(path = codexCompactSettingsPath(), signa
|
|
|
541
969
|
async function savePatch(path, patch, signal) {
|
|
542
970
|
const latest = await loadCodexCompactSettings(path, signal);
|
|
543
971
|
if (latest.kind === "invalid") {
|
|
544
|
-
throw new Error(
|
|
545
|
-
"Cannot overwrite an invalid pi-codex-compact.json; repair it and reload first"
|
|
546
|
-
);
|
|
972
|
+
throw new Error("Cannot overwrite an invalid pi-codex-compact.json; repair it and reload first");
|
|
547
973
|
}
|
|
548
974
|
const document = { ...latest.document, ...patch };
|
|
549
975
|
const settings = normalizeCodexCompactSettings(document);
|
|
@@ -607,35 +1033,39 @@ var STATUS_KEY = "codex-compact";
|
|
|
607
1033
|
function activeCheckpoint(ctx) {
|
|
608
1034
|
return latestCheckpoint(ctx.sessionManager.getBranch());
|
|
609
1035
|
}
|
|
610
|
-
function isCheckpointCompatible(details, model) {
|
|
611
|
-
|
|
1036
|
+
function isCheckpointCompatible(details, model, settings) {
|
|
1037
|
+
const route = resolveCompactionRoute(model, settings);
|
|
1038
|
+
return route.kind === "remote" && model !== void 0 && route.api === details.api && route.profile === details.profile && model.id === details.modelId;
|
|
612
1039
|
}
|
|
613
1040
|
function keptMessages(event) {
|
|
614
1041
|
const leafId = event.branchEntries.at(-1)?.id ?? null;
|
|
615
1042
|
const contextEntries = buildContextEntries(event.branchEntries, leafId);
|
|
616
|
-
const keptIndex = contextEntries.findIndex(
|
|
617
|
-
(entry) => entry.id === event.preparation.firstKeptEntryId
|
|
618
|
-
);
|
|
1043
|
+
const keptIndex = contextEntries.findIndex((entry) => entry.id === event.preparation.firstKeptEntryId);
|
|
619
1044
|
if (keptIndex < 0) {
|
|
620
1045
|
throw new Error("Pi compaction cut point is not present in the active context");
|
|
621
1046
|
}
|
|
622
1047
|
return contextEntries.slice(keptIndex).flatMap(sessionEntryToContextMessages);
|
|
623
1048
|
}
|
|
624
1049
|
function activeTools(pi) {
|
|
625
|
-
const
|
|
626
|
-
return pi.
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1050
|
+
const available = new Map(pi.getAllTools().map((tool) => [tool.name, tool]));
|
|
1051
|
+
return pi.getActiveTools().flatMap((name) => {
|
|
1052
|
+
const tool = available.get(name);
|
|
1053
|
+
return tool ? [
|
|
1054
|
+
{
|
|
1055
|
+
name: tool.name,
|
|
1056
|
+
description: tool.description,
|
|
1057
|
+
parameters: tool.parameters
|
|
1058
|
+
}
|
|
1059
|
+
] : [];
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
function projectedCurrentMessages(event, model, route) {
|
|
633
1063
|
const leafId = event.branchEntries.at(-1)?.id ?? null;
|
|
634
1064
|
const session = buildSessionContext(event.branchEntries, leafId);
|
|
635
1065
|
const prior = latestCheckpoint(event.branchEntries);
|
|
636
1066
|
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
|
|
1067
|
+
if (prior.details.api !== route.api || prior.details.profile !== route.profile || prior.details.modelId !== model.id) {
|
|
1068
|
+
throw new Error("The active opaque checkpoint belongs to a different Responses model");
|
|
639
1069
|
}
|
|
640
1070
|
const projected = projectCheckpointContext(session.messages, prior.details, prior.entry.summary);
|
|
641
1071
|
if (!projected) {
|
|
@@ -645,24 +1075,27 @@ function projectedCurrentMessages(event, model) {
|
|
|
645
1075
|
}
|
|
646
1076
|
function notifyFailure(ctx, error, settings) {
|
|
647
1077
|
if (!ctx.hasUI || !settings.notifyOnFallback) return;
|
|
648
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
649
|
-
ctx.ui.notify(`
|
|
1078
|
+
const message = terminalText(error instanceof Error ? error.message : String(error));
|
|
1079
|
+
ctx.ui.notify(`Responses compaction failed; using Pi compaction. ${message}`, "warning");
|
|
650
1080
|
}
|
|
651
1081
|
function sessionStillOwned(ctx, sessionId, signal) {
|
|
652
1082
|
return !signal.aborted && ctx.sessionManager.getSessionId() === sessionId;
|
|
653
1083
|
}
|
|
654
|
-
async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
1084
|
+
async function compactRemotely(pi, event, ctx, settings, ownerSignal, fetch) {
|
|
655
1085
|
const model = ctx.model;
|
|
656
|
-
|
|
1086
|
+
const route = resolveCompactionRoute(model, settings);
|
|
1087
|
+
if (route.kind === "native" || !model) return void 0;
|
|
1088
|
+
const signal = AbortSignal.any([event.signal, ownerSignal]);
|
|
1089
|
+
if (signal.aborted) return { cancel: true };
|
|
657
1090
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
658
|
-
ctx.ui.setStatus(STATUS_KEY, "
|
|
1091
|
+
ctx.ui.setStatus(STATUS_KEY, route.protocol === "remote-v2" ? "Responses Remote V2\u2026" : "Responses Compact API\u2026");
|
|
659
1092
|
try {
|
|
660
1093
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
661
|
-
if (!sessionStillOwned(ctx, sessionId,
|
|
1094
|
+
if (!sessionStillOwned(ctx, sessionId, signal)) return { cancel: true };
|
|
662
1095
|
if (!auth.ok) throw new Error(auth.error);
|
|
663
1096
|
const provider = ctx.modelRegistry.getProvider(model.provider);
|
|
664
|
-
if (!provider) throw new Error("The active
|
|
665
|
-
const current = projectedCurrentMessages(event, model);
|
|
1097
|
+
if (!provider) throw new Error("The active Responses provider is unavailable");
|
|
1098
|
+
const current = projectedCurrentMessages(event, model, route);
|
|
666
1099
|
const context = {
|
|
667
1100
|
systemPrompt: ctx.getSystemPrompt(),
|
|
668
1101
|
messages: convertToLlm(current.messages),
|
|
@@ -672,10 +1105,12 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
|
672
1105
|
provider,
|
|
673
1106
|
model,
|
|
674
1107
|
context,
|
|
1108
|
+
protocol: route.protocol,
|
|
1109
|
+
profile: route.profile,
|
|
675
1110
|
apiKey: auth.apiKey,
|
|
676
1111
|
headers: auth.headers,
|
|
677
1112
|
env: auth.env,
|
|
678
|
-
signal
|
|
1113
|
+
signal,
|
|
679
1114
|
priorCheckpoint: current.prior ? {
|
|
680
1115
|
marker: checkpointMarker(current.prior.checkpointId),
|
|
681
1116
|
replacementHistory: current.prior.replacementHistory
|
|
@@ -684,13 +1119,18 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
|
684
1119
|
maxRetries: settings.maxRetries,
|
|
685
1120
|
fetch
|
|
686
1121
|
});
|
|
687
|
-
if (!sessionStillOwned(ctx, sessionId,
|
|
688
|
-
const replacementHistory = buildReplacementHistory(
|
|
689
|
-
|
|
690
|
-
|
|
1122
|
+
if (!sessionStillOwned(ctx, sessionId, signal)) return { cancel: true };
|
|
1123
|
+
const replacementHistory = buildReplacementHistory(
|
|
1124
|
+
response.compactedOutput?.slice(0, -1) ?? response.promptInput,
|
|
1125
|
+
response.item,
|
|
1126
|
+
{ tokenBudget: settings.replacementTokenBudget }
|
|
1127
|
+
);
|
|
691
1128
|
const details = createCheckpointDetails({
|
|
692
1129
|
provider: model.provider,
|
|
1130
|
+
api: route.api,
|
|
1131
|
+
profile: route.profile,
|
|
693
1132
|
modelId: model.id,
|
|
1133
|
+
protocol: route.protocol,
|
|
694
1134
|
replacementHistory,
|
|
695
1135
|
keptMessages: keptMessages(event)
|
|
696
1136
|
});
|
|
@@ -704,7 +1144,7 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
|
|
|
704
1144
|
}
|
|
705
1145
|
};
|
|
706
1146
|
} catch (error) {
|
|
707
|
-
if (
|
|
1147
|
+
if (signal.aborted || ctx.sessionManager.getSessionId() !== sessionId) {
|
|
708
1148
|
return { cancel: true };
|
|
709
1149
|
}
|
|
710
1150
|
notifyFailure(ctx, error, settings);
|
|
@@ -720,11 +1160,12 @@ function createCodexCompactExtension(options = {}) {
|
|
|
720
1160
|
let sessionController = new AbortController();
|
|
721
1161
|
let generation = 0;
|
|
722
1162
|
pi.registerCommand("codex-compact", {
|
|
723
|
-
description: "Compact now or configure
|
|
724
|
-
handler: async (
|
|
1163
|
+
description: "Compact now or configure Responses compaction",
|
|
1164
|
+
handler: async (args, ctx) => {
|
|
1165
|
+
if (args.trim()) throw new Error("Usage: /codex-compact");
|
|
725
1166
|
const ownerGeneration = generation;
|
|
726
1167
|
const controller = sessionController;
|
|
727
|
-
const { showCodexCompactMenu } = await import("./chunks/settings-menu-
|
|
1168
|
+
const { showCodexCompactMenu } = await import("./chunks/settings-menu-TVWWDPBF.js");
|
|
728
1169
|
if (ownerGeneration !== generation || controller.signal.aborted) return;
|
|
729
1170
|
await showCodexCompactMenu(settingsRuntime, ctx, {
|
|
730
1171
|
signal: controller.signal,
|
|
@@ -746,7 +1187,7 @@ function createCodexCompactExtension(options = {}) {
|
|
|
746
1187
|
if (sessionController.signal.aborted || ownerGeneration !== generation) return;
|
|
747
1188
|
if (ctx.hasUI) {
|
|
748
1189
|
ctx.ui.notify(
|
|
749
|
-
`Could not load pi-codex-compact.json; using defaults. ${error instanceof Error ? error.message : String(error)}`,
|
|
1190
|
+
`Could not load pi-codex-compact.json; using defaults. ${terminalText(error instanceof Error ? error.message : String(error))}`,
|
|
750
1191
|
"warning"
|
|
751
1192
|
);
|
|
752
1193
|
}
|
|
@@ -757,44 +1198,43 @@ function createCodexCompactExtension(options = {}) {
|
|
|
757
1198
|
}
|
|
758
1199
|
if (ctx.hasUI && state.kind === "invalid") {
|
|
759
1200
|
ctx.ui.notify(
|
|
760
|
-
`Invalid pi-codex-compact.json; using defaults without overwriting it. ${state.issue}`,
|
|
1201
|
+
`Invalid pi-codex-compact.json; using defaults without overwriting it. ${terminalText(state.issue ?? "unknown validation error")}`,
|
|
761
1202
|
"warning"
|
|
762
1203
|
);
|
|
763
1204
|
}
|
|
764
1205
|
});
|
|
765
1206
|
pi.on(
|
|
766
1207
|
"session_before_compact",
|
|
767
|
-
(event, ctx) => compactRemotely(pi, event, ctx, settingsRuntime.get().settings, options.fetch)
|
|
1208
|
+
(event, ctx) => compactRemotely(pi, event, ctx, settingsRuntime.get().settings, sessionController.signal, options.fetch)
|
|
768
1209
|
);
|
|
769
1210
|
pi.on("context", (event, ctx) => {
|
|
770
1211
|
if (!settingsRuntime.get().settings.enabled) return void 0;
|
|
1212
|
+
const settings = settingsRuntime.get().settings;
|
|
771
1213
|
const checkpoint = activeCheckpoint(ctx);
|
|
772
|
-
if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model)) return void 0;
|
|
773
|
-
const messages = projectCheckpointContext(
|
|
774
|
-
event.messages,
|
|
775
|
-
checkpoint.details,
|
|
776
|
-
checkpoint.entry.summary
|
|
777
|
-
);
|
|
1214
|
+
if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model, settings)) return void 0;
|
|
1215
|
+
const messages = projectCheckpointContext(event.messages, checkpoint.details, checkpoint.entry.summary);
|
|
778
1216
|
return messages ? { messages } : void 0;
|
|
779
1217
|
});
|
|
780
1218
|
pi.on("before_provider_request", (event, ctx) => {
|
|
781
1219
|
if (!settingsRuntime.get().settings.enabled) return void 0;
|
|
1220
|
+
const settings = settingsRuntime.get().settings;
|
|
782
1221
|
const checkpoint = activeCheckpoint(ctx);
|
|
783
|
-
if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model)) return void 0;
|
|
1222
|
+
if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model, settings)) return void 0;
|
|
784
1223
|
const marker = checkpointMarker(checkpoint.details.checkpointId);
|
|
785
1224
|
if (!hasCheckpointMarker(event.payload, marker)) return void 0;
|
|
786
1225
|
return rewriteCheckpointMarker(event.payload, marker, checkpoint.details.replacementHistory);
|
|
787
1226
|
});
|
|
788
1227
|
pi.on("model_select", (event, ctx) => {
|
|
789
1228
|
if (!settingsRuntime.get().settings.enabled) return;
|
|
1229
|
+
const settings = settingsRuntime.get().settings;
|
|
790
1230
|
const checkpoint = activeCheckpoint(ctx);
|
|
791
|
-
if (!checkpoint || isCheckpointCompatible(checkpoint.details, event.model)) return;
|
|
1231
|
+
if (!checkpoint || isCheckpointCompatible(checkpoint.details, event.model, settings)) return;
|
|
792
1232
|
const key = `${ctx.sessionManager.getSessionId()}:${event.model.provider}:${event.model.id}`;
|
|
793
1233
|
if (providerWarnings.has(key)) return;
|
|
794
1234
|
providerWarnings.add(key);
|
|
795
1235
|
if (ctx.hasUI) {
|
|
796
1236
|
ctx.ui.notify(
|
|
797
|
-
"The active
|
|
1237
|
+
"The active Responses checkpoint cannot replay on this model; Pi will expose only its fallback marker and retained recent messages.",
|
|
798
1238
|
"warning"
|
|
799
1239
|
);
|
|
800
1240
|
}
|