pi-openai-toolkit 0.20.7 → 0.21.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 +8 -26
- package/README.zh.md +8 -26
- package/config.schema.json +278 -366
- package/docs/configuration.md +294 -310
- package/docs/internals.md +166 -175
- package/package.json +3 -13
- package/src/compact-client.ts +412 -389
- package/src/compaction-errors.ts +96 -0
- package/src/compaction-projection.ts +35 -35
- package/src/config/legacy.ts +519 -717
- package/src/config/migration.ts +103 -100
- package/src/config/notifications.ts +36 -28
- package/src/config/policy.ts +10 -18
- package/src/config/v2.ts +1167 -1380
- package/src/config.ts +310 -319
- package/src/context-management/codex-provider.ts +270 -270
- package/src/context-management/history-notes.ts +357 -357
- package/src/context-management/manual-compact.ts +0 -11
- package/src/context-management/tools.ts +371 -371
- package/src/context-management/types.ts +168 -168
- package/src/context-management/window-manager.ts +801 -801
- package/src/debug.ts +238 -237
- package/src/deferred-tool-carryover.ts +282 -282
- package/src/details-store.ts +151 -151
- package/src/extension-runtime.ts +1662 -1623
- 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 +4 -132
- package/src/payload-rewrite.ts +515 -518
- package/src/pi-context-hook.ts +117 -117
- package/src/remote-v2-client.ts +811 -814
- package/src/request-context-cache.ts +155 -155
- package/src/responses-headers.ts +180 -180
- package/src/runtime.ts +865 -832
- package/src/serializer.ts +882 -882
- package/src/supported-environment.ts +27 -27
- package/src/types.ts +565 -648
- 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 -54
- package/src/auto-mode/classifier.ts +0 -161
- package/src/auto-mode/extension.ts +0 -691
- package/src/auto-mode/model-selection-guard.ts +0 -52
- package/src/auto-mode/policy.ts +0 -146
- package/src/auto-mode/prompt.ts +0 -279
- package/src/auto-mode/reviewer.ts +0 -325
- package/src/auto-mode/tool-review-tui.ts +0 -211
- package/src/auto-mode/transcript.ts +0 -135
- package/src/auto-mode/types.ts +0 -137
package/src/runtime.ts
CHANGED
|
@@ -1,832 +1,865 @@
|
|
|
1
|
-
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type { Api, Message, Model, ModelThinkingLevel, ProviderHeaders } from "@earendil-works/pi-ai";
|
|
3
|
-
import { convertToLlm, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { isExactModelAllowed } from "./model-scope";
|
|
5
|
-
import {
|
|
6
|
-
CODEX_AFFINITY_SCOPE,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
| "
|
|
34
|
-
| "
|
|
35
|
-
| "
|
|
36
|
-
| "model-
|
|
37
|
-
| "
|
|
38
|
-
| "
|
|
39
|
-
| "
|
|
40
|
-
| "
|
|
41
|
-
| "missing-
|
|
42
|
-
| "
|
|
43
|
-
| "
|
|
44
|
-
| "payload
|
|
45
|
-
| "
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
model
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*
|
|
80
|
-
* requests
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
return
|
|
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
|
-
if (normalized.endsWith("/codex")) {
|
|
353
|
-
return
|
|
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
|
-
url.
|
|
384
|
-
url.
|
|
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
|
-
|
|
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
|
-
parsed.
|
|
463
|
-
parsed.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
pathname.endsWith("/
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
pathname.endsWith("/
|
|
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
|
-
export function
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
):
|
|
552
|
-
|
|
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
|
-
if (!
|
|
624
|
-
return {
|
|
625
|
-
ok: false,
|
|
626
|
-
reason: "
|
|
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
|
-
ctx
|
|
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
|
-
const
|
|
804
|
-
if (
|
|
805
|
-
return {
|
|
806
|
-
ok: false,
|
|
807
|
-
reason: "
|
|
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
|
-
}
|
|
1
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { Api, Message, Model, ModelThinkingLevel, ProviderHeaders } from "@earendil-works/pi-ai";
|
|
3
|
+
import { convertToLlm, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { isExactModelAllowed } from "./model-scope";
|
|
5
|
+
import {
|
|
6
|
+
CODEX_AFFINITY_SCOPE,
|
|
7
|
+
buildResponsesRequestHeaders,
|
|
8
|
+
type CodexAffinity,
|
|
9
|
+
} from "./responses-headers";
|
|
10
|
+
import { RESPONSES_COMPACT_CAPABLE_APIS } from "./types";
|
|
11
|
+
|
|
12
|
+
const OPENAI_RESPONSES_PATH = "responses";
|
|
13
|
+
const CODEX_RESPONSES_PATH = "codex/responses";
|
|
14
|
+
const AZURE_RESPONSES_PATH = "responses";
|
|
15
|
+
const OPENAI_COMPACT_PATH = "responses/compact";
|
|
16
|
+
const CODEX_COMPACT_PATH = "codex/responses/compact";
|
|
17
|
+
const AZURE_COMPACT_PATH = "responses/compact";
|
|
18
|
+
const AZURE_DEFAULT_API_VERSION = "v1";
|
|
19
|
+
const ALPHA_SEARCH_PATH = "alpha/search";
|
|
20
|
+
export const VIRTUAL_MODEL_API = "pi-virtual" as const;
|
|
21
|
+
|
|
22
|
+
export type ResponsesApi = (typeof RESPONSES_COMPACT_CAPABLE_APIS)[number];
|
|
23
|
+
|
|
24
|
+
type ResponsesCompactApi = ResponsesApi;
|
|
25
|
+
type RuntimeModel = Model<Api>;
|
|
26
|
+
|
|
27
|
+
export type ParsedModelSpec = {
|
|
28
|
+
provider: string;
|
|
29
|
+
modelId: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type NativeCompactionFailureReason =
|
|
33
|
+
| "disabled"
|
|
34
|
+
| "missing-model"
|
|
35
|
+
| "virtual-model-unresolved"
|
|
36
|
+
| "invalid-model-spec"
|
|
37
|
+
| "model-not-found"
|
|
38
|
+
| "unsupported-api"
|
|
39
|
+
| "missing-base-url"
|
|
40
|
+
| "invalid-base-url"
|
|
41
|
+
| "missing-session-id"
|
|
42
|
+
| "missing-api-key"
|
|
43
|
+
| "auth-resolution-failed"
|
|
44
|
+
| "unsupported-payload"
|
|
45
|
+
| "payload-model-mismatch"
|
|
46
|
+
| "base-url-mismatch";
|
|
47
|
+
|
|
48
|
+
export type NativeCompactionSupportOptions = {
|
|
49
|
+
enabled?: boolean;
|
|
50
|
+
/** Which Responses APIs should use the compact endpoint; defaults to all capable APIs. */
|
|
51
|
+
responsesApis?: readonly string[];
|
|
52
|
+
/** Exact provider/model keys whose Responses traffic carries gateway Codex affinity metadata. */
|
|
53
|
+
codexGatewayModels?: readonly string[];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type ResponsesSupportOptions = NativeCompactionSupportOptions;
|
|
57
|
+
|
|
58
|
+
export type ResponsesCompatibleRequestPayload = {
|
|
59
|
+
model: string;
|
|
60
|
+
input: unknown[];
|
|
61
|
+
instructions?: unknown;
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type ResponsesRuntime = {
|
|
66
|
+
provider: string;
|
|
67
|
+
api: ResponsesApi;
|
|
68
|
+
/** Logical Pi model id. */
|
|
69
|
+
model: string;
|
|
70
|
+
/** Wire model/deployment id; usually the same as `model`. */
|
|
71
|
+
requestModel?: string;
|
|
72
|
+
baseUrl: string;
|
|
73
|
+
apiKey: string;
|
|
74
|
+
env?: Record<string, string>;
|
|
75
|
+
headers?: ProviderHeaders;
|
|
76
|
+
responsesPath: string;
|
|
77
|
+
responsesUrl: string;
|
|
78
|
+
/**
|
|
79
|
+
* Conversation identity for Codex prompt-cache affinity headers. Synthetic
|
|
80
|
+
* compaction/review requests must present the same id the live transcript
|
|
81
|
+
* requests used or the backend treats them as a new conversation.
|
|
82
|
+
*/
|
|
83
|
+
sessionId?: string;
|
|
84
|
+
/** Opt-in gateway Codex routing metadata for synthetic Responses/compact requests. */
|
|
85
|
+
codexAffinity?: CodexAffinity;
|
|
86
|
+
currentModel: RuntimeModel;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type NativeCompactionRuntime = ResponsesRuntime & {
|
|
90
|
+
/** Public host snapshot for the resolved physical model; never inferred from token text. */
|
|
91
|
+
authMode?: "oauth" | "non-oauth" | "unknown";
|
|
92
|
+
compactPath: string;
|
|
93
|
+
compactUrl: string;
|
|
94
|
+
payload?: ResponsesCompatibleRequestPayload;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type NativeCompactionEnvironmentFailure = {
|
|
98
|
+
ok: false;
|
|
99
|
+
reason: NativeCompactionFailureReason;
|
|
100
|
+
provider?: string;
|
|
101
|
+
api?: string;
|
|
102
|
+
model?: string;
|
|
103
|
+
baseUrl?: string;
|
|
104
|
+
modelSpec?: string;
|
|
105
|
+
errorMessage?: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type NativeCompactionEnvironmentSuccess = {
|
|
109
|
+
ok: true;
|
|
110
|
+
runtime: NativeCompactionRuntime;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export type NativeCompactionEnvironmentResolution =
|
|
114
|
+
| NativeCompactionEnvironmentFailure
|
|
115
|
+
| NativeCompactionEnvironmentSuccess;
|
|
116
|
+
|
|
117
|
+
export type ResponsesEnvironmentSuccess = {
|
|
118
|
+
ok: true;
|
|
119
|
+
runtime: ResponsesRuntime;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type ResponsesEnvironmentResolution =
|
|
123
|
+
| NativeCompactionEnvironmentFailure
|
|
124
|
+
| ResponsesEnvironmentSuccess;
|
|
125
|
+
|
|
126
|
+
export type RemoteCompactionExecution = {
|
|
127
|
+
/** Active session model. Its identity owns replay matching and is never mutated. */
|
|
128
|
+
consumer: NativeCompactionRuntime;
|
|
129
|
+
/** Model used only for the synthetic remote_compaction_v2 request. */
|
|
130
|
+
compactor: NativeCompactionRuntime;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type RemoteCompactionExecutionSuccess = {
|
|
134
|
+
ok: true;
|
|
135
|
+
execution: RemoteCompactionExecution;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export type RemoteCompactionExecutionResolution =
|
|
139
|
+
| NativeCompactionEnvironmentFailure
|
|
140
|
+
| RemoteCompactionExecutionSuccess;
|
|
141
|
+
|
|
142
|
+
type ResolvedRequestAuth =
|
|
143
|
+
| {
|
|
144
|
+
ok: true;
|
|
145
|
+
apiKey?: string;
|
|
146
|
+
headers?: ProviderHeaders;
|
|
147
|
+
baseUrl?: string;
|
|
148
|
+
env?: Record<string, string>;
|
|
149
|
+
}
|
|
150
|
+
| { ok: false; error: string };
|
|
151
|
+
|
|
152
|
+
type VirtualModelRuntime = {
|
|
153
|
+
resolveModel?: (
|
|
154
|
+
model: RuntimeModel,
|
|
155
|
+
messages: readonly Message[],
|
|
156
|
+
options: {
|
|
157
|
+
reason: "direct";
|
|
158
|
+
thinkingLevel: ModelThinkingLevel;
|
|
159
|
+
signal?: AbortSignal;
|
|
160
|
+
state?: unknown;
|
|
161
|
+
},
|
|
162
|
+
) => Promise<{ model: RuntimeModel; thinkingLevel: ModelThinkingLevel; state?: unknown }>;
|
|
163
|
+
getPhysicalModel?: (provider: string, modelId: string) => RuntimeModel | undefined;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
type ModelRegistryRuntimeBridge = {
|
|
167
|
+
runtime?: VirtualModelRuntime;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export type EffectiveModelResolution =
|
|
171
|
+
| { ok: true; model: RuntimeModel; virtual: boolean }
|
|
172
|
+
| { ok: false; reason: "missing-model" | "virtual-model-unresolved"; errorMessage?: string };
|
|
173
|
+
|
|
174
|
+
const physicalModelCache = new WeakMap<object, Map<string, RuntimeModel>>();
|
|
175
|
+
const physicalModelObserverApis = new WeakSet<object>();
|
|
176
|
+
|
|
177
|
+
function modelKey(model: RuntimeModel): string {
|
|
178
|
+
return `${model.provider}/${model.id}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getPhysicalModelCache(ctx: ExtensionContext): Map<string, RuntimeModel> {
|
|
182
|
+
const registry = ctx.modelRegistry as object;
|
|
183
|
+
let cache = physicalModelCache.get(registry);
|
|
184
|
+
if (!cache) {
|
|
185
|
+
cache = new Map<string, RuntimeModel>();
|
|
186
|
+
physicalModelCache.set(registry, cache);
|
|
187
|
+
}
|
|
188
|
+
return cache;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function sessionModelKey(ctx: ExtensionContext, model: RuntimeModel): string | undefined {
|
|
192
|
+
try {
|
|
193
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
194
|
+
return sessionId ? `${sessionId}\u0000${modelKey(model)}` : undefined;
|
|
195
|
+
} catch {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isAssistantModelMessage(message: AgentMessage): message is AgentMessage & {
|
|
201
|
+
provider: string;
|
|
202
|
+
api: string;
|
|
203
|
+
model: string;
|
|
204
|
+
} {
|
|
205
|
+
if (message.role !== "assistant") return false;
|
|
206
|
+
const candidate = message as unknown as Record<string, unknown>;
|
|
207
|
+
return typeof candidate.provider === "string" &&
|
|
208
|
+
typeof candidate.api === "string" &&
|
|
209
|
+
typeof candidate.model === "string";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Remember the physical model Pi actually used as a fallback for a virtual selection. */
|
|
213
|
+
export function rememberPhysicalModel(ctx: ExtensionContext, message: AgentMessage): void {
|
|
214
|
+
const selected = ctx.model as RuntimeModel | undefined;
|
|
215
|
+
if (!selected || selected.api !== VIRTUAL_MODEL_API || !isAssistantModelMessage(message)) return;
|
|
216
|
+
const physical = ctx.modelRegistry.find(message.provider, message.model);
|
|
217
|
+
const key = sessionModelKey(ctx, selected);
|
|
218
|
+
if (physical && physical.api !== VIRTUAL_MODEL_API && key) {
|
|
219
|
+
getPhysicalModelCache(ctx).set(key, physical);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Register the fallback cache observer used by feature extensions. Pi 0.99
|
|
225
|
+
* exposes a resolver for fresh routes; the assistant message cache only helps
|
|
226
|
+
* when a host cannot expose that resolver after a physical response exists.
|
|
227
|
+
*/
|
|
228
|
+
export function registerPhysicalModelObserver(pi: ExtensionAPI): void {
|
|
229
|
+
if (physicalModelObserverApis.has(pi)) return;
|
|
230
|
+
physicalModelObserverApis.add(pi);
|
|
231
|
+
pi.on("message_end", (event, ctx) => {
|
|
232
|
+
rememberPhysicalModel(ctx, event.message);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function resolveVirtualModel(ctx: ExtensionContext, model: RuntimeModel): Promise<EffectiveModelResolution> {
|
|
237
|
+
const cache = getPhysicalModelCache(ctx);
|
|
238
|
+
const key = sessionModelKey(ctx, model);
|
|
239
|
+
const bridge = ctx.modelRegistry as unknown as ModelRegistryRuntimeBridge;
|
|
240
|
+
const runtime = bridge.runtime;
|
|
241
|
+
if (typeof runtime?.resolveModel !== "function") {
|
|
242
|
+
const cached = key ? cache.get(key) : undefined;
|
|
243
|
+
if (cached) return { ok: true, model: cached, virtual: true };
|
|
244
|
+
return {
|
|
245
|
+
ok: false,
|
|
246
|
+
reason: "virtual-model-unresolved",
|
|
247
|
+
errorMessage: "Pi does not expose virtual-model routing to extensions.",
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let messages: Message[];
|
|
252
|
+
try {
|
|
253
|
+
messages = convertToLlm(ctx.sessionManager.buildSessionProjection().messages);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
reason: "virtual-model-unresolved",
|
|
258
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const route = await runtime.resolveModel(model, messages, {
|
|
264
|
+
reason: "direct",
|
|
265
|
+
thinkingLevel: (ctx.thinkingLevel ?? "off") as ModelThinkingLevel,
|
|
266
|
+
signal: ctx.signal,
|
|
267
|
+
});
|
|
268
|
+
const physical = runtime.getPhysicalModel?.(route.model.provider, route.model.id)
|
|
269
|
+
?? ctx.modelRegistry.find(route.model.provider, route.model.id)
|
|
270
|
+
?? route.model;
|
|
271
|
+
if (!physical || physical.api === VIRTUAL_MODEL_API) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
reason: "virtual-model-unresolved",
|
|
275
|
+
errorMessage: "Pi virtual-model routing did not return a physical model.",
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (key) cache.set(key, physical);
|
|
279
|
+
return { ok: true, model: physical, virtual: true };
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
reason: "virtual-model-unresolved",
|
|
284
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function resolveEffectiveModel(
|
|
290
|
+
ctx: ExtensionContext,
|
|
291
|
+
model: ExtensionContext["model"] = ctx.model,
|
|
292
|
+
): Promise<EffectiveModelResolution> {
|
|
293
|
+
if (!model) return { ok: false, reason: "missing-model" };
|
|
294
|
+
const runtimeModel = model as RuntimeModel;
|
|
295
|
+
if (runtimeModel.api !== VIRTUAL_MODEL_API) return { ok: true, model: runtimeModel, virtual: false };
|
|
296
|
+
return resolveVirtualModel(ctx, runtimeModel);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function normalizeConfiguredApis(values: readonly string[] | undefined): Set<string> {
|
|
300
|
+
if (values === undefined) {
|
|
301
|
+
return new Set(RESPONSES_COMPACT_CAPABLE_APIS);
|
|
302
|
+
}
|
|
303
|
+
return new Set(values.map((value) => value.trim()).filter((value) => value.length > 0));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function resolveCodexGatewayAffinity(
|
|
307
|
+
model: RuntimeModel,
|
|
308
|
+
gatewayModels: readonly string[] | undefined,
|
|
309
|
+
logicalModel?: RuntimeModel,
|
|
310
|
+
): CodexAffinity | undefined {
|
|
311
|
+
const allowlist = gatewayModels ?? [];
|
|
312
|
+
const allowed = isExactModelAllowed(model, allowlist) ||
|
|
313
|
+
(logicalModel?.api === VIRTUAL_MODEL_API && isExactModelAllowed(logicalModel, allowlist));
|
|
314
|
+
if (model.api !== "openai-responses" || !allowed) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
model: model.id,
|
|
319
|
+
scope: CODEX_AFFINITY_SCOPE,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Parse "provider/model-id" (model ids may themselves contain slashes). */
|
|
324
|
+
export function parseModelSpec(spec: string): ParsedModelSpec | undefined {
|
|
325
|
+
const trimmed = spec.trim();
|
|
326
|
+
const separatorIndex = trimmed.indexOf("/");
|
|
327
|
+
if (separatorIndex <= 0 || separatorIndex >= trimmed.length - 1) {
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const provider = trimmed.slice(0, separatorIndex).trim();
|
|
332
|
+
const modelId = trimmed.slice(separatorIndex + 1).trim();
|
|
333
|
+
if (!provider || !modelId) {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return { provider, modelId };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function normalizeBaseUrl(baseUrl: string | undefined | null): string | undefined {
|
|
341
|
+
const normalized = baseUrl?.trim().replace(/\/+$/, "");
|
|
342
|
+
return normalized ? normalized : undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function buildOpenAIResponsesUrl(baseUrl: string): string {
|
|
346
|
+
const normalized = normalizeBaseUrl(baseUrl) ?? baseUrl;
|
|
347
|
+
return normalized.endsWith("/responses") ? normalized : `${normalized}/${OPENAI_RESPONSES_PATH}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function buildCodexResponsesUrl(baseUrl: string): string {
|
|
351
|
+
const normalized = normalizeBaseUrl(baseUrl) ?? baseUrl;
|
|
352
|
+
if (normalized.endsWith("/codex/responses")) {
|
|
353
|
+
return normalized;
|
|
354
|
+
}
|
|
355
|
+
if (normalized.endsWith("/codex")) {
|
|
356
|
+
return `${normalized}/responses`;
|
|
357
|
+
}
|
|
358
|
+
return `${normalized}/${CODEX_RESPONSES_PATH}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function buildOpenAICompactUrl(baseUrl: string): string {
|
|
362
|
+
return `${buildOpenAIResponsesUrl(baseUrl)}/compact`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function buildCodexCompactUrl(baseUrl: string): string {
|
|
366
|
+
return `${buildCodexResponsesUrl(baseUrl)}/compact`;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function envValue(env: Record<string, string> | undefined, name: string): string | undefined {
|
|
370
|
+
const value = env?.[name]?.trim();
|
|
371
|
+
return value || undefined;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function buildAzureBaseUrl(baseUrl: string | undefined, env: Record<string, string> | undefined): string | undefined {
|
|
375
|
+
const configured = envValue(env, "AZURE_OPENAI_BASE_URL") ?? normalizeBaseUrl(baseUrl);
|
|
376
|
+
if (configured) return configured;
|
|
377
|
+
const resource = envValue(env, "AZURE_OPENAI_RESOURCE_NAME");
|
|
378
|
+
return resource ? `https://${resource}.openai.azure.com/openai/v1` : undefined;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function normalizeAzureBaseUrl(baseUrl: string): string {
|
|
382
|
+
const url = new URL(baseUrl);
|
|
383
|
+
const pathname = url.pathname.replace(/\/+$/, "");
|
|
384
|
+
if (pathname === "" || pathname === "/" || pathname === "/openai") url.pathname = "/openai/v1";
|
|
385
|
+
else if (pathname.endsWith("/responses")) url.pathname = pathname.slice(0, -"/responses".length);
|
|
386
|
+
url.search = "";
|
|
387
|
+
url.hash = "";
|
|
388
|
+
return url.toString().replace(/\/$/, "");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function appendAzureApiVersion(urlValue: string, env: Record<string, string> | undefined): string {
|
|
392
|
+
const url = new URL(urlValue);
|
|
393
|
+
if (!url.searchParams.has("api-version")) {
|
|
394
|
+
url.searchParams.set("api-version", envValue(env, "AZURE_OPENAI_API_VERSION") ?? AZURE_DEFAULT_API_VERSION);
|
|
395
|
+
}
|
|
396
|
+
return url.toString();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function resolveAzureDeploymentName(modelId: string, env: Record<string, string> | undefined): string {
|
|
400
|
+
const mapping = envValue(env, "AZURE_OPENAI_DEPLOYMENT_NAME_MAP");
|
|
401
|
+
if (mapping) {
|
|
402
|
+
for (const entry of mapping.split(",")) {
|
|
403
|
+
const separator = entry.indexOf("=");
|
|
404
|
+
if (separator <= 0) continue;
|
|
405
|
+
if (entry.slice(0, separator).trim() === modelId) {
|
|
406
|
+
const deployment = entry.slice(separator + 1).trim();
|
|
407
|
+
if (deployment) return deployment;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return modelId;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function buildResponsesUrl(
|
|
415
|
+
baseUrl: string,
|
|
416
|
+
api: ResponsesCompactApi,
|
|
417
|
+
env?: Record<string, string>,
|
|
418
|
+
): string {
|
|
419
|
+
if (api === "openai-codex-responses") return buildCodexResponsesUrl(baseUrl);
|
|
420
|
+
if (api === "azure-openai-responses") {
|
|
421
|
+
return appendAzureApiVersion(`${normalizeAzureBaseUrl(baseUrl)}/${AZURE_RESPONSES_PATH}`, env);
|
|
422
|
+
}
|
|
423
|
+
return buildOpenAIResponsesUrl(baseUrl);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function buildResponsesPath(api: ResponsesCompactApi): string {
|
|
427
|
+
return api === "openai-codex-responses" ? CODEX_RESPONSES_PATH : AZURE_RESPONSES_PATH;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function buildCompactUrl(
|
|
431
|
+
baseUrl: string,
|
|
432
|
+
api: ResponsesCompactApi,
|
|
433
|
+
env?: Record<string, string>,
|
|
434
|
+
): string {
|
|
435
|
+
if (api === "openai-codex-responses") return buildCodexCompactUrl(baseUrl);
|
|
436
|
+
if (api === "azure-openai-responses") {
|
|
437
|
+
return appendAzureApiVersion(`${normalizeAzureBaseUrl(baseUrl)}/${AZURE_COMPACT_PATH}`, env);
|
|
438
|
+
}
|
|
439
|
+
return buildOpenAICompactUrl(baseUrl);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function buildCompactPath(api: ResponsesCompactApi): string {
|
|
443
|
+
return api === "openai-codex-responses" ? CODEX_COMPACT_PATH : AZURE_COMPACT_PATH;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Build the provider-relative standalone-search endpoint. This deliberately
|
|
448
|
+
* rejects a full Responses endpoint so an already-resolved URL cannot become
|
|
449
|
+
* `/responses/alpha/search` or `/codex/responses/alpha/search` by accident.
|
|
450
|
+
*/
|
|
451
|
+
export function buildAlphaSearchUrl(baseUrl: string): string | undefined {
|
|
452
|
+
const normalized = normalizeBaseUrl(baseUrl);
|
|
453
|
+
if (!normalized) return undefined;
|
|
454
|
+
|
|
455
|
+
let parsed: URL;
|
|
456
|
+
try {
|
|
457
|
+
parsed = new URL(normalized);
|
|
458
|
+
} catch {
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
if (
|
|
462
|
+
(parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
|
|
463
|
+
parsed.username ||
|
|
464
|
+
parsed.password ||
|
|
465
|
+
parsed.search ||
|
|
466
|
+
parsed.hash
|
|
467
|
+
) {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
472
|
+
if (
|
|
473
|
+
pathname.endsWith("/responses/alpha/search") ||
|
|
474
|
+
pathname.endsWith("/codex/responses/alpha/search") ||
|
|
475
|
+
pathname.endsWith("/responses/compact/alpha/search") ||
|
|
476
|
+
pathname.endsWith("/codex/responses/compact/alpha/search")
|
|
477
|
+
) {
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
if (pathname.endsWith("/alpha/search")) {
|
|
481
|
+
parsed.pathname = pathname;
|
|
482
|
+
return parsed.toString();
|
|
483
|
+
}
|
|
484
|
+
if (
|
|
485
|
+
pathname.endsWith("/responses") ||
|
|
486
|
+
pathname.endsWith("/codex/responses") ||
|
|
487
|
+
pathname.endsWith("/responses/compact") ||
|
|
488
|
+
pathname.endsWith("/codex/responses/compact")
|
|
489
|
+
) {
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
parsed.pathname = `${pathname}/${ALPHA_SEARCH_PATH}`.replace(/^\/\//, "/");
|
|
494
|
+
return parsed.toString();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function resolveRequestAuth(ctx: ExtensionContext, model: RuntimeModel): Promise<ResolvedRequestAuth> {
|
|
498
|
+
const modelRegistry = ctx.modelRegistry as {
|
|
499
|
+
getApiKeyAndHeaders?: (currentModel: RuntimeModel) => Promise<ResolvedRequestAuth>;
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
if (typeof modelRegistry.getApiKeyAndHeaders !== "function") {
|
|
503
|
+
return { ok: true };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return modelRegistry.getApiKeyAndHeaders(model);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function resolveAuthMode(ctx: ExtensionContext, model: RuntimeModel): NativeCompactionRuntime["authMode"] {
|
|
510
|
+
const registry = ctx.modelRegistry as unknown as {
|
|
511
|
+
isUsingOAuth?: (model: RuntimeModel) => unknown;
|
|
512
|
+
hasConfiguredAuth?: (model: RuntimeModel) => unknown;
|
|
513
|
+
};
|
|
514
|
+
try {
|
|
515
|
+
if (typeof registry.isUsingOAuth !== "function" || typeof registry.hasConfiguredAuth !== "function" ||
|
|
516
|
+
registry.hasConfiguredAuth(model) !== true) return "unknown";
|
|
517
|
+
const oauth = registry.isUsingOAuth(model);
|
|
518
|
+
return oauth === true ? "oauth" : oauth === false ? "non-oauth" : "unknown";
|
|
519
|
+
} catch {
|
|
520
|
+
return "unknown";
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Conservative automatic standalone routing; provider aliases alone prove nothing. */
|
|
525
|
+
export function isStandaloneCompactionEligible(runtime: NativeCompactionRuntime): boolean {
|
|
526
|
+
if (runtime.api !== "openai-responses" || runtime.codexAffinity || runtime.authMode !== "non-oauth") return false;
|
|
527
|
+
try {
|
|
528
|
+
const url = new URL(runtime.compactUrl);
|
|
529
|
+
if (url.protocol !== "https:" || url.hostname !== "api.openai.com" || url.port ||
|
|
530
|
+
url.pathname !== "/v1/responses/compact" || url.username || url.password || url.search || url.hash) return false;
|
|
531
|
+
const headers = buildResponsesRequestHeaders(runtime, { accept: "application/json" });
|
|
532
|
+
return headers.get("authorization") === `Bearer ${runtime.apiKey}`;
|
|
533
|
+
} catch {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function isSupportedApi(api: string): api is ResponsesCompactApi {
|
|
539
|
+
return (RESPONSES_COMPACT_CAPABLE_APIS as readonly string[]).includes(api);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export function isResponsesCompatiblePayload(payload: unknown): payload is ResponsesCompatibleRequestPayload {
|
|
543
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const candidate = payload as Record<string, unknown>;
|
|
548
|
+
return typeof candidate.model === "string" && Array.isArray(candidate.input);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export function getRuntimeModelDescriptor(model: RuntimeModel | undefined): {
|
|
552
|
+
provider?: string;
|
|
553
|
+
api?: string;
|
|
554
|
+
model?: string;
|
|
555
|
+
baseUrl?: string;
|
|
556
|
+
} {
|
|
557
|
+
if (!model) {
|
|
558
|
+
return {};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
provider: model.provider,
|
|
563
|
+
api: model.api,
|
|
564
|
+
model: model.id,
|
|
565
|
+
baseUrl: normalizeBaseUrl(model.baseUrl),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** Best-effort conversation id; header emission then simply skips the affinity headers. */
|
|
570
|
+
export function resolveRuntimeSessionId(ctx: ExtensionContext): string | undefined {
|
|
571
|
+
try {
|
|
572
|
+
return ctx.sessionManager.getSessionId();
|
|
573
|
+
} catch {
|
|
574
|
+
return undefined;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async function resolveNativeCompactionEnvironmentForModel(
|
|
579
|
+
ctx: ExtensionContext,
|
|
580
|
+
currentModel: RuntimeModel | undefined,
|
|
581
|
+
options: NativeCompactionSupportOptions,
|
|
582
|
+
payload?: unknown,
|
|
583
|
+
): Promise<NativeCompactionEnvironmentResolution> {
|
|
584
|
+
if (options.enabled === false) {
|
|
585
|
+
return {
|
|
586
|
+
ok: false,
|
|
587
|
+
reason: "disabled",
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const originalDescriptor = getRuntimeModelDescriptor(currentModel);
|
|
592
|
+
if (!currentModel || !originalDescriptor.provider || !originalDescriptor.api || !originalDescriptor.model) {
|
|
593
|
+
return {
|
|
594
|
+
ok: false,
|
|
595
|
+
reason: "missing-model",
|
|
596
|
+
...originalDescriptor,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const effective = await resolveEffectiveModel(ctx, currentModel);
|
|
601
|
+
if (!effective.ok) {
|
|
602
|
+
return {
|
|
603
|
+
ok: false,
|
|
604
|
+
reason: effective.reason,
|
|
605
|
+
errorMessage: effective.errorMessage,
|
|
606
|
+
...originalDescriptor,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const effectiveModel = effective.model;
|
|
610
|
+
const descriptor = getRuntimeModelDescriptor(effectiveModel);
|
|
611
|
+
if (!descriptor.provider || !descriptor.api || !descriptor.model) {
|
|
612
|
+
return {
|
|
613
|
+
ok: false,
|
|
614
|
+
reason: "missing-model",
|
|
615
|
+
...descriptor,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// The compact endpoint is selected purely by API family: any provider speaking
|
|
620
|
+
// a validated Responses API gets a native compact attempt and fails open (to
|
|
621
|
+
// the fallback model or pi's default) when the endpoint is missing.
|
|
622
|
+
const configuredApis = normalizeConfiguredApis(options.responsesApis);
|
|
623
|
+
if (!configuredApis.has(descriptor.api) || !isSupportedApi(descriptor.api)) {
|
|
624
|
+
return {
|
|
625
|
+
ok: false,
|
|
626
|
+
reason: "unsupported-api",
|
|
627
|
+
...descriptor,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
let requestPayload: ResponsesCompatibleRequestPayload | undefined;
|
|
632
|
+
if (payload !== undefined) {
|
|
633
|
+
if (!isResponsesCompatiblePayload(payload)) {
|
|
634
|
+
return {
|
|
635
|
+
ok: false,
|
|
636
|
+
reason: "unsupported-payload",
|
|
637
|
+
...descriptor,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
requestPayload = payload;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
let auth: ResolvedRequestAuth;
|
|
644
|
+
try {
|
|
645
|
+
auth = await resolveRequestAuth(ctx, effectiveModel);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
return {
|
|
648
|
+
ok: false,
|
|
649
|
+
reason: "auth-resolution-failed",
|
|
650
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
651
|
+
...descriptor,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (!auth.ok) {
|
|
656
|
+
return {
|
|
657
|
+
ok: false,
|
|
658
|
+
reason: "auth-resolution-failed",
|
|
659
|
+
errorMessage: auth.error,
|
|
660
|
+
...descriptor,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const rawBaseUrl = normalizeBaseUrl(auth.baseUrl) ?? descriptor.baseUrl;
|
|
665
|
+
let baseUrl = rawBaseUrl;
|
|
666
|
+
if (descriptor.api === "azure-openai-responses") {
|
|
667
|
+
baseUrl = buildAzureBaseUrl(rawBaseUrl, auth.env);
|
|
668
|
+
}
|
|
669
|
+
if (!baseUrl) {
|
|
670
|
+
return {
|
|
671
|
+
ok: false,
|
|
672
|
+
reason: "missing-base-url",
|
|
673
|
+
...descriptor,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
if (descriptor.api === "azure-openai-responses") {
|
|
677
|
+
try {
|
|
678
|
+
baseUrl = normalizeAzureBaseUrl(baseUrl);
|
|
679
|
+
} catch (error) {
|
|
680
|
+
return {
|
|
681
|
+
ok: false,
|
|
682
|
+
reason: "invalid-base-url",
|
|
683
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
684
|
+
...descriptor,
|
|
685
|
+
baseUrl,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const requestModel = descriptor.api === "azure-openai-responses"
|
|
691
|
+
? resolveAzureDeploymentName(descriptor.model, auth.env)
|
|
692
|
+
: descriptor.model;
|
|
693
|
+
if (requestPayload && requestPayload.model !== descriptor.model && requestPayload.model !== requestModel) {
|
|
694
|
+
return {
|
|
695
|
+
ok: false,
|
|
696
|
+
reason: "payload-model-mismatch",
|
|
697
|
+
...descriptor,
|
|
698
|
+
baseUrl,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const codexAffinity = resolveCodexGatewayAffinity(effectiveModel, options.codexGatewayModels, currentModel);
|
|
703
|
+
const sessionId = resolveRuntimeSessionId(ctx);
|
|
704
|
+
if (codexAffinity && !sessionId) {
|
|
705
|
+
return {
|
|
706
|
+
ok: false,
|
|
707
|
+
reason: "missing-session-id",
|
|
708
|
+
...descriptor,
|
|
709
|
+
baseUrl,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (!auth.apiKey) {
|
|
714
|
+
return {
|
|
715
|
+
ok: false,
|
|
716
|
+
reason: "missing-api-key",
|
|
717
|
+
...descriptor,
|
|
718
|
+
baseUrl,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return {
|
|
723
|
+
ok: true,
|
|
724
|
+
runtime: {
|
|
725
|
+
provider: descriptor.provider,
|
|
726
|
+
api: descriptor.api,
|
|
727
|
+
model: descriptor.model,
|
|
728
|
+
requestModel,
|
|
729
|
+
baseUrl,
|
|
730
|
+
apiKey: auth.apiKey,
|
|
731
|
+
authMode: resolveAuthMode(ctx, effectiveModel),
|
|
732
|
+
env: auth.env,
|
|
733
|
+
headers: auth.headers,
|
|
734
|
+
responsesPath: buildResponsesPath(descriptor.api),
|
|
735
|
+
responsesUrl: buildResponsesUrl(baseUrl, descriptor.api, auth.env),
|
|
736
|
+
compactPath: buildCompactPath(descriptor.api),
|
|
737
|
+
compactUrl: buildCompactUrl(baseUrl, descriptor.api, auth.env),
|
|
738
|
+
sessionId,
|
|
739
|
+
codexAffinity,
|
|
740
|
+
payload: requestPayload,
|
|
741
|
+
currentModel: effectiveModel,
|
|
742
|
+
},
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export async function resolveResponsesEnvironment(
|
|
747
|
+
ctx: ExtensionContext,
|
|
748
|
+
options: ResponsesSupportOptions = {},
|
|
749
|
+
): Promise<ResponsesEnvironmentResolution> {
|
|
750
|
+
const resolution = await resolveNativeCompactionEnvironmentForModel(ctx, ctx.model, options);
|
|
751
|
+
if (!resolution.ok) return resolution;
|
|
752
|
+
|
|
753
|
+
const runtime = resolution.runtime;
|
|
754
|
+
return {
|
|
755
|
+
ok: true,
|
|
756
|
+
runtime: {
|
|
757
|
+
provider: runtime.provider,
|
|
758
|
+
api: runtime.api,
|
|
759
|
+
model: runtime.model,
|
|
760
|
+
requestModel: runtime.requestModel,
|
|
761
|
+
baseUrl: runtime.baseUrl,
|
|
762
|
+
apiKey: runtime.apiKey,
|
|
763
|
+
env: runtime.env,
|
|
764
|
+
headers: runtime.headers,
|
|
765
|
+
responsesPath: runtime.responsesPath,
|
|
766
|
+
responsesUrl: runtime.responsesUrl,
|
|
767
|
+
sessionId: runtime.sessionId,
|
|
768
|
+
codexAffinity: runtime.codexAffinity,
|
|
769
|
+
currentModel: runtime.currentModel,
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export async function resolveNativeCompactionEnvironment(
|
|
775
|
+
ctx: ExtensionContext,
|
|
776
|
+
options: NativeCompactionSupportOptions = {},
|
|
777
|
+
payload?: unknown,
|
|
778
|
+
): Promise<NativeCompactionEnvironmentResolution> {
|
|
779
|
+
return resolveNativeCompactionEnvironmentForModel(ctx, ctx.model, options, payload);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export async function resolveRemoteCompactionExecution(
|
|
783
|
+
ctx: ExtensionContext,
|
|
784
|
+
options: NativeCompactionSupportOptions = {},
|
|
785
|
+
remoteModelSpec?: string,
|
|
786
|
+
): Promise<RemoteCompactionExecutionResolution> {
|
|
787
|
+
const consumerResolution = await resolveNativeCompactionEnvironment(ctx, options);
|
|
788
|
+
if (!consumerResolution.ok) {
|
|
789
|
+
return consumerResolution;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const spec = remoteModelSpec?.trim();
|
|
793
|
+
if (!spec) {
|
|
794
|
+
return {
|
|
795
|
+
ok: true,
|
|
796
|
+
execution: {
|
|
797
|
+
consumer: consumerResolution.runtime,
|
|
798
|
+
compactor: consumerResolution.runtime,
|
|
799
|
+
},
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const parsed = parseModelSpec(spec);
|
|
804
|
+
if (!parsed) {
|
|
805
|
+
return {
|
|
806
|
+
ok: false,
|
|
807
|
+
reason: "invalid-model-spec",
|
|
808
|
+
modelSpec: spec,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const compactorModel = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
|
|
813
|
+
if (!compactorModel) {
|
|
814
|
+
return {
|
|
815
|
+
ok: false,
|
|
816
|
+
reason: "model-not-found",
|
|
817
|
+
provider: parsed.provider,
|
|
818
|
+
model: parsed.modelId,
|
|
819
|
+
modelSpec: spec,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const compactorResolution = await resolveNativeCompactionEnvironmentForModel(
|
|
824
|
+
ctx,
|
|
825
|
+
compactorModel,
|
|
826
|
+
options,
|
|
827
|
+
);
|
|
828
|
+
if (!compactorResolution.ok) {
|
|
829
|
+
return {
|
|
830
|
+
...compactorResolution,
|
|
831
|
+
modelSpec: spec,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const consumer = consumerResolution.runtime;
|
|
836
|
+
const compactor = compactorResolution.runtime;
|
|
837
|
+
if (compactor.baseUrl !== consumer.baseUrl) {
|
|
838
|
+
return {
|
|
839
|
+
ok: false,
|
|
840
|
+
reason: "base-url-mismatch",
|
|
841
|
+
provider: compactor.provider,
|
|
842
|
+
api: compactor.api,
|
|
843
|
+
model: compactor.model,
|
|
844
|
+
baseUrl: compactor.baseUrl,
|
|
845
|
+
modelSpec: spec,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return {
|
|
850
|
+
ok: true,
|
|
851
|
+
execution: {
|
|
852
|
+
consumer,
|
|
853
|
+
compactor,
|
|
854
|
+
},
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export async function getNativeCompactionRuntime(
|
|
859
|
+
ctx: ExtensionContext,
|
|
860
|
+
options: NativeCompactionSupportOptions = {},
|
|
861
|
+
payload?: unknown,
|
|
862
|
+
): Promise<NativeCompactionRuntime | undefined> {
|
|
863
|
+
const resolution = await resolveNativeCompactionEnvironment(ctx, options, payload);
|
|
864
|
+
return resolution.ok ? resolution.runtime : undefined;
|
|
865
|
+
}
|