pi-openai-toolkit 0.14.13 → 0.15.1
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/README.md +250 -241
- package/README.zh.md +250 -241
- package/config.schema.json +352 -0
- package/docs/configuration.md +176 -0
- package/docs/internals.md +139 -128
- package/package.json +135 -127
- package/src/auto-mode/extension.ts +35 -13
- package/src/config/legacy.ts +717 -0
- package/src/config/migration.ts +100 -0
- package/src/config/notifications.ts +28 -0
- package/src/config/policy.ts +122 -0
- package/src/config/v2.ts +1347 -0
- package/src/config-command.ts +73 -0
- package/src/config.ts +318 -739
- package/src/context-management/tools.ts +353 -347
- package/src/context-management/window-manager.ts +696 -693
- package/src/debug.ts +5 -5
- package/src/deferred-tool-carryover.ts +3 -1
- package/src/extension-runtime.ts +1375 -1329
- package/src/image-generation/extension.ts +14 -18
- package/src/image-generation/protocol.ts +10 -4
- package/src/image-generation/service.ts +9 -3
- package/src/request-context-cache.ts +2 -1
- package/src/types.ts +641 -628
- package/src/web-search/alpha-client.ts +876 -875
- package/src/web-search/extension.ts +529 -523
- package/src/web-search/payload.ts +299 -295
- package/src/web-search/prompt.ts +60 -59
- package/src/web-search/types.ts +144 -144
package/src/config.ts
CHANGED
|
@@ -1,739 +1,318 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import type
|
|
5
|
-
import {
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
if (
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function toReviewerTimeoutMs(value: unknown, fieldPath: string, warnings: string[]): number | undefined {
|
|
322
|
-
if (value === undefined) return undefined;
|
|
323
|
-
if (
|
|
324
|
-
typeof value === "number" &&
|
|
325
|
-
Number.isInteger(value) &&
|
|
326
|
-
value >= REVIEWER_TIMEOUT_MIN_MS &&
|
|
327
|
-
value <= REVIEWER_TIMEOUT_MAX_MS
|
|
328
|
-
) {
|
|
329
|
-
return value;
|
|
330
|
-
}
|
|
331
|
-
warnings.push(
|
|
332
|
-
`Ignoring ${fieldPath}: expected an integer between ${REVIEWER_TIMEOUT_MIN_MS} and ${REVIEWER_TIMEOUT_MAX_MS} ms.`,
|
|
333
|
-
);
|
|
334
|
-
return undefined;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function cloneDefaults(): ToolkitConfig {
|
|
338
|
-
return {
|
|
339
|
-
compaction: {
|
|
340
|
-
...DEFAULT_COMPACTION_CONFIG,
|
|
341
|
-
nativeFallback: { ...DEFAULT_NATIVE_FALLBACK_CONFIG },
|
|
342
|
-
responsesApis: [...DEFAULT_COMPACTION_CONFIG.responsesApis],
|
|
343
|
-
},
|
|
344
|
-
webSearch: {
|
|
345
|
-
...DEFAULT_WEB_SEARCH_CONFIG,
|
|
346
|
-
models: [...DEFAULT_WEB_SEARCH_CONFIG.models],
|
|
347
|
-
...(DEFAULT_WEB_SEARCH_CONFIG.defaultRoute
|
|
348
|
-
? { defaultRoute: DEFAULT_WEB_SEARCH_CONFIG.defaultRoute }
|
|
349
|
-
: {}),
|
|
350
|
-
...(DEFAULT_WEB_SEARCH_CONFIG.routes
|
|
351
|
-
? { routes: { ...DEFAULT_WEB_SEARCH_CONFIG.routes } }
|
|
352
|
-
: {}),
|
|
353
|
-
},
|
|
354
|
-
imageGeneration: {
|
|
355
|
-
...DEFAULT_IMAGE_GENERATION_CONFIG,
|
|
356
|
-
models: [...DEFAULT_IMAGE_GENERATION_CONFIG.models],
|
|
357
|
-
},
|
|
358
|
-
autoMode: {
|
|
359
|
-
...DEFAULT_AUTO_MODE_CONFIG,
|
|
360
|
-
models: [...DEFAULT_AUTO_MODE_CONFIG.models],
|
|
361
|
-
extraTools: [...DEFAULT_AUTO_MODE_CONFIG.extraTools],
|
|
362
|
-
classifier: { ...DEFAULT_AUTO_MODE_CONFIG.classifier },
|
|
363
|
-
circuitBreaker: { ...DEFAULT_AUTO_MODE_CONFIG.circuitBreaker },
|
|
364
|
-
},
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function applyNativeFallbackConfig(
|
|
369
|
-
raw: Record<string, unknown>,
|
|
370
|
-
resolved: NativeFallbackConfig,
|
|
371
|
-
warnings: string[],
|
|
372
|
-
): void {
|
|
373
|
-
warnUnknownFields(raw, NATIVE_FALLBACK_FIELDS, "compaction.nativeFallback", warnings);
|
|
374
|
-
resolved.enabled =
|
|
375
|
-
toBoolean(raw.enabled, "compaction.nativeFallback.enabled", warnings) ?? resolved.enabled;
|
|
376
|
-
|
|
377
|
-
const modelSpec = toModelSpec(raw.model, "compaction.nativeFallback.model", warnings);
|
|
378
|
-
if (modelSpec !== undefined) {
|
|
379
|
-
resolved.model = modelSpec === null ? undefined : modelSpec;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
resolved.thinkingLevel =
|
|
383
|
-
toThinkingLevel(raw.thinkingLevel, "compaction.nativeFallback.thinkingLevel", warnings) ??
|
|
384
|
-
resolved.thinkingLevel;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function applyCompactionConfig(
|
|
388
|
-
raw: Record<string, unknown>,
|
|
389
|
-
resolved: CompactionConfig,
|
|
390
|
-
warnings: string[],
|
|
391
|
-
): void {
|
|
392
|
-
warnUnknownFields(raw, COMPACTION_FIELDS, "compaction", warnings);
|
|
393
|
-
|
|
394
|
-
resolved.enabled = toBoolean(raw.enabled, "compaction.enabled", warnings) ?? resolved.enabled;
|
|
395
|
-
resolved.contextManagement =
|
|
396
|
-
toContextManagementMode(raw.contextManagement, "compaction.contextManagement", warnings) ??
|
|
397
|
-
resolved.contextManagement;
|
|
398
|
-
resolved.leaveManagedMode =
|
|
399
|
-
toLeaveManagedMode(raw.leaveManagedMode, "compaction.leaveManagedMode", warnings) ??
|
|
400
|
-
resolved.leaveManagedMode;
|
|
401
|
-
resolved.allowCompactionContinuityBreak =
|
|
402
|
-
toBoolean(
|
|
403
|
-
raw.allowCompactionContinuityBreak,
|
|
404
|
-
"compaction.allowCompactionContinuityBreak",
|
|
405
|
-
warnings,
|
|
406
|
-
) ?? resolved.allowCompactionContinuityBreak;
|
|
407
|
-
resolved.notifyOnLoad =
|
|
408
|
-
toBoolean(raw.notifyOnLoad, "compaction.notifyOnLoad", warnings) ?? resolved.notifyOnLoad;
|
|
409
|
-
const reminderPercent = toBoundedInteger(
|
|
410
|
-
raw.contextReminderThresholdPercent,
|
|
411
|
-
"compaction.contextReminderThresholdPercent",
|
|
412
|
-
warnings,
|
|
413
|
-
0,
|
|
414
|
-
100,
|
|
415
|
-
);
|
|
416
|
-
if (reminderPercent !== undefined) {
|
|
417
|
-
resolved.contextReminderThresholdPercent = reminderPercent;
|
|
418
|
-
}
|
|
419
|
-
resolved.debug = toBoolean(raw.debug, "compaction.debug", warnings) ?? resolved.debug;
|
|
420
|
-
resolved.logProviderPayloads =
|
|
421
|
-
toBoolean(raw.logProviderPayloads, "compaction.logProviderPayloads", warnings) ??
|
|
422
|
-
resolved.logProviderPayloads;
|
|
423
|
-
resolved.logCompactResponses =
|
|
424
|
-
toBoolean(raw.logCompactResponses, "compaction.logCompactResponses", warnings) ??
|
|
425
|
-
resolved.logCompactResponses;
|
|
426
|
-
resolved.redactSensitiveData =
|
|
427
|
-
toBoolean(raw.redactSensitiveData, "compaction.redactSensitiveData", warnings) ??
|
|
428
|
-
resolved.redactSensitiveData;
|
|
429
|
-
|
|
430
|
-
const remoteCompactModelSpec = toModelSpec(
|
|
431
|
-
raw.remoteCompactModel,
|
|
432
|
-
"compaction.remoteCompactModel",
|
|
433
|
-
warnings,
|
|
434
|
-
);
|
|
435
|
-
if (remoteCompactModelSpec !== undefined) {
|
|
436
|
-
resolved.remoteCompactModel = remoteCompactModelSpec === null ? undefined : remoteCompactModelSpec;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
resolved.remoteV2ContextSource =
|
|
440
|
-
toRemoteV2ContextSource(raw.remoteV2ContextSource, "compaction.remoteV2ContextSource", warnings) ??
|
|
441
|
-
resolved.remoteV2ContextSource;
|
|
442
|
-
|
|
443
|
-
if (raw.nativeFallback !== undefined) {
|
|
444
|
-
if (isRecord(raw.nativeFallback)) {
|
|
445
|
-
applyNativeFallbackConfig(raw.nativeFallback, resolved.nativeFallback, warnings);
|
|
446
|
-
} else {
|
|
447
|
-
warnings.push("Ignoring compaction.nativeFallback: expected a JSON object.");
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const apis = toSupportedApis(
|
|
452
|
-
raw.responsesApis,
|
|
453
|
-
"compaction.responsesApis",
|
|
454
|
-
RESPONSES_COMPACT_CAPABLE_APIS,
|
|
455
|
-
warnings,
|
|
456
|
-
);
|
|
457
|
-
if (apis !== undefined) {
|
|
458
|
-
resolved.responsesApis = apis;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const gatewayContextModels = toStringList(
|
|
462
|
-
raw.gatewayContextModels,
|
|
463
|
-
"compaction.gatewayContextModels",
|
|
464
|
-
warnings,
|
|
465
|
-
);
|
|
466
|
-
if (gatewayContextModels !== undefined) {
|
|
467
|
-
resolved.gatewayContextModels = gatewayContextModels;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (typeof raw.artifactRoot === "string" && raw.artifactRoot.trim().length > 0) {
|
|
471
|
-
resolved.artifactRoot = raw.artifactRoot.trim();
|
|
472
|
-
} else if (raw.artifactRoot !== undefined) {
|
|
473
|
-
warnings.push("Ignoring compaction.artifactRoot: expected a non-empty string.");
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function applyWebSearchConfig(
|
|
478
|
-
raw: Record<string, unknown>,
|
|
479
|
-
resolved: WebSearchConfig,
|
|
480
|
-
warnings: string[],
|
|
481
|
-
): void {
|
|
482
|
-
warnUnknownFields(raw, WEB_SEARCH_FIELDS, "webSearch", warnings);
|
|
483
|
-
resolved.enabled = toBoolean(raw.enabled, "webSearch.enabled", warnings) ?? resolved.enabled;
|
|
484
|
-
|
|
485
|
-
const models = toStringList(raw.models, "webSearch.models", warnings);
|
|
486
|
-
if (models !== undefined) {
|
|
487
|
-
resolved.models = models;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const defaultRoute = toWebSearchRoute(raw.defaultRoute, "webSearch.defaultRoute", warnings);
|
|
491
|
-
if (defaultRoute !== undefined) {
|
|
492
|
-
resolved.defaultRoute = defaultRoute;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
const routes = toWebSearchRoutes(raw.routes, "webSearch.routes", warnings);
|
|
496
|
-
if (routes !== undefined) {
|
|
497
|
-
resolved.routes = routes;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
if (defaultRoute !== undefined && resolved.models.length > 0) {
|
|
501
|
-
warnings.push(
|
|
502
|
-
"webSearch.defaultRoute overrides legacy webSearch.models for models without an exact webSearch.routes entry.",
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
if (routes !== undefined) {
|
|
506
|
-
for (const key of Object.keys(routes)) {
|
|
507
|
-
if (resolved.models.includes(key)) {
|
|
508
|
-
warnings.push(
|
|
509
|
-
`webSearch.routes.${key} overrides the legacy webSearch.models entry for the same exact model.`,
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function applyImageGenerationConfig(
|
|
517
|
-
raw: Record<string, unknown>,
|
|
518
|
-
resolved: ImageGenerationConfig,
|
|
519
|
-
warnings: string[],
|
|
520
|
-
): void {
|
|
521
|
-
warnUnknownFields(raw, IMAGE_GENERATION_FIELDS, "imageGeneration", warnings);
|
|
522
|
-
resolved.enabled =
|
|
523
|
-
toBoolean(raw.enabled, "imageGeneration.enabled", warnings) ?? resolved.enabled;
|
|
524
|
-
|
|
525
|
-
const models = toImageGenerationModels(raw.models, "imageGeneration.models", warnings);
|
|
526
|
-
if (models !== undefined) {
|
|
527
|
-
// An empty or blank-only list cannot express a default, so keep the shipped model.
|
|
528
|
-
if (models.length === 0) {
|
|
529
|
-
warnings.push(
|
|
530
|
-
`Ignoring imageGeneration.models: expected at least one model id; using ${DEFAULT_IMAGE_GENERATION_MODEL}.`,
|
|
531
|
-
);
|
|
532
|
-
} else {
|
|
533
|
-
resolved.models = models;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function applyAutoModeConfig(
|
|
539
|
-
raw: Record<string, unknown>,
|
|
540
|
-
resolved: AutoModeConfig,
|
|
541
|
-
warnings: string[],
|
|
542
|
-
): void {
|
|
543
|
-
warnUnknownFields(raw, AUTO_MODE_FIELDS, "autoMode", warnings);
|
|
544
|
-
resolved.enabled = toBoolean(raw.enabled, "autoMode.enabled", warnings) ?? resolved.enabled;
|
|
545
|
-
|
|
546
|
-
const models = toStringList(raw.models, "autoMode.models", warnings);
|
|
547
|
-
if (models !== undefined) {
|
|
548
|
-
resolved.models = models;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const reviewerModel = toModelSpec(raw.reviewerModel, "autoMode.reviewerModel", warnings);
|
|
552
|
-
if (reviewerModel !== undefined) {
|
|
553
|
-
resolved.reviewerModel = reviewerModel === null ? undefined : reviewerModel;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
resolved.gate = toAutoModeGate(raw.gate, "autoMode.gate", warnings) ?? resolved.gate;
|
|
557
|
-
|
|
558
|
-
const extraTools = toStringList(raw.extraTools, "autoMode.extraTools", warnings);
|
|
559
|
-
if (extraTools !== undefined) {
|
|
560
|
-
resolved.extraTools = extraTools;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const timeoutMs = toReviewerTimeoutMs(raw.timeoutMs, "autoMode.timeoutMs", warnings);
|
|
564
|
-
if (timeoutMs !== undefined) {
|
|
565
|
-
resolved.timeoutMs = timeoutMs;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
resolved.transcript = toBoolean(raw.transcript, "autoMode.transcript", warnings) ?? resolved.transcript;
|
|
569
|
-
resolved.evidenceTools =
|
|
570
|
-
toBoolean(raw.evidenceTools, "autoMode.evidenceTools", warnings) ?? resolved.evidenceTools;
|
|
571
|
-
|
|
572
|
-
const maxEvidenceRounds = toBoundedInteger(
|
|
573
|
-
raw.maxEvidenceRounds,
|
|
574
|
-
"autoMode.maxEvidenceRounds",
|
|
575
|
-
warnings,
|
|
576
|
-
EVIDENCE_ROUNDS_MIN,
|
|
577
|
-
EVIDENCE_ROUNDS_MAX,
|
|
578
|
-
);
|
|
579
|
-
if (maxEvidenceRounds !== undefined) {
|
|
580
|
-
resolved.maxEvidenceRounds = maxEvidenceRounds;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
if (raw.classifier !== undefined) {
|
|
584
|
-
if (isRecord(raw.classifier)) {
|
|
585
|
-
applyAutoModeClassifierConfig(raw.classifier, resolved.classifier, warnings);
|
|
586
|
-
} else {
|
|
587
|
-
warnings.push("Ignoring autoMode.classifier: expected a JSON object.");
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (raw.circuitBreaker !== undefined) {
|
|
592
|
-
if (isRecord(raw.circuitBreaker)) {
|
|
593
|
-
applyAutoModeBreakerConfig(raw.circuitBreaker, resolved.circuitBreaker, warnings);
|
|
594
|
-
} else {
|
|
595
|
-
warnings.push("Ignoring autoMode.circuitBreaker: expected a JSON object.");
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function applyAutoModeClassifierConfig(
|
|
601
|
-
raw: Record<string, unknown>,
|
|
602
|
-
resolved: AutoModeClassifierConfig,
|
|
603
|
-
warnings: string[],
|
|
604
|
-
): void {
|
|
605
|
-
warnUnknownFields(raw, AUTO_MODE_CLASSIFIER_FIELDS, "autoMode.classifier", warnings);
|
|
606
|
-
resolved.enabled =
|
|
607
|
-
toBoolean(raw.enabled, "autoMode.classifier.enabled", warnings) ?? resolved.enabled;
|
|
608
|
-
|
|
609
|
-
const model = toModelSpec(raw.model, "autoMode.classifier.model", warnings);
|
|
610
|
-
if (model !== undefined) {
|
|
611
|
-
resolved.model = model === null ? undefined : model;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const timeoutMs =
|
|
615
|
-
toBoundedInteger(
|
|
616
|
-
raw.timeoutMs,
|
|
617
|
-
"autoMode.classifier.timeoutMs",
|
|
618
|
-
warnings,
|
|
619
|
-
REVIEWER_TIMEOUT_MIN_MS,
|
|
620
|
-
REVIEWER_TIMEOUT_MAX_MS,
|
|
621
|
-
) ?? DEFAULT_CLASSIFIER_TIMEOUT_MS;
|
|
622
|
-
if (raw.timeoutMs !== undefined) {
|
|
623
|
-
resolved.timeoutMs = timeoutMs;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const maxLag = toBoundedInteger(
|
|
627
|
-
raw.maxLag,
|
|
628
|
-
"autoMode.classifier.maxLag",
|
|
629
|
-
warnings,
|
|
630
|
-
CLASSIFIER_MAX_LAG_MIN,
|
|
631
|
-
CLASSIFIER_MAX_LAG_MAX,
|
|
632
|
-
);
|
|
633
|
-
if (maxLag !== undefined) {
|
|
634
|
-
resolved.maxLag = maxLag;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function applyAutoModeBreakerConfig(
|
|
639
|
-
raw: Record<string, unknown>,
|
|
640
|
-
resolved: AutoModeCircuitBreakerConfig,
|
|
641
|
-
warnings: string[],
|
|
642
|
-
): void {
|
|
643
|
-
warnUnknownFields(raw, AUTO_MODE_BREAKER_FIELDS, "autoMode.circuitBreaker", warnings);
|
|
644
|
-
|
|
645
|
-
const consecutiveDenials = toBoundedInteger(
|
|
646
|
-
raw.consecutiveDenials,
|
|
647
|
-
"autoMode.circuitBreaker.consecutiveDenials",
|
|
648
|
-
warnings,
|
|
649
|
-
BREAKER_LIMIT_MIN,
|
|
650
|
-
BREAKER_LIMIT_MAX,
|
|
651
|
-
);
|
|
652
|
-
if (consecutiveDenials !== undefined) {
|
|
653
|
-
resolved.consecutiveDenials = consecutiveDenials;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const recentDenials = toBoundedInteger(
|
|
657
|
-
raw.recentDenials,
|
|
658
|
-
"autoMode.circuitBreaker.recentDenials",
|
|
659
|
-
warnings,
|
|
660
|
-
BREAKER_LIMIT_MIN,
|
|
661
|
-
BREAKER_LIMIT_MAX,
|
|
662
|
-
);
|
|
663
|
-
if (recentDenials !== undefined) {
|
|
664
|
-
resolved.recentDenials = recentDenials;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
const windowSize = toBoundedInteger(
|
|
668
|
-
raw.windowSize,
|
|
669
|
-
"autoMode.circuitBreaker.windowSize",
|
|
670
|
-
warnings,
|
|
671
|
-
BREAKER_WINDOW_MIN,
|
|
672
|
-
BREAKER_WINDOW_MAX,
|
|
673
|
-
);
|
|
674
|
-
if (windowSize !== undefined) {
|
|
675
|
-
resolved.windowSize = windowSize;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* Load the canonical toolkit config from
|
|
681
|
-
* `~/.pi/agent/extensions/pi-openai-toolkit/config.json`.
|
|
682
|
-
* A missing file silently yields defaults; legacy branded paths are never read.
|
|
683
|
-
*/
|
|
684
|
-
export function loadToolkitConfig(configPath: string = CONFIG_PATH): LoadedToolkitConfig {
|
|
685
|
-
const warnings: string[] = [];
|
|
686
|
-
const resolved = cloneDefaults();
|
|
687
|
-
let source: string | undefined;
|
|
688
|
-
|
|
689
|
-
const raw = readJsonObject(configPath, warnings);
|
|
690
|
-
if (raw) {
|
|
691
|
-
source = configPath;
|
|
692
|
-
warnUnknownFields(raw, TOP_LEVEL_FIELDS, "", warnings);
|
|
693
|
-
|
|
694
|
-
if (raw.compaction !== undefined) {
|
|
695
|
-
if (isRecord(raw.compaction)) {
|
|
696
|
-
applyCompactionConfig(raw.compaction, resolved.compaction, warnings);
|
|
697
|
-
} else {
|
|
698
|
-
warnings.push("Ignoring compaction: expected a JSON object.");
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
if (raw.webSearch !== undefined) {
|
|
703
|
-
if (isRecord(raw.webSearch)) {
|
|
704
|
-
applyWebSearchConfig(raw.webSearch, resolved.webSearch, warnings);
|
|
705
|
-
} else {
|
|
706
|
-
warnings.push("Ignoring webSearch: expected a JSON object.");
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (raw.imageGeneration !== undefined) {
|
|
711
|
-
if (isRecord(raw.imageGeneration)) {
|
|
712
|
-
applyImageGenerationConfig(raw.imageGeneration, resolved.imageGeneration, warnings);
|
|
713
|
-
} else {
|
|
714
|
-
warnings.push("Ignoring imageGeneration: expected a JSON object.");
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (raw.autoMode !== undefined) {
|
|
719
|
-
if (isRecord(raw.autoMode)) {
|
|
720
|
-
applyAutoModeConfig(raw.autoMode, resolved.autoMode, warnings);
|
|
721
|
-
} else {
|
|
722
|
-
warnings.push("Ignoring autoMode: expected a JSON object.");
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
resolved.compaction.artifactRoot = resolveConfiguredPath(
|
|
728
|
-
resolved.compaction.artifactRoot,
|
|
729
|
-
path.dirname(configPath),
|
|
730
|
-
);
|
|
731
|
-
|
|
732
|
-
return {
|
|
733
|
-
config: resolved,
|
|
734
|
-
source,
|
|
735
|
-
warnings,
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
export { DEFAULT_TOOLKIT_CONFIG };
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { getExactModelKey, type ExactModelScopeModel } from "./model-scope";
|
|
5
|
+
import { resolveWebSearchRoute, type WebSearchModel } from "./web-search/types";
|
|
6
|
+
import { CONFIG_PATH, normalizeLegacyConfig } from "./config/legacy";
|
|
7
|
+
import { resolveV2Config } from "./config/v2";
|
|
8
|
+
import {
|
|
9
|
+
CONFIG_FEATURES,
|
|
10
|
+
createPolicyDefaults,
|
|
11
|
+
type ConfigDocumentSnapshot,
|
|
12
|
+
type ConfigFeature,
|
|
13
|
+
type ConfigIssue,
|
|
14
|
+
type ConfigOrigin,
|
|
15
|
+
type EffectiveToolkitPolicy,
|
|
16
|
+
type ResolvedToolkitPolicy,
|
|
17
|
+
} from "./config/policy";
|
|
18
|
+
import type { LoadedToolkitConfig, ToolkitConfig } from "./types";
|
|
19
|
+
|
|
20
|
+
export { CONFIG_DIR, CONFIG_PATH } from "./config/legacy";
|
|
21
|
+
export { DEFAULT_TOOLKIT_CONFIG } from "./types";
|
|
22
|
+
export type { ConfigFeature, ConfigIssue, ConfigOrigin, EffectiveToolkitPolicy } from "./config/policy";
|
|
23
|
+
|
|
24
|
+
export type ResolvedToolkitConfig = ResolvedToolkitPolicy & {
|
|
25
|
+
/** Narrow adapters for existing feature engines, never raw document sections. */
|
|
26
|
+
config: ToolkitConfig;
|
|
27
|
+
format: ConfigDocumentSnapshot["format"];
|
|
28
|
+
source?: string;
|
|
29
|
+
modelKey?: string;
|
|
30
|
+
/** Cross-feature protocol policy, independent of context enablement. */
|
|
31
|
+
gatewayModelKeys: string[];
|
|
32
|
+
/** Same decoded document for resolving another identity within this operation. */
|
|
33
|
+
snapshot: LoadedToolkitConfig;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
37
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
function has(value: Record<string, unknown>, key: string): boolean {
|
|
40
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
41
|
+
}
|
|
42
|
+
function freeze<T>(value: T): T {
|
|
43
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
44
|
+
Object.freeze(value);
|
|
45
|
+
for (const item of Object.values(value)) freeze(item);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function documentError(code: string): ConfigIssue {
|
|
50
|
+
return { severity: "error", code, path: "$", feature: "document" };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Read exactly one global file. No project discovery, cache, writes or auth/network work. */
|
|
54
|
+
export function loadToolkitConfig(configPath: string = CONFIG_PATH): LoadedToolkitConfig {
|
|
55
|
+
let raw: unknown;
|
|
56
|
+
let format: ConfigDocumentSnapshot["format"] = "missing";
|
|
57
|
+
const issues: ConfigIssue[] = [];
|
|
58
|
+
const warnings: string[] = [];
|
|
59
|
+
try {
|
|
60
|
+
const text = fs.readFileSync(configPath, "utf8");
|
|
61
|
+
try {
|
|
62
|
+
raw = JSON.parse(text);
|
|
63
|
+
if (!isRecord(raw)) {
|
|
64
|
+
format = "invalid";
|
|
65
|
+
issues.push(documentError("expected-object"));
|
|
66
|
+
} else if (has(raw, "schemaVersion")) {
|
|
67
|
+
format = raw.schemaVersion === 2 ? "v2" : "invalid";
|
|
68
|
+
if (format === "invalid") issues.push(documentError("unsupported-schema-version"));
|
|
69
|
+
} else if (has(raw, "defaults") || has(raw, "models") || has(raw, "diagnostics")) {
|
|
70
|
+
format = "invalid";
|
|
71
|
+
issues.push(documentError("missing-schema-version"));
|
|
72
|
+
} else {
|
|
73
|
+
format = "legacy";
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
format = "invalid";
|
|
77
|
+
issues.push(documentError("invalid-json"));
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (!isRecord(error) || error.code !== "ENOENT") {
|
|
81
|
+
format = "invalid";
|
|
82
|
+
issues.push(documentError("unreadable-config"));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const issue of issues) warnings.push(`Ignoring ${configPath}: ${issue.code}.`);
|
|
86
|
+
const legacy = normalizeLegacyConfig(format === "legacy" && isRecord(raw) ? raw : undefined, configPath, warnings);
|
|
87
|
+
return {
|
|
88
|
+
...legacy,
|
|
89
|
+
...(format === "v2" ? { source: configPath } : {}),
|
|
90
|
+
document: freeze({ format, configPath, raw, issues }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function configuredPath(value: string, configPath: string): string {
|
|
95
|
+
if (value.startsWith("~/")) return path.join(os.homedir(), value.slice(2));
|
|
96
|
+
return path.resolve(path.dirname(configPath), value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function leafPaths(value: unknown, prefix = ""): string[] {
|
|
100
|
+
if (!isRecord(value)) return [prefix];
|
|
101
|
+
return Object.entries(value).flatMap(([key, child]) => leafPaths(child, prefix ? `${prefix}.${key}` : key));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const LEGACY_PATHS: Record<string, string> = {
|
|
105
|
+
"context.mode": "compaction.contextManagement",
|
|
106
|
+
"context.remoteCompaction.model": "compaction.remoteCompactModel",
|
|
107
|
+
"context.remoteCompaction.inputSource": "compaction.remoteV2ContextSource",
|
|
108
|
+
"context.remoteCompaction.allowContinuityBreak": "compaction.allowCompactionContinuityBreak",
|
|
109
|
+
"context.remoteCompaction.apis": "compaction.responsesApis",
|
|
110
|
+
"context.nativeFallback.enabled": "compaction.nativeFallback.enabled",
|
|
111
|
+
"context.nativeFallback.model": "compaction.nativeFallback.model",
|
|
112
|
+
"context.nativeFallback.thinkingLevel": "compaction.nativeFallback.thinkingLevel",
|
|
113
|
+
"context.remoteWindows.leaveManagedMode": "compaction.leaveManagedMode",
|
|
114
|
+
"context.remoteWindows.reminderThresholdPercent": "compaction.contextReminderThresholdPercent",
|
|
115
|
+
"webSearch.route": "webSearch.models",
|
|
116
|
+
"imageGeneration.enabled": "imageGeneration.enabled",
|
|
117
|
+
"imageGeneration.defaultModel": "imageGeneration.models",
|
|
118
|
+
"imageGeneration.allowedModels": "imageGeneration.models",
|
|
119
|
+
"autoMode.available": "autoMode.models",
|
|
120
|
+
"compatibility.transport": "compaction.gatewayContextModels",
|
|
121
|
+
"diagnostics.level": "compaction.debug",
|
|
122
|
+
"diagnostics.notifyOnLoad": "compaction.notifyOnLoad",
|
|
123
|
+
"diagnostics.captureRequests": "compaction.logProviderPayloads",
|
|
124
|
+
"diagnostics.captureResponses": "compaction.logCompactResponses",
|
|
125
|
+
"diagnostics.redactSensitiveData": "compaction.redactSensitiveData",
|
|
126
|
+
"diagnostics.artifactRoot": "compaction.artifactRoot",
|
|
127
|
+
};
|
|
128
|
+
function containsPath(value: unknown, fieldPath: string): boolean {
|
|
129
|
+
for (const key of fieldPath.split(".")) {
|
|
130
|
+
if (!isRecord(value) || !has(value, key)) return false;
|
|
131
|
+
value = value[key];
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function legacyRouteKey(raw: unknown, modelKey: string | undefined): string | undefined {
|
|
137
|
+
if (!modelKey || !isRecord(raw) || !isRecord(raw.webSearch) || !isRecord(raw.webSearch.routes)) return undefined;
|
|
138
|
+
return Object.keys(raw.webSearch.routes).find((key) => key.trim() === modelKey);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function legacyIssues(loaded: LoadedToolkitConfig, modelKey: string | undefined): ConfigIssue[] {
|
|
142
|
+
const raw = loaded.document?.raw;
|
|
143
|
+
const rawWeb = isRecord(raw) && isRecord(raw.webSearch) ? raw.webSearch : undefined;
|
|
144
|
+
// Route keys may contain dots or colons. Never recover identity by splitting a warning.
|
|
145
|
+
const routeIssues: ConfigIssue[] = [];
|
|
146
|
+
if (isRecord(rawWeb?.routes)) {
|
|
147
|
+
for (const [key, value] of Object.entries(rawWeb.routes)) {
|
|
148
|
+
const normalized = key.trim();
|
|
149
|
+
const separator = normalized.indexOf("/");
|
|
150
|
+
const exact = separator > 0 && separator < normalized.length - 1 && !/[\s*?\[\]{}]/.test(normalized);
|
|
151
|
+
if (exact && typeof value === "string" && ["local", "hosted", "standalone-alpha"].includes(value)) continue;
|
|
152
|
+
routeIssues.push({ severity: "error", code: "invalid-legacy-value", feature: "webSearch",
|
|
153
|
+
path: exact ? `webSearch.routes[${JSON.stringify(normalized).slice(0, 180)}]` : "webSearch.routes.[invalid-model-key]",
|
|
154
|
+
modelKey: normalized });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const issues = loaded.warnings.filter((warning) => !warning.startsWith("Ignoring webSearch.routes.")).map((warning): ConfigIssue => {
|
|
158
|
+
const match = /^Ignoring ([^:]+):/.exec(warning);
|
|
159
|
+
const field = warning.startsWith("Ignoring compaction.responsesApis entry ") ? "compaction.responsesApis" : match?.[1] ?? "$";
|
|
160
|
+
const root = field.split(".")[0];
|
|
161
|
+
const diagnostic = Object.entries(LEGACY_PATHS).some(([leaf, input]) => leaf.startsWith("diagnostics.") && input === field);
|
|
162
|
+
const feature: ConfigFeature | "document" = diagnostic ? "diagnostics"
|
|
163
|
+
: field === "compaction.gatewayContextModels" ? "compatibility" : root === "compaction" ? "context"
|
|
164
|
+
: root === "webSearch" || root === "imageGeneration" || root === "autoMode" ? root : "document";
|
|
165
|
+
const supersededDefault = field === "webSearch.defaultRoute" && legacyRouteKey(raw, modelKey) !== undefined;
|
|
166
|
+
const warningOnly = warning.endsWith("unknown field.") || !match
|
|
167
|
+
|| warning.includes("expected at least one model id") || supersededDefault;
|
|
168
|
+
return {
|
|
169
|
+
severity: warningOnly ? "warning" : "error",
|
|
170
|
+
code: warning.endsWith("unknown field.") ? "unknown-legacy-field" : warningOnly ? "legacy-normalization" : "invalid-legacy-value",
|
|
171
|
+
path: warning.endsWith("unknown field.") ? `${feature}.[unknown-field]` : field.slice(0, 220),
|
|
172
|
+
feature,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
return [...issues, ...routeIssues];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function selectedInvalidFeatures(issues: ConfigIssue[], modelKey?: string): ConfigFeature[] {
|
|
179
|
+
const invalid = new Set<ConfigFeature>();
|
|
180
|
+
for (const issue of issues) {
|
|
181
|
+
if (issue.severity !== "error" || (issue.modelKey !== undefined && issue.modelKey !== modelKey)) continue;
|
|
182
|
+
if (issue.feature === "document") for (const feature of CONFIG_FEATURES) invalid.add(feature);
|
|
183
|
+
else invalid.add(issue.feature);
|
|
184
|
+
}
|
|
185
|
+
return [...invalid];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function fromLegacy(loaded: LoadedToolkitConfig, model: WebSearchModel | undefined): ResolvedToolkitPolicy {
|
|
189
|
+
const config = loaded.config;
|
|
190
|
+
const compaction = config.compaction;
|
|
191
|
+
const modelKey = getExactModelKey(model);
|
|
192
|
+
const route = resolveWebSearchRoute({ model, config: config.webSearch });
|
|
193
|
+
const policy = createPolicyDefaults();
|
|
194
|
+
policy.context = {
|
|
195
|
+
mode: !compaction.enabled ? "pi" : compaction.contextManagement === "remote" ? "remote-windows" : "remote-compaction",
|
|
196
|
+
remoteCompaction: {
|
|
197
|
+
model: compaction.remoteCompactModel ?? null,
|
|
198
|
+
inputSource: compaction.remoteV2ContextSource,
|
|
199
|
+
allowContinuityBreak: compaction.allowCompactionContinuityBreak,
|
|
200
|
+
apis: [...compaction.responsesApis],
|
|
201
|
+
},
|
|
202
|
+
nativeFallback: { ...compaction.nativeFallback, model: compaction.nativeFallback.model ?? null },
|
|
203
|
+
remoteWindows: { leaveManagedMode: compaction.leaveManagedMode, reminderThresholdPercent: compaction.contextReminderThresholdPercent },
|
|
204
|
+
};
|
|
205
|
+
policy.webSearch.route = route.route === "none" ? "unmanaged" : route.route;
|
|
206
|
+
policy.imageGeneration = {
|
|
207
|
+
enabled: config.imageGeneration.enabled,
|
|
208
|
+
defaultModel: config.imageGeneration.models[0],
|
|
209
|
+
allowedModels: [...config.imageGeneration.models],
|
|
210
|
+
};
|
|
211
|
+
const { enabled, models, reviewerModel, classifier, ...auto } = config.autoMode;
|
|
212
|
+
policy.autoMode = {
|
|
213
|
+
...structuredClone(auto),
|
|
214
|
+
available: enabled && modelKey !== undefined && models.includes(modelKey),
|
|
215
|
+
reviewerModel: reviewerModel ?? null,
|
|
216
|
+
classifier: { ...classifier, model: classifier.model ?? null },
|
|
217
|
+
};
|
|
218
|
+
policy.compatibility.transport = modelKey && compaction.gatewayContextModels.includes(modelKey) ? "codex-gateway" : "standard";
|
|
219
|
+
policy.diagnostics = {
|
|
220
|
+
level: compaction.debug ? "debug" : "info",
|
|
221
|
+
notifyOnLoad: compaction.notifyOnLoad,
|
|
222
|
+
captureRequests: compaction.logProviderPayloads,
|
|
223
|
+
captureResponses: compaction.logCompactResponses,
|
|
224
|
+
redactSensitiveData: compaction.redactSensitiveData,
|
|
225
|
+
artifactRoot: compaction.artifactRoot,
|
|
226
|
+
};
|
|
227
|
+
const origins: Record<string, ConfigOrigin> = Object.create(null);
|
|
228
|
+
const raw = loaded.document?.raw;
|
|
229
|
+
for (const leaf of leafPaths(policy)) {
|
|
230
|
+
let input = LEGACY_PATHS[leaf] ?? leaf;
|
|
231
|
+
if (leaf === "context.mode" && !compaction.enabled) input = "compaction.enabled";
|
|
232
|
+
if (leaf === "autoMode.available" && !enabled) input = "autoMode.enabled";
|
|
233
|
+
if (leaf === "webSearch.route") {
|
|
234
|
+
if (!config.webSearch.enabled) input = "webSearch.enabled";
|
|
235
|
+
else if (route.source === "exact") {
|
|
236
|
+
const rawKey = legacyRouteKey(raw, modelKey);
|
|
237
|
+
if (rawKey !== undefined) {
|
|
238
|
+
origins[leaf] = { kind: "legacy", path: `webSearch.routes[${JSON.stringify(rawKey)}]`, source: loaded.source };
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (route.source === "default") input = "webSearch.defaultRoute";
|
|
243
|
+
}
|
|
244
|
+
const ignored = loaded.warnings.some((warning) => warning.startsWith(`Ignoring ${input}:`));
|
|
245
|
+
origins[leaf] = !ignored && ((raw && containsPath(raw, input)) || (!loaded.document && loaded.source))
|
|
246
|
+
? { kind: "legacy", path: input, source: loaded.source } : { kind: "builtin" };
|
|
247
|
+
}
|
|
248
|
+
const issues = [...(loaded.document?.issues ?? []), ...legacyIssues(loaded, modelKey)];
|
|
249
|
+
return { policy, origins, issues, invalidFeatures: selectedInvalidFeatures(issues, modelKey) };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Internal engine adapter. All interpretation of document names stays in this module. */
|
|
253
|
+
function engineConfig(policy: EffectiveToolkitPolicy, modelKey: string | undefined, gatewayKeys: string[]): ToolkitConfig {
|
|
254
|
+
const { context, diagnostics, imageGeneration, autoMode } = policy;
|
|
255
|
+
const { available, reviewerModel, classifier, ...auto } = autoMode;
|
|
256
|
+
return {
|
|
257
|
+
compaction: {
|
|
258
|
+
enabled: context.mode !== "pi",
|
|
259
|
+
contextManagement: context.mode === "remote-windows" ? "remote" : "off",
|
|
260
|
+
remoteCompactModel: context.remoteCompaction.model ?? undefined,
|
|
261
|
+
remoteV2ContextSource: context.remoteCompaction.inputSource,
|
|
262
|
+
allowCompactionContinuityBreak: context.remoteCompaction.allowContinuityBreak,
|
|
263
|
+
responsesApis: [...context.remoteCompaction.apis],
|
|
264
|
+
gatewayContextModels: [...gatewayKeys],
|
|
265
|
+
nativeFallback: { ...context.nativeFallback, model: context.nativeFallback.model ?? undefined },
|
|
266
|
+
leaveManagedMode: context.remoteWindows.leaveManagedMode,
|
|
267
|
+
contextReminderThresholdPercent: context.remoteWindows.reminderThresholdPercent,
|
|
268
|
+
debug: diagnostics.level === "debug",
|
|
269
|
+
notifyOnLoad: diagnostics.notifyOnLoad,
|
|
270
|
+
logProviderPayloads: diagnostics.captureRequests,
|
|
271
|
+
logCompactResponses: diagnostics.captureResponses,
|
|
272
|
+
redactSensitiveData: diagnostics.redactSensitiveData,
|
|
273
|
+
artifactRoot: diagnostics.artifactRoot,
|
|
274
|
+
},
|
|
275
|
+
webSearch: policy.webSearch.route === "unmanaged" ? { enabled: false, models: [] }
|
|
276
|
+
: { enabled: true, models: [], defaultRoute: policy.webSearch.route },
|
|
277
|
+
imageGeneration: { enabled: imageGeneration.enabled, models: [...imageGeneration.allowedModels] },
|
|
278
|
+
autoMode: {
|
|
279
|
+
...structuredClone(auto), enabled: available, models: available && modelKey ? [modelKey] : [],
|
|
280
|
+
reviewerModel: reviewerModel ?? undefined, classifier: { ...classifier, model: classifier.model ?? undefined },
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Resolve one immutable snapshot for one model. Never reads disk or a model registry. */
|
|
286
|
+
export function resolveToolkitConfig(loaded: LoadedToolkitConfig, model?: ExactModelScopeModel & { api?: string }): ResolvedToolkitConfig {
|
|
287
|
+
// Injected readers are also decoded at the module seam; they may omit optional feature sections.
|
|
288
|
+
if (!loaded.document) {
|
|
289
|
+
const configPath = loaded.source ?? CONFIG_PATH;
|
|
290
|
+
const normalized = normalizeLegacyConfig(loaded.config, configPath);
|
|
291
|
+
loaded = { ...normalized, source: loaded.source, warnings: [...loaded.warnings, ...normalized.warnings],
|
|
292
|
+
document: { format: "legacy", configPath, raw: loaded.config, issues: [] } };
|
|
293
|
+
}
|
|
294
|
+
const modelKey = getExactModelKey(model);
|
|
295
|
+
const format = loaded.document?.format ?? "legacy";
|
|
296
|
+
const v2 = format === "v2" ? resolveV2Config(loaded.document?.raw, modelKey) : undefined;
|
|
297
|
+
const resolution = v2 ?? fromLegacy(loaded, model);
|
|
298
|
+
const source = loaded.source;
|
|
299
|
+
if (format === "v2") {
|
|
300
|
+
resolution.policy.diagnostics.artifactRoot = configuredPath(resolution.policy.diagnostics.artifactRoot, loaded.document?.configPath ?? CONFIG_PATH);
|
|
301
|
+
for (const origin of Object.values(resolution.origins)) if (origin.kind !== "builtin") origin.source = source;
|
|
302
|
+
}
|
|
303
|
+
const gatewayModelKeys = v2 ? v2.gatewayModelKeys : [...loaded.config.compaction.gatewayContextModels];
|
|
304
|
+
const config = format === "v2" ? engineConfig(resolution.policy, modelKey, gatewayModelKeys) : structuredClone(loaded.config);
|
|
305
|
+
return freeze({ ...resolution, config, format, source, modelKey, gatewayModelKeys, snapshot: structuredClone(loaded) });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export class ToolkitConfigurationError extends Error {
|
|
309
|
+
constructor(readonly features: readonly ConfigFeature[]) {
|
|
310
|
+
super(`Toolkit configuration is invalid for ${features.join(", ")}; use /toolkit-config validate. No fallback policy was selected.`);
|
|
311
|
+
this.name = "ToolkitConfigurationError";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function assertConfigValid(resolved: ResolvedToolkitConfig, ...features: ConfigFeature[]): void {
|
|
316
|
+
const invalid = features.filter((feature) => resolved.invalidFeatures.includes(feature));
|
|
317
|
+
if (invalid.length > 0) throw new ToolkitConfigurationError(invalid);
|
|
318
|
+
}
|