pi-openai-toolkit 0.23.1 → 0.24.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/LICENSE +21 -21
- package/NOTICE +83 -83
- package/README.md +4 -11
- package/README.zh.md +4 -11
- package/config.schema.json +264 -278
- package/docs/configuration.md +304 -298
- package/docs/internals.md +98 -181
- package/package.json +123 -135
- package/src/compact-client.ts +412 -412
- package/src/compaction-projection.ts +35 -35
- package/src/config/legacy.ts +12 -62
- package/src/config/migration.ts +115 -103
- package/src/config/notifications.ts +10 -4
- package/src/config/policy.ts +9 -10
- package/src/config/v2.ts +12 -51
- package/src/config.ts +320 -310
- package/src/debug.ts +238 -238
- package/src/deferred-tool-carryover.ts +282 -282
- package/src/details-store.ts +151 -151
- package/src/extension-runtime.ts +1095 -1986
- package/src/image-generation/extension.ts +189 -189
- package/src/image-generation/protocol.ts +579 -579
- package/src/image-generation/types.ts +300 -300
- package/src/native-fallback.ts +14 -23
- package/src/payload-rewrite.ts +515 -515
- package/src/pi-context-hook.ts +117 -117
- package/src/remote-v2-client.ts +811 -811
- package/src/request-context-cache.ts +155 -155
- package/src/responses-headers.ts +180 -180
- package/src/retired-remote-windows.ts +35 -0
- package/src/runtime.ts +875 -865
- package/src/serializer.ts +882 -882
- package/src/supported-environment.ts +27 -27
- package/src/types.ts +541 -565
- package/src/web-search/alpha-client.ts +876 -876
- package/src/web-search/extension.ts +530 -530
- package/src/web-search/payload.ts +299 -299
- package/src/web-search/prompt.ts +60 -60
- package/src/web-search/types.ts +166 -166
- package/tsconfig.check.json +50 -50
- package/src/context-management/codex-provider.ts +0 -270
- package/src/context-management/history-notes.ts +0 -357
- package/src/context-management/manual-compact.ts +0 -599
- package/src/context-management/messages.ts +0 -238
- package/src/context-management/namespace-tools.ts +0 -426
- package/src/context-management/responses-tools.ts +0 -238
- package/src/context-management/tool-contract.ts +0 -62
- package/src/context-management/tools.ts +0 -371
- package/src/context-management/types.ts +0 -168
- package/src/context-management/window-budget.ts +0 -83
- package/src/context-management/window-bulk.ts +0 -155
- package/src/context-management/window-manager.ts +0 -905
- package/src/context-management/window-request.ts +0 -125
package/src/extension-runtime.ts
CHANGED
|
@@ -1,1986 +1,1095 @@
|
|
|
1
|
-
import { isDeepStrictEqual } from "node:util";
|
|
2
|
-
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} from "./
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
} from "./
|
|
60
|
-
import {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
type
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
(
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
{
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
);
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
);
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
"
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
)
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
if (!
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
if (
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
registrationState,
|
|
1097
|
-
);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
dependencies.remoteManualCompact.cancel(manualOperation);
|
|
1101
|
-
try {
|
|
1102
|
-
const detached = dependencies.contextWindows.prepareDetachedCompaction(event);
|
|
1103
|
-
// A local consumer cannot summarize history owned by a Remote window,
|
|
1104
|
-
// even after switching modes or disabling Toolkit compaction.
|
|
1105
|
-
if (detached) return detached;
|
|
1106
|
-
// A boundary already persisted by an earlier detach still owns this session.
|
|
1107
|
-
// Retired Remote history is outside Pi's effective projection at that point, so
|
|
1108
|
-
// ordinary compaction may continue; record why it no longer detaches.
|
|
1109
|
-
const ownedIdentity = dependencies.contextWindows.currentIdentity();
|
|
1110
|
-
if (ownedIdentity) {
|
|
1111
|
-
writeDebugArtifact(
|
|
1112
|
-
"compaction-event",
|
|
1113
|
-
{
|
|
1114
|
-
event: "session_before_compact.detached-already-owned",
|
|
1115
|
-
windowId: ownedIdentity.currentWindowId,
|
|
1116
|
-
superseded: dependencies.contextWindows.hasSupersededWindow(),
|
|
1117
|
-
},
|
|
1118
|
-
config,
|
|
1119
|
-
ctx,
|
|
1120
|
-
);
|
|
1121
|
-
}
|
|
1122
|
-
} catch {
|
|
1123
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1124
|
-
return { cancel: true };
|
|
1125
|
-
}
|
|
1126
|
-
if (!config.enabled) return undefined;
|
|
1127
|
-
|
|
1128
|
-
// Resolve producer protocol policy from this operation's document, not a later disk read.
|
|
1129
|
-
if (resolved.format === "v2" && config.remoteCompactModel) {
|
|
1130
|
-
const producer = parseModelSpec(config.remoteCompactModel);
|
|
1131
|
-
if (producer) {
|
|
1132
|
-
const producerPolicy = resolveToolkitConfig(resolved.snapshot, { provider: producer.provider, id: producer.modelId });
|
|
1133
|
-
notifyConfigIssues(ctx, producerPolicy);
|
|
1134
|
-
if (producerPolicy.invalidFeatures.includes("compatibility")) return { cancel: true };
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
// Branch 1: Responses-family APIs use remote_compaction_v2 on the normal Responses stream.
|
|
1138
|
-
let remoteAttempted = false;
|
|
1139
|
-
const resolution = await resolveRemoteCompactionExecution(
|
|
1140
|
-
ctx,
|
|
1141
|
-
{
|
|
1142
|
-
enabled: config.enabled,
|
|
1143
|
-
responsesApis: config.responsesApis,
|
|
1144
|
-
codexGatewayModels: compactGatewayModels(resolved),
|
|
1145
|
-
},
|
|
1146
|
-
config.remoteCompactModel,
|
|
1147
|
-
);
|
|
1148
|
-
if (resolution.ok) {
|
|
1149
|
-
remoteAttempted = true;
|
|
1150
|
-
const responsesOutcome = await runResponsesNativeCompact(
|
|
1151
|
-
event,
|
|
1152
|
-
ctx,
|
|
1153
|
-
config,
|
|
1154
|
-
resolution.execution,
|
|
1155
|
-
dependencies.remoteCompact,
|
|
1156
|
-
dependencies.projectCompactionContext,
|
|
1157
|
-
dependencies.standaloneCompact,
|
|
1158
|
-
);
|
|
1159
|
-
if (responsesOutcome.outcome === "success") {
|
|
1160
|
-
return { compaction: responsesOutcome.compaction };
|
|
1161
|
-
}
|
|
1162
|
-
if (responsesOutcome.outcome === "aborted") {
|
|
1163
|
-
return { cancel: true };
|
|
1164
|
-
}
|
|
1165
|
-
if (responsesOutcome.outcome === "unprojected-input") {
|
|
1166
|
-
const message = responsesOutcome.reason === COMPACTION_CHECKPOINT_CONTEXT_EDITED
|
|
1167
|
-
? "A context edit changed history sealed in the opaque checkpoint. Start a new session or navigate before that checkpoint; its encrypted content cannot be selectively rewritten."
|
|
1168
|
-
: responsesOutcome.reason === COMPACTION_PROJECTION_UNAVAILABLE
|
|
1169
|
-
? "Pi's ordered context-hook projection is unavailable; Remote V2 was not sent raw session history."
|
|
1170
|
-
: responsesOutcome.reason === COMPACTION_SESSION_CONTEXT_UNAVAILABLE
|
|
1171
|
-
? "Pi did not provide the current session context required by its context-hook projection."
|
|
1172
|
-
: responsesOutcome.reason === COMPACTION_PROJECTED_CONTEXT_UNAVAILABLE
|
|
1173
|
-
? "Pi's projected context did not include the current compaction summary anchor."
|
|
1174
|
-
: "The latest opaque checkpoint has no marker for the configured Remote V2 context source, or was created in the other mode.";
|
|
1175
|
-
writeDebugArtifact(
|
|
1176
|
-
"compaction-event",
|
|
1177
|
-
{
|
|
1178
|
-
event: "session_before_compact.remote-v2-unprojected-input",
|
|
1179
|
-
reason: responsesOutcome.reason,
|
|
1180
|
-
contextSource: config.remoteV2ContextSource,
|
|
1181
|
-
inputProvenance: getRemoteV2InputProvenance(config.remoteV2ContextSource),
|
|
1182
|
-
piContextHookPatch: dependencies.piContextHookPatch,
|
|
1183
|
-
message,
|
|
1184
|
-
},
|
|
1185
|
-
config,
|
|
1186
|
-
ctx,
|
|
1187
|
-
);
|
|
1188
|
-
notifyWarning(
|
|
1189
|
-
ctx,
|
|
1190
|
-
`Remote V2 compaction cancelled (${responsesOutcome.reason}); ${message} The existing checkpoint and session history were left unchanged.`,
|
|
1191
|
-
);
|
|
1192
|
-
return { cancel: true };
|
|
1193
|
-
}
|
|
1194
|
-
// Only an actual exhausted remote attempt warns, never an input/routing skip.
|
|
1195
|
-
if (event.signal?.aborted) return { cancel: true };
|
|
1196
|
-
if (responsesOutcome.remoteFailure) {
|
|
1197
|
-
const failure = responsesOutcome.remoteFailure;
|
|
1198
|
-
writeDebugArtifact("compaction-event", {
|
|
1199
|
-
event: "session_before_compact.remote-to-native-summary",
|
|
1200
|
-
protocol: responsesOutcome.protocol,
|
|
1201
|
-
...failure,
|
|
1202
|
-
nextStep: "native-summary",
|
|
1203
|
-
}, config, ctx);
|
|
1204
|
-
notifyWarning(ctx, `Remote compaction failed (${failure.reason}${failure.status ? `, HTTP ${failure.status}` : ""}${failure.errorCode ? `, ${failure.errorCode}` : ""}); continuing with a native text summary. No encrypted remote checkpoint was produced.${failure.errorMessage ? ` ${failure.errorMessage}` : ""}`);
|
|
1205
|
-
}
|
|
1206
|
-
} else {
|
|
1207
|
-
writeDebugArtifact(
|
|
1208
|
-
"compaction-event",
|
|
1209
|
-
{
|
|
1210
|
-
event: "session_before_compact.remote-v2-unavailable",
|
|
1211
|
-
reason: resolution.reason,
|
|
1212
|
-
provider: resolution.provider,
|
|
1213
|
-
api: resolution.api,
|
|
1214
|
-
model: resolution.model,
|
|
1215
|
-
baseUrl: resolution.baseUrl,
|
|
1216
|
-
modelSpec: resolution.modelSpec,
|
|
1217
|
-
errorMessage: resolution.errorMessage,
|
|
1218
|
-
},
|
|
1219
|
-
config,
|
|
1220
|
-
ctx,
|
|
1221
|
-
);
|
|
1222
|
-
const expectedConsumerCapabilityMiss =
|
|
1223
|
-
resolution.reason === "unsupported-api" && resolution.modelSpec === undefined;
|
|
1224
|
-
if (config.remoteCompactModel && !expectedConsumerCapabilityMiss) {
|
|
1225
|
-
notifyWarning(
|
|
1226
|
-
ctx,
|
|
1227
|
-
`remote compaction model "${config.remoteCompactModel}" unusable (${resolution.reason}); using the native fallback chain`,
|
|
1228
|
-
);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
// Branch 2: run pi's native compaction method. A failed remote request is compacted by the
|
|
1233
|
-
// remote producer itself; a model that cannot use remote v2 at all uses nativeFallback.model.
|
|
1234
|
-
const fallbackModelSpec = remoteAttempted ? config.remoteCompactModel : config.nativeFallback.model;
|
|
1235
|
-
const fallback = await dependencies.nativeFallback({ ctx, event, config, modelSpec: fallbackModelSpec });
|
|
1236
|
-
if (fallback.ok) {
|
|
1237
|
-
if (ctx.hasUI) {
|
|
1238
|
-
ctx.ui.notify(
|
|
1239
|
-
`${COMPACTION_EXTENSION_ID}: compacted with ${fallback.model.provider}/${fallback.model.id} (native method)`,
|
|
1240
|
-
"info",
|
|
1241
|
-
);
|
|
1242
|
-
}
|
|
1243
|
-
writeDebugArtifact(
|
|
1244
|
-
"compaction-event",
|
|
1245
|
-
{
|
|
1246
|
-
event: "session_before_compact.fallback-success",
|
|
1247
|
-
model: fallback.model,
|
|
1248
|
-
},
|
|
1249
|
-
config,
|
|
1250
|
-
ctx,
|
|
1251
|
-
);
|
|
1252
|
-
return { compaction: fallback.result };
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
if (fallback.reason === "aborted") {
|
|
1256
|
-
return { cancel: true };
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
writeDebugArtifact(
|
|
1260
|
-
"compaction-event",
|
|
1261
|
-
{
|
|
1262
|
-
event: "session_before_compact.fallback-skip",
|
|
1263
|
-
reason: fallback.reason,
|
|
1264
|
-
modelSpec: fallback.modelSpec,
|
|
1265
|
-
errorMessage: fallback.errorMessage,
|
|
1266
|
-
},
|
|
1267
|
-
config,
|
|
1268
|
-
ctx,
|
|
1269
|
-
);
|
|
1270
|
-
|
|
1271
|
-
// Intentional pi-default paths: feature disabled, nothing configured, or it matches the current one.
|
|
1272
|
-
const intentionalSkip =
|
|
1273
|
-
fallback.reason === "disabled" ||
|
|
1274
|
-
fallback.reason === "no-model-configured" ||
|
|
1275
|
-
fallback.reason === "same-as-current-model";
|
|
1276
|
-
if (!intentionalSkip) {
|
|
1277
|
-
// An explicit summary model is authoritative: the active model may be
|
|
1278
|
-
// unable to summarize at all. Preserve context on failure, never reroute.
|
|
1279
|
-
notifyWarning(
|
|
1280
|
-
ctx,
|
|
1281
|
-
`compaction model "${fallback.modelSpec ?? fallbackModelSpec}" failed (${fallback.reason}${fallback.errorMessage ? `: ${fallback.errorMessage}` : ""}); compaction cancelled, context preserved; not switching to the current model`,
|
|
1282
|
-
);
|
|
1283
|
-
return { cancel: true };
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// Branch 3: pi's default native compaction with the current model.
|
|
1287
|
-
return undefined;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
function rejectInheritedCheckpointReplay(
|
|
1291
|
-
ctx: ExtensionContext,
|
|
1292
|
-
config: CompactionConfig,
|
|
1293
|
-
compactionEntryId: string,
|
|
1294
|
-
reason: string,
|
|
1295
|
-
): undefined {
|
|
1296
|
-
writeReplayFailureArtifact({ reason, compactionEntryId }, config, ctx);
|
|
1297
|
-
reportPiContextHookFailure(ctx, `replay-failed:${reason}`);
|
|
1298
|
-
if (ctx.hasUI) ctx.ui.notify(`${COMPACTION_EXTENSION_ID}: inherited checkpoint replay failed (${reason}); request aborted`, "error");
|
|
1299
|
-
ctx.abort();
|
|
1300
|
-
return undefined;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
async function handleContextInternal(
|
|
1304
|
-
event: ContextWithSystemEvent,
|
|
1305
|
-
ctx: ExtensionContext,
|
|
1306
|
-
pi: ExtensionAPI,
|
|
1307
|
-
loadConfig: typeof loadToolkitConfig,
|
|
1308
|
-
contextWindows: CodexContextWindowManager,
|
|
1309
|
-
remoteContextActive: RemoteContextActive,
|
|
1310
|
-
getContextToolRegistrationState: ContextToolRegistrationStateReader,
|
|
1311
|
-
) {
|
|
1312
|
-
const resolved = contextOperation(loadConfig, ctx);
|
|
1313
|
-
requireContextPolicy(resolved, ctx);
|
|
1314
|
-
const config = resolved.config.compaction;
|
|
1315
|
-
try {
|
|
1316
|
-
contextWindows.synchronize(ctx);
|
|
1317
|
-
} catch {
|
|
1318
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1319
|
-
reportPiContextHookFailure(ctx, "malformed-window-state");
|
|
1320
|
-
ctx.abort();
|
|
1321
|
-
return undefined;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
let localMessages: AgentMessage[] | undefined;
|
|
1325
|
-
let remoteReplayProjection = false;
|
|
1326
|
-
// Disabled and intentionally downgraded runtimes never enter opaque replay,
|
|
1327
|
-
// but still honor a window already owned by this session.
|
|
1328
|
-
let localProjectionOnly = !config.enabled;
|
|
1329
|
-
if (config.enabled && config.contextManagement === "remote") {
|
|
1330
|
-
const contextModel = await resolveContextCapabilityModel(ctx);
|
|
1331
|
-
const remoteActive = await remoteContextActive(ctx, config, contextModel);
|
|
1332
|
-
if (remoteActive) {
|
|
1333
|
-
try {
|
|
1334
|
-
// Measure the provider-visible current window, not Pi's durable transcript.
|
|
1335
|
-
// The latter still contains retired remote windows and can be inflated by
|
|
1336
|
-
// an upstream error even though this request only carries the live window.
|
|
1337
|
-
let projected = contextWindows.project(event.messages, "remote", false);
|
|
1338
|
-
if (contextWindows.isPreparingCompaction) {
|
|
1339
|
-
// Synthetic preparation is read-only. The caller owns the one strict
|
|
1340
|
-
// retained-copy match, then encrypted wire adaptation after all hooks.
|
|
1341
|
-
return { messages: projected };
|
|
1342
|
-
}
|
|
1343
|
-
// Projection may observe the exact queued rollover before Pi persists
|
|
1344
|
-
// it. Decide replay ownership from this request, not the old branch.
|
|
1345
|
-
const ownsWindow = contextWindows.hasEffectiveWindow() || !contextWindows.currentIdentity();
|
|
1346
|
-
if (ownsWindow) projected = contextWindows.project(projected, "remote");
|
|
1347
|
-
contextWindows.recordBudget(
|
|
1348
|
-
pi,
|
|
1349
|
-
ctx,
|
|
1350
|
-
true,
|
|
1351
|
-
config.contextReminderThresholdPercent,
|
|
1352
|
-
);
|
|
1353
|
-
reportProjectionDiagnostics(contextWindows, config, ctx);
|
|
1354
|
-
if (ownsWindow) {
|
|
1355
|
-
return projected.length === event.messages.length && projected.every((message, index) => message === event.messages[index])
|
|
1356
|
-
? undefined
|
|
1357
|
-
: { messages: projected };
|
|
1358
|
-
}
|
|
1359
|
-
localMessages = projected;
|
|
1360
|
-
remoteReplayProjection = true;
|
|
1361
|
-
} catch (error) {
|
|
1362
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1363
|
-
reportPiContextHookFailure(ctx, "malformed-window-state");
|
|
1364
|
-
ctx.abort();
|
|
1365
|
-
return undefined;
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// An unavailable covered runtime must not silently become an ordinary
|
|
1370
|
-
// replay consumer. Explicit host exclusion remains the one intentional
|
|
1371
|
-
// downgrade; active Remote can replay a newer inherited checkpoint.
|
|
1372
|
-
if (!remoteActive && isCodexContextModel(contextModel, config)) {
|
|
1373
|
-
const registrationState = getContextToolRegistrationState();
|
|
1374
|
-
if (!isExcludedGatewayContext(contextModel, config, registrationState)
|
|
1375
|
-
&& isCodexGatewayModel(contextModel, config.gatewayContextModels)) {
|
|
1376
|
-
const reason = contextManagementFailureReason(registrationState);
|
|
1377
|
-
notifyRemoteContextFailure(ctx, reason);
|
|
1378
|
-
reportPiContextHookFailure(ctx, reason);
|
|
1379
|
-
ctx.abort();
|
|
1380
|
-
return undefined;
|
|
1381
|
-
}
|
|
1382
|
-
localProjectionOnly = true;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
if (!remoteReplayProjection) {
|
|
1387
|
-
try {
|
|
1388
|
-
const projected = contextWindows.project(event.messages, "local");
|
|
1389
|
-
localMessages = projected;
|
|
1390
|
-
if (localProjectionOnly || contextWindows.hasEffectiveWindow()) {
|
|
1391
|
-
// A persisted boundary owns earlier history regardless of the selected
|
|
1392
|
-
// mode. Neither disabled policy nor ordinary replay may expand it.
|
|
1393
|
-
return projected.length === event.messages.length && projected.every((message, index) => message === event.messages[index])
|
|
1394
|
-
? undefined
|
|
1395
|
-
: { messages: projected };
|
|
1396
|
-
}
|
|
1397
|
-
} catch (error) {
|
|
1398
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1399
|
-
reportPiContextHookFailure(ctx, error instanceof Error ? error.message : String(error));
|
|
1400
|
-
ctx.abort();
|
|
1401
|
-
return undefined;
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
// Without an effective Remote boundary, the latest checkpoint owns replay.
|
|
1406
|
-
// This also covers a newer checkpoint superseding a historical window.
|
|
1407
|
-
const visibleMessages = localMessages ?? contextWindows.project(event.messages, "off");
|
|
1408
|
-
const finishProjection = (messages: AgentMessage[]) => {
|
|
1409
|
-
// Compare canonical retained copies before Remote encrypted-output encoding.
|
|
1410
|
-
const projected = remoteReplayProjection ? contextWindows.project(messages, "remote") : messages;
|
|
1411
|
-
return projected.length === event.messages.length && projected.every((message, index) => message === event.messages[index])
|
|
1412
|
-
? undefined : { messages: projected };
|
|
1413
|
-
};
|
|
1414
|
-
const replayEvent = visibleMessages === event.messages ? event : { ...event, messages: visibleMessages };
|
|
1415
|
-
const branchEntries = ctx.sessionManager.getBranch();
|
|
1416
|
-
const inherited = config.contextManagement === "remote" && contextWindows.hasSupersededWindow()
|
|
1417
|
-
? resolveLatestNativeCompactionEntry(branchEntries) : undefined;
|
|
1418
|
-
const inheritedCheckpoint = inherited?.ok ? inherited.entry : undefined;
|
|
1419
|
-
const resolution = await resolveNativeCompactionEnvironment(ctx, {
|
|
1420
|
-
enabled: config.enabled,
|
|
1421
|
-
// Disabling checkpoint generation cannot turn an inherited opaque
|
|
1422
|
-
// checkpoint into a usable text summary. Replay checks transport support.
|
|
1423
|
-
responsesApis: inheritedCheckpoint ? undefined : config.responsesApis,
|
|
1424
|
-
codexGatewayModels: compactGatewayModels(resolved),
|
|
1425
|
-
});
|
|
1426
|
-
if (!resolution.ok) {
|
|
1427
|
-
return inheritedCheckpoint
|
|
1428
|
-
? rejectInheritedCheckpointReplay(ctx, config, inheritedCheckpoint.id, resolution.reason)
|
|
1429
|
-
: finishProjection(visibleMessages);
|
|
1430
|
-
}
|
|
1431
|
-
const latest = resolveLatestNativeCompactionEntry(branchEntries, { baseUrl: resolution.runtime.baseUrl });
|
|
1432
|
-
if (!latest.ok) {
|
|
1433
|
-
return inheritedCheckpoint
|
|
1434
|
-
? rejectInheritedCheckpointReplay(ctx, config, inheritedCheckpoint.id, latest.reason)
|
|
1435
|
-
: finishProjection(visibleMessages);
|
|
1436
|
-
}
|
|
1437
|
-
const result = removeNativeCompactionRetainedMessages({
|
|
1438
|
-
messages: replayEvent.messages,
|
|
1439
|
-
branchEntries,
|
|
1440
|
-
compactionEntry: latest.entry,
|
|
1441
|
-
expectedInputProvenance: getRemoteV2InputProvenance(config.remoteV2ContextSource),
|
|
1442
|
-
});
|
|
1443
|
-
if (!result.ok) {
|
|
1444
|
-
writeReplayFailureArtifact({ reason: result.reason, compactionEntryId: latest.entry.id }, config, ctx);
|
|
1445
|
-
if (ctx.hasUI) ctx.ui.notify(`${COMPACTION_EXTENSION_ID}: replay failed (${result.reason}); request aborted`, "error");
|
|
1446
|
-
reportPiContextHookFailure(ctx, `replay-failed:${result.reason}`);
|
|
1447
|
-
ctx.abort();
|
|
1448
|
-
return undefined;
|
|
1449
|
-
}
|
|
1450
|
-
return finishProjection(result.messages);
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
/**
|
|
1454
|
-
* Pi's emitContext() catches handler exceptions. Re-throw as usual for Pi, but
|
|
1455
|
-
* also report the failure so the compaction projector cannot mistake the
|
|
1456
|
-
* caught exception for a successful projection.
|
|
1457
|
-
*/
|
|
1458
|
-
async function handleContext(
|
|
1459
|
-
event: ContextWithSystemEvent,
|
|
1460
|
-
ctx: ExtensionContext,
|
|
1461
|
-
pi: ExtensionAPI,
|
|
1462
|
-
loadConfig: typeof loadToolkitConfig,
|
|
1463
|
-
contextWindows: CodexContextWindowManager,
|
|
1464
|
-
remoteContextActive: RemoteContextActive,
|
|
1465
|
-
getContextToolRegistrationState: ContextToolRegistrationStateReader,
|
|
1466
|
-
) {
|
|
1467
|
-
try {
|
|
1468
|
-
return await handleContextInternal(event, ctx, pi, loadConfig, contextWindows, remoteContextActive, getContextToolRegistrationState);
|
|
1469
|
-
} catch (error) {
|
|
1470
|
-
reportPiContextHookFailure(ctx, error instanceof Error ? error.message : String(error));
|
|
1471
|
-
throw error;
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
async function handleBeforeProviderRequest(
|
|
1476
|
-
event: BeforeProviderRequestEvent,
|
|
1477
|
-
ctx: ExtensionContext,
|
|
1478
|
-
loadConfig: typeof loadToolkitConfig,
|
|
1479
|
-
contextWindows: CodexContextWindowManager,
|
|
1480
|
-
remoteContextActive: RemoteContextActive,
|
|
1481
|
-
getContextToolRegistrationState: ContextToolRegistrationStateReader,
|
|
1482
|
-
) {
|
|
1483
|
-
const resolved = contextOperation(loadConfig, ctx);
|
|
1484
|
-
requireContextPolicy(resolved, ctx);
|
|
1485
|
-
const toolkitConfig = resolved.config;
|
|
1486
|
-
const config = toolkitConfig.compaction;
|
|
1487
|
-
try {
|
|
1488
|
-
contextWindows.synchronize(ctx);
|
|
1489
|
-
} catch {
|
|
1490
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1491
|
-
ctx.abort();
|
|
1492
|
-
return undefined;
|
|
1493
|
-
}
|
|
1494
|
-
if (!config.enabled) {
|
|
1495
|
-
return undefined;
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
const contextModel = config.contextManagement === "remote"
|
|
1499
|
-
? await resolveContextCapabilityModel(ctx)
|
|
1500
|
-
: undefined;
|
|
1501
|
-
const remoteActive = config.contextManagement === "remote" && await remoteContextActive(ctx, config, contextModel);
|
|
1502
|
-
let remotePayload: unknown;
|
|
1503
|
-
let activeWindowPayload = false;
|
|
1504
|
-
if (remoteActive) {
|
|
1505
|
-
try {
|
|
1506
|
-
remotePayload = contextWindows.rewritePayload(event.payload, ctx);
|
|
1507
|
-
activeWindowPayload = contextWindows.hasEffectiveWindow() || !contextWindows.currentIdentity();
|
|
1508
|
-
event = { ...event, payload: remotePayload };
|
|
1509
|
-
} catch {
|
|
1510
|
-
notifyRemoteContextFailure(ctx, "malformed-request-state");
|
|
1511
|
-
ctx.abort();
|
|
1512
|
-
return undefined;
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
if (!remoteActive && isCodexContextModel(contextModel, config)) {
|
|
1516
|
-
const registrationState = getContextToolRegistrationState();
|
|
1517
|
-
// A readable empty registry is an intentional host exclusion. Do not
|
|
1518
|
-
// rewrite gateway identity or abort an ordinary child-session request.
|
|
1519
|
-
if (isExcludedGatewayContext(contextModel, config, registrationState)) return undefined;
|
|
1520
|
-
// Keep Codex Remote mutually exclusive with the legacy replay pipeline
|
|
1521
|
-
// when authentication or tool ownership is unavailable. Gateway traffic
|
|
1522
|
-
// cannot continue as an ordinary Responses request because that would
|
|
1523
|
-
// silently lose the required CPA session/account selection.
|
|
1524
|
-
if (isCodexGatewayModel(contextModel, config.gatewayContextModels)) {
|
|
1525
|
-
const reason = contextManagementFailureReason(registrationState);
|
|
1526
|
-
notifyRemoteContextFailure(ctx, reason);
|
|
1527
|
-
ctx.abort();
|
|
1528
|
-
}
|
|
1529
|
-
return undefined;
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// Do not reintroduce a pre-window opaque checkpoint after local projection.
|
|
1533
|
-
// Request affinity/encrypted-output rewriting is reserved for active Remote.
|
|
1534
|
-
if (contextWindows.hasEffectiveWindow() && !activeWindowPayload) return undefined;
|
|
1535
|
-
|
|
1536
|
-
const branchEntries = ctx.sessionManager.getBranch();
|
|
1537
|
-
const inherited = !activeWindowPayload && config.contextManagement === "remote" && contextWindows.hasSupersededWindow()
|
|
1538
|
-
? resolveLatestNativeCompactionEntry(branchEntries) : undefined;
|
|
1539
|
-
const inheritedCheckpoint = inherited?.ok ? inherited.entry : undefined;
|
|
1540
|
-
const resolution = await resolveNativeCompactionEnvironment(
|
|
1541
|
-
ctx,
|
|
1542
|
-
{
|
|
1543
|
-
enabled: config.enabled,
|
|
1544
|
-
responsesApis: inheritedCheckpoint ? undefined : config.responsesApis,
|
|
1545
|
-
codexGatewayModels: compactGatewayModels(resolved),
|
|
1546
|
-
},
|
|
1547
|
-
event.payload,
|
|
1548
|
-
);
|
|
1549
|
-
if (resolution.ok === false) {
|
|
1550
|
-
if (inheritedCheckpoint) return rejectInheritedCheckpointReplay(ctx, config, inheritedCheckpoint.id, resolution.reason);
|
|
1551
|
-
writeDebugArtifact(
|
|
1552
|
-
"provider-request",
|
|
1553
|
-
{
|
|
1554
|
-
event: "before_provider_request.skip",
|
|
1555
|
-
reason: resolution.reason,
|
|
1556
|
-
provider: resolution.provider,
|
|
1557
|
-
api: resolution.api,
|
|
1558
|
-
model: resolution.model,
|
|
1559
|
-
baseUrl: resolution.baseUrl,
|
|
1560
|
-
errorMessage: resolution.errorMessage,
|
|
1561
|
-
currentModel: getCurrentModelDebugInfo(ctx),
|
|
1562
|
-
payload: event.payload,
|
|
1563
|
-
},
|
|
1564
|
-
config,
|
|
1565
|
-
ctx,
|
|
1566
|
-
);
|
|
1567
|
-
return remotePayload;
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
const runtime = resolution.runtime;
|
|
1571
|
-
const payload = runtime.payload;
|
|
1572
|
-
if (!payload) {
|
|
1573
|
-
return undefined;
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
// Capture compact-relevant request fields (tools, reasoning, ...) for the next
|
|
1577
|
-
// synthetic compact request using the active consumer's effective runtime identity.
|
|
1578
|
-
// This hook runs before the separate Web Search transform, so injected native search
|
|
1579
|
-
// tools are not copied into remote_compaction_v2.
|
|
1580
|
-
const webSearchRoute = resolveWebSearchRoute({ model: ctx.model, config: toolkitConfig.webSearch });
|
|
1581
|
-
rememberRequestContext(
|
|
1582
|
-
payload,
|
|
1583
|
-
{
|
|
1584
|
-
provider: runtime.provider,
|
|
1585
|
-
api: runtime.api,
|
|
1586
|
-
model: runtime.requestModel ?? runtime.model,
|
|
1587
|
-
baseUrl: runtime.baseUrl,
|
|
1588
|
-
sessionId: getSessionId(ctx),
|
|
1589
|
-
},
|
|
1590
|
-
{
|
|
1591
|
-
excludeWebSearchTools: webSearchRoute.route === "standalone-alpha",
|
|
1592
|
-
},
|
|
1593
|
-
);
|
|
1594
|
-
|
|
1595
|
-
// Capture is pre-Web-Search metadata only. An effective Remote window still
|
|
1596
|
-
// owns this live payload and must NEVER enter ordinary opaque replay below.
|
|
1597
|
-
if (activeWindowPayload) return remotePayload;
|
|
1598
|
-
|
|
1599
|
-
const latestNativeCompaction = resolveLatestNativeCompactionEntry(branchEntries, {
|
|
1600
|
-
baseUrl: runtime.baseUrl,
|
|
1601
|
-
});
|
|
1602
|
-
if (!latestNativeCompaction.ok) {
|
|
1603
|
-
if (inheritedCheckpoint) return rejectInheritedCheckpointReplay(ctx, config, inheritedCheckpoint.id, latestNativeCompaction.reason);
|
|
1604
|
-
writeDebugArtifact(
|
|
1605
|
-
"provider-request",
|
|
1606
|
-
{
|
|
1607
|
-
event: "before_provider_request.no-native-compaction",
|
|
1608
|
-
reason: latestNativeCompaction.reason,
|
|
1609
|
-
provider: runtime.provider,
|
|
1610
|
-
api: runtime.api,
|
|
1611
|
-
model: runtime.model,
|
|
1612
|
-
baseUrl: runtime.baseUrl,
|
|
1613
|
-
branchEntries: branchEntries.length,
|
|
1614
|
-
latestCompactionIndex: latestNativeCompaction.latestCompactionIndex,
|
|
1615
|
-
latestCompactionIdentity: getCompactionIdentityDebugInfo(latestNativeCompaction.latestCompaction),
|
|
1616
|
-
payload,
|
|
1617
|
-
},
|
|
1618
|
-
config,
|
|
1619
|
-
ctx,
|
|
1620
|
-
);
|
|
1621
|
-
return remotePayload;
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
const latestNativeCompactionEntry = latestNativeCompaction.entry;
|
|
1625
|
-
const rewrite = rewriteResponsesPayloadWithNativeReplay({
|
|
1626
|
-
model: runtime.currentModel,
|
|
1627
|
-
payload,
|
|
1628
|
-
branchEntries,
|
|
1629
|
-
compactionEntry: latestNativeCompactionEntry,
|
|
1630
|
-
expectedInputProvenance: getRemoteV2InputProvenance(config.remoteV2ContextSource),
|
|
1631
|
-
});
|
|
1632
|
-
if (!rewrite.ok) {
|
|
1633
|
-
writeDebugArtifact(
|
|
1634
|
-
"provider-request",
|
|
1635
|
-
{
|
|
1636
|
-
event: "before_provider_request.rewrite-failed",
|
|
1637
|
-
reason: rewrite.reason,
|
|
1638
|
-
provider: runtime.provider,
|
|
1639
|
-
api: runtime.api,
|
|
1640
|
-
model: runtime.model,
|
|
1641
|
-
baseUrl: runtime.baseUrl,
|
|
1642
|
-
compactionEntryId: latestNativeCompactionEntry.id,
|
|
1643
|
-
parity: rewrite.parity,
|
|
1644
|
-
payload,
|
|
1645
|
-
},
|
|
1646
|
-
config,
|
|
1647
|
-
ctx,
|
|
1648
|
-
);
|
|
1649
|
-
|
|
1650
|
-
// Fail loud instead of letting Pi send the sentinel-only payload: the
|
|
1651
|
-
// compacted history would be silently lost while the request still succeeds.
|
|
1652
|
-
// A forced redacted failure record is written even when logProviderPayloads
|
|
1653
|
-
// is disabled so the incident is diagnosable without leaking content.
|
|
1654
|
-
writeReplayFailureArtifact(
|
|
1655
|
-
{
|
|
1656
|
-
reason: rewrite.reason,
|
|
1657
|
-
parity: rewrite.parity,
|
|
1658
|
-
compactionEntryId: latestNativeCompactionEntry.id,
|
|
1659
|
-
provider: runtime.provider,
|
|
1660
|
-
api: runtime.api,
|
|
1661
|
-
model: runtime.model,
|
|
1662
|
-
},
|
|
1663
|
-
config,
|
|
1664
|
-
ctx,
|
|
1665
|
-
);
|
|
1666
|
-
if (ctx.hasUI) {
|
|
1667
|
-
ctx.ui.notify(
|
|
1668
|
-
`${COMPACTION_EXTENSION_ID}: native compaction replay failed (${rewrite.reason}); provider request aborted`,
|
|
1669
|
-
"error",
|
|
1670
|
-
);
|
|
1671
|
-
}
|
|
1672
|
-
ctx.abort();
|
|
1673
|
-
return undefined;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
writeDebugArtifact(
|
|
1677
|
-
"provider-request",
|
|
1678
|
-
{
|
|
1679
|
-
event: "before_provider_request.native-rewrite",
|
|
1680
|
-
provider: runtime.provider,
|
|
1681
|
-
api: runtime.api,
|
|
1682
|
-
model: runtime.model,
|
|
1683
|
-
baseUrl: runtime.baseUrl,
|
|
1684
|
-
compactionEntryId: latestNativeCompactionEntry.id,
|
|
1685
|
-
boundaryIndex: rewrite.segments.boundaryIndex,
|
|
1686
|
-
firstKeptEntryIndex: rewrite.segments.firstKeptEntryIndex,
|
|
1687
|
-
originalInputItems: payload.input.length,
|
|
1688
|
-
rewrittenInputItems: rewrite.rewrittenPayload.input.length,
|
|
1689
|
-
leadingItems: rewrite.segments.leading.length,
|
|
1690
|
-
compactionSummaryItems: rewrite.segments.compactionSummary.length,
|
|
1691
|
-
compactedItems: rewrite.segments.compactedWindow.length,
|
|
1692
|
-
postItems: rewrite.segments.post.length,
|
|
1693
|
-
payload: rewrite.rewrittenPayload,
|
|
1694
|
-
originalPayload: payload,
|
|
1695
|
-
},
|
|
1696
|
-
config,
|
|
1697
|
-
ctx,
|
|
1698
|
-
);
|
|
1699
|
-
|
|
1700
|
-
return rewrite.rewrittenPayload;
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
export default function registerCompactionExtension(
|
|
1704
|
-
pi: ExtensionAPI,
|
|
1705
|
-
overrides: Partial<CompactionDependencies> = {},
|
|
1706
|
-
) {
|
|
1707
|
-
const loadConfig = overrides.loadConfig ?? loadToolkitConfig;
|
|
1708
|
-
registerToolkitConfigCommand(pi, loadConfig);
|
|
1709
|
-
const piContextHookPatch = installPiContextHookPatch();
|
|
1710
|
-
const contextWindows = overrides.contextWindows ?? new CodexContextWindowManager((ctx, signal, gatewayModels) =>
|
|
1711
|
-
loadHistoryNotesThreadHint(ctx, signal, gatewayModels)
|
|
1712
|
-
);
|
|
1713
|
-
const manualCompact = new ManagedManualCompact(pi, contextWindows);
|
|
1714
|
-
const remoteManualCompact = new RemoteManualCompact(pi, contextWindows);
|
|
1715
|
-
const dependencies: CompactionDependencies = {
|
|
1716
|
-
remoteCompact: executeRemoteV2Compaction,
|
|
1717
|
-
standaloneCompact: executeNativeCompaction,
|
|
1718
|
-
nativeFallback: runNativeFallbackCompaction,
|
|
1719
|
-
contextWindows,
|
|
1720
|
-
...overrides,
|
|
1721
|
-
manualCompact,
|
|
1722
|
-
remoteManualCompact,
|
|
1723
|
-
loadConfig: () => remoteManualCompact.snapshot ?? manualCompact.snapshot ?? loadConfig(),
|
|
1724
|
-
piContextHookPatch: overrides.piContextHookPatch ?? piContextHookPatch,
|
|
1725
|
-
};
|
|
1726
|
-
let tools!: ReturnType<typeof registerContextManagementTools>;
|
|
1727
|
-
let contextWindowReady = false;
|
|
1728
|
-
const isContextRuntimeActive = async (
|
|
1729
|
-
ctx: ExtensionContext,
|
|
1730
|
-
config: CompactionConfig,
|
|
1731
|
-
model = ctx.model,
|
|
1732
|
-
): Promise<boolean> =>
|
|
1733
|
-
contextWindowReady && tools.isRegistered && await isRemoteContextActive(ctx, config, model);
|
|
1734
|
-
tools = registerContextManagementTools(
|
|
1735
|
-
pi,
|
|
1736
|
-
contextWindows,
|
|
1737
|
-
async (ctx) => {
|
|
1738
|
-
const resolved = contextOperation(dependencies.loadConfig, ctx);
|
|
1739
|
-
assertConfigValid(resolved, "context", "compatibility");
|
|
1740
|
-
return { active: await isContextRuntimeActive(ctx, resolved.config.compaction), gatewayModels: resolved.gatewayModelKeys };
|
|
1741
|
-
},
|
|
1742
|
-
undefined,
|
|
1743
|
-
manualCompact,
|
|
1744
|
-
);
|
|
1745
|
-
const remoteContextActive: RemoteContextActive = isContextRuntimeActive;
|
|
1746
|
-
const syncTools = async (
|
|
1747
|
-
ctx: ExtensionContext,
|
|
1748
|
-
model = ctx.model,
|
|
1749
|
-
options: { notifyWindowFailure?: boolean } = {},
|
|
1750
|
-
resolved = contextOperation(dependencies.loadConfig, ctx, model),
|
|
1751
|
-
): Promise<ContextToolSyncOutcome> => {
|
|
1752
|
-
// Until this call proves otherwise, do not let a previous session/window
|
|
1753
|
-
// identity make a failed activation look usable.
|
|
1754
|
-
contextWindowReady = false;
|
|
1755
|
-
const config = resolved.config.compaction;
|
|
1756
|
-
const eligible = !resolved.invalidFeatures.some((feature) => ["context", "compatibility"].includes(feature)) && await isRemoteContextActive(ctx, config, model);
|
|
1757
|
-
const toolSync = tools.sync(eligible);
|
|
1758
|
-
const outcome: ContextToolSyncOutcome = {
|
|
1759
|
-
eligible,
|
|
1760
|
-
toolsSynced: toolSync.synced,
|
|
1761
|
-
registrationState: toolSync.registrationState,
|
|
1762
|
-
windowInitialized: false,
|
|
1763
|
-
};
|
|
1764
|
-
if (!eligible || !toolSync.synced) return outcome;
|
|
1765
|
-
// Activation can succeed after `session_start` missed it, because Pi 0.86 may
|
|
1766
|
-
// reject the registration read while a session replacement is still binding.
|
|
1767
|
-
// The window lifecycle has to open on that later activation too, or requests
|
|
1768
|
-
// carry no window metadata and the backend never ingests the turns.
|
|
1769
|
-
try {
|
|
1770
|
-
contextWindows.ensureInitialized(pi, ctx, true);
|
|
1771
|
-
contextWindowReady = true;
|
|
1772
|
-
return { ...outcome, windowInitialized: true };
|
|
1773
|
-
} catch {
|
|
1774
|
-
// Do not leave tools exposed while the request path has no valid window
|
|
1775
|
-
// identity. The next lifecycle hook may retry once Pi is usable again.
|
|
1776
|
-
tools.sync(false);
|
|
1777
|
-
if (options.notifyWindowFailure) notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1778
|
-
return outcome;
|
|
1779
|
-
}
|
|
1780
|
-
};
|
|
1781
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
1782
|
-
remoteManualCompact.invalidate();
|
|
1783
|
-
const resolved = contextOperation(dependencies.loadConfig, ctx);
|
|
1784
|
-
const syncOutcome = await syncTools(ctx, ctx.model, { notifyWindowFailure: true }, resolved);
|
|
1785
|
-
await manualCompact.recover(ctx);
|
|
1786
|
-
const active = syncOutcome.eligible && syncOutcome.toolsSynced && syncOutcome.windowInitialized;
|
|
1787
|
-
const { source } = resolved;
|
|
1788
|
-
const warnings = resolved.issues.map((issue) => `${issue.path}: ${issue.code}`);
|
|
1789
|
-
const config = resolved.config.compaction;
|
|
1790
|
-
if (resolved.invalidFeatures.length > 0) return;
|
|
1791
|
-
if (!config.enabled) return;
|
|
1792
|
-
const contextModel = await resolveContextCapabilityModel(ctx);
|
|
1793
|
-
|
|
1794
|
-
let activationReason: string | undefined;
|
|
1795
|
-
// Only models the built-in Remote Context coverage targets may activate or
|
|
1796
|
-
// warn; everything else silently runs Pi's normal compaction path.
|
|
1797
|
-
if (config.contextManagement === "remote" && isCodexContextModel(contextModel, config) && !active) {
|
|
1798
|
-
if (syncOutcome.registrationState === "excluded") {
|
|
1799
|
-
activationReason = "context-tools-excluded";
|
|
1800
|
-
} else if (syncOutcome.registrationState === "conflict") {
|
|
1801
|
-
activationReason = "tool-name-conflict";
|
|
1802
|
-
notifyRemoteContextFailure(ctx, activationReason);
|
|
1803
|
-
} else if (syncOutcome.registrationState === "unverified" || !syncOutcome.toolsSynced) {
|
|
1804
|
-
// Transient: the runtime has not published or accepted our tools yet. A
|
|
1805
|
-
// later before_agent_start re-verifies, so do not report provider failure.
|
|
1806
|
-
activationReason = "tool-registration-pending";
|
|
1807
|
-
} else if (syncOutcome.eligible && !syncOutcome.windowInitialized) {
|
|
1808
|
-
activationReason = "malformed-window-state";
|
|
1809
|
-
} else {
|
|
1810
|
-
const remoteResolution = await resolveCodexContextProvider(ctx, ctx.model, config.gatewayContextModels);
|
|
1811
|
-
activationReason = remoteResolution.ok ? "codex-context-unavailable" : remoteResolution.reason;
|
|
1812
|
-
notifyRemoteContextFailure(ctx, activationReason);
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
const artifactPath = writeDebugArtifact(
|
|
1818
|
-
"lifecycle",
|
|
1819
|
-
{
|
|
1820
|
-
event: "session_start",
|
|
1821
|
-
config,
|
|
1822
|
-
configSource: source,
|
|
1823
|
-
warnings,
|
|
1824
|
-
activation: {
|
|
1825
|
-
active,
|
|
1826
|
-
contextManagement: config.contextManagement,
|
|
1827
|
-
remoteV2ContextSource: config.remoteV2ContextSource,
|
|
1828
|
-
model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
|
|
1829
|
-
...(activationReason ? { reason: activationReason } : {}),
|
|
1830
|
-
},
|
|
1831
|
-
piContextHookPatch: dependencies.piContextHookPatch,
|
|
1832
|
-
},
|
|
1833
|
-
config,
|
|
1834
|
-
ctx,
|
|
1835
|
-
);
|
|
1836
|
-
|
|
1837
|
-
if (ctx.hasUI && ["info", "debug"].includes(resolved.policy.diagnostics.level) && (config.notifyOnLoad || config.debug)) {
|
|
1838
|
-
ctx.ui.notify(
|
|
1839
|
-
artifactPath
|
|
1840
|
-
? `${COMPACTION_EXTENSION_ID} loaded • debug artifacts → ${artifactPath}`
|
|
1841
|
-
: `${COMPACTION_EXTENSION_ID} loaded`,
|
|
1842
|
-
"info",
|
|
1843
|
-
);
|
|
1844
|
-
}
|
|
1845
|
-
});
|
|
1846
|
-
|
|
1847
|
-
const getContextToolRegistrationState: ContextToolRegistrationStateReader = () => tools.registrationState;
|
|
1848
|
-
pi.on("context_with_system", (event, ctx) => {
|
|
1849
|
-
manualCompact.guardRequest(ctx);
|
|
1850
|
-
return handleContext(event, ctx, pi, dependencies.loadConfig, contextWindows, remoteContextActive, getContextToolRegistrationState);
|
|
1851
|
-
});
|
|
1852
|
-
pi.on("session_before_compact", async (event, ctx) => {
|
|
1853
|
-
remoteManualCompact.invalidate();
|
|
1854
|
-
try {
|
|
1855
|
-
return await handleSessionBeforeCompact(event, ctx, dependencies, remoteContextActive, getContextToolRegistrationState);
|
|
1856
|
-
} catch {
|
|
1857
|
-
// Pi treats a rejected hook as absent and can invoke its implicit summary.
|
|
1858
|
-
// Unexpected preparation/service failures must never authorize that work.
|
|
1859
|
-
try { notifyWarning(ctx, "Compaction stopped; existing context preserved. Resume manually."); } catch { /* stale UI */ }
|
|
1860
|
-
return { cancel: true };
|
|
1861
|
-
}
|
|
1862
|
-
});
|
|
1863
|
-
pi.on("session_compact", (event, ctx) => {
|
|
1864
|
-
contextWindows.synchronize(ctx);
|
|
1865
|
-
if (event.reason === "manual" && event.fromExtension) remoteManualCompact.persisted(ctx);
|
|
1866
|
-
else remoteManualCompact.invalidate();
|
|
1867
|
-
});
|
|
1868
|
-
pi.on("session_compact_failed", async (event, ctx) => {
|
|
1869
|
-
remoteManualCompact.invalidate();
|
|
1870
|
-
if (event.reason === "manual" && !manualCompact.isMaintenance) await manualCompact.compactFailed(ctx);
|
|
1871
|
-
});
|
|
1872
|
-
pi.on("session_shutdown", async (_event, ctx) => {
|
|
1873
|
-
remoteManualCompact.invalidate();
|
|
1874
|
-
await manualCompact.shutdown(ctx);
|
|
1875
|
-
contextWindowReady = false;
|
|
1876
|
-
contextWindows.reset();
|
|
1877
|
-
tools.reset();
|
|
1878
|
-
});
|
|
1879
|
-
pi.on("agent_settled", async (_event, ctx) => {
|
|
1880
|
-
// Only the owned manual handoff restores/continues here; Pi owns normal
|
|
1881
|
-
// compaction scheduling. Never compact retired windows on model changes.
|
|
1882
|
-
await manualCompact.settled(ctx);
|
|
1883
|
-
});
|
|
1884
|
-
pi.on("model_select", async (event, ctx) => {
|
|
1885
|
-
remoteManualCompact.invalidate();
|
|
1886
|
-
manualCompact.modelSelected(event.model, ctx);
|
|
1887
|
-
// Switching into a covered model mid-session must open the window
|
|
1888
|
-
// lifecycle immediately: without an identity the request rewrite skips
|
|
1889
|
-
// window metadata, the backend never ingests those turns, and the first
|
|
1890
|
-
// new_context would trim pre-switch history that no history can recover.
|
|
1891
|
-
// syncTools() activates and initializes the window when the model is covered.
|
|
1892
|
-
const resolved = contextOperation(dependencies.loadConfig, ctx, event.model);
|
|
1893
|
-
await syncTools(ctx, event.model, { notifyWindowFailure: true }, resolved);
|
|
1894
|
-
});
|
|
1895
|
-
pi.on("before_agent_start", async (_event, ctx) => {
|
|
1896
|
-
remoteManualCompact.invalidate();
|
|
1897
|
-
await syncTools(ctx, ctx.model, { notifyWindowFailure: true });
|
|
1898
|
-
});
|
|
1899
|
-
pi.on("agent_start", () => remoteManualCompact.invalidate());
|
|
1900
|
-
pi.on("input", () => { remoteManualCompact.invalidate(); });
|
|
1901
|
-
pi.on("thinking_level_select", (event, ctx) => {
|
|
1902
|
-
remoteManualCompact.invalidate();
|
|
1903
|
-
manualCompact.thinkingSelected(event.level, ctx);
|
|
1904
|
-
});
|
|
1905
|
-
pi.on("message_start", (event) => {
|
|
1906
|
-
remoteManualCompact.invalidate();
|
|
1907
|
-
manualCompact.messageStarted(event.message);
|
|
1908
|
-
});
|
|
1909
|
-
pi.on("turn_end", (_event, ctx) => manualCompact.turnEnded(ctx));
|
|
1910
|
-
pi.on("tool_call", (event) => {
|
|
1911
|
-
remoteManualCompact.invalidate();
|
|
1912
|
-
return manualCompact.guardTool(event.toolName);
|
|
1913
|
-
});
|
|
1914
|
-
pi.on("session_before_switch", () => remoteManualCompact.invalidate());
|
|
1915
|
-
pi.on("session_before_fork", () => remoteManualCompact.invalidate());
|
|
1916
|
-
pi.on("session_before_tree", () => remoteManualCompact.invalidate());
|
|
1917
|
-
pi.on("session_tree", async (_event, ctx) => {
|
|
1918
|
-
remoteManualCompact.invalidate();
|
|
1919
|
-
contextWindows.synchronize(ctx);
|
|
1920
|
-
await manualCompact.recover(ctx);
|
|
1921
|
-
await syncTools(ctx);
|
|
1922
|
-
});
|
|
1923
|
-
pi.on("before_provider_request", (event, ctx) => {
|
|
1924
|
-
manualCompact.guardRequest(ctx);
|
|
1925
|
-
return handleBeforeProviderRequest(event, ctx, dependencies.loadConfig, contextWindows, remoteContextActive, getContextToolRegistrationState);
|
|
1926
|
-
});
|
|
1927
|
-
pi.on("before_provider_headers", async (event, ctx) => {
|
|
1928
|
-
const resolved = contextOperation(dependencies.loadConfig, ctx);
|
|
1929
|
-
requireContextPolicy(resolved, ctx);
|
|
1930
|
-
const config = resolved.config.compaction;
|
|
1931
|
-
const contextModel = await resolveContextCapabilityModel(ctx);
|
|
1932
|
-
if (!isCodexContextModel(contextModel, config)) return;
|
|
1933
|
-
const active = await remoteContextActive(ctx, config, contextModel);
|
|
1934
|
-
if (!active) {
|
|
1935
|
-
const registrationState = getContextToolRegistrationState();
|
|
1936
|
-
if (isExcludedGatewayContext(contextModel, config, registrationState)) return;
|
|
1937
|
-
if (isCodexGatewayModel(contextModel, config.gatewayContextModels)) {
|
|
1938
|
-
const reason = contextManagementFailureReason(registrationState);
|
|
1939
|
-
notifyRemoteContextFailure(ctx, reason);
|
|
1940
|
-
ctx.abort();
|
|
1941
|
-
}
|
|
1942
|
-
return;
|
|
1943
|
-
}
|
|
1944
|
-
const provider = await resolveCodexContextProvider(ctx, ctx.model, config.gatewayContextModels);
|
|
1945
|
-
if (provider.ok && provider.provider.kind === "codex-gateway") {
|
|
1946
|
-
const sessionId = getSessionId(ctx);
|
|
1947
|
-
const gatewayHeaders = codexContextProviderHeaders(provider.provider, {
|
|
1948
|
-
sessionId,
|
|
1949
|
-
clientRequestId: sessionId,
|
|
1950
|
-
});
|
|
1951
|
-
const allowedGatewayHeaders = new Set<string>(CODEX_GATEWAY_FORWARD_HEADERS);
|
|
1952
|
-
for (const existing of Object.keys(event.headers)) {
|
|
1953
|
-
if (!allowedGatewayHeaders.has(existing.toLowerCase())) delete event.headers[existing];
|
|
1954
|
-
}
|
|
1955
|
-
// Keep any caller-supplied version without forcing a Toolkit pin.
|
|
1956
|
-
// The gateway allowlist above already permits it.
|
|
1957
|
-
for (const name of [
|
|
1958
|
-
"authorization",
|
|
1959
|
-
"originator",
|
|
1960
|
-
"user-agent",
|
|
1961
|
-
"session-id",
|
|
1962
|
-
"x-client-request-id",
|
|
1963
|
-
"x-codex-affinity-scope",
|
|
1964
|
-
"x-codex-model",
|
|
1965
|
-
]) {
|
|
1966
|
-
for (const existing of Object.keys(event.headers)) {
|
|
1967
|
-
if (existing.toLowerCase() === name) delete event.headers[existing];
|
|
1968
|
-
}
|
|
1969
|
-
const value = gatewayHeaders.get(name);
|
|
1970
|
-
if (value) event.headers[name] = value;
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
contextWindows.rewriteHeaders(event.headers, ctx);
|
|
1974
|
-
});
|
|
1975
|
-
pi.on("message_end", async (event, ctx) => {
|
|
1976
|
-
rememberPhysicalModel(ctx, event.message);
|
|
1977
|
-
const resolved = contextOperation(dependencies.loadConfig, ctx);
|
|
1978
|
-
requireContextPolicy(resolved, ctx);
|
|
1979
|
-
const config = resolved.config.compaction;
|
|
1980
|
-
const contextModel = await resolveContextCapabilityModel(ctx);
|
|
1981
|
-
if (!isCodexContextModel(contextModel, config) || !tools.isRegistered) return undefined;
|
|
1982
|
-
if (!(await remoteContextActive(ctx, config, contextModel))) return undefined;
|
|
1983
|
-
const message = routeContextNamespaceToolMessage(event.message);
|
|
1984
|
-
return message === event.message ? undefined : { message };
|
|
1985
|
-
});
|
|
1986
|
-
}
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import type {
|
|
4
|
+
BeforeProviderRequestEvent,
|
|
5
|
+
ContextWithSystemEvent,
|
|
6
|
+
CompactionResult,
|
|
7
|
+
ExtensionAPI,
|
|
8
|
+
ExtensionContext,
|
|
9
|
+
SessionBeforeCompactEvent,
|
|
10
|
+
} from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { assertConfigValid, loadToolkitConfig, resolveToolkitConfig, type ResolvedToolkitConfig } from "./config";
|
|
12
|
+
import { notifyConfigIssues } from "./config/notifications";
|
|
13
|
+
import { registerToolkitConfigCommand } from "./config-command";
|
|
14
|
+
import { writeDebugArtifact, writeReplayFailureArtifact } from "./debug";
|
|
15
|
+
import {
|
|
16
|
+
COMPACTION_CHECKPOINT_PROVENANCE_UNAVAILABLE,
|
|
17
|
+
COMPACTION_CHECKPOINT_CONTEXT_EDITED,
|
|
18
|
+
COMPACTION_PROJECTION_UNAVAILABLE,
|
|
19
|
+
COMPACTION_SESSION_CONTEXT_UNAVAILABLE,
|
|
20
|
+
COMPACTION_PROJECTED_CONTEXT_UNAVAILABLE,
|
|
21
|
+
hasVerifiedCompactionInputProvenance,
|
|
22
|
+
type UnprojectedCompactionReason,
|
|
23
|
+
} from "./compaction-projection";
|
|
24
|
+
import { buildResponsesRequestHeaders, CODEX_GATEWAY_FORWARD_HEADERS } from "./responses-headers";
|
|
25
|
+
import { isExactModelAllowed } from "./model-scope";
|
|
26
|
+
import { detectRetiredRemoteWindows, RETIRED_REMOTE_WINDOWS_MESSAGE } from "./retired-remote-windows";
|
|
27
|
+
import { executeNativeCompaction, type NativeCompactionClientResult } from "./compact-client";
|
|
28
|
+
import { qualifiesForStandaloneCompaction, type CompactionErrorInfo } from "./compaction-errors";
|
|
29
|
+
import { resolveLatestNativeCompactionEntry } from "./details-store";
|
|
30
|
+
import {
|
|
31
|
+
getPiContextHookProjector,
|
|
32
|
+
installPiContextHookPatch,
|
|
33
|
+
reportPiContextHookFailure,
|
|
34
|
+
type PiContextHookPatchResult,
|
|
35
|
+
} from "./pi-context-hook";
|
|
36
|
+
import { runNativeFallbackCompaction } from "./native-fallback";
|
|
37
|
+
import {
|
|
38
|
+
rewriteResponsesPayloadWithNativeReplay,
|
|
39
|
+
hasInvalidatedCompactionContext,
|
|
40
|
+
removeNativeCompactionRetainedMessages,
|
|
41
|
+
serializeLiveTailToResponsesInputWithLimits,
|
|
42
|
+
} from "./payload-rewrite";
|
|
43
|
+
import { getCompactionRequestExtras, rememberRequestContext } from "./request-context-cache";
|
|
44
|
+
import { resolveWebSearchRoute } from "./web-search/types";
|
|
45
|
+
import { executeRemoteV2Compaction } from "./remote-v2-client";
|
|
46
|
+
import {
|
|
47
|
+
registerPhysicalModelObserver,
|
|
48
|
+
resolveNativeCompactionEnvironment,
|
|
49
|
+
resolveRemoteCompactionExecution,
|
|
50
|
+
isStandaloneCompactionEligible,
|
|
51
|
+
parseModelSpec,
|
|
52
|
+
type RemoteCompactionExecution,
|
|
53
|
+
} from "./runtime";
|
|
54
|
+
import {
|
|
55
|
+
serializeMessagesToCompactRequestWithLimits,
|
|
56
|
+
serializeMessagesToResponsesInputWithLimits,
|
|
57
|
+
type NativeCompactionRequestBody,
|
|
58
|
+
type ResponsesInputItem,
|
|
59
|
+
} from "./serializer";
|
|
60
|
+
import {
|
|
61
|
+
createNativeCompactionDetails,
|
|
62
|
+
createNativeCompactionResult,
|
|
63
|
+
COMPACTION_EXTENSION_ID,
|
|
64
|
+
LEGACY_NATIVE_COMPACTION_STRATEGY,
|
|
65
|
+
REMOTE_V2_COMPACTION_STRATEGY,
|
|
66
|
+
getLatestDeferredToolCarryover,
|
|
67
|
+
getRemoteV2InputProvenance,
|
|
68
|
+
isNativeCompactionDetails,
|
|
69
|
+
type CompactionConfig,
|
|
70
|
+
type NativeCompactionDetails,
|
|
71
|
+
type NativeCompactionRequestMeta,
|
|
72
|
+
} from "./types";
|
|
73
|
+
|
|
74
|
+
type CompactionContextProjection = (
|
|
75
|
+
messages: readonly AgentMessage[],
|
|
76
|
+
ctx: ExtensionContext,
|
|
77
|
+
) => readonly AgentMessage[] | undefined | Promise<readonly AgentMessage[] | undefined>;
|
|
78
|
+
|
|
79
|
+
function contextOperation(loadConfig: typeof loadToolkitConfig, ctx: ExtensionContext, model = ctx.model): ResolvedToolkitConfig {
|
|
80
|
+
const loaded = loadConfig();
|
|
81
|
+
const resolved = resolveToolkitConfig(loaded, model);
|
|
82
|
+
notifyConfigIssues(ctx, resolved);
|
|
83
|
+
return resolved;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function requireContextPolicy(resolved: ResolvedToolkitConfig, ctx: ExtensionContext): void {
|
|
87
|
+
try {
|
|
88
|
+
assertConfigValid(resolved, "context", "compatibility", "diagnostics");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (typeof ctx.abort === "function") ctx.abort();
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type CompactionDependencies = {
|
|
96
|
+
loadConfig: typeof loadToolkitConfig;
|
|
97
|
+
remoteCompact: typeof executeRemoteV2Compaction;
|
|
98
|
+
standaloneCompact: typeof executeNativeCompaction;
|
|
99
|
+
nativeFallback: typeof runNativeFallbackCompaction;
|
|
100
|
+
/** Revalidate operation ownership after awaits and at result consumption. */
|
|
101
|
+
isCurrentCompaction?: () => boolean;
|
|
102
|
+
/** Test seam for the same ordered projection exposed by the Pi host patch. */
|
|
103
|
+
projectCompactionContext?: CompactionContextProjection;
|
|
104
|
+
piContextHookPatch: PiContextHookPatchResult;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Notices are bounded, but every boundary freshly reads the current branch.
|
|
108
|
+
const retirementNotices = new WeakMap<object, string>();
|
|
109
|
+
function retirementReason(ctx: ExtensionContext): string | undefined {
|
|
110
|
+
try { return detectRetiredRemoteWindows(ctx.sessionManager.getBranch()); }
|
|
111
|
+
catch { return "branch-unavailable"; }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function refuseRetiredSession(ctx: ExtensionContext, request = false): boolean {
|
|
115
|
+
const reason = retirementReason(ctx);
|
|
116
|
+
const owner = ctx.sessionManager;
|
|
117
|
+
if (!reason) {
|
|
118
|
+
retirementNotices.delete(owner);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const fingerprint = `${getSessionId(ctx) ?? ""}:${reason}`;
|
|
122
|
+
if (ctx.hasUI && retirementNotices.get(owner) !== fingerprint) {
|
|
123
|
+
retirementNotices.set(owner, fingerprint);
|
|
124
|
+
try {
|
|
125
|
+
ctx.ui.notify(
|
|
126
|
+
reason === "branch-unavailable"
|
|
127
|
+
? `${COMPACTION_EXTENSION_ID}: Cannot verify the active session branch; request blocked. Reopen the session before continuing.`
|
|
128
|
+
: `${COMPACTION_EXTENSION_ID}: ${RETIRED_REMOTE_WINDOWS_MESSAGE}`,
|
|
129
|
+
"error",
|
|
130
|
+
);
|
|
131
|
+
} catch {
|
|
132
|
+
// A detached UI must not prevent abort or turn compact refusal into fallback.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (request) {
|
|
136
|
+
reportPiContextHookFailure(ctx, `retired-remote-windows:${reason}`);
|
|
137
|
+
ctx.abort();
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type ResponsesCompactOutcome =
|
|
143
|
+
| { outcome: "success"; compaction: CompactionResult<NativeCompactionDetails> }
|
|
144
|
+
| { outcome: "aborted" }
|
|
145
|
+
| { outcome: "failed"; remoteFailure?: CompactionErrorInfo & { reason: string; status?: number }; protocol?: string }
|
|
146
|
+
| {
|
|
147
|
+
outcome: "unprojected-input";
|
|
148
|
+
reason: UnprojectedCompactionReason;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
function buildCompactionRequestMeta(event: SessionBeforeCompactEvent): NativeCompactionRequestMeta {
|
|
152
|
+
return {
|
|
153
|
+
tokensBefore: event.preparation.tokensBefore,
|
|
154
|
+
previousSummaryPresent: Boolean(event.preparation.previousSummary),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getCurrentModelDebugInfo(ctx: ExtensionContext) {
|
|
159
|
+
return ctx.model
|
|
160
|
+
? {
|
|
161
|
+
provider: ctx.model.provider,
|
|
162
|
+
id: ctx.model.id,
|
|
163
|
+
}
|
|
164
|
+
: undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getCompactionIdentityDebugInfo(entry: { details?: unknown } | undefined) {
|
|
168
|
+
return isNativeCompactionDetails(entry?.details)
|
|
169
|
+
? {
|
|
170
|
+
provider: entry.details.provider,
|
|
171
|
+
api: entry.details.api,
|
|
172
|
+
model: entry.details.model,
|
|
173
|
+
baseUrl: entry.details.baseUrl,
|
|
174
|
+
compactionModel: entry.details.compactionModel,
|
|
175
|
+
}
|
|
176
|
+
: undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getSessionId(ctx: ExtensionContext): string | undefined {
|
|
180
|
+
try {
|
|
181
|
+
return ctx.sessionManager.getSessionId();
|
|
182
|
+
} catch {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function notifyWarning(ctx: ExtensionContext, message: string): void {
|
|
188
|
+
if (ctx.hasUI) {
|
|
189
|
+
ctx.ui.notify(`${COMPACTION_EXTENSION_ID}: ${message}`, "warning");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function cloneOpaqueWindow(window: readonly unknown[]): unknown[] {
|
|
194
|
+
return window.map((item) => structuredClone(item));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildCompactionInstructions(systemPrompt: string, customInstructions?: string): string {
|
|
198
|
+
const guidance = customInstructions?.trim();
|
|
199
|
+
if (!guidance) {
|
|
200
|
+
return systemPrompt;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return `${systemPrompt}\n\nAdditional user guidance for this manual /compact request:\n${guidance}`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function findLatestProjectedCompactionSummaryIndex(
|
|
207
|
+
messages: readonly AgentMessage[],
|
|
208
|
+
summary: string,
|
|
209
|
+
): number {
|
|
210
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
211
|
+
const message = messages[index];
|
|
212
|
+
if (message?.role === "compactionSummary" && message.summary === summary) {
|
|
213
|
+
return index;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return -1;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function readSessionContextMessages(ctx: ExtensionContext): AgentMessage[] | undefined {
|
|
221
|
+
try {
|
|
222
|
+
const projection = ctx.sessionManager.buildSessionProjection();
|
|
223
|
+
if (!projection || !Array.isArray(projection.messages)) return undefined;
|
|
224
|
+
return structuredClone(projection.messages);
|
|
225
|
+
} catch {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
type CompactionProjectionResult =
|
|
231
|
+
| { ok: true; messages: readonly AgentMessage[] }
|
|
232
|
+
| { ok: false; reason: typeof COMPACTION_PROJECTION_UNAVAILABLE | typeof COMPACTION_SESSION_CONTEXT_UNAVAILABLE };
|
|
233
|
+
|
|
234
|
+
async function projectSessionContextForCompaction(
|
|
235
|
+
ctx: ExtensionContext,
|
|
236
|
+
projectCompactionContext: CompactionContextProjection | undefined,
|
|
237
|
+
): Promise<CompactionProjectionResult> {
|
|
238
|
+
const messages = readSessionContextMessages(ctx);
|
|
239
|
+
if (!messages) {
|
|
240
|
+
return { ok: false, reason: COMPACTION_SESSION_CONTEXT_UNAVAILABLE };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const projector = projectCompactionContext ?? getPiContextHookProjector(ctx);
|
|
244
|
+
if (!projector) {
|
|
245
|
+
return { ok: false, reason: COMPACTION_PROJECTION_UNAVAILABLE };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const projected = await projector(messages, ctx);
|
|
250
|
+
return Array.isArray(projected)
|
|
251
|
+
? { ok: true, messages: projected }
|
|
252
|
+
: { ok: false, reason: COMPACTION_PROJECTION_UNAVAILABLE };
|
|
253
|
+
} catch {
|
|
254
|
+
return { ok: false, reason: COMPACTION_PROJECTION_UNAVAILABLE };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getLegacySessionContextMessages(
|
|
259
|
+
event: SessionBeforeCompactEvent,
|
|
260
|
+
ctx: ExtensionContext,
|
|
261
|
+
): AgentMessage[] {
|
|
262
|
+
return readSessionContextMessages(ctx) ?? [
|
|
263
|
+
...event.preparation.messagesToSummarize,
|
|
264
|
+
...event.preparation.turnPrefixMessages,
|
|
265
|
+
];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function runResponsesNativeCompact(
|
|
269
|
+
event: SessionBeforeCompactEvent,
|
|
270
|
+
ctx: ExtensionContext,
|
|
271
|
+
config: CompactionConfig,
|
|
272
|
+
execution: RemoteCompactionExecution,
|
|
273
|
+
remoteCompact: typeof executeRemoteV2Compaction,
|
|
274
|
+
projectCompactionContext: CompactionContextProjection | undefined,
|
|
275
|
+
standaloneCompact: typeof executeNativeCompaction,
|
|
276
|
+
isCurrent: () => boolean,
|
|
277
|
+
): Promise<ResponsesCompactOutcome> {
|
|
278
|
+
const { consumer, compactor } = execution;
|
|
279
|
+
const instructions = buildCompactionInstructions(ctx.getSystemPrompt(), event.customInstructions);
|
|
280
|
+
const branchEntries = event.branchEntries ?? ctx.sessionManager.getBranch();
|
|
281
|
+
const deferredToolCarryover = getLatestDeferredToolCarryover(branchEntries);
|
|
282
|
+
const latestNativeCompaction = resolveLatestNativeCompactionEntry(branchEntries, {
|
|
283
|
+
baseUrl: consumer.baseUrl,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const inputProvenance = getRemoteV2InputProvenance(config.remoteV2ContextSource);
|
|
287
|
+
let requestSource: "session-context" | "non-native-session-context" | "latest-native-replay";
|
|
288
|
+
let request: NativeCompactionRequestBody;
|
|
289
|
+
if (latestNativeCompaction.ok) {
|
|
290
|
+
if (hasInvalidatedCompactionContext(branchEntries, latestNativeCompaction.entry.id)) {
|
|
291
|
+
return { outcome: "unprojected-input", reason: COMPACTION_CHECKPOINT_CONTEXT_EDITED };
|
|
292
|
+
}
|
|
293
|
+
const details = latestNativeCompaction.entry.details;
|
|
294
|
+
if (!details) {
|
|
295
|
+
return { outcome: "failed" };
|
|
296
|
+
}
|
|
297
|
+
if (!hasVerifiedCompactionInputProvenance(details, inputProvenance)) {
|
|
298
|
+
return {
|
|
299
|
+
outcome: "unprojected-input",
|
|
300
|
+
reason: COMPACTION_CHECKPOINT_PROVENANCE_UNAVAILABLE,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
requestSource = "latest-native-replay";
|
|
304
|
+
|
|
305
|
+
if (config.remoteV2ContextSource === "pi-context-hook") {
|
|
306
|
+
const projection = await projectSessionContextForCompaction(ctx, projectCompactionContext);
|
|
307
|
+
if (!projection.ok) {
|
|
308
|
+
return { outcome: "unprojected-input", reason: projection.reason };
|
|
309
|
+
}
|
|
310
|
+
const summaryIndex = findLatestProjectedCompactionSummaryIndex(
|
|
311
|
+
projection.messages,
|
|
312
|
+
latestNativeCompaction.entry.summary,
|
|
313
|
+
);
|
|
314
|
+
if (summaryIndex < 0) {
|
|
315
|
+
return {
|
|
316
|
+
outcome: "unprojected-input",
|
|
317
|
+
reason: COMPACTION_PROJECTED_CONTEXT_UNAVAILABLE,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const input: ResponsesInputItem[] = [
|
|
321
|
+
...(cloneOpaqueWindow(details.compactedWindow) as ResponsesInputItem[]),
|
|
322
|
+
...(await serializeMessagesToResponsesInputWithLimits(
|
|
323
|
+
compactor.currentModel,
|
|
324
|
+
[...projection.messages.slice(summaryIndex + 1)],
|
|
325
|
+
{ firstSystemMessageIsUpdate: true },
|
|
326
|
+
)),
|
|
327
|
+
];
|
|
328
|
+
request = {
|
|
329
|
+
model: compactor.requestModel ?? compactor.model,
|
|
330
|
+
input,
|
|
331
|
+
instructions,
|
|
332
|
+
};
|
|
333
|
+
} else {
|
|
334
|
+
const liveTailEntries = branchEntries.slice(latestNativeCompaction.index + 1);
|
|
335
|
+
const input: ResponsesInputItem[] = [
|
|
336
|
+
...(cloneOpaqueWindow(details.compactedWindow) as ResponsesInputItem[]),
|
|
337
|
+
...(await serializeLiveTailToResponsesInputWithLimits({ model: compactor.currentModel, entries: liveTailEntries })),
|
|
338
|
+
];
|
|
339
|
+
request = {
|
|
340
|
+
model: compactor.requestModel ?? compactor.model,
|
|
341
|
+
input,
|
|
342
|
+
instructions,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
} else if (
|
|
346
|
+
latestNativeCompaction.reason === "no-compaction" ||
|
|
347
|
+
(latestNativeCompaction.reason === "latest-compaction-not-native" &&
|
|
348
|
+
config.allowCompactionContinuityBreak)
|
|
349
|
+
) {
|
|
350
|
+
requestSource =
|
|
351
|
+
latestNativeCompaction.reason === "no-compaction" ? "session-context" : "non-native-session-context";
|
|
352
|
+
if (config.remoteV2ContextSource === "pi-context-hook") {
|
|
353
|
+
const projection = await projectSessionContextForCompaction(ctx, projectCompactionContext);
|
|
354
|
+
if (!projection.ok) {
|
|
355
|
+
return { outcome: "unprojected-input", reason: projection.reason };
|
|
356
|
+
}
|
|
357
|
+
const serialized = await serializeMessagesToCompactRequestWithLimits({
|
|
358
|
+
model: compactor.currentModel,
|
|
359
|
+
messages: [...projection.messages],
|
|
360
|
+
instructions,
|
|
361
|
+
});
|
|
362
|
+
request = { ...serialized, model: compactor.requestModel ?? serialized.model };
|
|
363
|
+
} else {
|
|
364
|
+
const serialized = await serializeMessagesToCompactRequestWithLimits({
|
|
365
|
+
model: compactor.currentModel,
|
|
366
|
+
messages: getLegacySessionContextMessages(event, ctx),
|
|
367
|
+
instructions,
|
|
368
|
+
});
|
|
369
|
+
request = { ...serialized, model: compactor.requestModel ?? serialized.model };
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
writeDebugArtifact(
|
|
373
|
+
"compaction-event",
|
|
374
|
+
{
|
|
375
|
+
event: "session_before_compact.remote-v2-skip",
|
|
376
|
+
reason: latestNativeCompaction.reason,
|
|
377
|
+
consumer: {
|
|
378
|
+
provider: consumer.provider,
|
|
379
|
+
api: consumer.api,
|
|
380
|
+
model: consumer.model,
|
|
381
|
+
baseUrl: consumer.baseUrl,
|
|
382
|
+
},
|
|
383
|
+
compactor: {
|
|
384
|
+
provider: compactor.provider,
|
|
385
|
+
api: compactor.api,
|
|
386
|
+
model: compactor.model,
|
|
387
|
+
baseUrl: compactor.baseUrl,
|
|
388
|
+
},
|
|
389
|
+
latestCompactionIndex: latestNativeCompaction.latestCompactionIndex,
|
|
390
|
+
latestCompactionIdentity: getCompactionIdentityDebugInfo(latestNativeCompaction.latestCompaction),
|
|
391
|
+
},
|
|
392
|
+
config,
|
|
393
|
+
ctx,
|
|
394
|
+
);
|
|
395
|
+
return { outcome: "failed" };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Mirror the latest codex_rs CompactionInput fields captured from the most
|
|
399
|
+
// recent live provider request for this model (tools, reasoning, etc.).
|
|
400
|
+
const extras = getCompactionRequestExtras({
|
|
401
|
+
provider: consumer.provider,
|
|
402
|
+
api: consumer.api,
|
|
403
|
+
model: consumer.requestModel ?? consumer.model,
|
|
404
|
+
baseUrl: consumer.baseUrl,
|
|
405
|
+
sessionId: getSessionId(ctx),
|
|
406
|
+
}, compactor.currentModel);
|
|
407
|
+
if (extras) {
|
|
408
|
+
request = { ...request, ...extras };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (event.signal?.aborted || !isCurrent() || refuseRetiredSession(ctx)) return { outcome: "aborted" };
|
|
412
|
+
let protocol: "remote-v2" | "standalone-compact" = "remote-v2";
|
|
413
|
+
let compactResult: Awaited<ReturnType<typeof executeRemoteV2Compaction>> | NativeCompactionClientResult = await remoteCompact({
|
|
414
|
+
runtime: compactor,
|
|
415
|
+
request,
|
|
416
|
+
signal: event.signal,
|
|
417
|
+
settings: config,
|
|
418
|
+
context: ctx,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
if (!isCurrent() || refuseRetiredSession(ctx)) return { outcome: "aborted" };
|
|
422
|
+
if (!compactResult.ok) {
|
|
423
|
+
if (compactResult.reason === "aborted" || event.signal?.aborted) return { outcome: "aborted" };
|
|
424
|
+
const tryStandalone = !!standaloneCompact && isStandaloneCompactionEligible(compactor) && qualifiesForStandaloneCompaction(compactResult);
|
|
425
|
+
writeDebugArtifact("compaction-event", {
|
|
426
|
+
event: "session_before_compact.remote-v2-failure",
|
|
427
|
+
protocol,
|
|
428
|
+
reason: compactResult.reason,
|
|
429
|
+
status: compactResult.status,
|
|
430
|
+
errorCode: compactResult.errorCode,
|
|
431
|
+
errorType: compactResult.errorType,
|
|
432
|
+
errorParam: compactResult.errorParam,
|
|
433
|
+
errorMessage: compactResult.errorMessage,
|
|
434
|
+
nextStep: tryStandalone ? "standalone-compact" : "native-summary",
|
|
435
|
+
}, config, ctx);
|
|
436
|
+
if (tryStandalone && standaloneCompact) {
|
|
437
|
+
if (event.signal?.aborted || !isCurrent() || refuseRetiredSession(ctx)) return { outcome: "aborted" };
|
|
438
|
+
protocol = "standalone-compact";
|
|
439
|
+
compactResult = await standaloneCompact({ runtime: compactor, request, signal: event.signal, settings: config, context: ctx });
|
|
440
|
+
}
|
|
441
|
+
if (!compactResult.ok) {
|
|
442
|
+
if (compactResult.reason === "aborted" || event.signal?.aborted) return { outcome: "aborted" };
|
|
443
|
+
const remoteFailure = {
|
|
444
|
+
reason: compactResult.reason,
|
|
445
|
+
status: compactResult.status,
|
|
446
|
+
errorCode: compactResult.errorCode,
|
|
447
|
+
errorType: compactResult.errorType,
|
|
448
|
+
errorParam: compactResult.errorParam,
|
|
449
|
+
errorMessage: compactResult.errorMessage,
|
|
450
|
+
};
|
|
451
|
+
if (protocol === "standalone-compact") writeDebugArtifact("compaction-event", {
|
|
452
|
+
event: "session_before_compact.standalone-compact-failure", protocol, ...remoteFailure, nextStep: "native-summary",
|
|
453
|
+
}, config, ctx);
|
|
454
|
+
return { outcome: "failed", remoteFailure, protocol };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let details: NativeCompactionDetails;
|
|
459
|
+
try {
|
|
460
|
+
details = createNativeCompactionDetails({
|
|
461
|
+
strategy: protocol === "standalone-compact" ? LEGACY_NATIVE_COMPACTION_STRATEGY : REMOTE_V2_COMPACTION_STRATEGY,
|
|
462
|
+
provider: consumer.provider,
|
|
463
|
+
api: consumer.api,
|
|
464
|
+
model: consumer.model,
|
|
465
|
+
baseUrl: consumer.baseUrl,
|
|
466
|
+
compactionModel: {
|
|
467
|
+
provider: compactor.provider,
|
|
468
|
+
api: compactor.api,
|
|
469
|
+
model: compactor.model,
|
|
470
|
+
baseUrl: compactor.baseUrl,
|
|
471
|
+
},
|
|
472
|
+
deferredToolCarryover,
|
|
473
|
+
compactedWindow: compactResult.compactedWindow,
|
|
474
|
+
compactResponseId: compactResult.compactResponseId,
|
|
475
|
+
createdAt: compactResult.createdAt,
|
|
476
|
+
requestMeta: buildCompactionRequestMeta(event),
|
|
477
|
+
inputProvenance,
|
|
478
|
+
});
|
|
479
|
+
} catch (error) {
|
|
480
|
+
writeDebugArtifact(
|
|
481
|
+
"compaction-event",
|
|
482
|
+
{
|
|
483
|
+
event: "session_before_compact.invalid-native-details",
|
|
484
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
485
|
+
consumer: {
|
|
486
|
+
provider: consumer.provider,
|
|
487
|
+
api: consumer.api,
|
|
488
|
+
model: consumer.model,
|
|
489
|
+
baseUrl: consumer.baseUrl,
|
|
490
|
+
},
|
|
491
|
+
compactor: {
|
|
492
|
+
provider: compactor.provider,
|
|
493
|
+
api: compactor.api,
|
|
494
|
+
model: compactor.model,
|
|
495
|
+
baseUrl: compactor.baseUrl,
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
config,
|
|
499
|
+
ctx,
|
|
500
|
+
);
|
|
501
|
+
return { outcome: "failed", remoteFailure: { reason: "invalid-native-details" }, protocol };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const compaction = createNativeCompactionResult({
|
|
505
|
+
firstKeptEntryId: event.preparation.firstKeptEntryId,
|
|
506
|
+
tokensBefore: event.preparation.tokensBefore,
|
|
507
|
+
details,
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
writeDebugArtifact(
|
|
511
|
+
"compaction-event",
|
|
512
|
+
{
|
|
513
|
+
event: `session_before_compact.${protocol}-success`,
|
|
514
|
+
protocol,
|
|
515
|
+
consumer: {
|
|
516
|
+
provider: consumer.provider,
|
|
517
|
+
api: consumer.api,
|
|
518
|
+
model: consumer.model,
|
|
519
|
+
baseUrl: consumer.baseUrl,
|
|
520
|
+
},
|
|
521
|
+
compactor: {
|
|
522
|
+
provider: compactor.provider,
|
|
523
|
+
api: compactor.api,
|
|
524
|
+
model: compactor.model,
|
|
525
|
+
baseUrl: compactor.baseUrl,
|
|
526
|
+
},
|
|
527
|
+
requestSource,
|
|
528
|
+
remoteV2ContextSource: config.remoteV2ContextSource,
|
|
529
|
+
inputProvenance,
|
|
530
|
+
requestInputItems: request.input.length,
|
|
531
|
+
requestExtras: extras ? Object.keys(extras) : [],
|
|
532
|
+
compactResponseId: compactResult.compactResponseId,
|
|
533
|
+
compactedItems: compactResult.compactedWindow.length,
|
|
534
|
+
firstKeptEntryId: compaction.firstKeptEntryId,
|
|
535
|
+
},
|
|
536
|
+
config,
|
|
537
|
+
ctx,
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
if (!isCurrent() || refuseRetiredSession(ctx)) return { outcome: "aborted" };
|
|
541
|
+
return { outcome: "success", compaction };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function handleSessionBeforeCompact(
|
|
545
|
+
event: SessionBeforeCompactEvent,
|
|
546
|
+
ctx: ExtensionContext,
|
|
547
|
+
dependencies: CompactionDependencies,
|
|
548
|
+
) {
|
|
549
|
+
if (refuseRetiredSession(ctx)) return { cancel: true };
|
|
550
|
+
const resolved = contextOperation(dependencies.loadConfig, ctx);
|
|
551
|
+
if (resolved.invalidFeatures.some((feature) => ["context", "compatibility", "diagnostics"].includes(feature))) return { cancel: true };
|
|
552
|
+
const config = resolved.config.compaction;
|
|
553
|
+
if (config.enabled) {
|
|
554
|
+
writeDebugArtifact(
|
|
555
|
+
"compaction-event",
|
|
556
|
+
{
|
|
557
|
+
event: "session_before_compact",
|
|
558
|
+
customInstructions: event.customInstructions,
|
|
559
|
+
preparation: {
|
|
560
|
+
tokensBefore: event.preparation.tokensBefore,
|
|
561
|
+
firstKeptEntryId: event.preparation.firstKeptEntryId,
|
|
562
|
+
previousSummaryPresent: Boolean(event.preparation.previousSummary),
|
|
563
|
+
messagesToSummarizeCount: event.preparation.messagesToSummarize.length,
|
|
564
|
+
turnPrefixMessagesCount: event.preparation.turnPrefixMessages.length,
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
config,
|
|
568
|
+
ctx,
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (event.signal.aborted) {
|
|
573
|
+
return { cancel: true };
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const isCurrent = dependencies.isCurrentCompaction ?? (() => true);
|
|
577
|
+
if (!isCurrent()) return { cancel: true };
|
|
578
|
+
if (!config.enabled || resolved.policy.context.mode !== "remote-compaction") return undefined;
|
|
579
|
+
|
|
580
|
+
// Resolve producer protocol policy from this operation's document, not a later disk read.
|
|
581
|
+
if (resolved.format === "v2" && config.remoteCompactModel) {
|
|
582
|
+
const producer = parseModelSpec(config.remoteCompactModel);
|
|
583
|
+
if (producer) {
|
|
584
|
+
const producerPolicy = resolveToolkitConfig(resolved.snapshot, { provider: producer.provider, id: producer.modelId });
|
|
585
|
+
notifyConfigIssues(ctx, producerPolicy);
|
|
586
|
+
if (producerPolicy.invalidFeatures.includes("compatibility")) return { cancel: true };
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// Branch 1: Responses-family APIs use remote_compaction_v2 on the normal Responses stream.
|
|
590
|
+
let remoteAttempted = false;
|
|
591
|
+
const resolution = await resolveRemoteCompactionExecution(
|
|
592
|
+
ctx,
|
|
593
|
+
{
|
|
594
|
+
enabled: config.enabled,
|
|
595
|
+
responsesApis: config.responsesApis,
|
|
596
|
+
codexGatewayModels: resolved.gatewayModelKeys,
|
|
597
|
+
},
|
|
598
|
+
config.remoteCompactModel,
|
|
599
|
+
);
|
|
600
|
+
if (!isCurrent() || event.signal.aborted || refuseRetiredSession(ctx)) return { cancel: true };
|
|
601
|
+
if (resolution.ok) {
|
|
602
|
+
remoteAttempted = true;
|
|
603
|
+
const responsesOutcome = await runResponsesNativeCompact(
|
|
604
|
+
event,
|
|
605
|
+
ctx,
|
|
606
|
+
config,
|
|
607
|
+
resolution.execution,
|
|
608
|
+
dependencies.remoteCompact,
|
|
609
|
+
dependencies.projectCompactionContext,
|
|
610
|
+
dependencies.standaloneCompact,
|
|
611
|
+
isCurrent,
|
|
612
|
+
);
|
|
613
|
+
if (!isCurrent() || refuseRetiredSession(ctx)) return { cancel: true };
|
|
614
|
+
if (responsesOutcome.outcome === "success") {
|
|
615
|
+
return { compaction: responsesOutcome.compaction };
|
|
616
|
+
}
|
|
617
|
+
if (responsesOutcome.outcome === "aborted") {
|
|
618
|
+
return { cancel: true };
|
|
619
|
+
}
|
|
620
|
+
if (responsesOutcome.outcome === "unprojected-input") {
|
|
621
|
+
const message = responsesOutcome.reason === COMPACTION_CHECKPOINT_CONTEXT_EDITED
|
|
622
|
+
? "A context edit changed history sealed in the opaque checkpoint. Start a new session or navigate before that checkpoint; its encrypted content cannot be selectively rewritten."
|
|
623
|
+
: responsesOutcome.reason === COMPACTION_PROJECTION_UNAVAILABLE
|
|
624
|
+
? "Pi's ordered context-hook projection is unavailable; Remote V2 was not sent raw session history."
|
|
625
|
+
: responsesOutcome.reason === COMPACTION_SESSION_CONTEXT_UNAVAILABLE
|
|
626
|
+
? "Pi did not provide the current session context required by its context-hook projection."
|
|
627
|
+
: responsesOutcome.reason === COMPACTION_PROJECTED_CONTEXT_UNAVAILABLE
|
|
628
|
+
? "Pi's projected context did not include the current compaction summary anchor."
|
|
629
|
+
: "The latest opaque checkpoint has no marker for the configured Remote V2 context source, or was created in the other mode.";
|
|
630
|
+
writeDebugArtifact(
|
|
631
|
+
"compaction-event",
|
|
632
|
+
{
|
|
633
|
+
event: "session_before_compact.remote-v2-unprojected-input",
|
|
634
|
+
reason: responsesOutcome.reason,
|
|
635
|
+
contextSource: config.remoteV2ContextSource,
|
|
636
|
+
inputProvenance: getRemoteV2InputProvenance(config.remoteV2ContextSource),
|
|
637
|
+
piContextHookPatch: dependencies.piContextHookPatch,
|
|
638
|
+
message,
|
|
639
|
+
},
|
|
640
|
+
config,
|
|
641
|
+
ctx,
|
|
642
|
+
);
|
|
643
|
+
notifyWarning(
|
|
644
|
+
ctx,
|
|
645
|
+
`Remote V2 compaction cancelled (${responsesOutcome.reason}); ${message} The existing checkpoint and session history were left unchanged.`,
|
|
646
|
+
);
|
|
647
|
+
return { cancel: true };
|
|
648
|
+
}
|
|
649
|
+
// Only an actual exhausted remote attempt warns, never an input/routing skip.
|
|
650
|
+
if (event.signal?.aborted) return { cancel: true };
|
|
651
|
+
if (responsesOutcome.remoteFailure) {
|
|
652
|
+
const failure = responsesOutcome.remoteFailure;
|
|
653
|
+
writeDebugArtifact("compaction-event", {
|
|
654
|
+
event: "session_before_compact.remote-to-native-summary",
|
|
655
|
+
protocol: responsesOutcome.protocol,
|
|
656
|
+
...failure,
|
|
657
|
+
nextStep: "native-summary",
|
|
658
|
+
}, config, ctx);
|
|
659
|
+
notifyWarning(ctx, `Remote compaction failed (${failure.reason}${failure.status ? `, HTTP ${failure.status}` : ""}${failure.errorCode ? `, ${failure.errorCode}` : ""}); continuing with a native text summary. No encrypted remote checkpoint was produced.${failure.errorMessage ? ` ${failure.errorMessage}` : ""}`);
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
writeDebugArtifact(
|
|
663
|
+
"compaction-event",
|
|
664
|
+
{
|
|
665
|
+
event: "session_before_compact.remote-v2-unavailable",
|
|
666
|
+
reason: resolution.reason,
|
|
667
|
+
provider: resolution.provider,
|
|
668
|
+
api: resolution.api,
|
|
669
|
+
model: resolution.model,
|
|
670
|
+
baseUrl: resolution.baseUrl,
|
|
671
|
+
modelSpec: resolution.modelSpec,
|
|
672
|
+
errorMessage: resolution.errorMessage,
|
|
673
|
+
},
|
|
674
|
+
config,
|
|
675
|
+
ctx,
|
|
676
|
+
);
|
|
677
|
+
const expectedConsumerCapabilityMiss =
|
|
678
|
+
resolution.reason === "unsupported-api" && resolution.modelSpec === undefined;
|
|
679
|
+
if (config.remoteCompactModel && !expectedConsumerCapabilityMiss) {
|
|
680
|
+
notifyWarning(
|
|
681
|
+
ctx,
|
|
682
|
+
`remote compaction model "${config.remoteCompactModel}" unusable (${resolution.reason}); using the native fallback chain`,
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Branch 2: run pi's native compaction method. A failed remote request is compacted by the
|
|
688
|
+
// remote producer itself; a model that cannot use remote v2 at all uses nativeFallback.model.
|
|
689
|
+
const fallbackModelSpec = remoteAttempted ? config.remoteCompactModel : config.nativeFallback.model;
|
|
690
|
+
if (!isCurrent() || event.signal.aborted || refuseRetiredSession(ctx)) return { cancel: true };
|
|
691
|
+
const fallback = await dependencies.nativeFallback({ ctx, event, config, modelSpec: fallbackModelSpec, isCurrent });
|
|
692
|
+
if (!isCurrent() || refuseRetiredSession(ctx)) return { cancel: true };
|
|
693
|
+
if (fallback.ok) {
|
|
694
|
+
if (ctx.hasUI) {
|
|
695
|
+
ctx.ui.notify(
|
|
696
|
+
`${COMPACTION_EXTENSION_ID}: compacted with ${fallback.model.provider}/${fallback.model.id} (native method)`,
|
|
697
|
+
"info",
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
writeDebugArtifact(
|
|
701
|
+
"compaction-event",
|
|
702
|
+
{
|
|
703
|
+
event: "session_before_compact.fallback-success",
|
|
704
|
+
model: fallback.model,
|
|
705
|
+
},
|
|
706
|
+
config,
|
|
707
|
+
ctx,
|
|
708
|
+
);
|
|
709
|
+
return { compaction: fallback.result };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (fallback.reason === "aborted") {
|
|
713
|
+
return { cancel: true };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
writeDebugArtifact(
|
|
717
|
+
"compaction-event",
|
|
718
|
+
{
|
|
719
|
+
event: "session_before_compact.fallback-skip",
|
|
720
|
+
reason: fallback.reason,
|
|
721
|
+
modelSpec: fallback.modelSpec,
|
|
722
|
+
errorMessage: fallback.errorMessage,
|
|
723
|
+
},
|
|
724
|
+
config,
|
|
725
|
+
ctx,
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
// Intentional pi-default paths: feature disabled, nothing configured, or it matches the current one.
|
|
729
|
+
const intentionalSkip =
|
|
730
|
+
fallback.reason === "disabled" ||
|
|
731
|
+
fallback.reason === "no-model-configured" ||
|
|
732
|
+
fallback.reason === "same-as-current-model";
|
|
733
|
+
if (!intentionalSkip) {
|
|
734
|
+
// An explicit summary model is authoritative: the active model may be
|
|
735
|
+
// unable to summarize at all. Preserve context on failure, never reroute.
|
|
736
|
+
notifyWarning(
|
|
737
|
+
ctx,
|
|
738
|
+
`compaction model "${fallback.modelSpec ?? fallbackModelSpec}" failed (${fallback.reason}${fallback.errorMessage ? `: ${fallback.errorMessage}` : ""}); compaction cancelled, context preserved; not switching to the current model`,
|
|
739
|
+
);
|
|
740
|
+
return { cancel: true };
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return undefined;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async function handleContextInternal(
|
|
747
|
+
event: ContextWithSystemEvent,
|
|
748
|
+
ctx: ExtensionContext,
|
|
749
|
+
loadConfig: typeof loadToolkitConfig,
|
|
750
|
+
) {
|
|
751
|
+
if (refuseRetiredSession(ctx, true)) return undefined;
|
|
752
|
+
const resolved = contextOperation(loadConfig, ctx);
|
|
753
|
+
requireContextPolicy(resolved, ctx);
|
|
754
|
+
const config = resolved.config.compaction;
|
|
755
|
+
if (!config.enabled) return undefined;
|
|
756
|
+
const branchEntries = ctx.sessionManager.getBranch();
|
|
757
|
+
const resolution = await resolveNativeCompactionEnvironment(ctx, {
|
|
758
|
+
enabled: config.enabled,
|
|
759
|
+
responsesApis: config.responsesApis,
|
|
760
|
+
codexGatewayModels: resolved.gatewayModelKeys,
|
|
761
|
+
});
|
|
762
|
+
if (refuseRetiredSession(ctx, true)) return undefined;
|
|
763
|
+
if (!resolution.ok) return undefined;
|
|
764
|
+
const latest = resolveLatestNativeCompactionEntry(branchEntries, { baseUrl: resolution.runtime.baseUrl });
|
|
765
|
+
if (!latest.ok) return undefined;
|
|
766
|
+
const result = removeNativeCompactionRetainedMessages({
|
|
767
|
+
messages: event.messages,
|
|
768
|
+
branchEntries,
|
|
769
|
+
compactionEntry: latest.entry,
|
|
770
|
+
expectedInputProvenance: getRemoteV2InputProvenance(config.remoteV2ContextSource),
|
|
771
|
+
});
|
|
772
|
+
if (!result.ok) {
|
|
773
|
+
writeReplayFailureArtifact({ reason: result.reason, compactionEntryId: latest.entry.id }, config, ctx);
|
|
774
|
+
if (ctx.hasUI) ctx.ui.notify(`${COMPACTION_EXTENSION_ID}: replay failed (${result.reason}); request aborted`, "error");
|
|
775
|
+
reportPiContextHookFailure(ctx, `replay-failed:${result.reason}`);
|
|
776
|
+
ctx.abort();
|
|
777
|
+
return undefined;
|
|
778
|
+
}
|
|
779
|
+
return result.messages.length === event.messages.length && result.messages.every((message, index) => message === event.messages[index])
|
|
780
|
+
? undefined : { messages: result.messages };
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Pi's emitContext() catches handler exceptions. Re-throw as usual for Pi, but
|
|
785
|
+
* also report the failure so the compaction projector cannot mistake the
|
|
786
|
+
* caught exception for a successful projection.
|
|
787
|
+
*/
|
|
788
|
+
async function handleContext(
|
|
789
|
+
event: ContextWithSystemEvent,
|
|
790
|
+
ctx: ExtensionContext,
|
|
791
|
+
loadConfig: typeof loadToolkitConfig,
|
|
792
|
+
) {
|
|
793
|
+
try {
|
|
794
|
+
return await handleContextInternal(event, ctx, loadConfig);
|
|
795
|
+
} catch (error) {
|
|
796
|
+
reportPiContextHookFailure(ctx, error instanceof Error ? error.message : String(error));
|
|
797
|
+
throw error;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
async function handleBeforeProviderRequest(
|
|
802
|
+
event: BeforeProviderRequestEvent,
|
|
803
|
+
ctx: ExtensionContext,
|
|
804
|
+
loadConfig: typeof loadToolkitConfig,
|
|
805
|
+
) {
|
|
806
|
+
if (refuseRetiredSession(ctx, true)) return undefined;
|
|
807
|
+
const resolved = contextOperation(loadConfig, ctx);
|
|
808
|
+
requireContextPolicy(resolved, ctx);
|
|
809
|
+
const toolkitConfig = resolved.config;
|
|
810
|
+
const config = toolkitConfig.compaction;
|
|
811
|
+
if (!config.enabled) return undefined;
|
|
812
|
+
const resolution = await resolveNativeCompactionEnvironment(ctx, {
|
|
813
|
+
enabled: config.enabled,
|
|
814
|
+
responsesApis: config.responsesApis,
|
|
815
|
+
codexGatewayModels: resolved.gatewayModelKeys,
|
|
816
|
+
}, event.payload);
|
|
817
|
+
if (refuseRetiredSession(ctx, true)) return undefined;
|
|
818
|
+
const branchEntries = ctx.sessionManager.getBranch();
|
|
819
|
+
if (resolution.ok === false) {
|
|
820
|
+
writeDebugArtifact(
|
|
821
|
+
"provider-request",
|
|
822
|
+
{
|
|
823
|
+
event: "before_provider_request.skip",
|
|
824
|
+
reason: resolution.reason,
|
|
825
|
+
provider: resolution.provider,
|
|
826
|
+
api: resolution.api,
|
|
827
|
+
model: resolution.model,
|
|
828
|
+
baseUrl: resolution.baseUrl,
|
|
829
|
+
errorMessage: resolution.errorMessage,
|
|
830
|
+
currentModel: getCurrentModelDebugInfo(ctx),
|
|
831
|
+
payload: event.payload,
|
|
832
|
+
},
|
|
833
|
+
config,
|
|
834
|
+
ctx,
|
|
835
|
+
);
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const runtime = resolution.runtime;
|
|
840
|
+
const payload = runtime.payload;
|
|
841
|
+
if (!payload) {
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Capture compact-relevant request fields (tools, reasoning, ...) for the next
|
|
846
|
+
// synthetic compact request using the active consumer's effective runtime identity.
|
|
847
|
+
// This hook runs before the separate Web Search transform, so injected native search
|
|
848
|
+
// tools are not copied into remote_compaction_v2.
|
|
849
|
+
const webSearchRoute = resolveWebSearchRoute({ model: ctx.model, config: toolkitConfig.webSearch });
|
|
850
|
+
rememberRequestContext(
|
|
851
|
+
payload,
|
|
852
|
+
{
|
|
853
|
+
provider: runtime.provider,
|
|
854
|
+
api: runtime.api,
|
|
855
|
+
model: runtime.requestModel ?? runtime.model,
|
|
856
|
+
baseUrl: runtime.baseUrl,
|
|
857
|
+
sessionId: getSessionId(ctx),
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
excludeWebSearchTools: webSearchRoute.route === "standalone-alpha",
|
|
861
|
+
},
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
const latestNativeCompaction = resolveLatestNativeCompactionEntry(branchEntries, {
|
|
865
|
+
baseUrl: runtime.baseUrl,
|
|
866
|
+
});
|
|
867
|
+
if (!latestNativeCompaction.ok) {
|
|
868
|
+
writeDebugArtifact(
|
|
869
|
+
"provider-request",
|
|
870
|
+
{
|
|
871
|
+
event: "before_provider_request.no-native-compaction",
|
|
872
|
+
reason: latestNativeCompaction.reason,
|
|
873
|
+
provider: runtime.provider,
|
|
874
|
+
api: runtime.api,
|
|
875
|
+
model: runtime.model,
|
|
876
|
+
baseUrl: runtime.baseUrl,
|
|
877
|
+
branchEntries: branchEntries.length,
|
|
878
|
+
latestCompactionIndex: latestNativeCompaction.latestCompactionIndex,
|
|
879
|
+
latestCompactionIdentity: getCompactionIdentityDebugInfo(latestNativeCompaction.latestCompaction),
|
|
880
|
+
payload,
|
|
881
|
+
},
|
|
882
|
+
config,
|
|
883
|
+
ctx,
|
|
884
|
+
);
|
|
885
|
+
return undefined;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const latestNativeCompactionEntry = latestNativeCompaction.entry;
|
|
889
|
+
const rewrite = rewriteResponsesPayloadWithNativeReplay({
|
|
890
|
+
model: runtime.currentModel,
|
|
891
|
+
payload,
|
|
892
|
+
branchEntries,
|
|
893
|
+
compactionEntry: latestNativeCompactionEntry,
|
|
894
|
+
expectedInputProvenance: getRemoteV2InputProvenance(config.remoteV2ContextSource),
|
|
895
|
+
});
|
|
896
|
+
if (!rewrite.ok) {
|
|
897
|
+
writeDebugArtifact(
|
|
898
|
+
"provider-request",
|
|
899
|
+
{
|
|
900
|
+
event: "before_provider_request.rewrite-failed",
|
|
901
|
+
reason: rewrite.reason,
|
|
902
|
+
provider: runtime.provider,
|
|
903
|
+
api: runtime.api,
|
|
904
|
+
model: runtime.model,
|
|
905
|
+
baseUrl: runtime.baseUrl,
|
|
906
|
+
compactionEntryId: latestNativeCompactionEntry.id,
|
|
907
|
+
parity: rewrite.parity,
|
|
908
|
+
payload,
|
|
909
|
+
},
|
|
910
|
+
config,
|
|
911
|
+
ctx,
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
// Fail loud instead of letting Pi send the sentinel-only payload: the
|
|
915
|
+
// compacted history would be silently lost while the request still succeeds.
|
|
916
|
+
// A forced redacted failure record is written even when logProviderPayloads
|
|
917
|
+
// is disabled so the incident is diagnosable without leaking content.
|
|
918
|
+
writeReplayFailureArtifact(
|
|
919
|
+
{
|
|
920
|
+
reason: rewrite.reason,
|
|
921
|
+
parity: rewrite.parity,
|
|
922
|
+
compactionEntryId: latestNativeCompactionEntry.id,
|
|
923
|
+
provider: runtime.provider,
|
|
924
|
+
api: runtime.api,
|
|
925
|
+
model: runtime.model,
|
|
926
|
+
},
|
|
927
|
+
config,
|
|
928
|
+
ctx,
|
|
929
|
+
);
|
|
930
|
+
if (ctx.hasUI) {
|
|
931
|
+
ctx.ui.notify(
|
|
932
|
+
`${COMPACTION_EXTENSION_ID}: native compaction replay failed (${rewrite.reason}); provider request aborted`,
|
|
933
|
+
"error",
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
ctx.abort();
|
|
937
|
+
return undefined;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
writeDebugArtifact(
|
|
941
|
+
"provider-request",
|
|
942
|
+
{
|
|
943
|
+
event: "before_provider_request.native-rewrite",
|
|
944
|
+
provider: runtime.provider,
|
|
945
|
+
api: runtime.api,
|
|
946
|
+
model: runtime.model,
|
|
947
|
+
baseUrl: runtime.baseUrl,
|
|
948
|
+
compactionEntryId: latestNativeCompactionEntry.id,
|
|
949
|
+
boundaryIndex: rewrite.segments.boundaryIndex,
|
|
950
|
+
firstKeptEntryIndex: rewrite.segments.firstKeptEntryIndex,
|
|
951
|
+
originalInputItems: payload.input.length,
|
|
952
|
+
rewrittenInputItems: rewrite.rewrittenPayload.input.length,
|
|
953
|
+
leadingItems: rewrite.segments.leading.length,
|
|
954
|
+
compactionSummaryItems: rewrite.segments.compactionSummary.length,
|
|
955
|
+
compactedItems: rewrite.segments.compactedWindow.length,
|
|
956
|
+
postItems: rewrite.segments.post.length,
|
|
957
|
+
payload: rewrite.rewrittenPayload,
|
|
958
|
+
originalPayload: payload,
|
|
959
|
+
},
|
|
960
|
+
config,
|
|
961
|
+
ctx,
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
return rewrite.rewrittenPayload;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
export default function registerCompactionExtension(
|
|
968
|
+
pi: ExtensionAPI,
|
|
969
|
+
overrides: Partial<CompactionDependencies> = {},
|
|
970
|
+
) {
|
|
971
|
+
const loadConfig = overrides.loadConfig ?? loadToolkitConfig;
|
|
972
|
+
registerToolkitConfigCommand(pi, loadConfig);
|
|
973
|
+
registerPhysicalModelObserver(pi);
|
|
974
|
+
const piContextHookPatch = installPiContextHookPatch();
|
|
975
|
+
let compactionGeneration = 0;
|
|
976
|
+
const invalidateCompaction = () => { compactionGeneration++; };
|
|
977
|
+
const dependencies: CompactionDependencies = {
|
|
978
|
+
remoteCompact: executeRemoteV2Compaction,
|
|
979
|
+
standaloneCompact: executeNativeCompaction,
|
|
980
|
+
nativeFallback: runNativeFallbackCompaction,
|
|
981
|
+
...overrides,
|
|
982
|
+
loadConfig,
|
|
983
|
+
piContextHookPatch: overrides.piContextHookPatch ?? piContextHookPatch,
|
|
984
|
+
};
|
|
985
|
+
pi.on("session_start", (_event, ctx) => {
|
|
986
|
+
invalidateCompaction();
|
|
987
|
+
if (refuseRetiredSession(ctx)) return;
|
|
988
|
+
const resolved = contextOperation(loadConfig, ctx);
|
|
989
|
+
const config = resolved.config.compaction;
|
|
990
|
+
if (resolved.invalidFeatures.length || !config.enabled) return;
|
|
991
|
+
const artifactPath = writeDebugArtifact("lifecycle", {
|
|
992
|
+
event: "session_start", config, configSource: resolved.source,
|
|
993
|
+
warnings: resolved.issues.map((issue) => `${issue.path}: ${issue.code}`),
|
|
994
|
+
piContextHookPatch: dependencies.piContextHookPatch,
|
|
995
|
+
}, config, ctx);
|
|
996
|
+
if (ctx.hasUI && ["info", "debug"].includes(resolved.policy.diagnostics.level) && (config.notifyOnLoad || config.debug)) {
|
|
997
|
+
ctx.ui.notify(artifactPath ? `${COMPACTION_EXTENSION_ID} loaded • debug artifacts → ${artifactPath}` : `${COMPACTION_EXTENSION_ID} loaded`, "info");
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
1001
|
+
invalidateCompaction();
|
|
1002
|
+
refuseRetiredSession(ctx);
|
|
1003
|
+
});
|
|
1004
|
+
pi.on("context_with_system", (event, ctx) => handleContext(event, ctx, loadConfig));
|
|
1005
|
+
pi.on("before_provider_request", (event, ctx) => handleBeforeProviderRequest(event, ctx, loadConfig));
|
|
1006
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
1007
|
+
invalidateCompaction();
|
|
1008
|
+
if (refuseRetiredSession(ctx)) return { cancel: true };
|
|
1009
|
+
try {
|
|
1010
|
+
const generation = compactionGeneration;
|
|
1011
|
+
const branch = structuredClone(ctx.sessionManager.getBranch());
|
|
1012
|
+
const sessionId = getSessionId(ctx);
|
|
1013
|
+
const model = ctx.model ? { ...ctx.model } : undefined;
|
|
1014
|
+
const thinking = ctx.thinkingLevel;
|
|
1015
|
+
// Clients classify aborts. This fence handles stale ownership: a
|
|
1016
|
+
// validated V2 terminal still wins over cleanup-triggered cancellation.
|
|
1017
|
+
const isCurrent = () => {
|
|
1018
|
+
try {
|
|
1019
|
+
return generation === compactionGeneration
|
|
1020
|
+
&& getSessionId(ctx) === sessionId && isDeepStrictEqual(branch, ctx.sessionManager.getBranch())
|
|
1021
|
+
&& isDeepStrictEqual(model, ctx.model) && thinking === ctx.thinkingLevel
|
|
1022
|
+
&& !retirementReason(ctx)
|
|
1023
|
+
&& !(event.reason === "manual" && typeof ctx.hasPendingMessages === "function" && ctx.hasPendingMessages());
|
|
1024
|
+
} catch { return false; }
|
|
1025
|
+
};
|
|
1026
|
+
const result = await handleSessionBeforeCompact(event, ctx, { ...dependencies, isCurrentCompaction: isCurrent });
|
|
1027
|
+
if (!isCurrent()) return { cancel: true };
|
|
1028
|
+
if (result && "compaction" in result && result.compaction) {
|
|
1029
|
+
// Pi consumes results after later awaited observers. Never persist a
|
|
1030
|
+
// stale candidate or drain/consume the host's queues.
|
|
1031
|
+
return { compaction: result.compaction, get cancel() { return !isCurrent(); } };
|
|
1032
|
+
}
|
|
1033
|
+
if (result === undefined) {
|
|
1034
|
+
// Default summarization starts only after later observers finish too.
|
|
1035
|
+
// Keep the host handoff revocable without claiming its compaction.
|
|
1036
|
+
return { get cancel() { return !isCurrent(); } };
|
|
1037
|
+
}
|
|
1038
|
+
return result;
|
|
1039
|
+
} catch {
|
|
1040
|
+
// Pi can treat a rejected hook as absent and run its implicit summary.
|
|
1041
|
+
try { notifyWarning(ctx, "Compaction stopped; existing context preserved. Resume manually."); } catch { /* stale UI */ }
|
|
1042
|
+
return { cancel: true };
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
pi.on("session_compact", invalidateCompaction);
|
|
1046
|
+
pi.on("session_compact_failed", invalidateCompaction);
|
|
1047
|
+
pi.on("session_shutdown", invalidateCompaction);
|
|
1048
|
+
pi.on("model_select", invalidateCompaction);
|
|
1049
|
+
pi.on("before_agent_start", invalidateCompaction);
|
|
1050
|
+
pi.on("agent_start", invalidateCompaction);
|
|
1051
|
+
pi.on("input", invalidateCompaction);
|
|
1052
|
+
pi.on("thinking_level_select", invalidateCompaction);
|
|
1053
|
+
pi.on("message_start", invalidateCompaction);
|
|
1054
|
+
pi.on("tool_call", invalidateCompaction);
|
|
1055
|
+
pi.on("session_before_switch", invalidateCompaction);
|
|
1056
|
+
pi.on("session_before_fork", invalidateCompaction);
|
|
1057
|
+
pi.on("session_before_tree", invalidateCompaction);
|
|
1058
|
+
|
|
1059
|
+
// Exact gateway compatibility is shared transport policy, independent of
|
|
1060
|
+
// compaction enablement. No window identities or ingest headers are generated.
|
|
1061
|
+
pi.on("before_provider_headers", async (event, ctx) => {
|
|
1062
|
+
if (refuseRetiredSession(ctx, true)) return;
|
|
1063
|
+
const resolved = contextOperation(loadConfig, ctx);
|
|
1064
|
+
requireContextPolicy(resolved, ctx);
|
|
1065
|
+
if (!resolved.gatewayModelKeys.length) return;
|
|
1066
|
+
const resolution = await resolveNativeCompactionEnvironment(ctx, {
|
|
1067
|
+
responsesApis: ["openai-responses"], codexGatewayModels: resolved.gatewayModelKeys,
|
|
1068
|
+
});
|
|
1069
|
+
if (refuseRetiredSession(ctx, true)) return;
|
|
1070
|
+
if (!resolution.ok) {
|
|
1071
|
+
const selected = isExactModelAllowed(ctx.model, resolved.gatewayModelKeys);
|
|
1072
|
+
const physical = resolution.provider && resolution.model
|
|
1073
|
+
? isExactModelAllowed({ provider: resolution.provider, id: resolution.model }, resolved.gatewayModelKeys) : false;
|
|
1074
|
+
if (selected || physical) {
|
|
1075
|
+
notifyWarning(ctx, "Configured gateway transport is unavailable; provider request aborted.");
|
|
1076
|
+
ctx.abort();
|
|
1077
|
+
}
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
const runtime = resolution.runtime;
|
|
1081
|
+
if (!runtime.codexAffinity) return;
|
|
1082
|
+
const gatewayHeaders = buildResponsesRequestHeaders(runtime, { accept: "text/event-stream" });
|
|
1083
|
+
const allowed = new Set<string>(CODEX_GATEWAY_FORWARD_HEADERS);
|
|
1084
|
+
for (const existing of Object.keys(event.headers)) {
|
|
1085
|
+
if (!allowed.has(existing.toLowerCase())) delete event.headers[existing];
|
|
1086
|
+
}
|
|
1087
|
+
for (const name of ["authorization", "originator", "user-agent", "session-id", "x-client-request-id", "x-codex-affinity-scope", "x-codex-model"]) {
|
|
1088
|
+
for (const existing of Object.keys(event.headers)) {
|
|
1089
|
+
if (existing.toLowerCase() === name) delete event.headers[existing];
|
|
1090
|
+
}
|
|
1091
|
+
const value = gatewayHeaders.get(name);
|
|
1092
|
+
if (value) event.headers[name] = value;
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
}
|