pi-openai-toolkit 0.11.1 → 0.12.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 +11 -17
- package/README_zh.md +11 -17
- package/package.json +12 -10
- package/src/config.ts +0 -69
- package/src/extension-runtime.ts +62 -17
- package/src/payload-rewrite.ts +40 -10
- package/src/remote-v2-client.ts +340 -99
- package/src/request-context-cache.ts +36 -3
- package/src/serializer.ts +4 -12
- package/src/types.ts +0 -19
- package/tsconfig.check.json +0 -1
- package/src/inline-compaction.ts +0 -1264
package/src/remote-v2-client.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
1
2
|
import { writeDebugArtifact } from "./debug";
|
|
2
3
|
import { buildResponsesRequestHeaders } from "./responses-headers";
|
|
3
4
|
import type { NativeCompactionRuntime } from "./runtime";
|
|
@@ -23,7 +24,8 @@ export type RemoteV2ResponseEnvelope = Record<string, unknown> & {
|
|
|
23
24
|
id?: string;
|
|
24
25
|
created_at?: number | string;
|
|
25
26
|
status: "completed";
|
|
26
|
-
output
|
|
27
|
+
/** Normalized output always includes the reconciled checkpoint. */
|
|
28
|
+
output: unknown[];
|
|
27
29
|
usage?: RemoteV2ResponseUsage;
|
|
28
30
|
};
|
|
29
31
|
|
|
@@ -37,7 +39,11 @@ export type RemoteV2CompactionClientFailureReason =
|
|
|
37
39
|
| "missing-completed-event"
|
|
38
40
|
| "incomplete-response"
|
|
39
41
|
| "invalid-compaction-count"
|
|
40
|
-
| "malformed-compaction-item"
|
|
42
|
+
| "malformed-compaction-item"
|
|
43
|
+
| "conflicting-compaction-item"
|
|
44
|
+
| "invalid-compaction-metadata"
|
|
45
|
+
| "invalid-event-order"
|
|
46
|
+
| "duplicate-completed-event";
|
|
41
47
|
|
|
42
48
|
export type RemoteV2CompactionClientSuccess = {
|
|
43
49
|
ok: true;
|
|
@@ -230,6 +236,330 @@ function summarizeEvents(events: readonly ParsedSseEvent[]): unknown[] {
|
|
|
230
236
|
});
|
|
231
237
|
}
|
|
232
238
|
|
|
239
|
+
type OptionalMetadata<T> =
|
|
240
|
+
| { valid: true; value?: T }
|
|
241
|
+
| { valid: false };
|
|
242
|
+
|
|
243
|
+
type CompactionCheckpointCandidate = {
|
|
244
|
+
item: RemoteV2CompactionItem;
|
|
245
|
+
responseId?: string;
|
|
246
|
+
outputIndex?: number;
|
|
247
|
+
outputPosition?: number;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
type CheckpointReconciliation =
|
|
251
|
+
| {
|
|
252
|
+
ok: true;
|
|
253
|
+
response: RemoteV2ResponseEnvelope;
|
|
254
|
+
compactedWindow: [RemoteV2CompactionItem];
|
|
255
|
+
}
|
|
256
|
+
| {
|
|
257
|
+
ok: false;
|
|
258
|
+
reason:
|
|
259
|
+
| "error-event"
|
|
260
|
+
| "missing-completed-event"
|
|
261
|
+
| "incomplete-response"
|
|
262
|
+
| "invalid-compaction-count"
|
|
263
|
+
| "malformed-compaction-item"
|
|
264
|
+
| "conflicting-compaction-item"
|
|
265
|
+
| "invalid-compaction-metadata"
|
|
266
|
+
| "invalid-event-order"
|
|
267
|
+
| "duplicate-completed-event";
|
|
268
|
+
responseJson?: unknown;
|
|
269
|
+
errorMessage?: string;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
function hasOwn(value: Record<string, unknown>, key: string): boolean {
|
|
273
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function readOptionalNonEmptyString(value: Record<string, unknown>, key: string): OptionalMetadata<string> {
|
|
277
|
+
if (!hasOwn(value, key)) {
|
|
278
|
+
return { valid: true };
|
|
279
|
+
}
|
|
280
|
+
return isNonEmptyString(value[key]) ? { valid: true, value: value[key] } : { valid: false };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function readOptionalOutputIndex(value: Record<string, unknown>): OptionalMetadata<number> {
|
|
284
|
+
if (!hasOwn(value, "output_index")) {
|
|
285
|
+
return { valid: true };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const outputIndex = value.output_index;
|
|
289
|
+
return typeof outputIndex === "number" && Number.isSafeInteger(outputIndex) && outputIndex >= 0
|
|
290
|
+
? { valid: true, value: outputIndex }
|
|
291
|
+
: { valid: false };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function mergeCompactionItems(
|
|
295
|
+
itemDone: RemoteV2CompactionItem,
|
|
296
|
+
terminalOutput: RemoteV2CompactionItem,
|
|
297
|
+
): RemoteV2CompactionItem | undefined {
|
|
298
|
+
for (const [key, value] of Object.entries(itemDone)) {
|
|
299
|
+
if (hasOwn(terminalOutput, key) && !isDeepStrictEqual(value, terminalOutput[key])) return undefined;
|
|
300
|
+
}
|
|
301
|
+
return structuredClone({ ...itemDone, ...terminalOutput });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function readResponseId(value: Record<string, unknown>): OptionalMetadata<string> {
|
|
305
|
+
const direct = readOptionalNonEmptyString(value, "response_id");
|
|
306
|
+
if (!direct.valid) {
|
|
307
|
+
return direct;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const nestedResponse: OptionalMetadata<string> = isRecord(value.response) ? readOptionalNonEmptyString(value.response, "id") : { valid: true };
|
|
311
|
+
if (!nestedResponse.valid) {
|
|
312
|
+
return nestedResponse;
|
|
313
|
+
}
|
|
314
|
+
if (direct.value !== undefined && nestedResponse.value !== undefined && direct.value !== nestedResponse.value) {
|
|
315
|
+
return { valid: false };
|
|
316
|
+
}
|
|
317
|
+
return { valid: true, value: direct.value ?? nestedResponse.value };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function readCheckpointCandidate(
|
|
321
|
+
item: Record<string, unknown>,
|
|
322
|
+
metadataSource: Record<string, unknown> | undefined,
|
|
323
|
+
outputPosition?: number,
|
|
324
|
+
): { ok: true; candidate: CompactionCheckpointCandidate } | { ok: false; reason: "malformed-compaction-item" | "invalid-compaction-metadata" } {
|
|
325
|
+
if (!isRemoteV2CompactionItem(item)) {
|
|
326
|
+
return { ok: false, reason: "malformed-compaction-item" };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const itemId = readOptionalNonEmptyString(item, "id");
|
|
330
|
+
const itemResponseId = readResponseId(item);
|
|
331
|
+
const itemOutputIndex = readOptionalOutputIndex(item);
|
|
332
|
+
if (!itemId.valid || !itemResponseId.valid || !itemOutputIndex.valid) {
|
|
333
|
+
return { ok: false, reason: "invalid-compaction-metadata" };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const sourceResponseId: OptionalMetadata<string> = metadataSource ? readResponseId(metadataSource) : { valid: true };
|
|
337
|
+
const sourceOutputIndex: OptionalMetadata<number> = metadataSource ? readOptionalOutputIndex(metadataSource) : { valid: true };
|
|
338
|
+
if (!sourceResponseId.valid || !sourceOutputIndex.valid) {
|
|
339
|
+
return { ok: false, reason: "invalid-compaction-metadata" };
|
|
340
|
+
}
|
|
341
|
+
if (
|
|
342
|
+
itemResponseId.value !== undefined &&
|
|
343
|
+
sourceResponseId.value !== undefined &&
|
|
344
|
+
itemResponseId.value !== sourceResponseId.value
|
|
345
|
+
) {
|
|
346
|
+
return { ok: false, reason: "invalid-compaction-metadata" };
|
|
347
|
+
}
|
|
348
|
+
if (
|
|
349
|
+
itemOutputIndex.value !== undefined &&
|
|
350
|
+
sourceOutputIndex.value !== undefined &&
|
|
351
|
+
itemOutputIndex.value !== sourceOutputIndex.value
|
|
352
|
+
) {
|
|
353
|
+
return { ok: false, reason: "invalid-compaction-metadata" };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const outputIndex = sourceOutputIndex.value ?? itemOutputIndex.value;
|
|
357
|
+
if (outputPosition !== undefined && outputIndex !== undefined && outputIndex !== outputPosition) {
|
|
358
|
+
return { ok: false, reason: "invalid-compaction-metadata" };
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
ok: true,
|
|
362
|
+
candidate: {
|
|
363
|
+
item: structuredClone(item),
|
|
364
|
+
responseId: sourceResponseId.value ?? itemResponseId.value,
|
|
365
|
+
outputIndex,
|
|
366
|
+
outputPosition,
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function isPostTerminalEvent(event: ParsedSseEvent): boolean {
|
|
372
|
+
if (event.dataText === "[DONE]") {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
return getEventType(event) === "keepalive";
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function reconcileSseCheckpoint(events: readonly ParsedSseEvent[]): CheckpointReconciliation {
|
|
379
|
+
const errorEvent = events.find((event) => {
|
|
380
|
+
const type = getEventType(event);
|
|
381
|
+
return type === "error" || type === "response.failed" || type === "response.incomplete";
|
|
382
|
+
});
|
|
383
|
+
if (errorEvent) {
|
|
384
|
+
return {
|
|
385
|
+
ok: false,
|
|
386
|
+
reason: "error-event",
|
|
387
|
+
responseJson: errorEvent.data,
|
|
388
|
+
errorMessage: getErrorMessage(errorEvent.data),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const completedIndexes = events.flatMap((event, index) => (getEventType(event) === "response.completed" ? [index] : []));
|
|
393
|
+
if (completedIndexes.length === 0) {
|
|
394
|
+
return { ok: false, reason: "missing-completed-event" };
|
|
395
|
+
}
|
|
396
|
+
if (completedIndexes.length > 1) {
|
|
397
|
+
return { ok: false, reason: "duplicate-completed-event" };
|
|
398
|
+
}
|
|
399
|
+
const terminalIndex = completedIndexes[0]!;
|
|
400
|
+
|
|
401
|
+
for (let index = 0; index < events.length; index += 1) {
|
|
402
|
+
const event = events[index]!;
|
|
403
|
+
if (index < terminalIndex && event.dataText === "[DONE]") {
|
|
404
|
+
return { ok: false, reason: "invalid-event-order", responseJson: event.dataText };
|
|
405
|
+
}
|
|
406
|
+
if (index > terminalIndex && !isPostTerminalEvent(event)) {
|
|
407
|
+
return { ok: false, reason: "invalid-event-order", responseJson: event.data };
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const completedEvent = events[terminalIndex]!;
|
|
412
|
+
const completedData = isRecord(completedEvent.data) ? completedEvent.data : undefined;
|
|
413
|
+
const completedResponse = completedData && isRecord(completedData.response) ? completedData.response : undefined;
|
|
414
|
+
if (!completedResponse) {
|
|
415
|
+
return { ok: false, reason: "incomplete-response" };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (completedResponse.status !== "completed") {
|
|
419
|
+
return { ok: false, reason: "incomplete-response", responseJson: completedResponse };
|
|
420
|
+
}
|
|
421
|
+
const hasOutput = hasOwn(completedResponse, "output");
|
|
422
|
+
if (hasOutput && !Array.isArray(completedResponse.output)) {
|
|
423
|
+
return { ok: false, reason: "incomplete-response", responseJson: completedResponse };
|
|
424
|
+
}
|
|
425
|
+
const terminalOutput: unknown[] | undefined = Array.isArray(completedResponse.output) ? completedResponse.output : undefined;
|
|
426
|
+
|
|
427
|
+
const terminalResponseId = readOptionalNonEmptyString(completedResponse, "id");
|
|
428
|
+
const completedEventResponseId: OptionalMetadata<string> = completedData ? readResponseId(completedData) : { valid: true };
|
|
429
|
+
if (!terminalResponseId.valid || !completedEventResponseId.valid) {
|
|
430
|
+
return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
|
|
431
|
+
}
|
|
432
|
+
if (
|
|
433
|
+
terminalResponseId.value !== undefined &&
|
|
434
|
+
completedEventResponseId.value !== undefined &&
|
|
435
|
+
terminalResponseId.value !== completedEventResponseId.value
|
|
436
|
+
) {
|
|
437
|
+
return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
|
|
438
|
+
}
|
|
439
|
+
let responseId = terminalResponseId.value ?? completedEventResponseId.value;
|
|
440
|
+
for (const event of events.slice(0, terminalIndex)) {
|
|
441
|
+
if (getEventType(event) !== "response.created" || !isRecord(event.data)) continue;
|
|
442
|
+
const createdId = readResponseId(event.data);
|
|
443
|
+
if (!createdId.valid || (createdId.value !== undefined && responseId !== undefined && createdId.value !== responseId)) {
|
|
444
|
+
return { ok: false, reason: "invalid-compaction-metadata", responseJson: event.data };
|
|
445
|
+
}
|
|
446
|
+
responseId ??= createdId.value;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let itemDoneCandidate: CompactionCheckpointCandidate | undefined;
|
|
450
|
+
for (let index = 0; index < terminalIndex; index += 1) {
|
|
451
|
+
const event = events[index]!;
|
|
452
|
+
if (getEventType(event) !== "response.output_item.done") {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (!isRecord(event.data) || !isRecord(event.data.item)) {
|
|
456
|
+
return { ok: false, reason: "malformed-compaction-item", responseJson: event.data };
|
|
457
|
+
}
|
|
458
|
+
if (event.data.item.type !== "compaction") {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (itemDoneCandidate) {
|
|
462
|
+
return { ok: false, reason: "invalid-compaction-count", responseJson: event.data.item };
|
|
463
|
+
}
|
|
464
|
+
const candidate = readCheckpointCandidate(event.data.item, event.data);
|
|
465
|
+
if (!candidate.ok) {
|
|
466
|
+
return { ok: false, reason: candidate.reason, responseJson: event.data.item };
|
|
467
|
+
}
|
|
468
|
+
itemDoneCandidate = candidate.candidate;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
let terminalCandidate: CompactionCheckpointCandidate | undefined;
|
|
472
|
+
let terminalPosition: number | undefined;
|
|
473
|
+
if (terminalOutput) {
|
|
474
|
+
const compactionPositions = terminalOutput.flatMap((item, index) =>
|
|
475
|
+
isRecord(item) && item.type === "compaction" ? [index] : [],
|
|
476
|
+
);
|
|
477
|
+
if (compactionPositions.length > 1) {
|
|
478
|
+
return { ok: false, reason: "invalid-compaction-count", responseJson: completedResponse };
|
|
479
|
+
}
|
|
480
|
+
if (compactionPositions.length === 1) {
|
|
481
|
+
terminalPosition = compactionPositions[0]!;
|
|
482
|
+
const outputItem = terminalOutput[terminalPosition];
|
|
483
|
+
if (!isRecord(outputItem)) {
|
|
484
|
+
return { ok: false, reason: "malformed-compaction-item", responseJson: outputItem };
|
|
485
|
+
}
|
|
486
|
+
const candidate = readCheckpointCandidate(outputItem, undefined, terminalPosition);
|
|
487
|
+
if (!candidate.ok) {
|
|
488
|
+
return { ok: false, reason: candidate.reason, responseJson: outputItem };
|
|
489
|
+
}
|
|
490
|
+
terminalCandidate = candidate.candidate;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (!itemDoneCandidate && !terminalCandidate) {
|
|
495
|
+
return { ok: false, reason: "invalid-compaction-count", responseJson: completedResponse };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
for (const candidate of [itemDoneCandidate, terminalCandidate]) {
|
|
499
|
+
if (
|
|
500
|
+
candidate?.responseId !== undefined &&
|
|
501
|
+
responseId !== undefined &&
|
|
502
|
+
candidate.responseId !== responseId
|
|
503
|
+
) {
|
|
504
|
+
return { ok: false, reason: "invalid-compaction-metadata", responseJson: candidate.item };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (
|
|
508
|
+
itemDoneCandidate?.responseId !== undefined &&
|
|
509
|
+
terminalCandidate?.responseId !== undefined &&
|
|
510
|
+
itemDoneCandidate.responseId !== terminalCandidate.responseId
|
|
511
|
+
) {
|
|
512
|
+
return { ok: false, reason: "invalid-compaction-metadata", responseJson: terminalCandidate.item };
|
|
513
|
+
}
|
|
514
|
+
if (
|
|
515
|
+
itemDoneCandidate?.outputIndex !== undefined &&
|
|
516
|
+
terminalCandidate?.outputPosition !== undefined &&
|
|
517
|
+
itemDoneCandidate.outputIndex !== terminalCandidate.outputPosition
|
|
518
|
+
) {
|
|
519
|
+
return { ok: false, reason: "invalid-compaction-metadata", responseJson: terminalCandidate.item };
|
|
520
|
+
}
|
|
521
|
+
if (
|
|
522
|
+
itemDoneCandidate?.outputIndex !== undefined &&
|
|
523
|
+
terminalOutput !== undefined &&
|
|
524
|
+
terminalOutput.length > 0 &&
|
|
525
|
+
terminalCandidate === undefined &&
|
|
526
|
+
itemDoneCandidate.outputIndex > terminalOutput.length
|
|
527
|
+
) {
|
|
528
|
+
return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
let canonicalItem: RemoteV2CompactionItem;
|
|
532
|
+
if (itemDoneCandidate && terminalCandidate) {
|
|
533
|
+
const merged = mergeCompactionItems(itemDoneCandidate.item, terminalCandidate.item);
|
|
534
|
+
if (!merged) {
|
|
535
|
+
return { ok: false, reason: "conflicting-compaction-item", responseJson: terminalCandidate.item };
|
|
536
|
+
}
|
|
537
|
+
canonicalItem = merged;
|
|
538
|
+
} else {
|
|
539
|
+
canonicalItem = structuredClone((itemDoneCandidate ?? terminalCandidate)!.item);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const responseEnvelope = structuredClone(completedResponse) as RemoteV2ResponseEnvelope;
|
|
543
|
+
if (terminalOutput) {
|
|
544
|
+
const output = structuredClone(terminalOutput);
|
|
545
|
+
if (terminalPosition !== undefined) {
|
|
546
|
+
output[terminalPosition] = structuredClone(canonicalItem);
|
|
547
|
+
} else {
|
|
548
|
+
const insertionIndex = itemDoneCandidate?.outputIndex ?? output.length;
|
|
549
|
+
output.splice(insertionIndex, 0, structuredClone(canonicalItem));
|
|
550
|
+
}
|
|
551
|
+
responseEnvelope.output = output;
|
|
552
|
+
} else {
|
|
553
|
+
responseEnvelope.output = [structuredClone(canonicalItem)];
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
ok: true,
|
|
558
|
+
response: responseEnvelope,
|
|
559
|
+
compactedWindow: [structuredClone(canonicalItem)],
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
233
563
|
export async function executeRemoteV2Compaction(
|
|
234
564
|
options: ExecuteRemoteV2CompactionOptions,
|
|
235
565
|
): Promise<RemoteV2CompactionClientResult> {
|
|
@@ -333,103 +663,14 @@ export async function executeRemoteV2Compaction(
|
|
|
333
663
|
return failure;
|
|
334
664
|
}
|
|
335
665
|
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
return type === "error" || type === "response.failed" || type === "response.incomplete";
|
|
339
|
-
});
|
|
340
|
-
if (errorEvent) {
|
|
341
|
-
const failure: RemoteV2CompactionClientFailure = {
|
|
342
|
-
ok: false,
|
|
343
|
-
reason: "error-event",
|
|
344
|
-
status: response.status,
|
|
345
|
-
errorMessage: getErrorMessage(errorEvent.data),
|
|
346
|
-
responseJson: errorEvent.data,
|
|
347
|
-
};
|
|
348
|
-
writeCompactArtifact(
|
|
349
|
-
{
|
|
350
|
-
protocol: "remote_compaction_v2",
|
|
351
|
-
request: { url: runtime.responsesUrl, headers, body: request },
|
|
352
|
-
response: { status: response.status, headers: responseHeaders, events: summarizeEvents(events) },
|
|
353
|
-
outcome: failure,
|
|
354
|
-
},
|
|
355
|
-
settings,
|
|
356
|
-
context,
|
|
357
|
-
);
|
|
358
|
-
return failure;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const completedEvent = events.find((event) => getEventType(event) === "response.completed");
|
|
362
|
-
const completedData = completedEvent && isRecord(completedEvent.data) ? completedEvent.data : undefined;
|
|
363
|
-
const completedResponse = completedData && isRecord(completedData.response) ? completedData.response : undefined;
|
|
364
|
-
if (!completedResponse) {
|
|
365
|
-
const failure: RemoteV2CompactionClientFailure = {
|
|
366
|
-
ok: false,
|
|
367
|
-
reason: "missing-completed-event",
|
|
368
|
-
status: response.status,
|
|
369
|
-
};
|
|
370
|
-
writeCompactArtifact(
|
|
371
|
-
{
|
|
372
|
-
protocol: "remote_compaction_v2",
|
|
373
|
-
request: { url: runtime.responsesUrl, headers, body: request },
|
|
374
|
-
response: { status: response.status, headers: responseHeaders, events: summarizeEvents(events) },
|
|
375
|
-
outcome: failure,
|
|
376
|
-
},
|
|
377
|
-
settings,
|
|
378
|
-
context,
|
|
379
|
-
);
|
|
380
|
-
return failure;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (completedResponse.status !== "completed" || !Array.isArray(completedResponse.output)) {
|
|
384
|
-
const failure: RemoteV2CompactionClientFailure = {
|
|
385
|
-
ok: false,
|
|
386
|
-
reason: "incomplete-response",
|
|
387
|
-
status: response.status,
|
|
388
|
-
responseJson: completedResponse,
|
|
389
|
-
};
|
|
390
|
-
writeCompactArtifact(
|
|
391
|
-
{
|
|
392
|
-
protocol: "remote_compaction_v2",
|
|
393
|
-
request: { url: runtime.responsesUrl, headers, body: request },
|
|
394
|
-
response: { status: response.status, headers: responseHeaders, events: summarizeEvents(events) },
|
|
395
|
-
outcome: failure,
|
|
396
|
-
},
|
|
397
|
-
settings,
|
|
398
|
-
context,
|
|
399
|
-
);
|
|
400
|
-
return failure;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const compactionItems = completedResponse.output.filter(
|
|
404
|
-
(item): item is Record<string, unknown> => isRecord(item) && item.type === "compaction",
|
|
405
|
-
);
|
|
406
|
-
if (compactionItems.length !== 1) {
|
|
407
|
-
const failure: RemoteV2CompactionClientFailure = {
|
|
408
|
-
ok: false,
|
|
409
|
-
reason: "invalid-compaction-count",
|
|
410
|
-
status: response.status,
|
|
411
|
-
responseJson: completedResponse,
|
|
412
|
-
};
|
|
413
|
-
writeCompactArtifact(
|
|
414
|
-
{
|
|
415
|
-
protocol: "remote_compaction_v2",
|
|
416
|
-
request: { url: runtime.responsesUrl, headers, body: request },
|
|
417
|
-
response: { status: response.status, headers: responseHeaders, events: summarizeEvents(events) },
|
|
418
|
-
outcome: failure,
|
|
419
|
-
},
|
|
420
|
-
settings,
|
|
421
|
-
context,
|
|
422
|
-
);
|
|
423
|
-
return failure;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const compactionItem = compactionItems[0];
|
|
427
|
-
if (!isRemoteV2CompactionItem(compactionItem)) {
|
|
666
|
+
const reconciliation = reconcileSseCheckpoint(events);
|
|
667
|
+
if (!reconciliation.ok) {
|
|
428
668
|
const failure: RemoteV2CompactionClientFailure = {
|
|
429
669
|
ok: false,
|
|
430
|
-
reason:
|
|
670
|
+
reason: reconciliation.reason,
|
|
431
671
|
status: response.status,
|
|
432
|
-
|
|
672
|
+
errorMessage: reconciliation.errorMessage,
|
|
673
|
+
responseJson: reconciliation.responseJson,
|
|
433
674
|
};
|
|
434
675
|
writeCompactArtifact(
|
|
435
676
|
{
|
|
@@ -444,15 +685,15 @@ export async function executeRemoteV2Compaction(
|
|
|
444
685
|
return failure;
|
|
445
686
|
}
|
|
446
687
|
|
|
447
|
-
const responseEnvelope =
|
|
688
|
+
const responseEnvelope = reconciliation.response;
|
|
448
689
|
const success: RemoteV2CompactionClientSuccess = {
|
|
449
690
|
ok: true,
|
|
450
691
|
status: response.status,
|
|
451
|
-
compactedWindow:
|
|
692
|
+
compactedWindow: reconciliation.compactedWindow,
|
|
452
693
|
compactResponseId: isNonEmptyString(responseEnvelope.id) ? responseEnvelope.id.trim() : undefined,
|
|
453
694
|
createdAt: normalizeResponseTimestamp(responseEnvelope.created_at),
|
|
454
695
|
usage: isRecord(responseEnvelope.usage) ? (structuredClone(responseEnvelope.usage) as RemoteV2ResponseUsage) : undefined,
|
|
455
|
-
response:
|
|
696
|
+
response: responseEnvelope,
|
|
456
697
|
};
|
|
457
698
|
writeCompactArtifact(
|
|
458
699
|
{
|
|
@@ -12,6 +12,8 @@ export type CompactionRequestExtras = {
|
|
|
12
12
|
reasoning?: Record<string, unknown>;
|
|
13
13
|
service_tier?: string;
|
|
14
14
|
prompt_cache_key?: string;
|
|
15
|
+
prompt_cache_retention?: "in_memory" | "24h";
|
|
16
|
+
prompt_cache_options?: Record<string, unknown>;
|
|
15
17
|
text?: Record<string, unknown>;
|
|
16
18
|
};
|
|
17
19
|
|
|
@@ -75,6 +77,12 @@ export function rememberRequestContext(
|
|
|
75
77
|
if (typeof payload.prompt_cache_key === "string" && payload.prompt_cache_key.trim().length > 0) {
|
|
76
78
|
extras.prompt_cache_key = payload.prompt_cache_key;
|
|
77
79
|
}
|
|
80
|
+
if (payload.prompt_cache_retention === "in_memory" || payload.prompt_cache_retention === "24h") {
|
|
81
|
+
extras.prompt_cache_retention = payload.prompt_cache_retention;
|
|
82
|
+
}
|
|
83
|
+
if (isRecord(payload.prompt_cache_options)) {
|
|
84
|
+
extras.prompt_cache_options = structuredClone(payload.prompt_cache_options);
|
|
85
|
+
}
|
|
78
86
|
if (isRecord(payload.text)) {
|
|
79
87
|
extras.text = structuredClone(payload.text);
|
|
80
88
|
}
|
|
@@ -88,14 +96,39 @@ export function rememberRequestContext(
|
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Look up the exact live-request identity. When building a synthetic request,
|
|
101
|
+
* supply its producer to omit cache fields unsupported by that model/API.
|
|
102
|
+
* No TTL conversion or implicit cache policy is applied.
|
|
103
|
+
*/
|
|
104
|
+
export function getCompactionRequestExtras(
|
|
105
|
+
identity: RequestContextIdentity,
|
|
106
|
+
producer?: { api: string; compat?: unknown },
|
|
107
|
+
): CompactionRequestExtras | undefined {
|
|
93
108
|
if (!cached || !sameIdentity(cached.identity, identity)) {
|
|
94
109
|
return undefined;
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
try {
|
|
98
|
-
|
|
113
|
+
const extras = structuredClone(cached.extras);
|
|
114
|
+
if (producer) {
|
|
115
|
+
const compat = isRecord(producer.compat) ? producer.compat : {};
|
|
116
|
+
if (producer.api !== "openai-responses") {
|
|
117
|
+
delete extras.prompt_cache_options;
|
|
118
|
+
delete extras.prompt_cache_retention;
|
|
119
|
+
} else {
|
|
120
|
+
if (compat.supportsExplicitPromptCacheMode === true) {
|
|
121
|
+
delete extras.prompt_cache_retention;
|
|
122
|
+
} else {
|
|
123
|
+
delete extras.prompt_cache_options;
|
|
124
|
+
}
|
|
125
|
+
if (compat.supportsLongCacheRetention === false) {
|
|
126
|
+
if (extras.prompt_cache_options?.ttl !== undefined) delete extras.prompt_cache_options;
|
|
127
|
+
if (extras.prompt_cache_retention === "24h") delete extras.prompt_cache_retention;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return extras;
|
|
99
132
|
} catch {
|
|
100
133
|
return undefined;
|
|
101
134
|
}
|
package/src/serializer.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
UserMessage,
|
|
15
15
|
} from "@earendil-works/pi-ai";
|
|
16
16
|
import type { ResponsesCompatibleRequestPayload } from "./runtime";
|
|
17
|
+
import type { CompactionRequestExtras } from "./request-context-cache";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* pi stopped exporting the CompactionPreparation type name in 0.80.x, but it is still
|
|
@@ -95,21 +96,11 @@ export type ResponsesInputItem =
|
|
|
95
96
|
| ResponsesFunctionCallOutputItem
|
|
96
97
|
| ResponsesReasoningItem;
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
/** The synthetic request shares the live-request extras allowlist. */
|
|
100
|
+
export type NativeCompactionRequestBody = CompactionRequestExtras & {
|
|
99
101
|
model: string;
|
|
100
102
|
input: ResponsesInputItem[];
|
|
101
103
|
instructions: string;
|
|
102
|
-
/**
|
|
103
|
-
* Optional passthrough fields mirroring the latest codex_rs CompactionInput.
|
|
104
|
-
* Sourced from the most recent provider request payload when available;
|
|
105
|
-
* undefined fields are omitted from the serialized JSON body.
|
|
106
|
-
*/
|
|
107
|
-
tools?: unknown[];
|
|
108
|
-
parallel_tool_calls?: boolean;
|
|
109
|
-
reasoning?: Record<string, unknown>;
|
|
110
|
-
service_tier?: string;
|
|
111
|
-
prompt_cache_key?: string;
|
|
112
|
-
text?: Record<string, unknown>;
|
|
113
104
|
};
|
|
114
105
|
|
|
115
106
|
export type SerializeResponsesMessagesOptions = {
|
|
@@ -308,6 +299,7 @@ function transformMessagesForResponses(messages: Message[]): Message[] {
|
|
|
308
299
|
transformed.push(message);
|
|
309
300
|
}
|
|
310
301
|
|
|
302
|
+
transformed.push(...createSyntheticToolResults(pendingToolCalls, existingToolResultIds));
|
|
311
303
|
return transformed;
|
|
312
304
|
}
|
|
313
305
|
|
package/src/types.ts
CHANGED
|
@@ -38,8 +38,6 @@ export type DebugArtifactKind =
|
|
|
38
38
|
| "compaction-event"
|
|
39
39
|
| "lifecycle";
|
|
40
40
|
|
|
41
|
-
export type CompactionContinuationMode = "inline" | "followUp" | "off";
|
|
42
|
-
|
|
43
41
|
/** Native-method fallback compaction: which model runs pi's compact() and how deeply. */
|
|
44
42
|
export type NativeFallbackConfig = {
|
|
45
43
|
enabled: boolean;
|
|
@@ -54,13 +52,6 @@ export type NativeFallbackConfig = {
|
|
|
54
52
|
thinkingLevel: ThinkingLevel;
|
|
55
53
|
};
|
|
56
54
|
|
|
57
|
-
export type AutoCompactionConfig = {
|
|
58
|
-
enabled: boolean;
|
|
59
|
-
continuation: CompactionContinuationMode;
|
|
60
|
-
unsupportedFallback: "followUp" | "off";
|
|
61
|
-
reserveTokens?: number;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
55
|
export type CompactionConfig = {
|
|
65
56
|
enabled: boolean;
|
|
66
57
|
/**
|
|
@@ -75,8 +66,6 @@ export type CompactionConfig = {
|
|
|
75
66
|
remoteCompactModel?: string;
|
|
76
67
|
/** Native-method fallback compaction policy (non-Responses APIs, or when the compact endpoint fails). */
|
|
77
68
|
nativeFallback: NativeFallbackConfig;
|
|
78
|
-
/** Threshold-triggered compaction and same-tool-loop continuation policy. */
|
|
79
|
-
autoCompaction: AutoCompactionConfig;
|
|
80
69
|
/** Subset of RESPONSES_COMPACT_CAPABLE_APIS that should use remote compaction. */
|
|
81
70
|
responsesApis: string[];
|
|
82
71
|
notifyOnLoad: boolean;
|
|
@@ -494,13 +483,6 @@ export function createNativeCompactionResult(
|
|
|
494
483
|
};
|
|
495
484
|
}
|
|
496
485
|
|
|
497
|
-
export const DEFAULT_AUTO_COMPACTION_CONFIG: AutoCompactionConfig = {
|
|
498
|
-
enabled: true,
|
|
499
|
-
continuation: "inline",
|
|
500
|
-
unsupportedFallback: "followUp",
|
|
501
|
-
reserveTokens: undefined,
|
|
502
|
-
};
|
|
503
|
-
|
|
504
486
|
export const DEFAULT_NATIVE_FALLBACK_CONFIG: NativeFallbackConfig = {
|
|
505
487
|
enabled: true,
|
|
506
488
|
model: undefined,
|
|
@@ -512,7 +494,6 @@ export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
|
|
|
512
494
|
allowCompactionContinuityBreak: false,
|
|
513
495
|
remoteCompactModel: undefined,
|
|
514
496
|
nativeFallback: { ...DEFAULT_NATIVE_FALLBACK_CONFIG },
|
|
515
|
-
autoCompaction: { ...DEFAULT_AUTO_COMPACTION_CONFIG },
|
|
516
497
|
responsesApis: [...RESPONSES_COMPACT_CAPABLE_APIS],
|
|
517
498
|
notifyOnLoad: false,
|
|
518
499
|
debug: false,
|