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/config/legacy.ts
CHANGED
|
@@ -1,717 +1,519 @@
|
|
|
1
|
-
import * as os from "node:os";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_COMPACTION_CONFIG,
|
|
6
|
-
DEFAULT_TOOLKIT_CONFIG,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function
|
|
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
|
-
value
|
|
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
|
-
if (value
|
|
172
|
-
|
|
173
|
-
return
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
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
|
-
|
|
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
|
-
warnings
|
|
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
|
-
resolved
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
resolved.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
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
|
-
if (
|
|
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
|
-
warnings
|
|
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
|
-
warnUnknownFields(raw, AUTO_MODE_FIELDS, "autoMode", warnings);
|
|
521
|
-
resolved.enabled = toBoolean(raw.enabled, "autoMode.enabled", warnings) ?? resolved.enabled;
|
|
522
|
-
|
|
523
|
-
const models = toStringList(raw.models, "autoMode.models", warnings);
|
|
524
|
-
if (models !== undefined) {
|
|
525
|
-
resolved.models = models;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
const reviewerModel = toModelSpec(raw.reviewerModel, "autoMode.reviewerModel", warnings);
|
|
529
|
-
if (reviewerModel !== undefined) {
|
|
530
|
-
resolved.reviewerModel = reviewerModel === null ? undefined : reviewerModel;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
resolved.gate = toAutoModeGate(raw.gate, "autoMode.gate", warnings) ?? resolved.gate;
|
|
534
|
-
|
|
535
|
-
const extraTools = toStringList(raw.extraTools, "autoMode.extraTools", warnings);
|
|
536
|
-
if (extraTools !== undefined) {
|
|
537
|
-
resolved.extraTools = extraTools;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const timeoutMs = toReviewerTimeoutMs(raw.timeoutMs, "autoMode.timeoutMs", warnings);
|
|
541
|
-
if (timeoutMs !== undefined) {
|
|
542
|
-
resolved.timeoutMs = timeoutMs;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
resolved.transcript = toBoolean(raw.transcript, "autoMode.transcript", warnings) ?? resolved.transcript;
|
|
546
|
-
resolved.evidenceTools =
|
|
547
|
-
toBoolean(raw.evidenceTools, "autoMode.evidenceTools", warnings) ?? resolved.evidenceTools;
|
|
548
|
-
|
|
549
|
-
const maxEvidenceRounds = toBoundedInteger(
|
|
550
|
-
raw.maxEvidenceRounds,
|
|
551
|
-
"autoMode.maxEvidenceRounds",
|
|
552
|
-
warnings,
|
|
553
|
-
EVIDENCE_ROUNDS_MIN,
|
|
554
|
-
EVIDENCE_ROUNDS_MAX,
|
|
555
|
-
);
|
|
556
|
-
if (maxEvidenceRounds !== undefined) {
|
|
557
|
-
resolved.maxEvidenceRounds = maxEvidenceRounds;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
if (raw.classifier !== undefined) {
|
|
561
|
-
if (isRecord(raw.classifier)) {
|
|
562
|
-
applyAutoModeClassifierConfig(raw.classifier, resolved.classifier, warnings);
|
|
563
|
-
} else {
|
|
564
|
-
warnings.push("Ignoring autoMode.classifier: expected a JSON object.");
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
if (raw.circuitBreaker !== undefined) {
|
|
569
|
-
if (isRecord(raw.circuitBreaker)) {
|
|
570
|
-
applyAutoModeBreakerConfig(raw.circuitBreaker, resolved.circuitBreaker, warnings);
|
|
571
|
-
} else {
|
|
572
|
-
warnings.push("Ignoring autoMode.circuitBreaker: expected a JSON object.");
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function applyAutoModeClassifierConfig(
|
|
578
|
-
raw: Record<string, unknown>,
|
|
579
|
-
resolved: AutoModeClassifierConfig,
|
|
580
|
-
warnings: string[],
|
|
581
|
-
): void {
|
|
582
|
-
warnUnknownFields(raw, AUTO_MODE_CLASSIFIER_FIELDS, "autoMode.classifier", warnings);
|
|
583
|
-
resolved.enabled =
|
|
584
|
-
toBoolean(raw.enabled, "autoMode.classifier.enabled", warnings) ?? resolved.enabled;
|
|
585
|
-
|
|
586
|
-
const model = toModelSpec(raw.model, "autoMode.classifier.model", warnings);
|
|
587
|
-
if (model !== undefined) {
|
|
588
|
-
resolved.model = model === null ? undefined : model;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
const timeoutMs =
|
|
592
|
-
toBoundedInteger(
|
|
593
|
-
raw.timeoutMs,
|
|
594
|
-
"autoMode.classifier.timeoutMs",
|
|
595
|
-
warnings,
|
|
596
|
-
REVIEWER_TIMEOUT_MIN_MS,
|
|
597
|
-
REVIEWER_TIMEOUT_MAX_MS,
|
|
598
|
-
) ?? DEFAULT_CLASSIFIER_TIMEOUT_MS;
|
|
599
|
-
if (raw.timeoutMs !== undefined) {
|
|
600
|
-
resolved.timeoutMs = timeoutMs;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
const maxLag = toBoundedInteger(
|
|
604
|
-
raw.maxLag,
|
|
605
|
-
"autoMode.classifier.maxLag",
|
|
606
|
-
warnings,
|
|
607
|
-
CLASSIFIER_MAX_LAG_MIN,
|
|
608
|
-
CLASSIFIER_MAX_LAG_MAX,
|
|
609
|
-
);
|
|
610
|
-
if (maxLag !== undefined) {
|
|
611
|
-
resolved.maxLag = maxLag;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
function applyAutoModeBreakerConfig(
|
|
616
|
-
raw: Record<string, unknown>,
|
|
617
|
-
resolved: AutoModeCircuitBreakerConfig,
|
|
618
|
-
warnings: string[],
|
|
619
|
-
): void {
|
|
620
|
-
warnUnknownFields(raw, AUTO_MODE_BREAKER_FIELDS, "autoMode.circuitBreaker", warnings);
|
|
621
|
-
|
|
622
|
-
const consecutiveDenials = toBoundedInteger(
|
|
623
|
-
raw.consecutiveDenials,
|
|
624
|
-
"autoMode.circuitBreaker.consecutiveDenials",
|
|
625
|
-
warnings,
|
|
626
|
-
BREAKER_LIMIT_MIN,
|
|
627
|
-
BREAKER_LIMIT_MAX,
|
|
628
|
-
);
|
|
629
|
-
if (consecutiveDenials !== undefined) {
|
|
630
|
-
resolved.consecutiveDenials = consecutiveDenials;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
const recentDenials = toBoundedInteger(
|
|
634
|
-
raw.recentDenials,
|
|
635
|
-
"autoMode.circuitBreaker.recentDenials",
|
|
636
|
-
warnings,
|
|
637
|
-
BREAKER_LIMIT_MIN,
|
|
638
|
-
BREAKER_LIMIT_MAX,
|
|
639
|
-
);
|
|
640
|
-
if (recentDenials !== undefined) {
|
|
641
|
-
resolved.recentDenials = recentDenials;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
const windowSize = toBoundedInteger(
|
|
645
|
-
raw.windowSize,
|
|
646
|
-
"autoMode.circuitBreaker.windowSize",
|
|
647
|
-
warnings,
|
|
648
|
-
BREAKER_WINDOW_MIN,
|
|
649
|
-
BREAKER_WINDOW_MAX,
|
|
650
|
-
);
|
|
651
|
-
if (windowSize !== undefined) {
|
|
652
|
-
resolved.windowSize = windowSize;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* Normalize the supported unversioned document without I/O or mutation.
|
|
658
|
-
* Versioned decoding and operation resolution are owned by ../config.ts.
|
|
659
|
-
*/
|
|
660
|
-
export function normalizeLegacyConfig(
|
|
661
|
-
raw: Record<string, unknown> | undefined,
|
|
662
|
-
configPath: string = CONFIG_PATH,
|
|
663
|
-
warnings: string[] = [],
|
|
664
|
-
): LoadedToolkitConfig {
|
|
665
|
-
const resolved = cloneDefaults();
|
|
666
|
-
let source: string | undefined;
|
|
667
|
-
|
|
668
|
-
if (raw) {
|
|
669
|
-
source = configPath;
|
|
670
|
-
warnUnknownFields(raw, TOP_LEVEL_FIELDS, "", warnings);
|
|
671
|
-
|
|
672
|
-
if (raw.compaction !== undefined) {
|
|
673
|
-
if (isRecord(raw.compaction)) {
|
|
674
|
-
applyCompactionConfig(raw.compaction, resolved.compaction, warnings);
|
|
675
|
-
} else {
|
|
676
|
-
warnings.push("Ignoring compaction: expected a JSON object.");
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
if (raw.webSearch !== undefined) {
|
|
681
|
-
if (isRecord(raw.webSearch)) {
|
|
682
|
-
applyWebSearchConfig(raw.webSearch, resolved.webSearch, warnings);
|
|
683
|
-
} else {
|
|
684
|
-
warnings.push("Ignoring webSearch: expected a JSON object.");
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
if (raw.imageGeneration !== undefined) {
|
|
689
|
-
if (isRecord(raw.imageGeneration)) {
|
|
690
|
-
applyImageGenerationConfig(raw.imageGeneration, resolved.imageGeneration, warnings);
|
|
691
|
-
} else {
|
|
692
|
-
warnings.push("Ignoring imageGeneration: expected a JSON object.");
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
if (raw.autoMode !== undefined) {
|
|
697
|
-
if (isRecord(raw.autoMode)) {
|
|
698
|
-
applyAutoModeConfig(raw.autoMode, resolved.autoMode, warnings);
|
|
699
|
-
} else {
|
|
700
|
-
warnings.push("Ignoring autoMode: expected a JSON object.");
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
resolved.compaction.artifactRoot = resolveConfiguredPath(
|
|
706
|
-
resolved.compaction.artifactRoot,
|
|
707
|
-
path.dirname(configPath),
|
|
708
|
-
);
|
|
709
|
-
|
|
710
|
-
return {
|
|
711
|
-
config: resolved,
|
|
712
|
-
source,
|
|
713
|
-
warnings,
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
export { DEFAULT_TOOLKIT_CONFIG };
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_COMPACTION_CONFIG,
|
|
6
|
+
DEFAULT_TOOLKIT_CONFIG,
|
|
7
|
+
DEFAULT_IMAGE_GENERATION_CONFIG,
|
|
8
|
+
DEFAULT_IMAGE_GENERATION_MODEL,
|
|
9
|
+
DEFAULT_NATIVE_FALLBACK_CONFIG,
|
|
10
|
+
DEFAULT_WEB_SEARCH_CONFIG,
|
|
11
|
+
RESPONSES_COMPACT_CAPABLE_APIS,
|
|
12
|
+
THINKING_LEVELS,
|
|
13
|
+
TOOLKIT_ID,
|
|
14
|
+
type CompactionConfig,
|
|
15
|
+
type ContextManagementMode,
|
|
16
|
+
type LeaveManagedModePolicy,
|
|
17
|
+
type ImageGenerationConfig,
|
|
18
|
+
type RemoteV2ContextSource,
|
|
19
|
+
type LoadedToolkitConfig,
|
|
20
|
+
type NativeFallbackConfig,
|
|
21
|
+
type ToolkitConfig,
|
|
22
|
+
type WebSearchConfig,
|
|
23
|
+
type WebSearchRoute,
|
|
24
|
+
} from "../types";
|
|
25
|
+
import { RETIRED_AUTO_MODE_MESSAGE } from "./policy";
|
|
26
|
+
import { MAX_IMAGE_MODEL_ID_CHARS } from "../image-generation/types";
|
|
27
|
+
|
|
28
|
+
export const CONFIG_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", TOOLKIT_ID);
|
|
29
|
+
export const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
30
|
+
|
|
31
|
+
const TOP_LEVEL_FIELDS = new Set(["compaction", "webSearch", "imageGeneration", "autoMode"]);
|
|
32
|
+
const COMPACTION_FIELDS = new Set([
|
|
33
|
+
"enabled",
|
|
34
|
+
"leaveManagedMode",
|
|
35
|
+
"contextManagement",
|
|
36
|
+
"allowCompactionContinuityBreak",
|
|
37
|
+
"remoteCompactModel",
|
|
38
|
+
"remoteV2ContextSource",
|
|
39
|
+
"nativeFallback",
|
|
40
|
+
"responsesApis",
|
|
41
|
+
"gatewayContextModels",
|
|
42
|
+
"contextReminderThresholdPercent",
|
|
43
|
+
"notifyOnLoad",
|
|
44
|
+
"debug",
|
|
45
|
+
"logProviderPayloads",
|
|
46
|
+
"logCompactResponses",
|
|
47
|
+
"redactSensitiveData",
|
|
48
|
+
"artifactRoot",
|
|
49
|
+
]);
|
|
50
|
+
const NATIVE_FALLBACK_FIELDS = new Set(["enabled", "model", "thinkingLevel"]);
|
|
51
|
+
const WEB_SEARCH_FIELDS = new Set(["enabled", "models", "defaultRoute", "routes"]);
|
|
52
|
+
const IMAGE_GENERATION_FIELDS = new Set(["enabled", "models"]);
|
|
53
|
+
|
|
54
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
55
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveConfiguredPath(rawPath: string, baseDir: string): string {
|
|
59
|
+
if (rawPath.startsWith("~/")) {
|
|
60
|
+
return path.join(os.homedir(), rawPath.slice(2));
|
|
61
|
+
}
|
|
62
|
+
if (path.isAbsolute(rawPath)) {
|
|
63
|
+
return path.resolve(rawPath);
|
|
64
|
+
}
|
|
65
|
+
return path.resolve(baseDir, rawPath);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function warnUnknownFields(
|
|
69
|
+
value: Record<string, unknown>,
|
|
70
|
+
knownFields: ReadonlySet<string>,
|
|
71
|
+
fieldPath: string,
|
|
72
|
+
warnings: string[],
|
|
73
|
+
): void {
|
|
74
|
+
for (const key of Object.keys(value)) {
|
|
75
|
+
if (!knownFields.has(key)) {
|
|
76
|
+
warnings.push(`Ignoring ${fieldPath ? `${fieldPath}.` : ""}${key}: unknown field.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function toBoolean(value: unknown, fieldPath: string, warnings: string[]): boolean | undefined {
|
|
82
|
+
if (value === undefined) return undefined;
|
|
83
|
+
if (typeof value === "boolean") return value;
|
|
84
|
+
warnings.push(`Ignoring ${fieldPath}: expected a boolean.`);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toLeaveManagedMode(
|
|
89
|
+
value: unknown,
|
|
90
|
+
fieldPath: string,
|
|
91
|
+
warnings: string[],
|
|
92
|
+
): LeaveManagedModePolicy | undefined {
|
|
93
|
+
if (value === undefined) return undefined;
|
|
94
|
+
if (typeof value === "string") {
|
|
95
|
+
const normalized = value.trim();
|
|
96
|
+
if (normalized === "warn" || normalized === "compact") return normalized;
|
|
97
|
+
}
|
|
98
|
+
warnings.push(`Ignoring ${fieldPath}: expected one of warn, compact.`);
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function toContextManagementMode(
|
|
103
|
+
value: unknown,
|
|
104
|
+
fieldPath: string,
|
|
105
|
+
warnings: string[],
|
|
106
|
+
): ContextManagementMode | undefined {
|
|
107
|
+
if (value === undefined) return undefined;
|
|
108
|
+
if (typeof value === "string") {
|
|
109
|
+
const normalized = value.trim();
|
|
110
|
+
if (normalized === "off" || normalized === "remote") return normalized;
|
|
111
|
+
}
|
|
112
|
+
warnings.push(`Ignoring ${fieldPath}: expected one of off, remote.`);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function toRemoteV2ContextSource(
|
|
117
|
+
value: unknown,
|
|
118
|
+
fieldPath: string,
|
|
119
|
+
warnings: string[],
|
|
120
|
+
): RemoteV2ContextSource | undefined {
|
|
121
|
+
if (value === undefined) return undefined;
|
|
122
|
+
if (typeof value === "string") {
|
|
123
|
+
const normalized = value.trim();
|
|
124
|
+
if (normalized === "pi-context-hook" || normalized === "legacy") return normalized;
|
|
125
|
+
}
|
|
126
|
+
warnings.push(`Ignoring ${fieldPath}: expected one of pi-context-hook, legacy.`);
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function toModelSpec(value: unknown, fieldPath: string, warnings: string[]): string | null | undefined {
|
|
131
|
+
if (value === undefined) return undefined;
|
|
132
|
+
if (value === null) return null;
|
|
133
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
134
|
+
return value.trim();
|
|
135
|
+
}
|
|
136
|
+
warnings.push(`Ignoring ${fieldPath}: expected "provider/model-id" or null.`);
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function toThinkingLevel(value: unknown, fieldPath: string, warnings: string[]): ThinkingLevel | undefined {
|
|
141
|
+
if (value === undefined) return undefined;
|
|
142
|
+
if (typeof value === "string" && (THINKING_LEVELS as readonly string[]).includes(value)) {
|
|
143
|
+
return value as ThinkingLevel;
|
|
144
|
+
}
|
|
145
|
+
warnings.push(`Ignoring ${fieldPath}: expected one of ${THINKING_LEVELS.join(", ")}.`);
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function toBoundedInteger(
|
|
150
|
+
value: unknown,
|
|
151
|
+
fieldPath: string,
|
|
152
|
+
warnings: string[],
|
|
153
|
+
min: number,
|
|
154
|
+
max: number,
|
|
155
|
+
): number | undefined {
|
|
156
|
+
if (value === undefined) return undefined;
|
|
157
|
+
if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value >= min && value <= max) {
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
warnings.push(`Ignoring ${fieldPath}: expected an integer between ${min} and ${max}.`);
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function toSupportedApis(
|
|
165
|
+
value: unknown,
|
|
166
|
+
fieldPath: string,
|
|
167
|
+
capableApis: readonly string[],
|
|
168
|
+
warnings: string[],
|
|
169
|
+
): string[] | undefined {
|
|
170
|
+
if (value === undefined) return undefined;
|
|
171
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
172
|
+
warnings.push(`Ignoring ${fieldPath}: expected a string array.`);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const capable = new Set(capableApis);
|
|
177
|
+
const accepted: string[] = [];
|
|
178
|
+
for (const item of new Set(value.map((entry) => entry.trim()).filter(Boolean))) {
|
|
179
|
+
if (capable.has(item)) {
|
|
180
|
+
accepted.push(item);
|
|
181
|
+
} else {
|
|
182
|
+
warnings.push(
|
|
183
|
+
`Ignoring ${fieldPath} entry "${item}": only ${capableApis.join(", ")} are supported.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return accepted;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function toStringList(value: unknown, fieldPath: string, warnings: string[]): string[] | undefined {
|
|
192
|
+
if (value === undefined) return undefined;
|
|
193
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
194
|
+
warnings.push(`Ignoring ${fieldPath}: expected a string array.`);
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return [...new Set(value.map((entry) => entry.trim()).filter(Boolean))];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function toImageGenerationModels(value: unknown, fieldPath: string, warnings: string[]): string[] | undefined {
|
|
202
|
+
const models = toStringList(value, fieldPath, warnings);
|
|
203
|
+
if (models === undefined) return undefined;
|
|
204
|
+
if (models.some((model) => model.length > MAX_IMAGE_MODEL_ID_CHARS)) {
|
|
205
|
+
warnings.push(
|
|
206
|
+
`Ignoring ${fieldPath}: each model id must be at most ${MAX_IMAGE_MODEL_ID_CHARS} characters.`,
|
|
207
|
+
);
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
return models;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function toWebSearchRoute(
|
|
214
|
+
value: unknown,
|
|
215
|
+
fieldPath: string,
|
|
216
|
+
warnings: string[],
|
|
217
|
+
): WebSearchRoute | undefined {
|
|
218
|
+
if (value === undefined) return undefined;
|
|
219
|
+
if (value === "local" || value === "hosted" || value === "standalone-alpha") {
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
warnings.push(`Ignoring ${fieldPath}: expected one of local, hosted, standalone-alpha.`);
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isExactWebSearchModelKey(value: string): boolean {
|
|
227
|
+
const trimmed = value.trim();
|
|
228
|
+
const separatorIndex = trimmed.indexOf("/");
|
|
229
|
+
if (separatorIndex <= 0 || separatorIndex >= trimmed.length - 1) return false;
|
|
230
|
+
if (/[\s*?\[\]{}]/.test(trimmed)) return false;
|
|
231
|
+
const provider = trimmed.slice(0, separatorIndex);
|
|
232
|
+
const modelId = trimmed.slice(separatorIndex + 1);
|
|
233
|
+
return provider.length > 0 && modelId.length > 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function toWebSearchRoutes(
|
|
237
|
+
value: unknown,
|
|
238
|
+
fieldPath: string,
|
|
239
|
+
warnings: string[],
|
|
240
|
+
): Record<string, WebSearchRoute> | undefined {
|
|
241
|
+
if (value === undefined) return undefined;
|
|
242
|
+
if (!isRecord(value)) {
|
|
243
|
+
warnings.push(`Ignoring ${fieldPath}: expected a JSON object.`);
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const routes: Record<string, WebSearchRoute> = {};
|
|
248
|
+
for (const [rawKey, rawRoute] of Object.entries(value)) {
|
|
249
|
+
const key = rawKey.trim();
|
|
250
|
+
if (!isExactWebSearchModelKey(key)) {
|
|
251
|
+
warnings.push(`Ignoring ${fieldPath}.${rawKey}: expected an exact "provider/model-id" key.`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const route = toWebSearchRoute(rawRoute, `${fieldPath}.${rawKey}`, warnings);
|
|
255
|
+
if (route !== undefined) routes[key] = route;
|
|
256
|
+
}
|
|
257
|
+
return routes;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function cloneDefaults(): ToolkitConfig {
|
|
261
|
+
return {
|
|
262
|
+
compaction: {
|
|
263
|
+
...DEFAULT_COMPACTION_CONFIG,
|
|
264
|
+
nativeFallback: { ...DEFAULT_NATIVE_FALLBACK_CONFIG },
|
|
265
|
+
responsesApis: [...DEFAULT_COMPACTION_CONFIG.responsesApis],
|
|
266
|
+
gatewayContextModels: [...DEFAULT_COMPACTION_CONFIG.gatewayContextModels],
|
|
267
|
+
},
|
|
268
|
+
webSearch: {
|
|
269
|
+
...DEFAULT_WEB_SEARCH_CONFIG,
|
|
270
|
+
models: [...DEFAULT_WEB_SEARCH_CONFIG.models],
|
|
271
|
+
...(DEFAULT_WEB_SEARCH_CONFIG.defaultRoute
|
|
272
|
+
? { defaultRoute: DEFAULT_WEB_SEARCH_CONFIG.defaultRoute }
|
|
273
|
+
: {}),
|
|
274
|
+
...(DEFAULT_WEB_SEARCH_CONFIG.routes
|
|
275
|
+
? { routes: { ...DEFAULT_WEB_SEARCH_CONFIG.routes } }
|
|
276
|
+
: {}),
|
|
277
|
+
},
|
|
278
|
+
imageGeneration: {
|
|
279
|
+
...DEFAULT_IMAGE_GENERATION_CONFIG,
|
|
280
|
+
models: [...DEFAULT_IMAGE_GENERATION_CONFIG.models],
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function applyNativeFallbackConfig(
|
|
286
|
+
raw: Record<string, unknown>,
|
|
287
|
+
resolved: NativeFallbackConfig,
|
|
288
|
+
warnings: string[],
|
|
289
|
+
): void {
|
|
290
|
+
warnUnknownFields(raw, NATIVE_FALLBACK_FIELDS, "compaction.nativeFallback", warnings);
|
|
291
|
+
resolved.enabled =
|
|
292
|
+
toBoolean(raw.enabled, "compaction.nativeFallback.enabled", warnings) ?? resolved.enabled;
|
|
293
|
+
|
|
294
|
+
const modelSpec = toModelSpec(raw.model, "compaction.nativeFallback.model", warnings);
|
|
295
|
+
if (modelSpec !== undefined) {
|
|
296
|
+
resolved.model = modelSpec === null ? undefined : modelSpec;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
resolved.thinkingLevel =
|
|
300
|
+
toThinkingLevel(raw.thinkingLevel, "compaction.nativeFallback.thinkingLevel", warnings) ??
|
|
301
|
+
resolved.thinkingLevel;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function applyCompactionConfig(
|
|
305
|
+
raw: Record<string, unknown>,
|
|
306
|
+
resolved: CompactionConfig,
|
|
307
|
+
warnings: string[],
|
|
308
|
+
): void {
|
|
309
|
+
warnUnknownFields(raw, COMPACTION_FIELDS, "compaction", warnings);
|
|
310
|
+
|
|
311
|
+
resolved.enabled = toBoolean(raw.enabled, "compaction.enabled", warnings) ?? resolved.enabled;
|
|
312
|
+
resolved.contextManagement =
|
|
313
|
+
toContextManagementMode(raw.contextManagement, "compaction.contextManagement", warnings) ??
|
|
314
|
+
resolved.contextManagement;
|
|
315
|
+
resolved.leaveManagedMode =
|
|
316
|
+
toLeaveManagedMode(raw.leaveManagedMode, "compaction.leaveManagedMode", warnings) ??
|
|
317
|
+
resolved.leaveManagedMode;
|
|
318
|
+
resolved.allowCompactionContinuityBreak =
|
|
319
|
+
toBoolean(
|
|
320
|
+
raw.allowCompactionContinuityBreak,
|
|
321
|
+
"compaction.allowCompactionContinuityBreak",
|
|
322
|
+
warnings,
|
|
323
|
+
) ?? resolved.allowCompactionContinuityBreak;
|
|
324
|
+
resolved.notifyOnLoad =
|
|
325
|
+
toBoolean(raw.notifyOnLoad, "compaction.notifyOnLoad", warnings) ?? resolved.notifyOnLoad;
|
|
326
|
+
const reminderPercent = toBoundedInteger(
|
|
327
|
+
raw.contextReminderThresholdPercent,
|
|
328
|
+
"compaction.contextReminderThresholdPercent",
|
|
329
|
+
warnings,
|
|
330
|
+
0,
|
|
331
|
+
100,
|
|
332
|
+
);
|
|
333
|
+
if (reminderPercent !== undefined) {
|
|
334
|
+
resolved.contextReminderThresholdPercent = reminderPercent;
|
|
335
|
+
}
|
|
336
|
+
resolved.debug = toBoolean(raw.debug, "compaction.debug", warnings) ?? resolved.debug;
|
|
337
|
+
resolved.logProviderPayloads =
|
|
338
|
+
toBoolean(raw.logProviderPayloads, "compaction.logProviderPayloads", warnings) ??
|
|
339
|
+
resolved.logProviderPayloads;
|
|
340
|
+
resolved.logCompactResponses =
|
|
341
|
+
toBoolean(raw.logCompactResponses, "compaction.logCompactResponses", warnings) ??
|
|
342
|
+
resolved.logCompactResponses;
|
|
343
|
+
resolved.redactSensitiveData =
|
|
344
|
+
toBoolean(raw.redactSensitiveData, "compaction.redactSensitiveData", warnings) ??
|
|
345
|
+
resolved.redactSensitiveData;
|
|
346
|
+
|
|
347
|
+
const remoteCompactModelSpec = toModelSpec(
|
|
348
|
+
raw.remoteCompactModel,
|
|
349
|
+
"compaction.remoteCompactModel",
|
|
350
|
+
warnings,
|
|
351
|
+
);
|
|
352
|
+
if (remoteCompactModelSpec !== undefined) {
|
|
353
|
+
resolved.remoteCompactModel = remoteCompactModelSpec === null ? undefined : remoteCompactModelSpec;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
resolved.remoteV2ContextSource =
|
|
357
|
+
toRemoteV2ContextSource(raw.remoteV2ContextSource, "compaction.remoteV2ContextSource", warnings) ??
|
|
358
|
+
resolved.remoteV2ContextSource;
|
|
359
|
+
|
|
360
|
+
if (raw.nativeFallback !== undefined) {
|
|
361
|
+
if (isRecord(raw.nativeFallback)) {
|
|
362
|
+
applyNativeFallbackConfig(raw.nativeFallback, resolved.nativeFallback, warnings);
|
|
363
|
+
} else {
|
|
364
|
+
warnings.push("Ignoring compaction.nativeFallback: expected a JSON object.");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const apis = toSupportedApis(
|
|
369
|
+
raw.responsesApis,
|
|
370
|
+
"compaction.responsesApis",
|
|
371
|
+
RESPONSES_COMPACT_CAPABLE_APIS,
|
|
372
|
+
warnings,
|
|
373
|
+
);
|
|
374
|
+
if (apis !== undefined) {
|
|
375
|
+
resolved.responsesApis = apis;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const gatewayContextModels = toStringList(
|
|
379
|
+
raw.gatewayContextModels,
|
|
380
|
+
"compaction.gatewayContextModels",
|
|
381
|
+
warnings,
|
|
382
|
+
);
|
|
383
|
+
if (gatewayContextModels !== undefined) {
|
|
384
|
+
resolved.gatewayContextModels = gatewayContextModels;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (typeof raw.artifactRoot === "string" && raw.artifactRoot.trim().length > 0) {
|
|
388
|
+
resolved.artifactRoot = raw.artifactRoot.trim();
|
|
389
|
+
} else if (raw.artifactRoot !== undefined) {
|
|
390
|
+
warnings.push("Ignoring compaction.artifactRoot: expected a non-empty string.");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function applyWebSearchConfig(
|
|
395
|
+
raw: Record<string, unknown>,
|
|
396
|
+
resolved: WebSearchConfig,
|
|
397
|
+
warnings: string[],
|
|
398
|
+
): void {
|
|
399
|
+
warnUnknownFields(raw, WEB_SEARCH_FIELDS, "webSearch", warnings);
|
|
400
|
+
resolved.enabled = toBoolean(raw.enabled, "webSearch.enabled", warnings) ?? resolved.enabled;
|
|
401
|
+
|
|
402
|
+
const models = toStringList(raw.models, "webSearch.models", warnings);
|
|
403
|
+
if (models !== undefined) {
|
|
404
|
+
resolved.models = models;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const defaultRoute = toWebSearchRoute(raw.defaultRoute, "webSearch.defaultRoute", warnings);
|
|
408
|
+
if (defaultRoute !== undefined) {
|
|
409
|
+
resolved.defaultRoute = defaultRoute;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const routes = toWebSearchRoutes(raw.routes, "webSearch.routes", warnings);
|
|
413
|
+
if (routes !== undefined) {
|
|
414
|
+
resolved.routes = routes;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (defaultRoute !== undefined && resolved.models.length > 0) {
|
|
418
|
+
warnings.push(
|
|
419
|
+
"webSearch.defaultRoute overrides legacy webSearch.models for models without an exact webSearch.routes entry.",
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (routes !== undefined) {
|
|
423
|
+
for (const key of Object.keys(routes)) {
|
|
424
|
+
if (resolved.models.includes(key)) {
|
|
425
|
+
warnings.push(
|
|
426
|
+
`webSearch.routes.${key} overrides the legacy webSearch.models entry for the same exact model.`,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function applyImageGenerationConfig(
|
|
434
|
+
raw: Record<string, unknown>,
|
|
435
|
+
resolved: ImageGenerationConfig,
|
|
436
|
+
warnings: string[],
|
|
437
|
+
): void {
|
|
438
|
+
warnUnknownFields(raw, IMAGE_GENERATION_FIELDS, "imageGeneration", warnings);
|
|
439
|
+
resolved.enabled =
|
|
440
|
+
toBoolean(raw.enabled, "imageGeneration.enabled", warnings) ?? resolved.enabled;
|
|
441
|
+
|
|
442
|
+
const models = toImageGenerationModels(raw.models, "imageGeneration.models", warnings);
|
|
443
|
+
if (models !== undefined) {
|
|
444
|
+
// An empty or blank-only list cannot express a default, so keep the shipped model.
|
|
445
|
+
if (models.length === 0) {
|
|
446
|
+
warnings.push(
|
|
447
|
+
`Ignoring imageGeneration.models: expected at least one model id; using ${DEFAULT_IMAGE_GENERATION_MODEL}.`,
|
|
448
|
+
);
|
|
449
|
+
} else {
|
|
450
|
+
resolved.models = models;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* A retired `autoMode` section is ignored in any shape: an object, `null`, a scalar or a malformed
|
|
457
|
+
* nested value. Nothing inside it is parsed, so its old validation can never invalidate another
|
|
458
|
+
* feature, and its contents are never echoed back to the user.
|
|
459
|
+
*/
|
|
460
|
+
function warnRetiredAutoMode(warnings: string[]): void {
|
|
461
|
+
warnings.push(`Ignoring autoMode: ${RETIRED_AUTO_MODE_MESSAGE}`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Normalize the supported unversioned document without I/O or mutation.
|
|
466
|
+
* Versioned decoding and operation resolution are owned by ../config.ts.
|
|
467
|
+
*/
|
|
468
|
+
export function normalizeLegacyConfig(
|
|
469
|
+
raw: Record<string, unknown> | undefined,
|
|
470
|
+
configPath: string = CONFIG_PATH,
|
|
471
|
+
warnings: string[] = [],
|
|
472
|
+
): LoadedToolkitConfig {
|
|
473
|
+
const resolved = cloneDefaults();
|
|
474
|
+
let source: string | undefined;
|
|
475
|
+
|
|
476
|
+
if (raw) {
|
|
477
|
+
source = configPath;
|
|
478
|
+
warnUnknownFields(raw, TOP_LEVEL_FIELDS, "", warnings);
|
|
479
|
+
|
|
480
|
+
if (raw.compaction !== undefined) {
|
|
481
|
+
if (isRecord(raw.compaction)) {
|
|
482
|
+
applyCompactionConfig(raw.compaction, resolved.compaction, warnings);
|
|
483
|
+
} else {
|
|
484
|
+
warnings.push("Ignoring compaction: expected a JSON object.");
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (raw.webSearch !== undefined) {
|
|
489
|
+
if (isRecord(raw.webSearch)) {
|
|
490
|
+
applyWebSearchConfig(raw.webSearch, resolved.webSearch, warnings);
|
|
491
|
+
} else {
|
|
492
|
+
warnings.push("Ignoring webSearch: expected a JSON object.");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (raw.imageGeneration !== undefined) {
|
|
497
|
+
if (isRecord(raw.imageGeneration)) {
|
|
498
|
+
applyImageGenerationConfig(raw.imageGeneration, resolved.imageGeneration, warnings);
|
|
499
|
+
} else {
|
|
500
|
+
warnings.push("Ignoring imageGeneration: expected a JSON object.");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (Object.hasOwn(raw, "autoMode")) warnRetiredAutoMode(warnings);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
resolved.compaction.artifactRoot = resolveConfiguredPath(
|
|
508
|
+
resolved.compaction.artifactRoot,
|
|
509
|
+
path.dirname(configPath),
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
config: resolved,
|
|
514
|
+
source,
|
|
515
|
+
warnings,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export { DEFAULT_TOOLKIT_CONFIG };
|