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
|
@@ -1,523 +1,529 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { loadToolkitConfig } from "../config";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
type
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
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
|
-
state.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
state.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
keptName
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
state.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
state.
|
|
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
|
-
case "
|
|
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
|
-
const
|
|
231
|
-
const
|
|
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
|
-
|
|
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
|
-
return resolution;
|
|
338
|
-
}
|
|
339
|
-
if (resolution.route === "
|
|
340
|
-
claimAndRemove(pi, LOCAL_WEB_SEARCH_TOOL_NAME, states.webSearch);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function
|
|
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
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { assertConfigValid, loadToolkitConfig, resolveToolkitConfig, type ResolvedToolkitConfig } from "../config";
|
|
3
|
+
import { notifyConfigIssues } from "../config/notifications";
|
|
4
|
+
import { resolveResponsesEnvironment, type ResponsesEnvironmentResolution } from "../runtime";
|
|
5
|
+
import {
|
|
6
|
+
requestAlphaSearch,
|
|
7
|
+
normalizeStandaloneWebRunCommands,
|
|
8
|
+
sanitizeAlphaDiagnostic,
|
|
9
|
+
STANDALONE_WEB_RUN_PARAMETERS,
|
|
10
|
+
StandaloneWebRunError,
|
|
11
|
+
type AlphaSearchDetails,
|
|
12
|
+
type AlphaSearchClientResult,
|
|
13
|
+
type StandaloneWebRunParameters,
|
|
14
|
+
} from "./alpha-client";
|
|
15
|
+
import { transformWebSearchPayload } from "./payload";
|
|
16
|
+
import { appendWebSearchPrompt } from "./prompt";
|
|
17
|
+
import {
|
|
18
|
+
LOCAL_WEB_SEARCH_TOOL_NAME,
|
|
19
|
+
resolveWebSearchRoute,
|
|
20
|
+
WEB_RUN_TOOL_NAME,
|
|
21
|
+
WEB_SEARCH_CAPABLE_APIS,
|
|
22
|
+
type WebSearchModel,
|
|
23
|
+
type WebSearchRouteResolution,
|
|
24
|
+
} from "./types";
|
|
25
|
+
|
|
26
|
+
const registeredApis = new WeakSet<object>();
|
|
27
|
+
|
|
28
|
+
const WEB_RUN_LABEL = WEB_RUN_TOOL_NAME;
|
|
29
|
+
const WEB_RUN_DESCRIPTION =
|
|
30
|
+
"Run sequential standalone Web Search commands through the provider's CPA/Codex alpha-search endpoint. " +
|
|
31
|
+
"Use only when the current Web Search route explicitly selects standalone-alpha.";
|
|
32
|
+
const WEB_RUN_PROMPT_SNIPPET =
|
|
33
|
+
"Run search, image, page, finance, weather, sports, or time commands through CPA/Codex standalone Web Search.";
|
|
34
|
+
const WEB_RUN_PROMPT_GUIDELINES = [
|
|
35
|
+
`Use ${WEB_RUN_TOOL_NAME} only when the configured Web Search route is standalone-alpha; do not assume it is available for an unconfigured model.`,
|
|
36
|
+
"Choose exactly the command fields needed for the current request and preserve ref_id values for open, click, find, and screenshot follow-ups.",
|
|
37
|
+
"Supported commands are search_query, image_query, open, click, find, screenshot, finance, weather, sports, and time; response_length may tune the result size.",
|
|
38
|
+
"The route requires a CPA/Codex gateway that exposes /alpha/search and supports standalone web search; a failed request is not retried or redirected to another search implementation.",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const DEFAULT_TOOL_STATE = {
|
|
42
|
+
wasActiveBeforeOwnership: undefined as boolean | undefined,
|
|
43
|
+
toolkitOwnsTool: false,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type ToolOwnershipState = {
|
|
47
|
+
wasActiveBeforeOwnership?: boolean;
|
|
48
|
+
toolkitOwnsTool: boolean;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type WebRunExecutorDependencies = {
|
|
52
|
+
loadConfig: typeof loadToolkitConfig;
|
|
53
|
+
resolveRuntime: typeof resolveResponsesEnvironment;
|
|
54
|
+
requestSearch: typeof requestAlphaSearch;
|
|
55
|
+
isRegistered: () => boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function cloneToolState(): ToolOwnershipState {
|
|
59
|
+
return { ...DEFAULT_TOOL_STATE };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sanitizeMessage(message: string): string {
|
|
63
|
+
return sanitizeAlphaDiagnostic(message, "Standalone Web Search failed.");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function activeToolsApi(pi: ExtensionAPI): {
|
|
67
|
+
getActiveTools: () => string[];
|
|
68
|
+
setActiveTools: (names: string[]) => void;
|
|
69
|
+
} | undefined {
|
|
70
|
+
const api = pi as ExtensionAPI & {
|
|
71
|
+
getActiveTools?: () => string[];
|
|
72
|
+
setActiveTools?: (names: string[]) => void;
|
|
73
|
+
};
|
|
74
|
+
if (typeof api.getActiveTools !== "function" || typeof api.setActiveTools !== "function") return undefined;
|
|
75
|
+
return { getActiveTools: api.getActiveTools, setActiveTools: api.setActiveTools };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function canManageActiveTools(pi: ExtensionAPI): boolean {
|
|
79
|
+
const api = activeToolsApi(pi);
|
|
80
|
+
if (!api) return false;
|
|
81
|
+
try {
|
|
82
|
+
api.getActiveTools();
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isActiveTool(pi: ExtensionAPI, name: string): boolean | undefined {
|
|
90
|
+
const api = activeToolsApi(pi);
|
|
91
|
+
if (!api) return undefined;
|
|
92
|
+
try {
|
|
93
|
+
return api.getActiveTools().includes(name);
|
|
94
|
+
} catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function setActiveToolsIfChanged(
|
|
100
|
+
api: { getActiveTools: () => string[]; setActiveTools: (names: string[]) => void },
|
|
101
|
+
current: string[],
|
|
102
|
+
next: string[],
|
|
103
|
+
): void {
|
|
104
|
+
if (current.length === next.length && current.every((name, index) => name === next[index])) return;
|
|
105
|
+
api.setActiveTools(next);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function claimAndRemove(
|
|
109
|
+
pi: ExtensionAPI,
|
|
110
|
+
name: string,
|
|
111
|
+
state: ToolOwnershipState,
|
|
112
|
+
): void {
|
|
113
|
+
const api = activeToolsApi(pi);
|
|
114
|
+
if (!api) return;
|
|
115
|
+
try {
|
|
116
|
+
const current = api.getActiveTools();
|
|
117
|
+
if (!state.toolkitOwnsTool) {
|
|
118
|
+
state.wasActiveBeforeOwnership = current.includes(name);
|
|
119
|
+
state.toolkitOwnsTool = true;
|
|
120
|
+
}
|
|
121
|
+
const next = current.filter((toolName) => toolName !== name);
|
|
122
|
+
setActiveToolsIfChanged(api, current, next);
|
|
123
|
+
} catch {
|
|
124
|
+
// An unavailable active-tool API is handled as an unavailable route by the
|
|
125
|
+
// execution guard; it must never trigger a different search implementation.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function activateOwned(pi: ExtensionAPI, name: string, state: ToolOwnershipState): void {
|
|
130
|
+
const api = activeToolsApi(pi);
|
|
131
|
+
if (!api) return;
|
|
132
|
+
try {
|
|
133
|
+
const current = api.getActiveTools();
|
|
134
|
+
if (!state.toolkitOwnsTool) {
|
|
135
|
+
state.wasActiveBeforeOwnership = current.includes(name);
|
|
136
|
+
state.toolkitOwnsTool = true;
|
|
137
|
+
}
|
|
138
|
+
let keptName = false;
|
|
139
|
+
const next = current.filter((toolName) => {
|
|
140
|
+
if (toolName !== name) return true;
|
|
141
|
+
if (keptName) return false;
|
|
142
|
+
keptName = true;
|
|
143
|
+
return true;
|
|
144
|
+
});
|
|
145
|
+
if (!keptName) next.push(name);
|
|
146
|
+
setActiveToolsIfChanged(api, current, next);
|
|
147
|
+
} catch {
|
|
148
|
+
// The tool-call guard remains fail closed if active-tool mutation fails.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function releaseOwnership(
|
|
153
|
+
pi: ExtensionAPI,
|
|
154
|
+
name: string,
|
|
155
|
+
state: ToolOwnershipState,
|
|
156
|
+
): void {
|
|
157
|
+
if (!state.toolkitOwnsTool) return;
|
|
158
|
+
const api = activeToolsApi(pi);
|
|
159
|
+
if (!api) {
|
|
160
|
+
state.wasActiveBeforeOwnership = undefined;
|
|
161
|
+
state.toolkitOwnsTool = false;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const current = api.getActiveTools();
|
|
166
|
+
const withoutOwnedName = current.filter((toolName) => toolName !== name);
|
|
167
|
+
const next = state.wasActiveBeforeOwnership ? [...withoutOwnedName, name] : withoutOwnedName;
|
|
168
|
+
setActiveToolsIfChanged(api, current, next);
|
|
169
|
+
} catch {
|
|
170
|
+
// Do not claim that a failed release restored a third-party tool.
|
|
171
|
+
}
|
|
172
|
+
state.wasActiveBeforeOwnership = undefined;
|
|
173
|
+
state.toolkitOwnsTool = false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function describeRouteFailure(resolution: WebSearchRouteResolution): string {
|
|
177
|
+
if (resolution.route === "none") {
|
|
178
|
+
return resolution.reason === "disabled"
|
|
179
|
+
? "Web Search is disabled."
|
|
180
|
+
: "No Web Search route is configured for the current provider/model-id.";
|
|
181
|
+
}
|
|
182
|
+
return `Web Search route "${resolution.route}" is unavailable${resolution.reason ? ` (${resolution.reason})` : ""}.`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function abortAndThrow(ctx: ExtensionContext, message: string): never {
|
|
186
|
+
try {
|
|
187
|
+
ctx.abort();
|
|
188
|
+
} catch {
|
|
189
|
+
// Test harnesses and older hosts may omit abort; throwing still prevents
|
|
190
|
+
// Pi from sending a payload that violates the selected route.
|
|
191
|
+
}
|
|
192
|
+
throw new Error(sanitizeMessage(message));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function standaloneRuntimeFailure(resolution: ResponsesEnvironmentResolution): Error {
|
|
196
|
+
if (resolution.ok) return new Error("Unexpected standalone Web Search runtime state.");
|
|
197
|
+
const reason = resolution.reason;
|
|
198
|
+
switch (reason) {
|
|
199
|
+
case "missing-api-key":
|
|
200
|
+
case "auth-resolution-failed":
|
|
201
|
+
return new Error(
|
|
202
|
+
sanitizeMessage(
|
|
203
|
+
resolution.errorMessage ??
|
|
204
|
+
"Standalone Web Search authentication could not be resolved for the current model.",
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
case "missing-base-url":
|
|
208
|
+
return new Error("Standalone Web Search has no usable provider base URL.");
|
|
209
|
+
case "missing-session-id":
|
|
210
|
+
return new Error("Standalone Web Search requires a session identity for this gateway route.");
|
|
211
|
+
case "unsupported-api":
|
|
212
|
+
return new Error("Standalone Web Search requires an openai-responses or openai-codex-responses model.");
|
|
213
|
+
default:
|
|
214
|
+
return new Error(`Standalone Web Search runtime is unavailable (${reason}).`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function executeStandaloneWebRun(args: {
|
|
219
|
+
params: unknown;
|
|
220
|
+
signal?: AbortSignal;
|
|
221
|
+
ctx: ExtensionContext;
|
|
222
|
+
loadConfig?: typeof loadToolkitConfig;
|
|
223
|
+
resolveRuntime?: typeof resolveResponsesEnvironment;
|
|
224
|
+
requestSearch?: typeof requestAlphaSearch;
|
|
225
|
+
isRegistered?: () => boolean;
|
|
226
|
+
}): Promise<{ text: string; details: AlphaSearchDetails }> {
|
|
227
|
+
const commands = normalizeStandaloneWebRunCommands(args.params);
|
|
228
|
+
if (args.signal?.aborted) throw new StandaloneWebRunError(`${WEB_RUN_TOOL_NAME} was cancelled.`);
|
|
229
|
+
|
|
230
|
+
const loadConfig = args.loadConfig ?? loadToolkitConfig;
|
|
231
|
+
const resolveRuntime = args.resolveRuntime ?? resolveResponsesEnvironment;
|
|
232
|
+
const requestSearch = args.requestSearch ?? requestAlphaSearch;
|
|
233
|
+
if (args.isRegistered && !args.isRegistered()) {
|
|
234
|
+
throw new Error(`${WEB_RUN_TOOL_NAME} is not registered by the toolkit; standalone Web Search is unavailable.`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const resolved = resolveToolkitConfig(loadConfig(), args.ctx.model);
|
|
238
|
+
notifyConfigIssues(args.ctx, resolved);
|
|
239
|
+
assertConfigValid(resolved, "webSearch", "compatibility");
|
|
240
|
+
const { config } = resolved;
|
|
241
|
+
const route = resolveWebSearchRoute({ model: args.ctx.model, config: config.webSearch });
|
|
242
|
+
if (route.route !== "standalone-alpha" || !route.available) {
|
|
243
|
+
throw new Error(describeRouteFailure(route));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const runtime = await resolveRuntime(args.ctx, {
|
|
247
|
+
enabled: config.webSearch.enabled,
|
|
248
|
+
responsesApis: WEB_SEARCH_CAPABLE_APIS,
|
|
249
|
+
codexGatewayModels: resolved.gatewayModelKeys,
|
|
250
|
+
});
|
|
251
|
+
if (!runtime.ok) throw standaloneRuntimeFailure(runtime);
|
|
252
|
+
|
|
253
|
+
const result: AlphaSearchClientResult = await requestSearch({
|
|
254
|
+
runtime: runtime.runtime,
|
|
255
|
+
commands,
|
|
256
|
+
signal: args.signal,
|
|
257
|
+
});
|
|
258
|
+
if (!result.ok) throw new Error(sanitizeMessage(result.errorMessage));
|
|
259
|
+
return { text: result.output, details: result.details };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function createWebRunTool(deps: WebRunExecutorDependencies): ToolDefinition<
|
|
263
|
+
typeof STANDALONE_WEB_RUN_PARAMETERS,
|
|
264
|
+
AlphaSearchDetails
|
|
265
|
+
> {
|
|
266
|
+
return {
|
|
267
|
+
name: WEB_RUN_TOOL_NAME,
|
|
268
|
+
label: WEB_RUN_LABEL,
|
|
269
|
+
description: WEB_RUN_DESCRIPTION,
|
|
270
|
+
promptSnippet: WEB_RUN_PROMPT_SNIPPET,
|
|
271
|
+
promptGuidelines: WEB_RUN_PROMPT_GUIDELINES,
|
|
272
|
+
parameters: STANDALONE_WEB_RUN_PARAMETERS,
|
|
273
|
+
executionMode: "sequential",
|
|
274
|
+
async execute(_toolCallId, params: StandaloneWebRunParameters, signal, _onUpdate, ctx) {
|
|
275
|
+
try {
|
|
276
|
+
const result = await executeStandaloneWebRun({
|
|
277
|
+
params,
|
|
278
|
+
signal,
|
|
279
|
+
ctx,
|
|
280
|
+
loadConfig: deps.loadConfig,
|
|
281
|
+
resolveRuntime: deps.resolveRuntime,
|
|
282
|
+
requestSearch: deps.requestSearch,
|
|
283
|
+
isRegistered: deps.isRegistered,
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
content: [{ type: "text", text: result.text }],
|
|
287
|
+
details: result.details,
|
|
288
|
+
};
|
|
289
|
+
} catch (error) {
|
|
290
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
291
|
+
throw new Error(sanitizeMessage(message));
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
type StandaloneRegistrationState = "verified" | "excluded" | "unavailable";
|
|
298
|
+
|
|
299
|
+
function verifyStandaloneRegistration(
|
|
300
|
+
pi: ExtensionAPI,
|
|
301
|
+
definition: ToolDefinition<typeof STANDALONE_WEB_RUN_PARAMETERS, AlphaSearchDetails>,
|
|
302
|
+
): StandaloneRegistrationState {
|
|
303
|
+
try {
|
|
304
|
+
const api = pi as ExtensionAPI & {
|
|
305
|
+
getAllTools?: () => Array<{
|
|
306
|
+
name: string;
|
|
307
|
+
description: string;
|
|
308
|
+
parameters: unknown;
|
|
309
|
+
promptGuidelines?: string[];
|
|
310
|
+
}>;
|
|
311
|
+
};
|
|
312
|
+
if (typeof api.getAllTools !== "function") return "unavailable";
|
|
313
|
+
const actual = api.getAllTools().find((tool) => tool.name === WEB_RUN_TOOL_NAME);
|
|
314
|
+
// Pi publishes only tools permitted by --tools/--exclude-tools/--no-tools.
|
|
315
|
+
// After successful registration, an absent definition means this session
|
|
316
|
+
// cannot use search; it is not a reason to abort ordinary model requests.
|
|
317
|
+
if (actual === undefined) return "excluded";
|
|
318
|
+
return actual.description === definition.description &&
|
|
319
|
+
actual.parameters === definition.parameters &&
|
|
320
|
+
actual.promptGuidelines === definition.promptGuidelines ? "verified" : "unavailable";
|
|
321
|
+
} catch {
|
|
322
|
+
return "unavailable";
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function syncWebSearchRoute(
|
|
327
|
+
pi: ExtensionAPI,
|
|
328
|
+
model: WebSearchModel | undefined,
|
|
329
|
+
config: Parameters<typeof resolveWebSearchRoute>[0]["config"],
|
|
330
|
+
states: { webSearch: ToolOwnershipState; webRun: ToolOwnershipState },
|
|
331
|
+
standaloneReady: boolean,
|
|
332
|
+
): WebSearchRouteResolution {
|
|
333
|
+
const resolution = resolveWebSearchRoute({ model, config });
|
|
334
|
+
if (resolution.route === "local" && resolution.available) {
|
|
335
|
+
releaseOwnership(pi, LOCAL_WEB_SEARCH_TOOL_NAME, states.webSearch);
|
|
336
|
+
claimAndRemove(pi, WEB_RUN_TOOL_NAME, states.webRun);
|
|
337
|
+
return resolution;
|
|
338
|
+
}
|
|
339
|
+
if (resolution.route === "standalone-alpha") {
|
|
340
|
+
claimAndRemove(pi, LOCAL_WEB_SEARCH_TOOL_NAME, states.webSearch);
|
|
341
|
+
if (resolution.available && standaloneReady) {
|
|
342
|
+
activateOwned(pi, WEB_RUN_TOOL_NAME, states.webRun);
|
|
343
|
+
} else {
|
|
344
|
+
claimAndRemove(pi, WEB_RUN_TOOL_NAME, states.webRun);
|
|
345
|
+
}
|
|
346
|
+
return resolution;
|
|
347
|
+
}
|
|
348
|
+
if (resolution.route === "hosted") {
|
|
349
|
+
claimAndRemove(pi, LOCAL_WEB_SEARCH_TOOL_NAME, states.webSearch);
|
|
350
|
+
claimAndRemove(pi, WEB_RUN_TOOL_NAME, states.webRun);
|
|
351
|
+
return resolution;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// `none`, and an explicit local route with no model, are fail-closed states:
|
|
355
|
+
// release any toolkit ownership, but do not activate another implementation.
|
|
356
|
+
if (resolution.route === "none" || (resolution.route === "local" && !resolution.available)) {
|
|
357
|
+
releaseOwnership(pi, LOCAL_WEB_SEARCH_TOOL_NAME, states.webSearch);
|
|
358
|
+
releaseOwnership(pi, WEB_RUN_TOOL_NAME, states.webRun);
|
|
359
|
+
} else {
|
|
360
|
+
claimAndRemove(pi, LOCAL_WEB_SEARCH_TOOL_NAME, states.webSearch);
|
|
361
|
+
claimAndRemove(pi, WEB_RUN_TOOL_NAME, states.webRun);
|
|
362
|
+
}
|
|
363
|
+
return resolution;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function routeReadyForPrompt(
|
|
367
|
+
resolution: WebSearchRouteResolution,
|
|
368
|
+
standaloneReady: boolean,
|
|
369
|
+
): boolean {
|
|
370
|
+
return resolution.route !== "none" && resolution.available &&
|
|
371
|
+
(resolution.route !== "standalone-alpha" || standaloneReady);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function registerWebSearchExtension(
|
|
375
|
+
pi: ExtensionAPI,
|
|
376
|
+
loadConfig: typeof loadToolkitConfig = loadToolkitConfig,
|
|
377
|
+
requestSearch: typeof requestAlphaSearch = requestAlphaSearch,
|
|
378
|
+
resolveRuntime: typeof resolveResponsesEnvironment = resolveResponsesEnvironment,
|
|
379
|
+
): void {
|
|
380
|
+
if (registeredApis.has(pi)) return;
|
|
381
|
+
registeredApis.add(pi);
|
|
382
|
+
|
|
383
|
+
const states = { webSearch: cloneToolState(), webRun: cloneToolState() };
|
|
384
|
+
let standaloneRegistrationSucceeded = false;
|
|
385
|
+
|
|
386
|
+
const standaloneTool = createWebRunTool({
|
|
387
|
+
loadConfig,
|
|
388
|
+
resolveRuntime,
|
|
389
|
+
requestSearch,
|
|
390
|
+
isRegistered: () => standaloneReady(),
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
const api = pi as ExtensionAPI & { registerTool?: (tool: typeof standaloneTool) => void };
|
|
395
|
+
if (typeof api.registerTool === "function") {
|
|
396
|
+
api.registerTool(standaloneTool);
|
|
397
|
+
standaloneRegistrationSucceeded = true;
|
|
398
|
+
// Pi initially activates newly registered extension tools. Treat that
|
|
399
|
+
// activation as toolkit-owned rather than as a third-party preference;
|
|
400
|
+
// otherwise an unconfigured route would leak web_run into every prompt.
|
|
401
|
+
states.webRun = { wasActiveBeforeOwnership: false, toolkitOwnsTool: true };
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
standaloneRegistrationSucceeded = false;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function standaloneRegistrationState(): StandaloneRegistrationState {
|
|
408
|
+
if (!standaloneRegistrationSucceeded || !canManageActiveTools(pi)) return "unavailable";
|
|
409
|
+
// Recheck on every lifecycle boundary so a later extension cannot replace
|
|
410
|
+
// the definition after startup and leave a stale active name executable.
|
|
411
|
+
// Do not use getAllTools() during extension loading; lifecycle callbacks
|
|
412
|
+
// are the first point where Pi's action methods are bound.
|
|
413
|
+
return verifyStandaloneRegistration(pi, standaloneTool);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function standaloneReady(): boolean {
|
|
417
|
+
return standaloneRegistrationState() === "verified";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function readConfig(model: WebSearchModel | undefined, ctx?: ExtensionContext): ResolvedToolkitConfig {
|
|
421
|
+
const resolved = resolveToolkitConfig(loadConfig(), model);
|
|
422
|
+
if (ctx) notifyConfigIssues(ctx, resolved);
|
|
423
|
+
return resolved;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function synchronizeResolved(model: WebSearchModel | undefined, resolved: ResolvedToolkitConfig): WebSearchRouteResolution {
|
|
427
|
+
if (resolved.invalidFeatures.some((feature) => feature === "webSearch" || feature === "compatibility")) {
|
|
428
|
+
claimAndRemove(pi, LOCAL_WEB_SEARCH_TOOL_NAME, states.webSearch);
|
|
429
|
+
claimAndRemove(pi, WEB_RUN_TOOL_NAME, states.webRun);
|
|
430
|
+
return { route: "none", source: "none", modelKey: resolved.modelKey, reason: "unconfigured" };
|
|
431
|
+
}
|
|
432
|
+
return syncWebSearchRoute(pi, model, resolved.config.webSearch, states, standaloneReady());
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function synchronize(model: WebSearchModel | undefined, ctx?: ExtensionContext): WebSearchRouteResolution {
|
|
436
|
+
return synchronizeResolved(model, readConfig(model, ctx));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
pi.on("session_start", (_event, ctx) => {
|
|
440
|
+
synchronize(ctx.model, ctx);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
pi.on("model_select", (event, ctx) => {
|
|
444
|
+
synchronize(event.model, ctx);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
448
|
+
const resolved = readConfig(ctx.model, ctx);
|
|
449
|
+
const { config } = resolved;
|
|
450
|
+
const resolution = synchronizeResolved(ctx.model, resolved);
|
|
451
|
+
const localToolIsActive = resolution.route === "local"
|
|
452
|
+
? isActiveTool(pi, LOCAL_WEB_SEARCH_TOOL_NAME)
|
|
453
|
+
: undefined;
|
|
454
|
+
const promptRouteReady = routeReadyForPrompt(resolution, standaloneReady()) &&
|
|
455
|
+
(resolution.route !== "local" || localToolIsActive === true);
|
|
456
|
+
const systemPrompt = appendWebSearchPrompt({
|
|
457
|
+
model: ctx.model,
|
|
458
|
+
config: config.webSearch,
|
|
459
|
+
systemPrompt: event.systemPrompt,
|
|
460
|
+
...(promptRouteReady ? {} : { routeAvailable: false }),
|
|
461
|
+
});
|
|
462
|
+
return systemPrompt === event.systemPrompt ? undefined : { systemPrompt };
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
466
|
+
const resolved = readConfig(ctx.model, ctx);
|
|
467
|
+
try { assertConfigValid(resolved, "webSearch", "compatibility"); }
|
|
468
|
+
catch (error) { abortAndThrow(ctx, error instanceof Error ? error.message : "Invalid Web Search configuration."); }
|
|
469
|
+
const { config } = resolved;
|
|
470
|
+
const resolution = synchronizeResolved(ctx.model, resolved);
|
|
471
|
+
const registration = standaloneRegistrationState();
|
|
472
|
+
if (resolution.route === "standalone-alpha" && resolution.available && registration === "unavailable") {
|
|
473
|
+
abortAndThrow(ctx, `${WEB_RUN_TOOL_NAME} is not registered by the toolkit; standalone Web Search request aborted.`);
|
|
474
|
+
}
|
|
475
|
+
const transformed = transformWebSearchPayload({
|
|
476
|
+
model: ctx.model,
|
|
477
|
+
config: config.webSearch,
|
|
478
|
+
payload: event.payload,
|
|
479
|
+
standaloneToolExcluded: registration === "excluded",
|
|
480
|
+
});
|
|
481
|
+
if (transformed.fatal) {
|
|
482
|
+
abortAndThrow(ctx, transformed.errorMessage ?? "Web Search route request aborted.");
|
|
483
|
+
}
|
|
484
|
+
return transformed.changed ? transformed.payload : undefined;
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
pi.on("tool_call", (event, ctx) => {
|
|
488
|
+
if (event.toolName !== LOCAL_WEB_SEARCH_TOOL_NAME && event.toolName !== WEB_RUN_TOOL_NAME) {
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
const resolved = readConfig(ctx.model, ctx);
|
|
492
|
+
const resolution = synchronizeResolved(ctx.model, resolved);
|
|
493
|
+
try { assertConfigValid(resolved, "webSearch", "compatibility"); }
|
|
494
|
+
catch (error) { return { block: true, reason: error instanceof Error ? error.message : "Invalid Web Search configuration." }; }
|
|
495
|
+
if (event.toolName === LOCAL_WEB_SEARCH_TOOL_NAME) {
|
|
496
|
+
if (resolution.route === "none") return undefined;
|
|
497
|
+
if (resolution.route === "local" && resolution.available) {
|
|
498
|
+
if (isActiveTool(pi, LOCAL_WEB_SEARCH_TOOL_NAME) === true) return undefined;
|
|
499
|
+
return {
|
|
500
|
+
block: true,
|
|
501
|
+
reason: "The local web_search tool is not active for the selected local Web Search route; the call was blocked instead of falling back.",
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
block: true,
|
|
506
|
+
reason: `${describeRouteFailure(resolution)} The local web_search call was blocked instead of falling back.`,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (resolution.route === "none") return undefined;
|
|
510
|
+
if (resolution.route === "standalone-alpha" && resolution.available && standaloneReady()) {
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
block: true,
|
|
515
|
+
reason: `${describeRouteFailure(resolution)} The ${WEB_RUN_TOOL_NAME} call was blocked instead of falling back.`,
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export default function webSearchExtension(pi: ExtensionAPI): void {
|
|
521
|
+
registerWebSearchExtension(pi);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export const _extensionTest = {
|
|
525
|
+
createWebRunTool,
|
|
526
|
+
verifyStandaloneRegistration,
|
|
527
|
+
syncWebSearchRoute,
|
|
528
|
+
standaloneRuntimeFailure,
|
|
529
|
+
};
|