@howaboua/pi-codex-conversion 1.5.5 → 1.5.6-dev.32.699e826

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.
@@ -0,0 +1,151 @@
1
+ import type { CompactionEntry, SessionEntry } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ isNativeCompactionDetails,
4
+ isNativeCompactionEntry,
5
+ type NativeCompactionDetails,
6
+ type NativeCompactionEntry,
7
+ type NativeCompactionIdentity,
8
+ } from "./types";
9
+
10
+ export type NativeCompactionEntryMatch = Partial<NativeCompactionIdentity>;
11
+
12
+ export type LatestNativeCompactionResolutionFailureReason =
13
+ | "no-compaction"
14
+ | "latest-compaction-not-native"
15
+ | "latest-native-compaction-mismatch";
16
+
17
+ export type LatestNativeCompactionResolution =
18
+ | {
19
+ ok: true;
20
+ entry: NativeCompactionEntry;
21
+ index: number;
22
+ latestCompactionIndex: number;
23
+ }
24
+ | {
25
+ ok: false;
26
+ reason: LatestNativeCompactionResolutionFailureReason;
27
+ latestCompactionIndex?: number;
28
+ latestCompaction?: CompactionEntry;
29
+ };
30
+
31
+ function entryMatches(entry: NativeCompactionEntry, match: NativeCompactionEntryMatch): boolean {
32
+ const details = entry.details;
33
+ if (!details) {
34
+ return false;
35
+ }
36
+
37
+ return (
38
+ (match.provider === undefined || details.provider === match.provider) &&
39
+ (match.api === undefined || details.api === match.api) &&
40
+ (match.model === undefined || details.model === match.model) &&
41
+ (match.baseUrl === undefined || details.baseUrl === match.baseUrl)
42
+ );
43
+ }
44
+
45
+ export function getNativeCompactionDetails(
46
+ entry: CompactionEntry | SessionEntry | undefined,
47
+ ): NativeCompactionDetails | undefined {
48
+ if (!entry || entry.type !== "compaction") {
49
+ return undefined;
50
+ }
51
+
52
+ return isNativeCompactionDetails(entry.details) ? entry.details : undefined;
53
+ }
54
+
55
+ export function isPersistedNativeCompactionEntry(
56
+ entry: CompactionEntry | SessionEntry | undefined,
57
+ ): entry is NativeCompactionEntry {
58
+ return isNativeCompactionEntry(entry);
59
+ }
60
+
61
+ export function findLatestCompactionEntryIndex(entries: readonly SessionEntry[]): number | undefined {
62
+ for (let index = entries.length - 1; index >= 0; index--) {
63
+ if (entries[index]?.type === "compaction") {
64
+ return index;
65
+ }
66
+ }
67
+
68
+ return undefined;
69
+ }
70
+
71
+ export function findLatestCompactionEntry(entries: readonly SessionEntry[]): CompactionEntry | undefined {
72
+ const index = findLatestCompactionEntryIndex(entries);
73
+ return index === undefined ? undefined : (entries[index] as CompactionEntry);
74
+ }
75
+
76
+ export function findLatestNativeCompactionEntryIndex(
77
+ entries: readonly SessionEntry[],
78
+ match: NativeCompactionEntryMatch = {},
79
+ ): number | undefined {
80
+ for (let index = entries.length - 1; index >= 0; index--) {
81
+ const entry = entries[index];
82
+ if (!isPersistedNativeCompactionEntry(entry)) {
83
+ continue;
84
+ }
85
+
86
+ if (!entryMatches(entry, match)) {
87
+ continue;
88
+ }
89
+
90
+ return index;
91
+ }
92
+
93
+ return undefined;
94
+ }
95
+
96
+ export function findLatestNativeCompactionEntry(
97
+ entries: readonly SessionEntry[],
98
+ match: NativeCompactionEntryMatch = {},
99
+ ): NativeCompactionEntry | undefined {
100
+ const index = findLatestNativeCompactionEntryIndex(entries, match);
101
+ return index === undefined ? undefined : (entries[index] as NativeCompactionEntry);
102
+ }
103
+
104
+ export function findLatestNativeCompactionDetails(
105
+ entries: readonly SessionEntry[],
106
+ match: NativeCompactionEntryMatch = {},
107
+ ): NativeCompactionDetails | undefined {
108
+ return findLatestNativeCompactionEntry(entries, match)?.details;
109
+ }
110
+
111
+ export function resolveLatestNativeCompactionEntry(
112
+ entries: readonly SessionEntry[],
113
+ match: NativeCompactionEntryMatch = {},
114
+ ): LatestNativeCompactionResolution {
115
+ const latestCompactionIndex = findLatestCompactionEntryIndex(entries);
116
+ if (latestCompactionIndex === undefined) {
117
+ return {
118
+ ok: false,
119
+ reason: "no-compaction",
120
+ };
121
+ }
122
+
123
+ const latestCompaction = entries[latestCompactionIndex];
124
+ if (!latestCompaction || latestCompaction.type !== "compaction" || !isPersistedNativeCompactionEntry(latestCompaction)) {
125
+ return {
126
+ ok: false,
127
+ reason: "latest-compaction-not-native",
128
+ latestCompactionIndex,
129
+ latestCompaction:
130
+ latestCompaction && latestCompaction.type === "compaction"
131
+ ? (latestCompaction as CompactionEntry)
132
+ : undefined,
133
+ };
134
+ }
135
+
136
+ if (!entryMatches(latestCompaction, match)) {
137
+ return {
138
+ ok: false,
139
+ reason: "latest-native-compaction-mismatch",
140
+ latestCompactionIndex,
141
+ latestCompaction,
142
+ };
143
+ }
144
+
145
+ return {
146
+ ok: true,
147
+ entry: latestCompaction,
148
+ index: latestCompactionIndex,
149
+ latestCompactionIndex,
150
+ };
151
+ }
@@ -0,0 +1,550 @@
1
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
+ import type { Api, Model } from "@earendil-works/pi-ai";
3
+ import type {
4
+ BranchSummaryEntry,
5
+ CustomMessageEntry,
6
+ SessionEntry,
7
+ SessionMessageEntry,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import type { ResponsesCompatibleRequestPayload } from "./compaction-runtime.ts";
10
+ import type { NativeCompactionEntry } from "./types";
11
+ import {
12
+ compareResponsesInputParity,
13
+ serializeMessagesToResponsesInput,
14
+ type ResponsesInputContentItem,
15
+ type ResponsesInputItem,
16
+ type ResponsesInputMessageItem,
17
+ } from "./serializer";
18
+ import { isAdapterContextExcludedCustomMessageEntry } from "./context-filter.ts";
19
+
20
+ export type FreshAuthoritativePreamble = {
21
+ instructions?: string;
22
+ leadingInput: ResponsesInputMessageItem[];
23
+ trailingInput: ResponsesInputMessageItem[];
24
+ };
25
+
26
+ export type SerializedReplaySlice = {
27
+ entries: SessionEntry[];
28
+ messages: AgentMessage[];
29
+ input: ResponsesInputItem[];
30
+ };
31
+
32
+ export type NativeReplaySegments = {
33
+ boundaryIndex: number;
34
+ firstKeptEntryIndex: number;
35
+ instructions?: string;
36
+ freshPreamble: ResponsesInputMessageItem[];
37
+ trailingPreamble: ResponsesInputMessageItem[];
38
+ compactionSummary: ResponsesInputItem[];
39
+ preCompactionKeptWindow: SerializedReplaySlice;
40
+ compactedWindow: unknown[];
41
+ postCompactionTail: SerializedReplaySlice;
42
+ originalPiReplayInput: ResponsesInputItem[];
43
+ replayInput: unknown[];
44
+ };
45
+
46
+ export type NativeReplayPayloadRewrite = {
47
+ ok: true;
48
+ segments: NativeReplaySegments;
49
+ rewrittenPayload: ResponsesCompatibleRequestPayload;
50
+ };
51
+
52
+ export type NativeReplayPayloadRewriteFailureReason =
53
+ | "compaction-boundary-not-found"
54
+ | "first-kept-entry-not-found"
55
+ | "unsupported-instructions"
56
+ | "invalid-compacted-window"
57
+ | "unexpected-compaction-after-boundary"
58
+ | "expected-pi-replay-mismatch";
59
+
60
+ export type NativeReplayPayloadRewriteFailure = {
61
+ ok: false;
62
+ reason: NativeReplayPayloadRewriteFailureReason;
63
+ parity?: {
64
+ actual: string[];
65
+ expected: string[];
66
+ mismatches: string[];
67
+ };
68
+ };
69
+
70
+ export type NativeReplayPayloadRewriteResult =
71
+ | NativeReplayPayloadRewrite
72
+ | NativeReplayPayloadRewriteFailure;
73
+
74
+ function isRecord(value: unknown): value is Record<string, unknown> {
75
+ return !!value && typeof value === "object" && !Array.isArray(value);
76
+ }
77
+
78
+ function isResponsesInputContentItem(value: unknown): value is ResponsesInputContentItem {
79
+ if (!isRecord(value) || typeof value.type !== "string") {
80
+ return false;
81
+ }
82
+
83
+ if (value.type === "input_text") {
84
+ return typeof value.text === "string";
85
+ }
86
+
87
+ if (value.type === "input_image") {
88
+ return value.detail === "auto" && typeof value.image_url === "string";
89
+ }
90
+
91
+ return false;
92
+ }
93
+
94
+ function isResponsesInputMessageRole(value: unknown): value is ResponsesInputMessageItem["role"] {
95
+ return value === "user" || value === "developer" || value === "system";
96
+ }
97
+
98
+ function isPreambleRole(value: ResponsesInputMessageItem["role"]): value is "developer" | "system" {
99
+ return value === "developer" || value === "system";
100
+ }
101
+
102
+ function isResponsesInputMessageItem(value: unknown): value is ResponsesInputMessageItem {
103
+ if (!isRecord(value) || !isResponsesInputMessageRole(value.role)) {
104
+ return false;
105
+ }
106
+
107
+ const { content } = value;
108
+ return typeof content === "string" || (Array.isArray(content) && content.every(isResponsesInputContentItem));
109
+ }
110
+
111
+ function cloneResponsesInputContentItem(item: ResponsesInputContentItem): ResponsesInputContentItem {
112
+ return item.type === "input_text"
113
+ ? {
114
+ type: "input_text",
115
+ text: item.text,
116
+ }
117
+ : {
118
+ type: "input_image",
119
+ detail: "auto",
120
+ image_url: item.image_url,
121
+ };
122
+ }
123
+
124
+ function cloneResponsesInputMessageItem(item: ResponsesInputMessageItem): ResponsesInputMessageItem {
125
+ return {
126
+ role: item.role,
127
+ content: typeof item.content === "string" ? item.content : item.content.map(cloneResponsesInputContentItem),
128
+ };
129
+ }
130
+
131
+ function cloneStructuredValue(value: unknown): unknown {
132
+ if (
133
+ value === undefined ||
134
+ value === null ||
135
+ typeof value === "string" ||
136
+ typeof value === "number" ||
137
+ typeof value === "boolean"
138
+ ) {
139
+ return value;
140
+ }
141
+
142
+ if (Array.isArray(value)) {
143
+ return value.map(cloneStructuredValue);
144
+ }
145
+
146
+ if (isRecord(value)) {
147
+ const clone: Record<string, unknown> = {};
148
+ for (const [key, nested] of Object.entries(value)) {
149
+ clone[key] = cloneStructuredValue(nested);
150
+ }
151
+ return clone;
152
+ }
153
+
154
+ throw new Error(`Unsupported structured value: ${typeof value}`);
155
+ }
156
+
157
+ function cloneOpaqueCompactedWindow(compactedWindow: readonly unknown[]): unknown[] | undefined {
158
+ const cloned: unknown[] = [];
159
+
160
+ for (const item of compactedWindow) {
161
+ if (!isRecord(item)) {
162
+ return undefined;
163
+ }
164
+
165
+ try {
166
+ cloned.push(cloneStructuredValue(item));
167
+ } catch {
168
+ return undefined;
169
+ }
170
+ }
171
+
172
+ return cloned;
173
+ }
174
+
175
+ function cloneResponsesInputSlice(items: readonly unknown[]): ResponsesInputItem[] | undefined {
176
+ const cloned: ResponsesInputItem[] = [];
177
+
178
+ for (const item of items) {
179
+ try {
180
+ cloned.push(cloneStructuredValue(item) as ResponsesInputItem);
181
+ } catch {
182
+ return undefined;
183
+ }
184
+ }
185
+
186
+ return cloned;
187
+ }
188
+
189
+ function areEquivalentValues(left: unknown, right: unknown): boolean {
190
+ if (Object.is(left, right)) {
191
+ return true;
192
+ }
193
+
194
+ if (Array.isArray(left) || Array.isArray(right)) {
195
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
196
+ return false;
197
+ }
198
+
199
+ for (let index = 0; index < left.length; index++) {
200
+ if (!areEquivalentValues(left[index], right[index])) {
201
+ return false;
202
+ }
203
+ }
204
+
205
+ return true;
206
+ }
207
+
208
+ if (isRecord(left) || isRecord(right)) {
209
+ if (!isRecord(left) || !isRecord(right)) {
210
+ return false;
211
+ }
212
+
213
+ const leftKeys = Object.keys(left).sort();
214
+ const rightKeys = Object.keys(right).sort();
215
+ if (!areEquivalentValues(leftKeys, rightKeys)) {
216
+ return false;
217
+ }
218
+
219
+ for (const key of leftKeys) {
220
+ if (!areEquivalentValues(left[key], right[key])) {
221
+ return false;
222
+ }
223
+ }
224
+
225
+ return true;
226
+ }
227
+
228
+ return false;
229
+ }
230
+
231
+ function toBranchSummaryMessage(entry: BranchSummaryEntry): AgentMessage {
232
+ return {
233
+ role: "branchSummary",
234
+ summary: entry.summary,
235
+ fromId: entry.fromId,
236
+ timestamp: new Date(entry.timestamp).getTime(),
237
+ } as AgentMessage;
238
+ }
239
+
240
+ function toCustomMessage(entry: CustomMessageEntry): AgentMessage {
241
+ return {
242
+ role: "custom",
243
+ customType: entry.customType,
244
+ content: entry.content,
245
+ display: entry.display,
246
+ details: entry.details,
247
+ timestamp: new Date(entry.timestamp).getTime(),
248
+ } as AgentMessage;
249
+ }
250
+
251
+ function toSessionMessage(entry: SessionMessageEntry): AgentMessage {
252
+ return entry.message;
253
+ }
254
+
255
+ function toReplayAgentMessage(entry: SessionEntry): AgentMessage | undefined {
256
+ if (entry.type === "message") {
257
+ return toSessionMessage(entry);
258
+ }
259
+
260
+ if (entry.type === "custom_message") {
261
+ if (isAdapterContextExcludedCustomMessageEntry(entry)) return undefined;
262
+ return toCustomMessage(entry);
263
+ }
264
+
265
+ if (entry.type === "branch_summary") {
266
+ return toBranchSummaryMessage(entry);
267
+ }
268
+
269
+ return undefined;
270
+ }
271
+
272
+ function isPromptEnvelopeItem(item: unknown): item is ResponsesInputMessageItem {
273
+ return isResponsesInputMessageItem(item) && isPreambleRole(item.role);
274
+ }
275
+
276
+ export function extractFreshAuthoritativePreamble(
277
+ payload: ResponsesCompatibleRequestPayload,
278
+ ): FreshAuthoritativePreamble | undefined {
279
+ if (payload.instructions !== undefined && typeof payload.instructions !== "string") {
280
+ return undefined;
281
+ }
282
+
283
+ // Developer/system items in Pi's Responses payload are prompt-level instructions,
284
+ // not transcript entries from session history. Preserve them in the same leading
285
+ // or trailing position that Pi authored so provider-added suffix prompts like
286
+ // GPT-5's trailing developer "# Juice: 0 !important" survive replay unchanged.
287
+ let leadingBoundary = 0;
288
+ while (leadingBoundary < payload.input.length && isPromptEnvelopeItem(payload.input[leadingBoundary])) {
289
+ leadingBoundary += 1;
290
+ }
291
+
292
+ let trailingBoundary = payload.input.length;
293
+ while (trailingBoundary > leadingBoundary && isPromptEnvelopeItem(payload.input[trailingBoundary - 1])) {
294
+ trailingBoundary -= 1;
295
+ }
296
+
297
+ for (let index = leadingBoundary; index < trailingBoundary; index++) {
298
+ if (isPromptEnvelopeItem(payload.input[index])) {
299
+ return undefined;
300
+ }
301
+ }
302
+
303
+ return {
304
+ ...(typeof payload.instructions === "string" ? { instructions: payload.instructions } : {}),
305
+ leadingInput: payload.input.slice(0, leadingBoundary).map((item) => cloneResponsesInputMessageItem(item as ResponsesInputMessageItem)),
306
+ trailingInput: payload.input
307
+ .slice(trailingBoundary)
308
+ .map((item) => cloneResponsesInputMessageItem(item as ResponsesInputMessageItem)),
309
+ };
310
+ }
311
+
312
+ export function collectReplayMessages(entries: readonly SessionEntry[]): AgentMessage[] {
313
+ const messages: AgentMessage[] = [];
314
+
315
+ for (const entry of entries) {
316
+ const message = toReplayAgentMessage(entry);
317
+ if (message) {
318
+ messages.push(message);
319
+ }
320
+ }
321
+
322
+ return messages;
323
+ }
324
+
325
+ function createCompactionSummaryAgentMessage(entry: NativeCompactionEntry): AgentMessage {
326
+ return {
327
+ role: "compactionSummary",
328
+ summary: entry.summary,
329
+ tokensBefore: entry.tokensBefore,
330
+ timestamp: new Date(entry.timestamp).getTime(),
331
+ } as AgentMessage;
332
+ }
333
+
334
+ function createReplaySlice(
335
+ entries: readonly SessionEntry[],
336
+ messages: readonly AgentMessage[],
337
+ input: readonly ResponsesInputItem[],
338
+ ): SerializedReplaySlice {
339
+ return {
340
+ entries: [...entries],
341
+ messages: [...messages],
342
+ input: [...input],
343
+ };
344
+ }
345
+
346
+ function findEntryIndexByIdBeforeBoundary(
347
+ entries: readonly SessionEntry[],
348
+ entryId: string,
349
+ boundaryIndex: number,
350
+ ): number | undefined {
351
+ const index = entries.findIndex((entry, candidateIndex) => candidateIndex < boundaryIndex && entry.id === entryId);
352
+ return index >= 0 ? index : undefined;
353
+ }
354
+
355
+ export function findCompactionBoundaryIndex(
356
+ entries: readonly SessionEntry[],
357
+ compactionEntryId: string,
358
+ ): number | undefined {
359
+ const boundaryIndex = entries.findIndex((entry) => entry.id === compactionEntryId);
360
+ return boundaryIndex >= 0 ? boundaryIndex : undefined;
361
+ }
362
+
363
+ export function findEntriesStrictlyAfterCompactionBoundary(
364
+ entries: readonly SessionEntry[],
365
+ compactionEntryId: string,
366
+ ): SessionEntry[] | undefined {
367
+ const boundaryIndex = findCompactionBoundaryIndex(entries, compactionEntryId);
368
+ if (boundaryIndex === undefined) {
369
+ return undefined;
370
+ }
371
+
372
+ return entries.slice(boundaryIndex + 1);
373
+ }
374
+
375
+ export function collectLiveTailMessages(entries: readonly SessionEntry[]): AgentMessage[] {
376
+ return collectReplayMessages(entries);
377
+ }
378
+
379
+ export function serializeLiveTailToResponsesInput<TApi extends Api>(args: {
380
+ model: Model<TApi>;
381
+ entries: readonly SessionEntry[];
382
+ }): ResponsesInputItem[] {
383
+ return serializeMessagesToResponsesInput(args.model, collectReplayMessages(args.entries));
384
+ }
385
+
386
+ function buildNativeReplaySegmentsInternal<TApi extends Api>(args: {
387
+ model: Model<TApi>;
388
+ payload: ResponsesCompatibleRequestPayload;
389
+ branchEntries: readonly SessionEntry[];
390
+ compactionEntry: NativeCompactionEntry;
391
+ }): NativeReplayPayloadRewriteResult {
392
+ const boundaryIndex = findCompactionBoundaryIndex(args.branchEntries, args.compactionEntry.id);
393
+ if (boundaryIndex === undefined) {
394
+ return {
395
+ ok: false,
396
+ reason: "compaction-boundary-not-found",
397
+ };
398
+ }
399
+
400
+ const firstKeptEntryIndex = findEntryIndexByIdBeforeBoundary(
401
+ args.branchEntries,
402
+ args.compactionEntry.firstKeptEntryId,
403
+ boundaryIndex,
404
+ );
405
+ if (firstKeptEntryIndex === undefined) {
406
+ return {
407
+ ok: false,
408
+ reason: "first-kept-entry-not-found",
409
+ };
410
+ }
411
+
412
+ const freshPreamble = extractFreshAuthoritativePreamble(args.payload);
413
+ if (!freshPreamble) {
414
+ return {
415
+ ok: false,
416
+ reason: "unsupported-instructions",
417
+ };
418
+ }
419
+
420
+ const newerCompactionEntry = args.branchEntries
421
+ .slice(boundaryIndex + 1)
422
+ .some((entry) => entry.type === "compaction");
423
+ if (newerCompactionEntry) {
424
+ return {
425
+ ok: false,
426
+ reason: "unexpected-compaction-after-boundary",
427
+ };
428
+ }
429
+
430
+ const compactedWindow = cloneOpaqueCompactedWindow(args.compactionEntry.details?.compactedWindow ?? []);
431
+ if (!compactedWindow) {
432
+ return {
433
+ ok: false,
434
+ reason: "invalid-compacted-window",
435
+ };
436
+ }
437
+
438
+ const preCompactionEntries = args.branchEntries.slice(firstKeptEntryIndex, boundaryIndex);
439
+ const postCompactionEntries = args.branchEntries.slice(boundaryIndex + 1);
440
+ const preCompactionKeptMessages = collectReplayMessages(preCompactionEntries);
441
+ const postCompactionTailMessages = collectReplayMessages(postCompactionEntries);
442
+ const compactionSummaryMessage = createCompactionSummaryAgentMessage(args.compactionEntry);
443
+ const serializedPiHistoryInput = serializeMessagesToResponsesInput(args.model, [
444
+ compactionSummaryMessage,
445
+ ...preCompactionKeptMessages,
446
+ ...postCompactionTailMessages,
447
+ ]);
448
+ const originalPiReplayInput: ResponsesInputItem[] = [
449
+ ...freshPreamble.leadingInput,
450
+ ...serializedPiHistoryInput,
451
+ ...freshPreamble.trailingInput,
452
+ ];
453
+
454
+ if (!areEquivalentValues(args.payload.input, originalPiReplayInput)) {
455
+ const parity = compareResponsesInputParity(args.payload.input, originalPiReplayInput);
456
+ return {
457
+ ok: false,
458
+ reason: "expected-pi-replay-mismatch",
459
+ parity: {
460
+ actual: parity.actual,
461
+ expected: parity.expected,
462
+ mismatches: parity.mismatches,
463
+ },
464
+ };
465
+ }
466
+
467
+ const freshPreambleCount = freshPreamble.leadingInput.length;
468
+ const trailingPreambleCount = freshPreamble.trailingInput.length;
469
+ const compactionSummaryCount = serializeMessagesToResponsesInput(args.model, [compactionSummaryMessage]).length;
470
+ const preCompactionKeptCount = serializeMessagesToResponsesInput(args.model, preCompactionKeptMessages).length;
471
+ const tailStartIndex = freshPreambleCount + compactionSummaryCount + preCompactionKeptCount;
472
+ const tailEndIndex = args.payload.input.length - trailingPreambleCount;
473
+ const actualCompactionSummary = cloneResponsesInputSlice(
474
+ args.payload.input.slice(freshPreambleCount, freshPreambleCount + compactionSummaryCount),
475
+ );
476
+ const actualPreCompactionKeptWindow = cloneResponsesInputSlice(
477
+ args.payload.input.slice(
478
+ freshPreambleCount + compactionSummaryCount,
479
+ freshPreambleCount + compactionSummaryCount + preCompactionKeptCount,
480
+ ),
481
+ );
482
+ const actualPostCompactionTail = cloneResponsesInputSlice(args.payload.input.slice(tailStartIndex, tailEndIndex));
483
+ if (!actualCompactionSummary || !actualPreCompactionKeptWindow || !actualPostCompactionTail) {
484
+ return {
485
+ ok: false,
486
+ reason: "expected-pi-replay-mismatch",
487
+ };
488
+ }
489
+
490
+ const preCompactionKeptWindow = createReplaySlice(
491
+ preCompactionEntries,
492
+ preCompactionKeptMessages,
493
+ actualPreCompactionKeptWindow,
494
+ );
495
+ const postCompactionTail = createReplaySlice(
496
+ postCompactionEntries,
497
+ postCompactionTailMessages,
498
+ actualPostCompactionTail,
499
+ );
500
+
501
+ return {
502
+ ok: true,
503
+ segments: {
504
+ boundaryIndex,
505
+ firstKeptEntryIndex,
506
+ instructions: freshPreamble.instructions,
507
+ freshPreamble: freshPreamble.leadingInput,
508
+ trailingPreamble: freshPreamble.trailingInput,
509
+ compactionSummary: actualCompactionSummary,
510
+ preCompactionKeptWindow,
511
+ compactedWindow,
512
+ postCompactionTail,
513
+ originalPiReplayInput,
514
+ replayInput: [
515
+ ...freshPreamble.leadingInput,
516
+ ...compactedWindow,
517
+ ...actualPostCompactionTail,
518
+ ...freshPreamble.trailingInput,
519
+ ],
520
+ },
521
+ rewrittenPayload: {
522
+ ...args.payload,
523
+ ...(freshPreamble.instructions !== undefined ? { instructions: freshPreamble.instructions } : {}),
524
+ input: [
525
+ ...freshPreamble.leadingInput,
526
+ ...compactedWindow,
527
+ ...actualPostCompactionTail,
528
+ ...freshPreamble.trailingInput,
529
+ ],
530
+ },
531
+ };
532
+ }
533
+
534
+ export function buildNativeReplaySegments<TApi extends Api>(args: {
535
+ model: Model<TApi>;
536
+ payload: ResponsesCompatibleRequestPayload;
537
+ branchEntries: readonly SessionEntry[];
538
+ compactionEntry: NativeCompactionEntry;
539
+ }): NativeReplayPayloadRewriteResult {
540
+ return buildNativeReplaySegmentsInternal(args);
541
+ }
542
+
543
+ export function rewriteResponsesPayloadWithNativeReplay<TApi extends Api>(args: {
544
+ model: Model<TApi>;
545
+ payload: ResponsesCompatibleRequestPayload;
546
+ branchEntries: readonly SessionEntry[];
547
+ compactionEntry: NativeCompactionEntry;
548
+ }): NativeReplayPayloadRewriteResult {
549
+ return buildNativeReplaySegmentsInternal(args);
550
+ }