pi-openai-toolkit 0.11.1 → 0.13.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.
@@ -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: RemoteV2CompactionItem[];
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 errorEvent = events.find((event) => {
337
- const type = getEventType(event);
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: "malformed-compaction-item",
670
+ reason: reconciliation.reason,
431
671
  status: response.status,
432
- responseJson: compactionItem,
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 = completedResponse as RemoteV2ResponseEnvelope;
688
+ const responseEnvelope = reconciliation.response;
448
689
  const success: RemoteV2CompactionClientSuccess = {
449
690
  ok: true,
450
691
  status: response.status,
451
- compactedWindow: [structuredClone(compactionItem)],
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: structuredClone(responseEnvelope),
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
- /** Return cached extras only for the exact provider/API/model/base URL/session identity. */
92
- export function getCompactionRequestExtras(identity: RequestContextIdentity): CompactionRequestExtras | undefined {
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
- return structuredClone(cached.extras);
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
- export type NativeCompactionRequestBody = {
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,7 +38,7 @@ export type DebugArtifactKind =
38
38
  | "compaction-event"
39
39
  | "lifecycle";
40
40
 
41
- export type CompactionContinuationMode = "inline" | "followUp" | "off";
41
+ export type ContextManagementMode = "off" | "remote";
42
42
 
43
43
  /** Native-method fallback compaction: which model runs pi's compact() and how deeply. */
44
44
  export type NativeFallbackConfig = {
@@ -54,15 +54,12 @@ export type NativeFallbackConfig = {
54
54
  thinkingLevel: ThinkingLevel;
55
55
  };
56
56
 
57
- export type AutoCompactionConfig = {
58
- enabled: boolean;
59
- continuation: CompactionContinuationMode;
60
- unsupportedFallback: "followUp" | "off";
61
- reserveTokens?: number;
62
- };
63
-
64
57
  export type CompactionConfig = {
65
58
  enabled: boolean;
59
+ /** Optional Codex Remote Context management. Disabled by default for compatibility. */
60
+ contextManagement: ContextManagementMode;
61
+ /** Exact provider/model specs allowed to use the extension's CPA-backed Codex gateway. */
62
+ codexGatewayModels: string[];
66
63
  /**
67
64
  * Allow a Responses session whose latest compaction was not created by this extension
68
65
  * to restart native compaction from Pi's current serialized session context.
@@ -75,10 +72,15 @@ export type CompactionConfig = {
75
72
  remoteCompactModel?: string;
76
73
  /** Native-method fallback compaction policy (non-Responses APIs, or when the compact endpoint fails). */
77
74
  nativeFallback: NativeFallbackConfig;
78
- /** Threshold-triggered compaction and same-tool-loop continuation policy. */
79
- autoCompaction: AutoCompactionConfig;
80
- /** Subset of RESPONSES_COMPACT_CAPABLE_APIS that should use remote compaction. */
75
+ /**
76
+ * Subset of RESPONSES_COMPACT_CAPABLE_APIS that should use remote compaction.
77
+ */
81
78
  responsesApis: string[];
79
+ /**
80
+ * Percentage of the context window that, when remaining tokens drop below it,
81
+ * triggers a checkpoint/new_context reminder (0-100; 0 disables reminders).
82
+ */
83
+ contextReminderThresholdPercent: number;
82
84
  notifyOnLoad: boolean;
83
85
  debug: boolean;
84
86
  logProviderPayloads: boolean;
@@ -494,13 +496,6 @@ export function createNativeCompactionResult(
494
496
  };
495
497
  }
496
498
 
497
- export const DEFAULT_AUTO_COMPACTION_CONFIG: AutoCompactionConfig = {
498
- enabled: true,
499
- continuation: "inline",
500
- unsupportedFallback: "followUp",
501
- reserveTokens: undefined,
502
- };
503
-
504
499
  export const DEFAULT_NATIVE_FALLBACK_CONFIG: NativeFallbackConfig = {
505
500
  enabled: true,
506
501
  model: undefined,
@@ -509,11 +504,13 @@ export const DEFAULT_NATIVE_FALLBACK_CONFIG: NativeFallbackConfig = {
509
504
 
510
505
  export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
511
506
  enabled: true,
507
+ contextManagement: "off",
508
+ codexGatewayModels: [],
512
509
  allowCompactionContinuityBreak: false,
513
510
  remoteCompactModel: undefined,
514
511
  nativeFallback: { ...DEFAULT_NATIVE_FALLBACK_CONFIG },
515
- autoCompaction: { ...DEFAULT_AUTO_COMPACTION_CONFIG },
516
512
  responsesApis: [...RESPONSES_COMPACT_CAPABLE_APIS],
513
+ contextReminderThresholdPercent: 5,
517
514
  notifyOnLoad: false,
518
515
  debug: false,
519
516
  logProviderPayloads: false,
@@ -21,7 +21,6 @@
21
21
  "src/debug.ts",
22
22
  "src/details-store.ts",
23
23
  "src/deferred-tool-carryover.ts",
24
- "src/inline-compaction.ts",
25
24
  "src/extension-runtime.ts",
26
25
  "src/image-generation/artifacts.ts",
27
26
  "src/image-generation/client.ts",