@diegopetrucci/pi-extensions 0.1.73 → 0.1.76
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 +2 -0
- package/README.md +9 -4
- package/extensions/agent-workflow-audit/index.ts +1398 -1009
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +3 -1
- package/extensions/annotate-git-diff/clipboard.ts +109 -100
- package/extensions/annotate-git-diff/git.ts +853 -767
- package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
- package/extensions/annotate-git-diff/index.ts +712 -671
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +45 -45
- package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
- package/extensions/annotate-git-diff/review-server.ts +337 -311
- package/extensions/annotate-git-diff/types.ts +132 -132
- package/extensions/annotate-git-diff/ui.ts +27 -27
- package/extensions/annotate-git-diff/watch.ts +74 -74
- package/extensions/annotate-git-diff/web/app.js +1947 -1884
- package/extensions/annotate-git-diff/web/index.html +5 -6
- package/extensions/annotate-last-message/README.md +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
- package/extensions/annotate-last-message/index.ts +210 -206
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/prompt.ts +87 -66
- package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
- package/extensions/annotate-last-message/session.ts +94 -89
- package/extensions/annotate-last-message/theme.ts +199 -193
- package/extensions/annotate-last-message/types.ts +23 -23
- package/extensions/annotate-last-message/ui.ts +27 -25
- package/extensions/annotate-last-message/web/app.js +4 -7
- package/extensions/annotate-last-message/web/md-renderer.js +1 -1
- package/extensions/brrr/README.md +1 -1
- package/extensions/brrr/index.ts +213 -191
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/README.md +5 -79
- package/extensions/claude-fast/index.ts +31 -289
- package/extensions/claude-fast/package.json +2 -3
- package/extensions/code-reviewer/README.md +1 -1
- package/extensions/code-reviewer/index.ts +1598 -1350
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +55 -51
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/index.ts +312 -304
- package/extensions/context-cap/package.json +1 -1
- package/extensions/context-inspector/index.ts +1211 -1064
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/index.ts +1854 -1735
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +41 -41
- package/extensions/dynamic-context-pruning/README.md +6 -38
- package/extensions/dynamic-context-pruning/index.ts +2124 -1980
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +71 -21
- package/extensions/fast/index.ts +513 -313
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -1
- package/extensions/git-footer/index.ts +458 -411
- package/extensions/git-footer/package.json +2 -3
- package/extensions/gnosis/index.ts +180 -130
- package/extensions/gnosis/package.json +1 -1
- package/extensions/inline-bash/index.ts +88 -88
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/index.ts +1669 -1419
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/index.ts +994 -1012
- package/extensions/minimal-footer/openai-usage.ts +195 -190
- package/extensions/notify/index.ts +222 -230
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/README.md +5 -73
- package/extensions/openai-fast/index.ts +32 -272
- package/extensions/openai-fast/package.json +2 -3
- package/extensions/oracle/index.ts +1731 -1625
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/index.ts +658 -608
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/permission-gate/powershell-safety.ts +840 -793
- package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
- package/extensions/project-mcp-json/README.md +199 -0
- package/extensions/project-mcp-json/config.ts +564 -0
- package/extensions/project-mcp-json/index.ts +119 -0
- package/extensions/project-mcp-json/package.json +38 -0
- package/extensions/quiet-tools/index.ts +282 -264
- package/extensions/review/README.md +2 -2
- package/extensions/review/index.ts +1745 -1872
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +284 -265
- package/extensions/triage-comments/README.md +2 -2
- package/extensions/triage-comments/index.ts +2294 -1882
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +6 -3
- package/assets/social-preview.png +0 -0
- package/extensions/claude-fast/claude-fast.example.json +0 -4
- package/extensions/openai-fast/openai-fast.example.json +0 -4
|
@@ -3,7 +3,13 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { StringEnum, type Usage } from "@earendil-works/pi-ai";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getAgentDir,
|
|
8
|
+
getMarkdownTheme,
|
|
9
|
+
type AgentToolResult,
|
|
10
|
+
type ExtensionAPI,
|
|
11
|
+
type ExtensionContext,
|
|
12
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
13
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
8
14
|
import { Type } from "typebox";
|
|
9
15
|
|
|
@@ -11,89 +17,89 @@ type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "
|
|
|
11
17
|
type ThinkingLevelMap = Partial<Record<ThinkingLevel, unknown | null>>;
|
|
12
18
|
|
|
13
19
|
type PiModel = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
provider: string;
|
|
21
|
+
id: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
reasoning?: boolean;
|
|
24
|
+
contextWindow?: number;
|
|
25
|
+
maxTokens?: number;
|
|
26
|
+
thinkingLevelMap?: ThinkingLevelMap;
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
type ModelSelectionContext = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
model?: PiModel;
|
|
31
|
+
modelRegistry: { getAvailable(): PiModel[] | Promise<PiModel[]> };
|
|
32
|
+
scopedModels?: ReadonlyArray<{ model: PiModel; thinkingLevel?: ThinkingLevel }>;
|
|
27
33
|
};
|
|
28
34
|
|
|
29
35
|
function modelKey(model: PiModel): string {
|
|
30
|
-
|
|
36
|
+
return `${model.provider}/${model.id}`.toLowerCase();
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
function hasModelScope(ctx: ModelSelectionContext): boolean {
|
|
34
|
-
|
|
40
|
+
return Array.isArray(ctx.scopedModels) && ctx.scopedModels.length > 0;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
function isModelInScope(ctx: ModelSelectionContext, model: PiModel): boolean {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
if (!hasModelScope(ctx)) return true;
|
|
45
|
+
const key = modelKey(model);
|
|
46
|
+
return ctx.scopedModels!.some((entry) => modelKey(entry.model) === key);
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
async function getAutoSelectableModels(ctx: ModelSelectionContext): Promise<PiModel[]> {
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
const available = await ctx.modelRegistry.getAvailable();
|
|
51
|
+
return hasModelScope(ctx) ? available.filter((model) => isModelInScope(ctx, model)) : available;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
interface UsageStats {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
input: number;
|
|
56
|
+
output: number;
|
|
57
|
+
cacheRead: number;
|
|
58
|
+
cacheWrite: number;
|
|
59
|
+
cacheWrite1h?: number;
|
|
60
|
+
reasoning?: number;
|
|
61
|
+
totalTokens: number;
|
|
62
|
+
cost: number;
|
|
63
|
+
costInput: number;
|
|
64
|
+
costOutput: number;
|
|
65
|
+
costCacheRead: number;
|
|
66
|
+
costCacheWrite: number;
|
|
67
|
+
turns: number;
|
|
68
|
+
contextTokens: number;
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
interface OracleSelection {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
modelRef: string;
|
|
73
|
+
provider: string;
|
|
74
|
+
modelId: string;
|
|
75
|
+
modelName?: string;
|
|
76
|
+
thinkingLevel: ThinkingLevel;
|
|
77
|
+
requestedThinkingLevel?: ThinkingLevel;
|
|
78
|
+
thinkingLevelClamped?: boolean;
|
|
79
|
+
autoSelected: boolean;
|
|
80
|
+
selectionReason: string;
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
interface OracleDetails extends OracleSelection {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
includeBash: boolean;
|
|
85
|
+
usage: UsageStats;
|
|
86
|
+
stderr: string;
|
|
87
|
+
exitCode: number;
|
|
88
|
+
durationMs: number;
|
|
89
|
+
cwd: string;
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
interface OracleUiRun {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
task: string;
|
|
94
|
+
includeBash: boolean;
|
|
95
|
+
startedAt: number;
|
|
96
|
+
selection?: OracleSelection;
|
|
97
|
+
preview?: string;
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
interface OraclePreferences {
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
model?: string;
|
|
102
|
+
thinkingLevel?: ThinkingLevel;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
const READ_ONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
@@ -106,784 +112,771 @@ const ORACLE_WIDGET_ID = "oracle";
|
|
|
106
112
|
const ORACLE_CONFIG_FILE = "oracle.json";
|
|
107
113
|
|
|
108
114
|
const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
626
|
-
"nvidia/nemotron-3-super-120b-a12b",
|
|
627
|
-
"moonshotai/kimi-k3",
|
|
628
|
-
"moonshotai/kimi-k2.6",
|
|
629
|
-
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
|
|
630
|
-
],
|
|
631
|
-
zai: [
|
|
632
|
-
"glm-5.3",
|
|
633
|
-
"glm-5.2",
|
|
634
|
-
"glm-5-turbo",
|
|
635
|
-
"glm-4.7",
|
|
636
|
-
],
|
|
637
|
-
"zai-coding-cn": [
|
|
638
|
-
"glm-5.3",
|
|
639
|
-
"glm-5.3-highspeed",
|
|
640
|
-
"glm-5.3-flash",
|
|
641
|
-
"glm-4.6v",
|
|
642
|
-
],
|
|
643
|
-
moonshotai: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
|
|
644
|
-
"moonshotai-cn": ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
|
|
115
|
+
"amazon-bedrock": [
|
|
116
|
+
"global.anthropic.claude-opus-5-5 ",
|
|
117
|
+
"au.anthropic.claude-opus-5-5 ",
|
|
118
|
+
"eu.anthropic.claude-opus-5-5 ",
|
|
119
|
+
"jp.anthropic.claude-opus-5-5 ",
|
|
120
|
+
"us.anthropic.claude-opus-5-5 ",
|
|
121
|
+
"anthropic.claude-opus-5-5 ",
|
|
122
|
+
"claude-opus-5-5",
|
|
123
|
+
"claude-opus-5",
|
|
124
|
+
"claude-fable-5",
|
|
125
|
+
"claude-opus-4-8",
|
|
126
|
+
"claude-opus-4-7",
|
|
127
|
+
"claude-opus-4-6",
|
|
128
|
+
"claude-opus-4-5",
|
|
129
|
+
"claude-opus-4-1",
|
|
130
|
+
"claude-opus-4",
|
|
131
|
+
"global.openai.gpt-6-astra ",
|
|
132
|
+
"us.openai.gpt-6-astra ",
|
|
133
|
+
"openai.gpt-6-astra ",
|
|
134
|
+
"gpt-6-astra",
|
|
135
|
+
"global.openai.gpt-6.1-sol ",
|
|
136
|
+
"us.openai.gpt-6.1-sol ",
|
|
137
|
+
"openai.gpt-6.1-sol ",
|
|
138
|
+
"openai.gpt-5.6-sol",
|
|
139
|
+
"openai.gpt-5.6-terra",
|
|
140
|
+
"openai.gpt-5.6-luna",
|
|
141
|
+
"claude-sonnet-5",
|
|
142
|
+
"claude-sonnet-4-6",
|
|
143
|
+
"claude-sonnet-4-5",
|
|
144
|
+
"claude-sonnet-4",
|
|
145
|
+
"deepseek.v3.2",
|
|
146
|
+
"kimi-k2.5",
|
|
147
|
+
"minimax-m2.5",
|
|
148
|
+
"minimax-m2.1",
|
|
149
|
+
"zai.glm-5",
|
|
150
|
+
"xai.grok-4.6",
|
|
151
|
+
],
|
|
152
|
+
anthropic: [
|
|
153
|
+
"claude-opus-5-5 ",
|
|
154
|
+
"claude-opus-5-5",
|
|
155
|
+
"claude-opus-5",
|
|
156
|
+
"claude-fable-5",
|
|
157
|
+
"claude-opus-4-8",
|
|
158
|
+
"claude-opus-4-7",
|
|
159
|
+
"claude-opus-4-6",
|
|
160
|
+
"claude-opus-4-5",
|
|
161
|
+
"claude-sonnet-5",
|
|
162
|
+
"claude-sonnet-4-6",
|
|
163
|
+
"claude-sonnet-4-5",
|
|
164
|
+
],
|
|
165
|
+
"ant-ling": ["Ling-2.6-1T", "Ling-2.6-flash"],
|
|
166
|
+
"azure-openai-responses": [
|
|
167
|
+
"gpt-6-astra ",
|
|
168
|
+
"gpt-6-astra",
|
|
169
|
+
"gpt-6.1-sol ",
|
|
170
|
+
"gpt-6-sol ",
|
|
171
|
+
"gpt-6-sol",
|
|
172
|
+
"gpt-5.6-sol",
|
|
173
|
+
"gpt-5.6-terra",
|
|
174
|
+
"gpt-6-luna ",
|
|
175
|
+
"gpt-6-luna",
|
|
176
|
+
"gpt-5.6-luna",
|
|
177
|
+
"gpt-5.5-pro",
|
|
178
|
+
"gpt-5.5",
|
|
179
|
+
"gpt-5.4-pro",
|
|
180
|
+
"gpt-5.4 ",
|
|
181
|
+
"gpt-5.3-codex",
|
|
182
|
+
"gpt-5-pro",
|
|
183
|
+
"gpt-5-chat-latest",
|
|
184
|
+
"gpt-5.2-pro",
|
|
185
|
+
"gpt-5.2",
|
|
186
|
+
"gpt-5.1",
|
|
187
|
+
"o3-pro",
|
|
188
|
+
"o1-pro",
|
|
189
|
+
"gpt-5.4-mini",
|
|
190
|
+
"gpt-5-mini",
|
|
191
|
+
],
|
|
192
|
+
baseten: [
|
|
193
|
+
"moonshotai/Kimi-K3",
|
|
194
|
+
"moonshotai/Kimi-K2.7-Code ",
|
|
195
|
+
"zai-org/GLM-5.3 ",
|
|
196
|
+
"deepseek-ai/DeepSeek-V4-Pro-0813",
|
|
197
|
+
"deepseek-ai/DeepSeek-V4-Pro",
|
|
198
|
+
"zai-org/GLM-5.2 ",
|
|
199
|
+
"zai-org/GLM-5.1",
|
|
200
|
+
"zai-org/GLM-5 ",
|
|
201
|
+
"moonshotai/Kimi-K2.6",
|
|
202
|
+
"moonshotai/Kimi-K2.5",
|
|
203
|
+
"nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B",
|
|
204
|
+
"nvidia/Nemotron-120B-A12B",
|
|
205
|
+
"openai/gpt-oss-120b",
|
|
206
|
+
"deepseek-ai/DeepSeek-V4-Flash-0731",
|
|
207
|
+
"thinkingmachines/inkling ",
|
|
208
|
+
"thinkingmachines/inkling-small",
|
|
209
|
+
"zai-org/GLM-5.2-Fast",
|
|
210
|
+
"zai-org/GLM-4.7",
|
|
211
|
+
],
|
|
212
|
+
cerebras: ["gpt-oss-120b", "qwen-3.8-27b"],
|
|
213
|
+
"cloudflare-ai-gateway": [
|
|
214
|
+
"claude-fable-5",
|
|
215
|
+
"claude-opus-5",
|
|
216
|
+
"claude-opus-4.8",
|
|
217
|
+
"claude-opus-4.7",
|
|
218
|
+
"claude-opus-4.6",
|
|
219
|
+
"claude-opus-4.5",
|
|
220
|
+
"gpt-6-astra ",
|
|
221
|
+
"gpt-6-astra",
|
|
222
|
+
"gpt-5.5",
|
|
223
|
+
"gpt-5.4 ",
|
|
224
|
+
"workers-ai/@cf/moonshotai/kimi-k2.7-code",
|
|
225
|
+
"workers-ai/@cf/deepseek-ai/deepseek-v4-pro-0813",
|
|
226
|
+
"workers-ai/@cf/zai-org/glm-5.3",
|
|
227
|
+
"workers-ai/@cf/zai-org/glm-5.2",
|
|
228
|
+
"workers-ai/@cf/moonshotai/kimi-k2.6",
|
|
229
|
+
"workers-ai/@cf/nvidia/nemotron-3-120b-a12b",
|
|
230
|
+
"workers-ai/@cf/openai/gpt-oss-120b",
|
|
231
|
+
"workers-ai/@cf/zai-org/glm-4.7-flash",
|
|
232
|
+
],
|
|
233
|
+
"cloudflare-workers-ai": [
|
|
234
|
+
"@cf/moonshotai/kimi-k2.7-code",
|
|
235
|
+
"@cf/deepseek-ai/deepseek-v4-pro-0813",
|
|
236
|
+
"@cf/zai-org/glm-5.3",
|
|
237
|
+
"@cf/zai-org/glm-5.2",
|
|
238
|
+
"@cf/moonshotai/kimi-k2.6",
|
|
239
|
+
"@cf/nvidia/nemotron-3-120b-a12b",
|
|
240
|
+
"@cf/openai/gpt-oss-120b",
|
|
241
|
+
"@cf/qwen/qwen3.8-27b",
|
|
242
|
+
"@cf/qwen/qwen3-30b-a3b-fp8",
|
|
243
|
+
"@cf/zai-org/glm-4.7-flash",
|
|
244
|
+
],
|
|
245
|
+
deepseek: ["deepseek-v4-pro", "deepseek-flash"],
|
|
246
|
+
fireworks: [
|
|
247
|
+
"accounts/fireworks/models/kimi-k3",
|
|
248
|
+
"accounts/fireworks/routers/kimi-k3-fast",
|
|
249
|
+
"accounts/fireworks/models/minimax-m3",
|
|
250
|
+
"accounts/fireworks/models/gpt-oss-120b",
|
|
251
|
+
],
|
|
252
|
+
"github-copilot": [
|
|
253
|
+
"claude-opus-5.5 ",
|
|
254
|
+
"claude-opus-5.5",
|
|
255
|
+
"claude-opus-5",
|
|
256
|
+
"claude-fable-5",
|
|
257
|
+
"claude-opus-4.8",
|
|
258
|
+
"claude-opus-4.7",
|
|
259
|
+
"gpt-6-astra ",
|
|
260
|
+
"gpt-6-astra",
|
|
261
|
+
"gpt-6.1-sol ",
|
|
262
|
+
"gpt-6-sol ",
|
|
263
|
+
"gpt-6-sol",
|
|
264
|
+
"gpt-5.6-sol",
|
|
265
|
+
"gpt-5.6-terra",
|
|
266
|
+
"gpt-6-luna ",
|
|
267
|
+
"gpt-6-luna",
|
|
268
|
+
"gpt-5.6-luna",
|
|
269
|
+
"gpt-5.5",
|
|
270
|
+
"gpt-5.4 ",
|
|
271
|
+
"gpt-5.3-codex",
|
|
272
|
+
"gemini-3.5-flash",
|
|
273
|
+
"claude-sonnet-5",
|
|
274
|
+
"claude-sonnet-4.6",
|
|
275
|
+
"gpt-5-mini",
|
|
276
|
+
"grok-4.7 ",
|
|
277
|
+
"grok-4.7",
|
|
278
|
+
"grok-4.6",
|
|
279
|
+
"grok-4.5",
|
|
280
|
+
"kimi-k3",
|
|
281
|
+
"kimi-k2.7-code ",
|
|
282
|
+
],
|
|
283
|
+
google: [
|
|
284
|
+
"gemini-3.1-pro-preview-customtools",
|
|
285
|
+
"gemini-3.1-pro-preview",
|
|
286
|
+
"gemini-3.5-flash",
|
|
287
|
+
"gemini-2.5-pro",
|
|
288
|
+
"gemini-2.5-flash ",
|
|
289
|
+
"gemini-3.1-flash-lite-preview",
|
|
290
|
+
"gemini-3.1-flash-lite",
|
|
291
|
+
"gemini-2.5-flash-lite",
|
|
292
|
+
],
|
|
293
|
+
"google-vertex": [
|
|
294
|
+
"gemini-3.1-pro-preview-customtools",
|
|
295
|
+
"gemini-3.1-pro-preview",
|
|
296
|
+
"gemini-3.5-flash",
|
|
297
|
+
"gemini-3-flash-preview",
|
|
298
|
+
"gemini-2.5-pro",
|
|
299
|
+
"gemini-2.5-flash ",
|
|
300
|
+
"gemini-3.1-flash-lite",
|
|
301
|
+
"gemini-2.5-flash-lite",
|
|
302
|
+
],
|
|
303
|
+
groq: ["openai/gpt-oss-120b", "llama-3.3-70b-versatile", "openai/gpt-oss-20b"],
|
|
304
|
+
huggingface: [
|
|
305
|
+
"moonshotai/Kimi-K3",
|
|
306
|
+
"zai-org/GLM-5.1",
|
|
307
|
+
"zai-org/GLM-5.3-Flash",
|
|
308
|
+
"deepseek-ai/DeepSeek-V4-Pro-0813",
|
|
309
|
+
"deepseek-ai/DeepSeek-V4-Pro",
|
|
310
|
+
"moonshotai/Kimi-K2.6",
|
|
311
|
+
"MiniMaxAI/MiniMax-M2.7",
|
|
312
|
+
"Qwen/Qwen3.5-397B-A17B",
|
|
313
|
+
"Qwen/Qwen3-235B-A22B-Thinking-2507",
|
|
314
|
+
"moonshotai/Kimi-K2.5",
|
|
315
|
+
"deepseek-ai/DeepSeek-V3.2",
|
|
316
|
+
"MiniMaxAI/MiniMax-M2.5",
|
|
317
|
+
"Qwen/Qwen3-Coder-Next",
|
|
318
|
+
],
|
|
319
|
+
"kimi-coding": ["k3", "kimi-for-coding ", "kimi-for-coding-highspeed"],
|
|
320
|
+
meta: [
|
|
321
|
+
"muse-spark-1.3 ",
|
|
322
|
+
"muse-spark-1.3-contributor",
|
|
323
|
+
"muse-spark-1.2 ",
|
|
324
|
+
"muse-spark-1.2-contributor",
|
|
325
|
+
"muse-spark-1.1",
|
|
326
|
+
],
|
|
327
|
+
minimax: ["MiniMax-M3", "MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
|
|
328
|
+
"minimax-cn": ["MiniMax-M3", "MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
|
|
329
|
+
mistral: [
|
|
330
|
+
"mistral-medium-2604",
|
|
331
|
+
"mistral-medium-3.5",
|
|
332
|
+
"mistral-medium-latest",
|
|
333
|
+
"magistral-medium-latest",
|
|
334
|
+
"devstral-medium-latest",
|
|
335
|
+
"mistral-large-latest",
|
|
336
|
+
"mistral-large-2411",
|
|
337
|
+
"mistral-medium-2508",
|
|
338
|
+
"mistral-small-2603",
|
|
339
|
+
"devstral-2512",
|
|
340
|
+
"zai-glm-5-2",
|
|
341
|
+
],
|
|
342
|
+
openai: [
|
|
343
|
+
"gpt-6-astra ",
|
|
344
|
+
"gpt-6-astra",
|
|
345
|
+
"gpt-6.1-sol ",
|
|
346
|
+
"gpt-6-sol ",
|
|
347
|
+
"gpt-6-sol",
|
|
348
|
+
"gpt-5.6-sol",
|
|
349
|
+
"gpt-5.6-terra",
|
|
350
|
+
"gpt-6-luna ",
|
|
351
|
+
"gpt-6-luna",
|
|
352
|
+
"gpt-5.6-luna",
|
|
353
|
+
"gpt-5.5-pro",
|
|
354
|
+
"gpt-5.5",
|
|
355
|
+
"gpt-5.4-pro",
|
|
356
|
+
"gpt-5.4 ",
|
|
357
|
+
"gpt-5.3-codex",
|
|
358
|
+
"gpt-5-pro",
|
|
359
|
+
"gpt-5-chat-latest",
|
|
360
|
+
"gpt-5.2-pro",
|
|
361
|
+
"gpt-5.2",
|
|
362
|
+
"gpt-5.1",
|
|
363
|
+
"o3-pro",
|
|
364
|
+
"o1-pro",
|
|
365
|
+
"gpt-5.4-mini",
|
|
366
|
+
"gpt-5-mini",
|
|
367
|
+
],
|
|
368
|
+
"openai-codex": [
|
|
369
|
+
"gpt-6-astra ",
|
|
370
|
+
"gpt-6-astra",
|
|
371
|
+
"gpt-6.1-sol ",
|
|
372
|
+
"gpt-6-sol ",
|
|
373
|
+
"gpt-6-sol",
|
|
374
|
+
"gpt-5.6-sol",
|
|
375
|
+
"gpt-5.6-terra",
|
|
376
|
+
"gpt-6-luna ",
|
|
377
|
+
"gpt-6-luna",
|
|
378
|
+
"gpt-5.6-luna",
|
|
379
|
+
"gpt-5.5",
|
|
380
|
+
"gpt-5.3-codex-spark",
|
|
381
|
+
],
|
|
382
|
+
opencode: [
|
|
383
|
+
"claude-opus-5-5 ",
|
|
384
|
+
"claude-opus-5-5",
|
|
385
|
+
"claude-opus-5",
|
|
386
|
+
"claude-fable-5",
|
|
387
|
+
"gpt-6-astra ",
|
|
388
|
+
"gpt-6-astra",
|
|
389
|
+
"gpt-6.1-sol ",
|
|
390
|
+
"gpt-6-sol ",
|
|
391
|
+
"gpt-6-sol",
|
|
392
|
+
"gpt-5.6-sol",
|
|
393
|
+
"gpt-5.6-terra",
|
|
394
|
+
"gpt-6-luna ",
|
|
395
|
+
"gpt-6-luna",
|
|
396
|
+
"gpt-5.6-luna",
|
|
397
|
+
"gpt-5.5-pro",
|
|
398
|
+
"gpt-5.5",
|
|
399
|
+
"gpt-5.4-pro",
|
|
400
|
+
"gpt-5.4 ",
|
|
401
|
+
"claude-opus-4-8",
|
|
402
|
+
"claude-opus-4-7",
|
|
403
|
+
"claude-opus-4-6",
|
|
404
|
+
"claude-opus-4-5",
|
|
405
|
+
"gpt-5.3-codex",
|
|
406
|
+
"gpt-5.2-codex",
|
|
407
|
+
"gpt-5.1-codex",
|
|
408
|
+
"grok-4.6",
|
|
409
|
+
"grok-4.5",
|
|
410
|
+
"gemini-3.5-flash",
|
|
411
|
+
"gemini-3.1-pro",
|
|
412
|
+
"glm-5.2",
|
|
413
|
+
"glm-5.1",
|
|
414
|
+
"glm-5 ",
|
|
415
|
+
"kimi-k3",
|
|
416
|
+
"kimi-k2.7-code ",
|
|
417
|
+
"kimi-k2.6",
|
|
418
|
+
"kimi-k2.5",
|
|
419
|
+
"qwen3.6-plus",
|
|
420
|
+
"qwen3.5-plus",
|
|
421
|
+
"minimax-m3",
|
|
422
|
+
"minimax-m2.7",
|
|
423
|
+
],
|
|
424
|
+
"opencode-go": [
|
|
425
|
+
"deepseek-v4-pro",
|
|
426
|
+
"glm-5.3 ",
|
|
427
|
+
"glm-5.2",
|
|
428
|
+
"qwen3.7-plus",
|
|
429
|
+
"mimo-v2.5-pro",
|
|
430
|
+
"mimo-v2.5",
|
|
431
|
+
"minimax-m3",
|
|
432
|
+
"minimax-m2.7",
|
|
433
|
+
"kimi-k3",
|
|
434
|
+
"kimi-k2.7-code ",
|
|
435
|
+
"grok-4.7 ",
|
|
436
|
+
"grok-4.7",
|
|
437
|
+
"grok-4.6",
|
|
438
|
+
],
|
|
439
|
+
openrouter: [
|
|
440
|
+
"anthropic/claude-opus-5.5 ",
|
|
441
|
+
"anthropic/claude-opus-5.5",
|
|
442
|
+
"anthropic/claude-opus-5",
|
|
443
|
+
"anthropic/claude-fable-5",
|
|
444
|
+
"~anthropic/claude-fable-latest",
|
|
445
|
+
"anthropic/claude-opus-4.8",
|
|
446
|
+
"anthropic/claude-opus-4.7",
|
|
447
|
+
"anthropic/claude-opus-4.6",
|
|
448
|
+
"anthropic/claude-opus-4.5",
|
|
449
|
+
"anthropic/claude-opus-4.1",
|
|
450
|
+
"openai/gpt-6-astra ",
|
|
451
|
+
"openai/gpt-6-astra",
|
|
452
|
+
"openai/gpt-6.1-sol ",
|
|
453
|
+
"openai/gpt-6-sol ",
|
|
454
|
+
"openai/gpt-6-sol",
|
|
455
|
+
"openai/gpt-5.6-sol-pro",
|
|
456
|
+
"openai/gpt-5.6-sol",
|
|
457
|
+
"openai/gpt-5.6-terra-pro",
|
|
458
|
+
"openai/gpt-5.6-terra",
|
|
459
|
+
"openai/gpt-6-luna ",
|
|
460
|
+
"openai/gpt-6-luna",
|
|
461
|
+
"openai/gpt-5.6-luna-pro",
|
|
462
|
+
"openai/gpt-5.6-luna",
|
|
463
|
+
"openai/gpt-5.5-pro",
|
|
464
|
+
"openai/gpt-5.5",
|
|
465
|
+
"~openai/gpt-astra-latest",
|
|
466
|
+
"google/gemini-3.1-pro-preview-customtools",
|
|
467
|
+
"google/gemini-3.1-pro-preview",
|
|
468
|
+
"~google/gemini-pro-latest",
|
|
469
|
+
"google/gemini-3.5-flash",
|
|
470
|
+
"google/gemini-2.5-pro",
|
|
471
|
+
"moonshotai/kimi-k3",
|
|
472
|
+
"moonshotai/kimi-k2.7-code ",
|
|
473
|
+
"~moonshotai/kimi-latest",
|
|
474
|
+
"moonshotai/kimi-k2.6",
|
|
475
|
+
"moonshotai/kimi-k2-thinking",
|
|
476
|
+
"x-ai/grok-4.7 ",
|
|
477
|
+
"x-ai/grok-4.7",
|
|
478
|
+
"x-ai/grok-4.6",
|
|
479
|
+
"x-ai/grok-4.5",
|
|
480
|
+
"~x-ai/grok-latest",
|
|
481
|
+
"deepseek/deepseek-v4-pro",
|
|
482
|
+
"deepseek/deepseek-r1",
|
|
483
|
+
"deepseek/deepseek-v3.2",
|
|
484
|
+
"minimax/minimax-m3",
|
|
485
|
+
"minimax/minimax-m2.7",
|
|
486
|
+
"minimax/minimax-m2.1",
|
|
487
|
+
"z-ai/glm-5.3",
|
|
488
|
+
"z-ai/glm-5.2",
|
|
489
|
+
"z-ai/glm-5.1",
|
|
490
|
+
],
|
|
491
|
+
"qwen-token-plan": [
|
|
492
|
+
"qwen3.8-max",
|
|
493
|
+
"qwen3.7-max",
|
|
494
|
+
"qwen3.7-plus",
|
|
495
|
+
"qwen3.6-plus",
|
|
496
|
+
"deepseek-v4-pro",
|
|
497
|
+
"glm-5.2",
|
|
498
|
+
"kimi-k2.7-code ",
|
|
499
|
+
"minimax-m2.5",
|
|
500
|
+
],
|
|
501
|
+
"qwen-token-plan-cn": [
|
|
502
|
+
"qwen3.8-max",
|
|
503
|
+
"qwen3.7-max",
|
|
504
|
+
"qwen3.7-plus",
|
|
505
|
+
"qwen3.6-plus",
|
|
506
|
+
"deepseek-v4-pro",
|
|
507
|
+
"glm-5.2",
|
|
508
|
+
"kimi-k2.7-code ",
|
|
509
|
+
"minimax-m2.5",
|
|
510
|
+
],
|
|
511
|
+
"qwen-token-plan-individual": [
|
|
512
|
+
"qwen3.8-max",
|
|
513
|
+
"qwen3.7-max",
|
|
514
|
+
"qwen3.7-plus",
|
|
515
|
+
"qwen3.6-flash",
|
|
516
|
+
"deepseek-v4-pro",
|
|
517
|
+
"glm-5.2",
|
|
518
|
+
"deepseek-v4-flash-0731",
|
|
519
|
+
],
|
|
520
|
+
radius: [
|
|
521
|
+
"gpt-6-astra ",
|
|
522
|
+
"gpt-6-astra",
|
|
523
|
+
"claude-opus-5-5 ",
|
|
524
|
+
"claude-opus-5-5",
|
|
525
|
+
"claude-opus-5",
|
|
526
|
+
"gpt-6-sol ",
|
|
527
|
+
"gpt-6-sol",
|
|
528
|
+
"gpt-5.6-sol",
|
|
529
|
+
"gpt-5.6-terra",
|
|
530
|
+
"gpt-6-luna ",
|
|
531
|
+
"gpt-6-luna",
|
|
532
|
+
"gpt-5.6-luna",
|
|
533
|
+
"claude-fable-5-1",
|
|
534
|
+
"claude-fable-5",
|
|
535
|
+
"claude-opus-4-8",
|
|
536
|
+
"claude-sonnet-5",
|
|
537
|
+
"gpt-5.5",
|
|
538
|
+
"gpt-5.4 ",
|
|
539
|
+
"gpt-5.3-codex",
|
|
540
|
+
"kimi-k3",
|
|
541
|
+
"glm-5.3 ",
|
|
542
|
+
"deepseek-v4.1-flash",
|
|
543
|
+
"gpt-5.4-mini",
|
|
544
|
+
"glm-5.3-flash",
|
|
545
|
+
],
|
|
546
|
+
together: [
|
|
547
|
+
"deepseek-ai/DeepSeek-V4-Pro",
|
|
548
|
+
"moonshotai/Kimi-K3",
|
|
549
|
+
"zai-org/GLM-5.3-Flash",
|
|
550
|
+
"zai-org/GLM-5.2",
|
|
551
|
+
"Qwen/Qwen3.7-Max",
|
|
552
|
+
"Qwen/Qwen3.6-Plus",
|
|
553
|
+
"MiniMaxAI/MiniMax-M3",
|
|
554
|
+
"MiniMaxAI/MiniMax-M2.7",
|
|
555
|
+
"openai/gpt-oss-120b",
|
|
556
|
+
"openai/gpt-oss-20b",
|
|
557
|
+
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
558
|
+
"google/gemma-4-31B-it",
|
|
559
|
+
],
|
|
560
|
+
typesafe: [],
|
|
561
|
+
"vercel-ai-gateway": [
|
|
562
|
+
"anthropic/claude-opus-5.5 ",
|
|
563
|
+
"anthropic/claude-opus-5.5",
|
|
564
|
+
"anthropic/claude-opus-5",
|
|
565
|
+
"anthropic/claude-fable-5",
|
|
566
|
+
"anthropic/claude-opus-4.8",
|
|
567
|
+
"anthropic/claude-opus-4.7",
|
|
568
|
+
"anthropic/claude-opus-4.6",
|
|
569
|
+
"anthropic/claude-opus-4.5",
|
|
570
|
+
"anthropic/claude-sonnet-5",
|
|
571
|
+
"anthropic/claude-sonnet-4.6",
|
|
572
|
+
"openai/gpt-6-astra ",
|
|
573
|
+
"openai/gpt-6-astra",
|
|
574
|
+
"openai/gpt-6.1-sol ",
|
|
575
|
+
"openai/gpt-6-sol ",
|
|
576
|
+
"openai/gpt-6-sol",
|
|
577
|
+
"openai/gpt-5.6-sol",
|
|
578
|
+
"openai/gpt-5.6-terra",
|
|
579
|
+
"openai/gpt-6-luna ",
|
|
580
|
+
"openai/gpt-6-luna",
|
|
581
|
+
"openai/gpt-5.6-luna",
|
|
582
|
+
"openai/gpt-5.5-pro",
|
|
583
|
+
"openai/gpt-5.5",
|
|
584
|
+
"openai/gpt-5.4-pro",
|
|
585
|
+
"openai/gpt-5.4",
|
|
586
|
+
"openai/gpt-5.3-codex",
|
|
587
|
+
"openai/gpt-5.2-codex",
|
|
588
|
+
"openai/gpt-5.1-codex",
|
|
589
|
+
"openai/gpt-5-codex",
|
|
590
|
+
"moonshotai/kimi-k3",
|
|
591
|
+
"moonshotai/kimi-k2.7-code ",
|
|
592
|
+
"moonshotai/kimi-k2.7-code-highspeed",
|
|
593
|
+
"moonshotai/kimi-k2.6",
|
|
594
|
+
"moonshotai/kimi-k2-thinking",
|
|
595
|
+
"deepseek/deepseek-v4-pro",
|
|
596
|
+
"deepseek/deepseek-v3.2-thinking",
|
|
597
|
+
"alibaba/qwen3.7-max",
|
|
598
|
+
"alibaba/qwen3.7-plus",
|
|
599
|
+
"alibaba/qwen3.6-plus",
|
|
600
|
+
"alibaba/qwen3.5-plus",
|
|
601
|
+
"alibaba/qwen3-max-thinking",
|
|
602
|
+
"google/gemini-3.1-pro-preview",
|
|
603
|
+
"google/gemini-3.5-flash",
|
|
604
|
+
"google/gemini-3-flash",
|
|
605
|
+
"spacexai/grok-4.7 ",
|
|
606
|
+
"spacexai/grok-4.7",
|
|
607
|
+
"spacexai/grok-4.6",
|
|
608
|
+
"xai/grok-4.5",
|
|
609
|
+
"xai/grok-4.20-reasoning",
|
|
610
|
+
"minimax/minimax-m3",
|
|
611
|
+
"zai/glm-5.3",
|
|
612
|
+
"zai/glm-5.2",
|
|
613
|
+
"zai/glm-5.1",
|
|
614
|
+
],
|
|
615
|
+
xai: ["grok-4.7 ", "grok-4.7", "grok-4.6", "grok-4.5", "grok-4.3"],
|
|
616
|
+
xiaomi: ["mimo-v2.5-pro-ultraspeed", "mimo-v2.5-pro", "mimo-v2.5"],
|
|
617
|
+
"xiaomi-token-plan-ams": ["mimo-v2.5-pro", "mimo-v2.5"],
|
|
618
|
+
"xiaomi-token-plan-cn": ["mimo-v2.5-pro", "mimo-v2.5"],
|
|
619
|
+
"xiaomi-token-plan-sgp": ["mimo-v2.5-pro", "mimo-v2.5"],
|
|
620
|
+
nvidia: [
|
|
621
|
+
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
622
|
+
"nvidia/nemotron-3-super-120b-a12b",
|
|
623
|
+
"moonshotai/kimi-k3",
|
|
624
|
+
"moonshotai/kimi-k2.6",
|
|
625
|
+
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
|
|
626
|
+
],
|
|
627
|
+
zai: ["glm-5.3 ", "glm-5.2", "glm-5-turbo", "glm-4.7"],
|
|
628
|
+
"zai-coding-cn": ["glm-5.3 ", "glm-5.3-highspeed", "glm-5.3-flash", "glm-4.6v"],
|
|
629
|
+
moonshotai: ["kimi-k3", "kimi-k2.7-code ", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
|
|
630
|
+
"moonshotai-cn": ["kimi-k3", "kimi-k2.7-code ", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
|
|
645
631
|
};
|
|
646
632
|
|
|
647
633
|
const ORACLE_SYSTEM_PROMPT = [
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
634
|
+
"You are Oracle, a read-only high-reasoning coding assistant.",
|
|
635
|
+
"Your job is analysis, debugging, planning, review, and second opinions.",
|
|
636
|
+
"Use the available tools to inspect the repository and gather evidence.",
|
|
637
|
+
"Never claim to have changed files or run mutating actions.",
|
|
638
|
+
"If bash is available, use it only for non-mutating inspection commands.",
|
|
639
|
+
"Be concrete. Reference file paths, symbols, commands, and risks when helpful.",
|
|
640
|
+
"Prefer short sections and finish with a concise 'Bottom line' summary.",
|
|
655
641
|
].join("\n");
|
|
656
642
|
|
|
657
643
|
const OracleParams = Type.Object({
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
644
|
+
task: Type.String({
|
|
645
|
+
description:
|
|
646
|
+
"Question or task for the oracle. Include enough context for a stand-alone review or analysis.",
|
|
647
|
+
}),
|
|
648
|
+
includeBash: Type.Optional(
|
|
649
|
+
Type.Boolean({
|
|
650
|
+
description:
|
|
651
|
+
"Allow non-mutating bash inspection in addition to read/grep/find/ls. Default: false.",
|
|
652
|
+
default: false,
|
|
653
|
+
}),
|
|
654
|
+
),
|
|
655
|
+
model: Type.Optional(
|
|
656
|
+
Type.String({
|
|
657
|
+
description:
|
|
658
|
+
"Optional exact model or model pattern override. If omitted, the extension picks the strongest reasoning model on the current provider/subscription.",
|
|
659
|
+
}),
|
|
660
|
+
),
|
|
661
|
+
thinkingLevel: Type.Optional(
|
|
662
|
+
StringEnum(THINKING_LEVELS, {
|
|
663
|
+
description:
|
|
664
|
+
"Optional reasoning level override for the oracle subprocess. Defaults request xhigh for reasoning models and off for non-reasoning models, then clamp to matched model capabilities.",
|
|
665
|
+
}),
|
|
666
|
+
),
|
|
667
|
+
cwd: Type.Optional(
|
|
668
|
+
Type.String({ description: "Optional working directory for the oracle subprocess." }),
|
|
669
|
+
),
|
|
680
670
|
});
|
|
681
671
|
|
|
682
672
|
function createEmptyUsage(): UsageStats {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
673
|
+
return {
|
|
674
|
+
input: 0,
|
|
675
|
+
output: 0,
|
|
676
|
+
cacheRead: 0,
|
|
677
|
+
cacheWrite: 0,
|
|
678
|
+
totalTokens: 0,
|
|
679
|
+
cost: 0,
|
|
680
|
+
costInput: 0,
|
|
681
|
+
costOutput: 0,
|
|
682
|
+
costCacheRead: 0,
|
|
683
|
+
costCacheWrite: 0,
|
|
684
|
+
turns: 0,
|
|
685
|
+
contextTokens: 0,
|
|
686
|
+
};
|
|
697
687
|
}
|
|
698
688
|
|
|
699
689
|
function cloneUsageStats(stats: UsageStats): UsageStats {
|
|
700
|
-
|
|
690
|
+
return { ...stats };
|
|
701
691
|
}
|
|
702
692
|
|
|
703
693
|
function mergeUsageStats(target: UsageStats, source: UsageStats | undefined): UsageStats {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
694
|
+
if (!source) return target;
|
|
695
|
+
target.input += source.input;
|
|
696
|
+
target.output += source.output;
|
|
697
|
+
target.cacheRead += source.cacheRead;
|
|
698
|
+
target.cacheWrite += source.cacheWrite;
|
|
699
|
+
if (source.cacheWrite1h !== undefined) {
|
|
700
|
+
target.cacheWrite1h = (target.cacheWrite1h ?? 0) + source.cacheWrite1h;
|
|
701
|
+
}
|
|
702
|
+
if (source.reasoning !== undefined) {
|
|
703
|
+
target.reasoning = (target.reasoning ?? 0) + source.reasoning;
|
|
704
|
+
}
|
|
705
|
+
target.totalTokens += source.totalTokens;
|
|
706
|
+
target.cost += source.cost;
|
|
707
|
+
target.costInput += source.costInput;
|
|
708
|
+
target.costOutput += source.costOutput;
|
|
709
|
+
target.costCacheRead += source.costCacheRead;
|
|
710
|
+
target.costCacheWrite += source.costCacheWrite;
|
|
711
|
+
target.turns += source.turns;
|
|
712
|
+
target.contextTokens = source.contextTokens || target.contextTokens;
|
|
713
|
+
return target;
|
|
724
714
|
}
|
|
725
715
|
|
|
726
716
|
function usageStatsToToolUsage(stats: UsageStats): Usage {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
717
|
+
return {
|
|
718
|
+
input: stats.input,
|
|
719
|
+
output: stats.output,
|
|
720
|
+
cacheRead: stats.cacheRead,
|
|
721
|
+
cacheWrite: stats.cacheWrite,
|
|
722
|
+
...(stats.cacheWrite1h !== undefined ? { cacheWrite1h: stats.cacheWrite1h } : {}),
|
|
723
|
+
...(stats.reasoning !== undefined ? { reasoning: stats.reasoning } : {}),
|
|
724
|
+
totalTokens: stats.totalTokens,
|
|
725
|
+
cost: {
|
|
726
|
+
input: stats.costInput,
|
|
727
|
+
output: stats.costOutput,
|
|
728
|
+
cacheRead: stats.costCacheRead,
|
|
729
|
+
cacheWrite: stats.costCacheWrite,
|
|
730
|
+
total: stats.cost,
|
|
731
|
+
},
|
|
732
|
+
};
|
|
743
733
|
}
|
|
744
734
|
|
|
745
735
|
function addUsageStats(
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
736
|
+
target: UsageStats,
|
|
737
|
+
source: Usage | undefined,
|
|
738
|
+
options: { countTurn?: boolean; updateContextTokens?: boolean } = {},
|
|
749
739
|
): void {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
740
|
+
if (!source) return;
|
|
741
|
+
if (options.countTurn) target.turns += 1;
|
|
742
|
+
target.input += source.input || 0;
|
|
743
|
+
target.output += source.output || 0;
|
|
744
|
+
target.cacheRead += source.cacheRead || 0;
|
|
745
|
+
target.cacheWrite += source.cacheWrite || 0;
|
|
746
|
+
if (source.cacheWrite1h !== undefined) {
|
|
747
|
+
target.cacheWrite1h = (target.cacheWrite1h ?? 0) + source.cacheWrite1h;
|
|
748
|
+
}
|
|
749
|
+
if (source.reasoning !== undefined) {
|
|
750
|
+
target.reasoning = (target.reasoning ?? 0) + source.reasoning;
|
|
751
|
+
}
|
|
752
|
+
target.totalTokens += source.totalTokens || 0;
|
|
753
|
+
target.cost += source.cost?.total || 0;
|
|
754
|
+
target.costInput += source.cost?.input || 0;
|
|
755
|
+
target.costOutput += source.cost?.output || 0;
|
|
756
|
+
target.costCacheRead += source.cost?.cacheRead || 0;
|
|
757
|
+
target.costCacheWrite += source.cost?.cacheWrite || 0;
|
|
758
|
+
if (options.updateContextTokens) {
|
|
759
|
+
target.contextTokens = source.totalTokens || target.contextTokens;
|
|
760
|
+
}
|
|
771
761
|
}
|
|
772
762
|
|
|
773
763
|
function getOracleConfigPath(): string {
|
|
774
|
-
|
|
764
|
+
return path.join(getAgentDir(), "extensions", ORACLE_CONFIG_FILE);
|
|
775
765
|
}
|
|
776
766
|
|
|
777
767
|
function parseThinkingLevel(value: unknown): ThinkingLevel | undefined {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
768
|
+
if (typeof value !== "string") return undefined;
|
|
769
|
+
const normalized = value.trim().toLowerCase();
|
|
770
|
+
return (THINKING_LEVELS as readonly string[]).includes(normalized)
|
|
771
|
+
? (normalized as ThinkingLevel)
|
|
772
|
+
: undefined;
|
|
781
773
|
}
|
|
782
774
|
|
|
783
775
|
function normalizeModelPreference(value: unknown): string | undefined {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
776
|
+
if (typeof value !== "string") return undefined;
|
|
777
|
+
const trimmed = value.trim();
|
|
778
|
+
return trimmed ? trimmed : undefined;
|
|
787
779
|
}
|
|
788
780
|
|
|
789
781
|
function parseModelPreference(value: unknown): { model?: string; thinkingLevel?: ThinkingLevel } {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
782
|
+
const model = normalizeModelPreference(value);
|
|
783
|
+
if (!model) return {};
|
|
784
|
+
const match = model.match(/^(.*):(off|minimal|low|medium|high|xhigh|max)$/i);
|
|
785
|
+
if (!match?.[1]) return { model };
|
|
786
|
+
return { model: match[1], thinkingLevel: parseThinkingLevel(match[2]) };
|
|
795
787
|
}
|
|
796
788
|
|
|
797
789
|
async function readOraclePreferences(): Promise<OraclePreferences> {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
790
|
+
try {
|
|
791
|
+
const raw = await fs.readFile(getOracleConfigPath(), "utf8");
|
|
792
|
+
const parsed = JSON.parse(raw) as {
|
|
793
|
+
model?: unknown;
|
|
794
|
+
defaultModel?: unknown;
|
|
795
|
+
thinkingLevel?: unknown;
|
|
796
|
+
defaultThinkingLevel?: unknown;
|
|
797
|
+
};
|
|
798
|
+
return {
|
|
799
|
+
model:
|
|
800
|
+
parseModelPreference(parsed.model).model ?? parseModelPreference(parsed.defaultModel).model,
|
|
801
|
+
thinkingLevel:
|
|
802
|
+
parseThinkingLevel(parsed.thinkingLevel) ??
|
|
803
|
+
parseThinkingLevel(parsed.defaultThinkingLevel) ??
|
|
804
|
+
parseModelPreference(parsed.model).thinkingLevel ??
|
|
805
|
+
parseModelPreference(parsed.defaultModel).thinkingLevel,
|
|
806
|
+
};
|
|
807
|
+
} catch {
|
|
808
|
+
return {};
|
|
809
|
+
}
|
|
817
810
|
}
|
|
818
811
|
|
|
819
812
|
async function writeOraclePreferences(preferences: OraclePreferences): Promise<void> {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
813
|
+
const configPath = getOracleConfigPath();
|
|
814
|
+
const config = {
|
|
815
|
+
...(preferences.model ? { model: preferences.model } : {}),
|
|
816
|
+
...(preferences.thinkingLevel ? { thinkingLevel: preferences.thinkingLevel } : {}),
|
|
817
|
+
updatedAt: new Date().toISOString(),
|
|
818
|
+
};
|
|
819
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
820
|
+
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
828
821
|
}
|
|
829
822
|
|
|
830
823
|
function formatOraclePreferences(preferences: OraclePreferences): string {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
824
|
+
const model = preferences.model ?? "auto";
|
|
825
|
+
const thinkingLevel = preferences.thinkingLevel ?? "auto";
|
|
826
|
+
return `Oracle defaults: model=${model}, thinkingLevel=${thinkingLevel}. Config: ${getOracleConfigPath()}`;
|
|
834
827
|
}
|
|
835
828
|
|
|
836
829
|
function notifyCommand(ctx: any, message: string, kind = "info"): void {
|
|
837
|
-
|
|
838
|
-
|
|
830
|
+
if (ctx.hasUI && ctx.ui) ctx.ui.notify(message, kind);
|
|
831
|
+
else console.log(message);
|
|
839
832
|
}
|
|
840
833
|
|
|
841
834
|
function formatTokens(count: number): string {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
835
|
+
if (count < 1000) return count.toString();
|
|
836
|
+
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
837
|
+
if (count < 1000000) return `${Math.round(count / 1000)}k`;
|
|
838
|
+
return `${(count / 1000000).toFixed(1)}M`;
|
|
846
839
|
}
|
|
847
840
|
|
|
848
841
|
function formatDuration(durationMs: number): string {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
842
|
+
if (durationMs < 1000) return `${durationMs}ms`;
|
|
843
|
+
if (durationMs < 60_000) return `${(durationMs / 1000).toFixed(1)}s`;
|
|
844
|
+
return `${(durationMs / 60_000).toFixed(1)}m`;
|
|
852
845
|
}
|
|
853
846
|
|
|
854
847
|
function formatUsage(stats: UsageStats): string {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
848
|
+
const parts: string[] = [];
|
|
849
|
+
if (stats.turns) parts.push(`${stats.turns} turn${stats.turns === 1 ? "" : "s"}`);
|
|
850
|
+
if (stats.input) parts.push(`↑${formatTokens(stats.input)}`);
|
|
851
|
+
if (stats.output) parts.push(`↓${formatTokens(stats.output)}`);
|
|
852
|
+
if (stats.cacheRead) parts.push(`R${formatTokens(stats.cacheRead)}`);
|
|
853
|
+
if (stats.cacheWrite) parts.push(`W${formatTokens(stats.cacheWrite)}`);
|
|
854
|
+
if (stats.reasoning) parts.push(`T${formatTokens(stats.reasoning)}`);
|
|
855
|
+
if (stats.cost) parts.push(`$${stats.cost.toFixed(4)}`);
|
|
856
|
+
if (stats.contextTokens) parts.push(`ctx:${formatTokens(stats.contextTokens)}`);
|
|
857
|
+
return parts.join(" ");
|
|
865
858
|
}
|
|
866
859
|
|
|
867
860
|
function extractTextFromContent(content: unknown): string {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
861
|
+
if (!Array.isArray(content)) return "";
|
|
862
|
+
const parts: string[] = [];
|
|
863
|
+
for (const part of content) {
|
|
864
|
+
if (part && typeof part === "object" && (part as { type?: string }).type === "text") {
|
|
865
|
+
const text = (part as { text?: string }).text;
|
|
866
|
+
if (typeof text === "string" && text.trim()) parts.push(text);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return parts.join("\n\n").trim();
|
|
877
870
|
}
|
|
878
871
|
|
|
879
872
|
// Errors that typically resolve on retry: provider overload, rate limiting,
|
|
880
873
|
// transient 5xx, gateway/network failures, and request timeouts. Keep this list
|
|
881
874
|
// pattern-based so we recognize variants across providers without enumerating them.
|
|
882
875
|
const TRANSIENT_ERROR_PATTERN =
|
|
883
|
-
|
|
876
|
+
/\b(overload(?:ed)?|rate[ _-]?limit(?:ed)?|too many requests|429|500|502|503|504|bad gateway|service unavailable|gateway timeout|temporarily unavailable|timed?[ _-]?out|timeout|econnreset|econnrefused|etimedout|enetunreach|socket hang up|fetch failed)\b/i;
|
|
884
877
|
|
|
885
878
|
function isTransientErrorMessage(message: string): boolean {
|
|
886
|
-
|
|
879
|
+
return TRANSIENT_ERROR_PATTERN.test(message);
|
|
887
880
|
}
|
|
888
881
|
|
|
889
882
|
// A model the catalog advertises but the active provider/subscription cannot
|
|
@@ -891,944 +884,1057 @@ function isTransientErrorMessage(message: string): boolean {
|
|
|
891
884
|
// access-gated tiers). These are NOT transient: the same model keeps failing,
|
|
892
885
|
// so callers should fall back to a different model instead of retrying it.
|
|
893
886
|
const MODEL_AVAILABILITY_ERROR_PATTERN =
|
|
894
|
-
|
|
887
|
+
/\b(?:404|403|not[_ ]?found(?:[_ ]?error)?|model[_ ]?not[_ ]?found(?:[_ ]?error)?|no such model|unknown model|does not exist|is not available|not available|model[_ ]?not[_ ]?available|unsupported model|invalid model|forbidden|access[ _-]?denied|permission[ _-]?denied|not[ _-]?entitled|do(?:es)? not have access)\b/i;
|
|
895
888
|
|
|
896
889
|
function isModelAvailabilityError(message: string | undefined): boolean {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
890
|
+
if (!message) return false;
|
|
891
|
+
if (isTransientErrorMessage(message)) return false;
|
|
892
|
+
return MODEL_AVAILABILITY_ERROR_PATTERN.test(message);
|
|
900
893
|
}
|
|
901
894
|
|
|
902
|
-
function formatOracleModelError(
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
895
|
+
function formatOracleModelError(
|
|
896
|
+
stopReason: "error" | "aborted",
|
|
897
|
+
errorMessage: string | undefined,
|
|
898
|
+
): string {
|
|
899
|
+
const trimmed = errorMessage?.trim();
|
|
900
|
+
if (stopReason === "aborted") {
|
|
901
|
+
return trimmed ? `Oracle model turn aborted: ${trimmed}` : "Oracle model turn aborted.";
|
|
902
|
+
}
|
|
903
|
+
if (!trimmed) return "Oracle model error (no detail provided by provider).";
|
|
904
|
+
const base = `Oracle model error: ${trimmed}`;
|
|
905
|
+
return isTransientErrorMessage(trimmed) ? `${base} (transient; retry may succeed)` : base;
|
|
910
906
|
}
|
|
911
907
|
|
|
912
908
|
function parseVersionScore(text: string): number {
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
909
|
+
const matches = text.match(/\d+(?:\.\d+){0,2}/g) ?? [];
|
|
910
|
+
let best = 0;
|
|
911
|
+
for (const match of matches) {
|
|
912
|
+
const [major = "0", minor = "0", patch = "0"] = match.split(".");
|
|
913
|
+
const score = Number(major) * 1_000_000 + Number(minor) * 1_000 + Number(patch);
|
|
914
|
+
if (score > best) best = score;
|
|
915
|
+
}
|
|
916
|
+
return best;
|
|
921
917
|
}
|
|
922
918
|
|
|
923
919
|
function rankModel(model: PiModel): number {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
920
|
+
const text = `${model.id} ${model.name ?? ""}`.toLowerCase();
|
|
921
|
+
const has = (regex: RegExp): boolean => regex.test(text);
|
|
922
|
+
let score = 0;
|
|
923
|
+
|
|
924
|
+
if (model.reasoning) score += 10_000_000;
|
|
925
|
+
score += parseVersionScore(text) * 1_000;
|
|
926
|
+
score += Math.min(model.maxTokens ?? 0, 200_000);
|
|
927
|
+
score += Math.floor(Math.min(model.contextWindow ?? 0, 1_000_000) / 100);
|
|
928
|
+
|
|
929
|
+
if (has(/\bopus\b/)) score += 350_000;
|
|
930
|
+
if (has(/\bpro\b/)) score += 180_000;
|
|
931
|
+
if (has(/\bmax\b/)) score += 60_000;
|
|
932
|
+
if (has(/\bultra\b/)) score += 150_000;
|
|
933
|
+
if (has(/\bsonnet\b/)) score += 120_000;
|
|
934
|
+
if (has(/\bcodex\b|\bcoder\b|\bcode\b/)) score += 40_000;
|
|
935
|
+
if (has(/\breasoning\b|\bthink(?:ing)?\b/)) score += 60_000;
|
|
936
|
+
|
|
937
|
+
if (has(/\bhaiku\b/)) score -= 420_000;
|
|
938
|
+
if (has(/\bmini\b/)) score -= 520_000;
|
|
939
|
+
if (has(/\bnano\b/)) score -= 700_000;
|
|
940
|
+
if (has(/\bflash\b/)) score -= 520_000;
|
|
941
|
+
if (has(/\bspark\b/)) score -= 650_000;
|
|
942
|
+
if (has(/\blite\b|\bsmall\b|\bfast\b|\binstant\b/)) score -= 300_000;
|
|
943
|
+
|
|
944
|
+
return score;
|
|
949
945
|
}
|
|
950
946
|
|
|
951
947
|
function shorten(text: string, max: number): string {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
948
|
+
const singleLine = text.replace(/\s+/g, " ").trim();
|
|
949
|
+
if (singleLine.length <= max) return singleLine;
|
|
950
|
+
return `${singleLine.slice(0, Math.max(1, max - 3))}...`;
|
|
955
951
|
}
|
|
956
952
|
|
|
957
953
|
function getProviderPreferenceList(provider: string | undefined): string[] | undefined {
|
|
958
|
-
|
|
959
|
-
|
|
954
|
+
if (!provider) return undefined;
|
|
955
|
+
return PROVIDER_MODEL_PREFERENCES[provider.toLowerCase()];
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function matchesPreferencePattern(haystack: string, pattern: string): boolean {
|
|
959
|
+
const needle = pattern.toLowerCase();
|
|
960
|
+
if (!needle.endsWith(" ")) return haystack.includes(needle);
|
|
961
|
+
const separator = haystack.indexOf(" ");
|
|
962
|
+
const id = separator === -1 ? haystack : haystack.slice(0, separator);
|
|
963
|
+
return `${id} `.includes(needle);
|
|
960
964
|
}
|
|
961
965
|
|
|
962
|
-
function selectPreferredModel(
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
966
|
+
function selectPreferredModel(
|
|
967
|
+
models: PiModel[],
|
|
968
|
+
provider: string | undefined,
|
|
969
|
+
): PiModel | undefined {
|
|
970
|
+
const preferences = getProviderPreferenceList(provider);
|
|
971
|
+
if (!preferences || preferences.length === 0) return undefined;
|
|
972
|
+
const lowered = models.map((model) => ({
|
|
973
|
+
model,
|
|
974
|
+
haystack: `${model.id} ${model.name ?? ""}`.toLowerCase(),
|
|
975
|
+
}));
|
|
976
|
+
for (const pattern of preferences) {
|
|
977
|
+
const match = lowered.find((entry) => matchesPreferencePattern(entry.haystack, pattern));
|
|
978
|
+
if (match) return match.model;
|
|
979
|
+
}
|
|
980
|
+
return undefined;
|
|
971
981
|
}
|
|
972
982
|
|
|
973
983
|
function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
984
|
+
const currentScript = process.argv[1];
|
|
985
|
+
const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/");
|
|
986
|
+
if (currentScript && !isBunVirtualScript && existsSync(currentScript)) {
|
|
987
|
+
return { command: process.execPath, args: [currentScript, ...args] };
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
const execName = path.basename(process.execPath).toLowerCase();
|
|
991
|
+
const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
|
|
992
|
+
if (!isGenericRuntime) {
|
|
993
|
+
return { command: process.execPath, args };
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
return { command: "pi", args };
|
|
987
997
|
}
|
|
988
998
|
|
|
989
999
|
function withThinking(modelRef: string, thinkingLevel: ThinkingLevel): string {
|
|
990
|
-
|
|
991
|
-
|
|
1000
|
+
if (/(?:^|\/)[^:]+:(off|minimal|low|medium|high|xhigh|max)$/i.test(modelRef)) return modelRef;
|
|
1001
|
+
return `${modelRef}:${thinkingLevel}`;
|
|
992
1002
|
}
|
|
993
1003
|
|
|
994
1004
|
// Keep these local so the extension stays compatible with older pi peer installs that do not export clamp helpers.
|
|
995
1005
|
function isThinkingLevelSupported(model: PiModel, level: ThinkingLevel): boolean {
|
|
996
|
-
|
|
1006
|
+
if (!model.reasoning) return level === "off";
|
|
997
1007
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1008
|
+
const map = model.thinkingLevelMap;
|
|
1009
|
+
if (level === "xhigh" || level === "max") {
|
|
1010
|
+
return !!map && Object.prototype.hasOwnProperty.call(map, level) && map[level] != null;
|
|
1011
|
+
}
|
|
1012
|
+
return map?.[level] !== null;
|
|
1003
1013
|
}
|
|
1004
1014
|
|
|
1005
1015
|
function clampThinkingLevel(model: PiModel, requested: ThinkingLevel): ThinkingLevel {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1016
|
+
if (isThinkingLevelSupported(model, requested)) return requested;
|
|
1017
|
+
|
|
1018
|
+
const requestedIndex = THINKING_LEVELS.indexOf(requested);
|
|
1019
|
+
for (let index = requestedIndex + 1; index < THINKING_LEVELS.length; index++) {
|
|
1020
|
+
const level = THINKING_LEVELS[index];
|
|
1021
|
+
if (isThinkingLevelSupported(model, level)) return level;
|
|
1022
|
+
}
|
|
1023
|
+
for (let index = requestedIndex - 1; index >= 0; index--) {
|
|
1024
|
+
const level = THINKING_LEVELS[index];
|
|
1025
|
+
if (isThinkingLevelSupported(model, level)) return level;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return "off";
|
|
1019
1029
|
}
|
|
1020
1030
|
|
|
1021
1031
|
function isGptSolModel(model: PiModel | undefined): boolean {
|
|
1022
|
-
|
|
1023
|
-
|
|
1032
|
+
if (!model) return false;
|
|
1033
|
+
return /(?:\bgpt-(?:5\.6|6)-sol\b|\bgpt-6\.1-sol(?:\s|$))/i.test(
|
|
1034
|
+
`${model.id} ${model.name ?? ""}`,
|
|
1035
|
+
);
|
|
1024
1036
|
}
|
|
1025
1037
|
|
|
1026
1038
|
function resolveThinkingLevel(
|
|
1027
|
-
|
|
1028
|
-
|
|
1039
|
+
model: PiModel | undefined,
|
|
1040
|
+
override: ThinkingLevel | undefined,
|
|
1029
1041
|
): { requested: ThinkingLevel; effective: ThinkingLevel; clamped: boolean } {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1042
|
+
const requested =
|
|
1043
|
+
override ??
|
|
1044
|
+
(model?.reasoning ? (isGptSolModel(model) ? "high" : DEFAULT_THINKING_LEVEL) : "off");
|
|
1045
|
+
const effective = model ? clampThinkingLevel(model, requested) : requested;
|
|
1046
|
+
return { requested, effective, clamped: effective !== requested };
|
|
1033
1047
|
}
|
|
1034
1048
|
|
|
1035
1049
|
function appendThinkingLevelClampReason(
|
|
1036
|
-
|
|
1037
|
-
|
|
1050
|
+
reason: string,
|
|
1051
|
+
resolution: { requested: ThinkingLevel; effective: ThinkingLevel; clamped: boolean },
|
|
1038
1052
|
): string {
|
|
1039
|
-
|
|
1040
|
-
|
|
1053
|
+
if (!resolution.clamped) return reason;
|
|
1054
|
+
return `${reason} Requested thinking level ${resolution.requested} was clamped to ${resolution.effective} based on the matched model's capabilities.`;
|
|
1041
1055
|
}
|
|
1042
1056
|
|
|
1043
1057
|
async function findAvailableModel(
|
|
1044
|
-
|
|
1045
|
-
|
|
1058
|
+
ctx: ModelSelectionContext,
|
|
1059
|
+
modelRef: string,
|
|
1046
1060
|
): Promise<PiModel | undefined> {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1061
|
+
const available = await ctx.modelRegistry.getAvailable();
|
|
1062
|
+
const trimmed = modelRef.trim();
|
|
1063
|
+
const provider = trimmed.includes("/")
|
|
1064
|
+
? trimmed.split("/")[0].toLowerCase()
|
|
1065
|
+
: ctx.model?.provider?.toLowerCase();
|
|
1066
|
+
const id = trimmed.includes("/")
|
|
1067
|
+
? trimmed.split("/").slice(1).join("/").toLowerCase()
|
|
1068
|
+
: trimmed.toLowerCase();
|
|
1069
|
+
|
|
1070
|
+
const exact = available.find(
|
|
1071
|
+
(model) =>
|
|
1072
|
+
model.id.toLowerCase() === id && (!provider || model.provider.toLowerCase() === provider),
|
|
1073
|
+
);
|
|
1074
|
+
if (exact) return exact;
|
|
1075
|
+
|
|
1076
|
+
const partial = available.find(
|
|
1077
|
+
(model) =>
|
|
1078
|
+
model.id.toLowerCase().includes(id) &&
|
|
1079
|
+
(!provider || model.provider.toLowerCase() === provider),
|
|
1080
|
+
);
|
|
1081
|
+
if (partial) return partial;
|
|
1082
|
+
|
|
1083
|
+
if (!provider) {
|
|
1084
|
+
const uniqueById = available.filter((model) => model.id.toLowerCase() === id);
|
|
1085
|
+
if (uniqueById.length === 1) return uniqueById[0];
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
return undefined;
|
|
1069
1089
|
}
|
|
1070
1090
|
|
|
1071
1091
|
function toOracleSelection(
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1092
|
+
model: PiModel,
|
|
1093
|
+
thinkingLevelOverride: ThinkingLevel | undefined,
|
|
1094
|
+
reason: string,
|
|
1075
1095
|
): OracleSelection {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1096
|
+
const thinking = resolveThinkingLevel(model, thinkingLevelOverride);
|
|
1097
|
+
return {
|
|
1098
|
+
modelRef: `${model.provider}/${model.id}`,
|
|
1099
|
+
provider: model.provider,
|
|
1100
|
+
modelId: model.id,
|
|
1101
|
+
modelName: model.name,
|
|
1102
|
+
thinkingLevel: thinking.effective,
|
|
1103
|
+
...(thinking.clamped
|
|
1104
|
+
? { requestedThinkingLevel: thinking.requested, thinkingLevelClamped: true }
|
|
1105
|
+
: {}),
|
|
1106
|
+
autoSelected: true,
|
|
1107
|
+
selectionReason: appendThinkingLevelClampReason(reason, thinking),
|
|
1108
|
+
};
|
|
1087
1109
|
}
|
|
1088
1110
|
|
|
1089
1111
|
function buildSessionFallbackSelection(
|
|
1090
|
-
|
|
1091
|
-
|
|
1112
|
+
ctx: ModelSelectionContext,
|
|
1113
|
+
thinkingLevelOverride: ThinkingLevel | undefined,
|
|
1092
1114
|
): OracleSelection | undefined {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1115
|
+
const model = ctx.model;
|
|
1116
|
+
if (!model || !isModelInScope(ctx, model)) return undefined;
|
|
1117
|
+
return toOracleSelection(
|
|
1118
|
+
model,
|
|
1119
|
+
thinkingLevelOverride,
|
|
1120
|
+
`Used the current session model ${model.provider}/${model.id} as a final fallback.`,
|
|
1121
|
+
);
|
|
1100
1122
|
}
|
|
1101
1123
|
|
|
1102
|
-
function withFallbackReason(
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1124
|
+
function withFallbackReason(
|
|
1125
|
+
selection: OracleSelection,
|
|
1126
|
+
previous: OracleSelection,
|
|
1127
|
+
): OracleSelection {
|
|
1128
|
+
return {
|
|
1129
|
+
...selection,
|
|
1130
|
+
selectionReason: `${selection.selectionReason} (Fell back from ${previous.modelRef} after a model-availability error.)`,
|
|
1131
|
+
};
|
|
1107
1132
|
}
|
|
1108
1133
|
|
|
1109
1134
|
async function selectOracleModel(
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
): Promise<
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1135
|
+
ctx: ModelSelectionContext,
|
|
1136
|
+
thinkingLevelOverride?: ThinkingLevel,
|
|
1137
|
+
): Promise<
|
|
1138
|
+
| { ok: true; selection: OracleSelection; ordered: OracleSelection[] }
|
|
1139
|
+
| { ok: false; error: string }
|
|
1140
|
+
> {
|
|
1141
|
+
const available = await getAutoSelectableModels(ctx);
|
|
1142
|
+
if (available.length === 0) {
|
|
1143
|
+
return {
|
|
1144
|
+
ok: false,
|
|
1145
|
+
error: hasModelScope(ctx)
|
|
1146
|
+
? "No authenticated models are available in the current session model scope. Adjust the scope, log in, or configure an API key."
|
|
1147
|
+
: "No authenticated models are available. Log in or configure an API key first.",
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const currentProvider = ctx.model?.provider;
|
|
1152
|
+
const sameProvider = currentProvider
|
|
1153
|
+
? available.filter((model) => model.provider === currentProvider)
|
|
1154
|
+
: [];
|
|
1155
|
+
const sameProviderReasoning = sameProvider.filter((model) => model.reasoning);
|
|
1156
|
+
const allReasoning = available.filter((model) => model.reasoning);
|
|
1157
|
+
|
|
1158
|
+
let candidates = sameProviderReasoning;
|
|
1159
|
+
let reason = "Selected the top-ranked reasoning model on the current provider.";
|
|
1160
|
+
let providerForPreferences = currentProvider;
|
|
1161
|
+
|
|
1162
|
+
if (candidates.length === 0 && sameProvider.length > 0) {
|
|
1163
|
+
candidates = sameProvider;
|
|
1164
|
+
reason =
|
|
1165
|
+
"The current provider has no reasoning models available, so the top-ranked model on that provider was used.";
|
|
1166
|
+
} else if (candidates.length === 0 && allReasoning.length > 0) {
|
|
1167
|
+
candidates = allReasoning;
|
|
1168
|
+
providerForPreferences = undefined;
|
|
1169
|
+
reason =
|
|
1170
|
+
"No current model/provider was active, so the top-ranked reasoning model across all available providers was used.";
|
|
1171
|
+
} else if (candidates.length === 0) {
|
|
1172
|
+
candidates = available;
|
|
1173
|
+
providerForPreferences = undefined;
|
|
1174
|
+
reason =
|
|
1175
|
+
"No reasoning models were available, so the top-ranked model across all available providers was used.";
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const sorted = [...candidates].sort((a, b) => rankModel(b) - rankModel(a));
|
|
1179
|
+
const preferred = selectPreferredModel(candidates, providerForPreferences);
|
|
1180
|
+
const orderedModels = preferred
|
|
1181
|
+
? [preferred, ...sorted.filter((model) => model !== preferred)]
|
|
1182
|
+
: sorted;
|
|
1183
|
+
|
|
1184
|
+
const ordered = orderedModels.map((model, index) =>
|
|
1185
|
+
toOracleSelection(
|
|
1186
|
+
model,
|
|
1187
|
+
thinkingLevelOverride,
|
|
1188
|
+
index === 0
|
|
1189
|
+
? preferred
|
|
1190
|
+
? `Selected ${model.id} via the hardcoded preference list for ${model.provider}.`
|
|
1191
|
+
: reason
|
|
1192
|
+
: `Auto-selected ${model.provider}/${model.id} as a lower-ranked fallback.`,
|
|
1193
|
+
),
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
return { ok: true, selection: ordered[0], ordered };
|
|
1162
1197
|
}
|
|
1163
1198
|
|
|
1164
1199
|
function updateOracleUi(ctx: ExtensionContext, activeRuns: Map<string, OracleUiRun>): void {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1200
|
+
if (!ctx.hasUI) return;
|
|
1201
|
+
const theme = ctx.ui.theme;
|
|
1202
|
+
if (activeRuns.size === 0) {
|
|
1203
|
+
ctx.ui.setStatus(ORACLE_STATUS_ID, undefined);
|
|
1204
|
+
ctx.ui.setWidget(ORACLE_WIDGET_ID, undefined);
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const runs = [...activeRuns.values()].sort((a, b) => b.startedAt - a.startedAt);
|
|
1209
|
+
const primary = runs[0];
|
|
1210
|
+
const activeCount = runs.length;
|
|
1211
|
+
const modelText = primary.selection?.modelId ?? "selecting…";
|
|
1212
|
+
const thinkingText =
|
|
1213
|
+
primary.selection?.thinkingLevel && primary.selection.thinkingLevel !== "off"
|
|
1214
|
+
? ` ${primary.selection.thinkingLevel}`
|
|
1215
|
+
: "";
|
|
1216
|
+
const elapsed = formatDuration(Date.now() - primary.startedAt);
|
|
1217
|
+
const mode = primary.includeBash ? "read-only+bash" : "read-only";
|
|
1218
|
+
const status =
|
|
1219
|
+
theme.fg("accent", "🔮 oracle") +
|
|
1220
|
+
theme.fg("dim", ` ${modelText}${thinkingText} · ${mode} · ${elapsed}`) +
|
|
1221
|
+
(activeCount > 1 ? theme.fg("warning", ` · ${activeCount} active`) : "");
|
|
1222
|
+
ctx.ui.setStatus(ORACLE_STATUS_ID, status);
|
|
1223
|
+
|
|
1224
|
+
const lines = [
|
|
1225
|
+
`🔮 Oracle ${activeCount > 1 ? `(${activeCount} active)` : ""}`.trim(),
|
|
1226
|
+
`${primary.selection?.modelRef ?? "selecting model…"} · ${mode} · ${elapsed}`,
|
|
1227
|
+
`task: ${shorten(primary.task, 110)}`,
|
|
1228
|
+
];
|
|
1229
|
+
if (primary.preview && primary.preview.trim())
|
|
1230
|
+
lines.push(`preview: ${shorten(primary.preview, 110)}`);
|
|
1231
|
+
ctx.ui.setWidget(ORACLE_WIDGET_ID, lines, { placement: "belowEditor" });
|
|
1195
1232
|
}
|
|
1196
1233
|
|
|
1197
1234
|
async function runOracle(
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1235
|
+
selection: OracleSelection,
|
|
1236
|
+
params: { task: string; includeBash?: boolean; cwd?: string },
|
|
1237
|
+
signal: AbortSignal | undefined,
|
|
1238
|
+
onUpdate:
|
|
1239
|
+
| ((result: { content: Array<{ type: "text"; text: string }>; details: OracleDetails }) => void)
|
|
1240
|
+
| undefined,
|
|
1241
|
+
defaultCwd: string,
|
|
1242
|
+
spawnImpl: typeof spawn = spawn,
|
|
1243
|
+
): Promise<
|
|
1244
|
+
| { ok: true; output: string; details: OracleDetails }
|
|
1245
|
+
| { ok: false; error: string; details: OracleDetails }
|
|
1246
|
+
> {
|
|
1247
|
+
const cwd = params.cwd ?? defaultCwd;
|
|
1248
|
+
const includeBash = params.includeBash ?? false;
|
|
1249
|
+
const tools = includeBash ? READ_ONLY_PLUS_BASH_TOOLS : READ_ONLY_TOOLS;
|
|
1250
|
+
const startedAt = Date.now();
|
|
1251
|
+
const usage = createEmptyUsage();
|
|
1252
|
+
let currentText = "";
|
|
1253
|
+
let finalOutput = "";
|
|
1254
|
+
let stderr = "";
|
|
1255
|
+
|
|
1256
|
+
let lastStopReason: string | undefined;
|
|
1257
|
+
let lastErrorMessage: string | undefined;
|
|
1258
|
+
|
|
1259
|
+
const details: OracleDetails = {
|
|
1260
|
+
...selection,
|
|
1261
|
+
includeBash,
|
|
1262
|
+
usage,
|
|
1263
|
+
stderr,
|
|
1264
|
+
exitCode: 0,
|
|
1265
|
+
durationMs: 0,
|
|
1266
|
+
cwd,
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
const emit = () => {
|
|
1270
|
+
details.stderr = stderr;
|
|
1271
|
+
details.durationMs = Date.now() - startedAt;
|
|
1272
|
+
onUpdate?.({
|
|
1273
|
+
content: [{ type: "text", text: currentText || finalOutput || "Consulting oracle..." }],
|
|
1274
|
+
details,
|
|
1275
|
+
});
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
const modelArg = withThinking(selection.modelRef, selection.thinkingLevel);
|
|
1279
|
+
const args = [
|
|
1280
|
+
"--mode",
|
|
1281
|
+
"json",
|
|
1282
|
+
"-p",
|
|
1283
|
+
"--no-session",
|
|
1284
|
+
"--model",
|
|
1285
|
+
modelArg,
|
|
1286
|
+
"--tools",
|
|
1287
|
+
tools.join(","),
|
|
1288
|
+
"--append-system-prompt",
|
|
1289
|
+
ORACLE_SYSTEM_PROMPT,
|
|
1290
|
+
params.task,
|
|
1291
|
+
];
|
|
1292
|
+
|
|
1293
|
+
const invocation = getPiInvocation(args);
|
|
1294
|
+
let wasAborted = false;
|
|
1295
|
+
|
|
1296
|
+
const exitCode = await new Promise<number>((resolve) => {
|
|
1297
|
+
const proc = spawnImpl(invocation.command, invocation.args, {
|
|
1298
|
+
cwd,
|
|
1299
|
+
shell: false,
|
|
1300
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1301
|
+
});
|
|
1302
|
+
let buffer = "";
|
|
1303
|
+
|
|
1304
|
+
const processLine = (line: string) => {
|
|
1305
|
+
if (!line.trim()) return;
|
|
1306
|
+
let event: any;
|
|
1307
|
+
try {
|
|
1308
|
+
event = JSON.parse(line);
|
|
1309
|
+
} catch {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
if (event.type === "message_start" && event.message?.role === "assistant") {
|
|
1314
|
+
// A later assistant turn supersedes any earlier tool/intermediate answer.
|
|
1315
|
+
// Keep only the current message so a tool-only final turn cannot return stale text.
|
|
1316
|
+
currentText = "";
|
|
1317
|
+
finalOutput = "";
|
|
1318
|
+
emit();
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
if (event.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
1323
|
+
currentText += event.assistantMessageEvent.delta ?? "";
|
|
1324
|
+
emit();
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
1329
|
+
const text = extractTextFromContent(event.message.content) || currentText;
|
|
1330
|
+
// message_end is authoritative, including an empty tool-only message.
|
|
1331
|
+
finalOutput = text;
|
|
1332
|
+
currentText = "";
|
|
1333
|
+
|
|
1334
|
+
const stopReason = event.message.stopReason;
|
|
1335
|
+
lastStopReason = typeof stopReason === "string" ? stopReason : undefined;
|
|
1336
|
+
const errorMessageField = event.message.errorMessage;
|
|
1337
|
+
lastErrorMessage = typeof errorMessageField === "string" ? errorMessageField : undefined;
|
|
1338
|
+
|
|
1339
|
+
addUsageStats(usage, event.message.usage as Usage | undefined, {
|
|
1340
|
+
countTurn: true,
|
|
1341
|
+
updateContextTokens: true,
|
|
1342
|
+
});
|
|
1343
|
+
emit();
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
if (event.type === "compaction_end") {
|
|
1348
|
+
addUsageStats(usage, event.result?.usage as Usage | undefined);
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
proc.stdout.on("data", (data) => {
|
|
1353
|
+
buffer += data.toString();
|
|
1354
|
+
const lines = buffer.split("\n");
|
|
1355
|
+
buffer = lines.pop() || "";
|
|
1356
|
+
for (const line of lines) processLine(line);
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
proc.stderr.on("data", (data) => {
|
|
1360
|
+
stderr += data.toString();
|
|
1361
|
+
emit();
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
let closed = false;
|
|
1365
|
+
let killTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1366
|
+
let abortListener: (() => void) | undefined;
|
|
1367
|
+
|
|
1368
|
+
const childHasExited = () => proc.exitCode !== null || proc.signalCode !== null;
|
|
1369
|
+
const clearEscalation = () => {
|
|
1370
|
+
if (killTimer) {
|
|
1371
|
+
clearTimeout(killTimer);
|
|
1372
|
+
killTimer = undefined;
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
const cleanup = () => {
|
|
1376
|
+
clearEscalation();
|
|
1377
|
+
if (signal && abortListener) {
|
|
1378
|
+
signal.removeEventListener("abort", abortListener);
|
|
1379
|
+
abortListener = undefined;
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
proc.on("exit", () => {
|
|
1384
|
+
clearEscalation();
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
proc.on("close", (code) => {
|
|
1388
|
+
closed = true;
|
|
1389
|
+
cleanup();
|
|
1390
|
+
if (buffer.trim()) processLine(buffer);
|
|
1391
|
+
resolve(code ?? 0);
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
proc.on("error", (error) => {
|
|
1395
|
+
cleanup();
|
|
1396
|
+
stderr += `${error instanceof Error ? error.message : String(error)}\n`;
|
|
1397
|
+
resolve(1);
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
if (signal) {
|
|
1401
|
+
abortListener = () => {
|
|
1402
|
+
wasAborted = true;
|
|
1403
|
+
proc.kill("SIGTERM");
|
|
1404
|
+
if (closed || childHasExited()) return;
|
|
1405
|
+
killTimer = setTimeout(() => {
|
|
1406
|
+
if (!closed && !childHasExited()) proc.kill("SIGKILL");
|
|
1407
|
+
}, 5000);
|
|
1408
|
+
};
|
|
1409
|
+
if (signal.aborted) abortListener();
|
|
1410
|
+
else signal.addEventListener("abort", abortListener, { once: true });
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
details.stderr = stderr.trim();
|
|
1415
|
+
details.exitCode = exitCode;
|
|
1416
|
+
details.durationMs = Date.now() - startedAt;
|
|
1417
|
+
|
|
1418
|
+
if (wasAborted) {
|
|
1419
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1420
|
+
return { ok: false, error: "Oracle was aborted.", details };
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// The pi subprocess in `--mode json` does not promote an errored assistant
|
|
1424
|
+
// turn to a non-zero exit code or stderr; the error is only carried on the
|
|
1425
|
+
// streamed assistant message via stopReason/errorMessage. Surface that here
|
|
1426
|
+
// so callers can distinguish transient provider errors from a genuinely
|
|
1427
|
+
// empty response.
|
|
1428
|
+
if (lastStopReason === "error" || lastStopReason === "aborted") {
|
|
1429
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1430
|
+
return {
|
|
1431
|
+
ok: false,
|
|
1432
|
+
error: formatOracleModelError(lastStopReason, lastErrorMessage),
|
|
1433
|
+
details,
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
if (exitCode !== 0) {
|
|
1438
|
+
return {
|
|
1439
|
+
ok: false,
|
|
1440
|
+
error: details.stderr || finalOutput || "Oracle subprocess failed.",
|
|
1441
|
+
details,
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (!finalOutput.trim()) {
|
|
1446
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1447
|
+
return {
|
|
1448
|
+
ok: false,
|
|
1449
|
+
error: details.stderr || "Oracle finished without returning any text.",
|
|
1450
|
+
details,
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
return {
|
|
1455
|
+
ok: true,
|
|
1456
|
+
output: finalOutput.trim(),
|
|
1457
|
+
details,
|
|
1458
|
+
};
|
|
1417
1459
|
}
|
|
1418
1460
|
|
|
1419
1461
|
function renderCollapsedText(text: string, lineLimit = COLLAPSED_LINE_LIMIT): string {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1462
|
+
const lines = text.trim().split("\n");
|
|
1463
|
+
if (lines.length <= lineLimit) return lines.join("\n");
|
|
1464
|
+
return [...lines.slice(0, lineLimit), `... (${lines.length - lineLimit} more lines)`].join("\n");
|
|
1423
1465
|
}
|
|
1424
1466
|
|
|
1425
1467
|
type OracleExtensionDeps = {
|
|
1426
|
-
|
|
1468
|
+
spawnImpl?: typeof spawn;
|
|
1427
1469
|
};
|
|
1428
1470
|
|
|
1429
1471
|
export const __test__ = {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1472
|
+
buildSessionFallbackSelection,
|
|
1473
|
+
createOracleExtension,
|
|
1474
|
+
findAvailableModel,
|
|
1475
|
+
isModelAvailabilityError,
|
|
1476
|
+
parseModelPreference,
|
|
1477
|
+
runOracle,
|
|
1478
|
+
resolveThinkingLevel,
|
|
1479
|
+
selectOracleModel,
|
|
1438
1480
|
};
|
|
1439
1481
|
|
|
1440
1482
|
export default function oracleExtension(pi: ExtensionAPI) {
|
|
1441
|
-
|
|
1483
|
+
createOracleExtension(pi);
|
|
1442
1484
|
}
|
|
1443
1485
|
|
|
1444
1486
|
function createOracleExtension(pi: ExtensionAPI, deps: OracleExtensionDeps = {}) {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1487
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
1488
|
+
const activeRuns = new Map<string, OracleUiRun>();
|
|
1489
|
+
let preferences: OraclePreferences = {};
|
|
1490
|
+
|
|
1491
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
1492
|
+
preferences = await readOraclePreferences();
|
|
1493
|
+
activeRuns.clear();
|
|
1494
|
+
updateOracleUi(ctx, activeRuns);
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
pi.registerCommand("oracle", {
|
|
1498
|
+
description: "Configure Oracle default model and thinking level for future oracle tool calls",
|
|
1499
|
+
getArgumentCompletions: (prefix) => {
|
|
1500
|
+
const parts = prefix.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
1501
|
+
const first = parts[0] ?? "";
|
|
1502
|
+
if (parts.length <= 1) {
|
|
1503
|
+
const commands = ["status", "model", "thinking", "clear"];
|
|
1504
|
+
const matches = commands.filter((command) => command.startsWith(first));
|
|
1505
|
+
return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
|
|
1506
|
+
}
|
|
1507
|
+
if (parts[0] === "thinking") {
|
|
1508
|
+
const query = parts[1] ?? "";
|
|
1509
|
+
const values = ["auto", ...THINKING_LEVELS];
|
|
1510
|
+
const matches = values.filter((value) => value.startsWith(query));
|
|
1511
|
+
return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
|
|
1512
|
+
}
|
|
1513
|
+
if (parts[0] === "clear") {
|
|
1514
|
+
const query = parts[1] ?? "";
|
|
1515
|
+
const values = ["all", "model", "thinking"];
|
|
1516
|
+
const matches = values.filter((value) => value.startsWith(query));
|
|
1517
|
+
return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
|
|
1518
|
+
}
|
|
1519
|
+
return null;
|
|
1520
|
+
},
|
|
1521
|
+
handler: async (args, ctx) => {
|
|
1522
|
+
const raw = args.trim();
|
|
1523
|
+
const tokens = raw ? raw.split(/\s+/) : [];
|
|
1524
|
+
const [command = "status", ...rest] = tokens;
|
|
1525
|
+
const action = command.toLowerCase();
|
|
1526
|
+
const configPath = getOracleConfigPath();
|
|
1527
|
+
|
|
1528
|
+
const save = async (next: OraclePreferences): Promise<string | undefined> => {
|
|
1529
|
+
preferences = next;
|
|
1530
|
+
try {
|
|
1531
|
+
await writeOraclePreferences(preferences);
|
|
1532
|
+
return undefined;
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1535
|
+
return `Preference changed for this process, but could not save ${configPath}: ${message}`;
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
if (action === "status" || action === "show") {
|
|
1540
|
+
notifyCommand(ctx, formatOraclePreferences(preferences));
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (action === "model") {
|
|
1545
|
+
const model = rest.join(" ").trim();
|
|
1546
|
+
const normalizedModelAction = model.toLowerCase();
|
|
1547
|
+
if (!model) {
|
|
1548
|
+
notifyCommand(ctx, "Usage: /oracle model <provider/model|auto>", "warning");
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
if (
|
|
1552
|
+
normalizedModelAction === "auto" ||
|
|
1553
|
+
normalizedModelAction === "clear" ||
|
|
1554
|
+
normalizedModelAction === "default"
|
|
1555
|
+
) {
|
|
1556
|
+
const warning = await save({ ...preferences, model: undefined });
|
|
1557
|
+
notifyCommand(
|
|
1558
|
+
ctx,
|
|
1559
|
+
`Oracle default model cleared; future oracle calls will auto-select. ${warning ?? formatOraclePreferences(preferences)}`,
|
|
1560
|
+
warning ? "warning" : "info",
|
|
1561
|
+
);
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const parsedModel = parseModelPreference(model);
|
|
1565
|
+
const next = {
|
|
1566
|
+
...preferences,
|
|
1567
|
+
model: parsedModel.model,
|
|
1568
|
+
thinkingLevel: parsedModel.thinkingLevel ?? preferences.thinkingLevel,
|
|
1569
|
+
};
|
|
1570
|
+
const warning = await save(next);
|
|
1571
|
+
notifyCommand(
|
|
1572
|
+
ctx,
|
|
1573
|
+
`Oracle default model set to ${parsedModel.model}. ${warning ?? formatOraclePreferences(preferences)}`,
|
|
1574
|
+
warning ? "warning" : "info",
|
|
1575
|
+
);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
if (action === "thinking" || action === "think" || action === "thinking-level") {
|
|
1580
|
+
const value = rest[0]?.trim().toLowerCase();
|
|
1581
|
+
if (!value) {
|
|
1582
|
+
notifyCommand(
|
|
1583
|
+
ctx,
|
|
1584
|
+
`Usage: /oracle thinking ${THINKING_LEVELS.join(" | ")} | auto`,
|
|
1585
|
+
"warning",
|
|
1586
|
+
);
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (value === "auto" || value === "clear" || value === "default") {
|
|
1590
|
+
const warning = await save({ ...preferences, thinkingLevel: undefined });
|
|
1591
|
+
notifyCommand(
|
|
1592
|
+
ctx,
|
|
1593
|
+
`Oracle default thinking level cleared; future oracle calls will use built-in defaults. ${warning ?? formatOraclePreferences(preferences)}`,
|
|
1594
|
+
warning ? "warning" : "info",
|
|
1595
|
+
);
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const thinkingLevel = parseThinkingLevel(value);
|
|
1599
|
+
if (!thinkingLevel) {
|
|
1600
|
+
notifyCommand(
|
|
1601
|
+
ctx,
|
|
1602
|
+
`Usage: /oracle thinking ${THINKING_LEVELS.join(" | ")} | auto`,
|
|
1603
|
+
"warning",
|
|
1604
|
+
);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
const warning = await save({ ...preferences, thinkingLevel });
|
|
1608
|
+
notifyCommand(
|
|
1609
|
+
ctx,
|
|
1610
|
+
`Oracle default thinking level set to ${thinkingLevel}. ${warning ?? formatOraclePreferences(preferences)}`,
|
|
1611
|
+
warning ? "warning" : "info",
|
|
1612
|
+
);
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (action === "clear" || action === "reset") {
|
|
1617
|
+
const target = rest[0]?.trim().toLowerCase() || "all";
|
|
1618
|
+
let next: OraclePreferences;
|
|
1619
|
+
if (target === "all") next = {};
|
|
1620
|
+
else if (target === "model") next = { ...preferences, model: undefined };
|
|
1621
|
+
else if (target === "thinking" || target === "thinking-level")
|
|
1622
|
+
next = { ...preferences, thinkingLevel: undefined };
|
|
1623
|
+
else {
|
|
1624
|
+
notifyCommand(ctx, "Usage: /oracle clear [all|model|thinking]", "warning");
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const warning = await save(next);
|
|
1628
|
+
notifyCommand(
|
|
1629
|
+
ctx,
|
|
1630
|
+
`Oracle defaults cleared (${target}). ${warning ?? formatOraclePreferences(preferences)}`,
|
|
1631
|
+
warning ? "warning" : "info",
|
|
1632
|
+
);
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
notifyCommand(
|
|
1637
|
+
ctx,
|
|
1638
|
+
"Usage: /oracle status | model <provider/model|auto> | thinking <off|minimal|low|medium|high|xhigh|max|auto> | clear [all|model|thinking]",
|
|
1639
|
+
"warning",
|
|
1640
|
+
);
|
|
1641
|
+
},
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
pi.registerCommand("oracle-model", {
|
|
1645
|
+
description: "Show which model the oracle would use right now",
|
|
1646
|
+
handler: async (_args, ctx) => {
|
|
1647
|
+
const defaultModel = parseModelPreference(preferences.model);
|
|
1648
|
+
if (defaultModel.model) {
|
|
1649
|
+
const matched = await findAvailableModel(ctx, defaultModel.model);
|
|
1650
|
+
const thinking = resolveThinkingLevel(
|
|
1651
|
+
matched,
|
|
1652
|
+
preferences.thinkingLevel ?? defaultModel.thinkingLevel,
|
|
1653
|
+
);
|
|
1654
|
+
const modelRef = matched ? `${matched.provider}/${matched.id}` : defaultModel.model;
|
|
1655
|
+
const suffix = matched
|
|
1656
|
+
? appendThinkingLevelClampReason("Configured default oracle model is active.", thinking)
|
|
1657
|
+
: "Configured default oracle model is active, but it was not matched against the authenticated model list.";
|
|
1658
|
+
notifyCommand(ctx, `Oracle: ${modelRef} (${thinking.effective}) — ${suffix}`);
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
const selectionResult = await selectOracleModel(ctx, preferences.thinkingLevel);
|
|
1662
|
+
if (!selectionResult.ok) {
|
|
1663
|
+
notifyCommand(ctx, selectionResult.error, "error");
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const { selection } = selectionResult;
|
|
1668
|
+
const message = `Oracle: ${selection.modelRef} (${selection.thinkingLevel}) — ${selection.selectionReason}`;
|
|
1669
|
+
notifyCommand(ctx, message);
|
|
1670
|
+
},
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
pi.on("tool_result", async (event) => {
|
|
1674
|
+
if (event.toolName !== "oracle") return undefined;
|
|
1675
|
+
const details = event.details as OracleDetails | undefined;
|
|
1676
|
+
if ((details?.exitCode ?? 0) === 0) return undefined;
|
|
1677
|
+
return { isError: true };
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
pi.registerTool({
|
|
1681
|
+
name: "oracle",
|
|
1682
|
+
label: "Oracle",
|
|
1683
|
+
description:
|
|
1684
|
+
"Consult a separate read-only oracle subprocess for deep analysis, code review, debugging, planning, and second opinions.",
|
|
1685
|
+
promptSnippet:
|
|
1686
|
+
"Consult a read-only oracle that auto-selects the strongest reasoning model on the current provider/subscription.",
|
|
1687
|
+
promptGuidelines: [
|
|
1688
|
+
"Use oracle sparingly when you want a second opinion, deeper analysis, code review, debugging help, or a higher-reasoning pass.",
|
|
1689
|
+
"Do not use oracle for routine low-value work; oracle is slower than the main agent.",
|
|
1690
|
+
"The oracle tool is read-only by default and only exposes read, grep, find, and ls unless oracle includeBash is enabled.",
|
|
1691
|
+
"Set oracle includeBash only when the extra bash inspection tool is genuinely useful; keep oracle relying on read, grep, find, and ls otherwise.",
|
|
1692
|
+
"The oracle tool requests xhigh by default for reasoning models; oracle defaults and explicit thinkingLevel overrides are clamped to the effective model-supported level when the model is matched.",
|
|
1693
|
+
],
|
|
1694
|
+
parameters: OracleParams,
|
|
1695
|
+
|
|
1696
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
1697
|
+
const rawTask = (params as { task?: unknown }).task;
|
|
1698
|
+
const task = typeof rawTask === "string" ? rawTask.trim() : "";
|
|
1699
|
+
if (!task) throw new Error("Invalid parameters: expected task to be a non-empty string.");
|
|
1700
|
+
|
|
1701
|
+
const explicitModel = parseModelPreference(params.model);
|
|
1702
|
+
const defaultModel = parseModelPreference(preferences.model);
|
|
1703
|
+
const configuredModel = explicitModel.model ?? defaultModel.model;
|
|
1704
|
+
const configuredThinkingLevel =
|
|
1705
|
+
params.thinkingLevel ??
|
|
1706
|
+
explicitModel.thinkingLevel ??
|
|
1707
|
+
preferences.thinkingLevel ??
|
|
1708
|
+
defaultModel.thinkingLevel;
|
|
1709
|
+
const uiRun: OracleUiRun = {
|
|
1710
|
+
task,
|
|
1711
|
+
includeBash: params.includeBash ?? false,
|
|
1712
|
+
startedAt: Date.now(),
|
|
1713
|
+
};
|
|
1714
|
+
activeRuns.set(toolCallId, uiRun);
|
|
1715
|
+
updateOracleUi(ctx, activeRuns);
|
|
1716
|
+
|
|
1717
|
+
try {
|
|
1718
|
+
const attempts: OracleSelection[] = [];
|
|
1719
|
+
const seen = new Set<string>();
|
|
1720
|
+
const aggregateUsage = createEmptyUsage();
|
|
1721
|
+
const finalizeResult = (result: {
|
|
1722
|
+
content: Array<{ type: "text"; text: string }>;
|
|
1723
|
+
details: OracleDetails;
|
|
1724
|
+
}): AgentToolResult<OracleDetails> => ({
|
|
1725
|
+
...result,
|
|
1726
|
+
details: { ...result.details, usage: cloneUsageStats(aggregateUsage) },
|
|
1727
|
+
usage: usageStatsToToolUsage(aggregateUsage),
|
|
1728
|
+
});
|
|
1729
|
+
const pushAttempt = (candidate: OracleSelection | undefined) => {
|
|
1730
|
+
if (!candidate) return;
|
|
1731
|
+
const key = `${candidate.provider}/${candidate.modelId}`.toLowerCase();
|
|
1732
|
+
if (seen.has(key)) return;
|
|
1733
|
+
seen.add(key);
|
|
1734
|
+
attempts.push(candidate);
|
|
1735
|
+
};
|
|
1736
|
+
|
|
1737
|
+
if (configuredModel) {
|
|
1738
|
+
const modelRef = configuredModel;
|
|
1739
|
+
const matched = await findAvailableModel(ctx, modelRef);
|
|
1740
|
+
const provider =
|
|
1741
|
+
matched?.provider ??
|
|
1742
|
+
(modelRef.includes("/") ? modelRef.split("/")[0] : (ctx.model?.provider ?? "unknown"));
|
|
1743
|
+
const modelId =
|
|
1744
|
+
matched?.id ??
|
|
1745
|
+
(modelRef.includes("/") ? modelRef.split("/").slice(1).join("/") : modelRef);
|
|
1746
|
+
const thinking = resolveThinkingLevel(matched, configuredThinkingLevel);
|
|
1747
|
+
const usedToolOverride = !!explicitModel.model;
|
|
1748
|
+
const selectionReason = matched
|
|
1749
|
+
? appendThinkingLevelClampReason(
|
|
1750
|
+
usedToolOverride
|
|
1751
|
+
? "Used the explicit model override provided in the tool call."
|
|
1752
|
+
: "Used the configured default oracle model.",
|
|
1753
|
+
thinking,
|
|
1754
|
+
)
|
|
1755
|
+
: usedToolOverride
|
|
1756
|
+
? "Used the explicit model override provided in the tool call. The model was not matched against the authenticated model list, so the reasoning level fallback was applied."
|
|
1757
|
+
: "Used the configured default oracle model. The model was not matched against the authenticated model list, so the reasoning level fallback was applied.";
|
|
1758
|
+
pushAttempt({
|
|
1759
|
+
modelRef: matched ? `${matched.provider}/${matched.id}` : modelRef,
|
|
1760
|
+
provider,
|
|
1761
|
+
modelId,
|
|
1762
|
+
modelName: matched?.name,
|
|
1763
|
+
thinkingLevel: thinking.effective,
|
|
1764
|
+
...(matched && thinking.clamped
|
|
1765
|
+
? { requestedThinkingLevel: thinking.requested, thinkingLevelClamped: true }
|
|
1766
|
+
: {}),
|
|
1767
|
+
autoSelected: false,
|
|
1768
|
+
selectionReason,
|
|
1769
|
+
});
|
|
1770
|
+
} else {
|
|
1771
|
+
const selectionResult = await selectOracleModel(ctx, configuredThinkingLevel);
|
|
1772
|
+
if (!selectionResult.ok) {
|
|
1773
|
+
return finalizeResult({
|
|
1774
|
+
content: [{ type: "text", text: selectionResult.error }],
|
|
1775
|
+
details: {
|
|
1776
|
+
modelRef: "",
|
|
1777
|
+
provider: ctx.model?.provider ?? "unknown",
|
|
1778
|
+
modelId: "",
|
|
1779
|
+
modelName: undefined,
|
|
1780
|
+
thinkingLevel: configuredThinkingLevel ?? DEFAULT_THINKING_LEVEL,
|
|
1781
|
+
autoSelected: true,
|
|
1782
|
+
selectionReason: selectionResult.error,
|
|
1783
|
+
includeBash: params.includeBash ?? false,
|
|
1784
|
+
usage: createEmptyUsage(),
|
|
1785
|
+
stderr: "",
|
|
1786
|
+
exitCode: 1,
|
|
1787
|
+
durationMs: 0,
|
|
1788
|
+
cwd: params.cwd ?? ctx.cwd,
|
|
1789
|
+
},
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
for (const candidate of selectionResult.ordered) pushAttempt(candidate);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
pushAttempt(buildSessionFallbackSelection(ctx, configuredThinkingLevel));
|
|
1796
|
+
|
|
1797
|
+
const handleUpdate = (partial: {
|
|
1798
|
+
content: Array<{ type: "text"; text: string }>;
|
|
1799
|
+
details: OracleDetails;
|
|
1800
|
+
}) => {
|
|
1801
|
+
uiRun.preview = partial.content[0]?.text ?? uiRun.preview;
|
|
1802
|
+
updateOracleUi(ctx, activeRuns);
|
|
1803
|
+
onUpdate?.(partial);
|
|
1804
|
+
};
|
|
1805
|
+
|
|
1806
|
+
let lastErrorResult: { ok: false; error: string; details: OracleDetails } | undefined;
|
|
1807
|
+
for (let index = 0; index < attempts.length; index++) {
|
|
1808
|
+
const previous = index > 0 ? attempts[index - 1] : undefined;
|
|
1809
|
+
const attempt = previous
|
|
1810
|
+
? withFallbackReason(attempts[index], previous)
|
|
1811
|
+
: attempts[index];
|
|
1812
|
+
uiRun.selection = attempt;
|
|
1813
|
+
updateOracleUi(ctx, activeRuns);
|
|
1814
|
+
|
|
1815
|
+
const result = await runOracle(
|
|
1816
|
+
attempt,
|
|
1817
|
+
{ ...params, task },
|
|
1818
|
+
signal,
|
|
1819
|
+
handleUpdate,
|
|
1820
|
+
ctx.cwd,
|
|
1821
|
+
spawnImpl,
|
|
1822
|
+
);
|
|
1823
|
+
mergeUsageStats(aggregateUsage, result.details.usage);
|
|
1824
|
+
if (result.ok) {
|
|
1825
|
+
return finalizeResult({
|
|
1826
|
+
content: [{ type: "text", text: result.output }],
|
|
1827
|
+
details: result.details,
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
lastErrorResult = result;
|
|
1832
|
+
const canFallBack = index < attempts.length - 1 && isModelAvailabilityError(result.error);
|
|
1833
|
+
if (!canFallBack) {
|
|
1834
|
+
return finalizeResult({
|
|
1835
|
+
content: [{ type: "text", text: result.error }],
|
|
1836
|
+
details: result.details,
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
return finalizeResult({
|
|
1842
|
+
content: [
|
|
1843
|
+
{
|
|
1844
|
+
type: "text",
|
|
1845
|
+
text: lastErrorResult?.error ?? "Oracle could not select an available model.",
|
|
1846
|
+
},
|
|
1847
|
+
],
|
|
1848
|
+
details: lastErrorResult?.details ?? {
|
|
1849
|
+
modelRef: "",
|
|
1850
|
+
provider: ctx.model?.provider ?? "unknown",
|
|
1851
|
+
modelId: "",
|
|
1852
|
+
modelName: undefined,
|
|
1853
|
+
thinkingLevel: configuredThinkingLevel ?? DEFAULT_THINKING_LEVEL,
|
|
1854
|
+
autoSelected: true,
|
|
1855
|
+
selectionReason: "No authenticated models were available to run the oracle.",
|
|
1856
|
+
includeBash: params.includeBash ?? false,
|
|
1857
|
+
usage: createEmptyUsage(),
|
|
1858
|
+
stderr: "",
|
|
1859
|
+
exitCode: 1,
|
|
1860
|
+
durationMs: 0,
|
|
1861
|
+
cwd: params.cwd ?? ctx.cwd,
|
|
1862
|
+
},
|
|
1863
|
+
});
|
|
1864
|
+
} finally {
|
|
1865
|
+
activeRuns.delete(toolCallId);
|
|
1866
|
+
updateOracleUi(ctx, activeRuns);
|
|
1867
|
+
}
|
|
1868
|
+
},
|
|
1869
|
+
|
|
1870
|
+
renderCall(args, theme) {
|
|
1871
|
+
const task = args.task.length > 90 ? `${args.task.slice(0, 90)}...` : args.task;
|
|
1872
|
+
const mode = args.includeBash ? "read-only+bash" : "read-only";
|
|
1873
|
+
const override = args.model ? ` ${theme.fg("muted", `[${args.model}]`)}` : "";
|
|
1874
|
+
const thinking = args.thinkingLevel
|
|
1875
|
+
? ` ${theme.fg("warning", `(${args.thinkingLevel})`)}`
|
|
1876
|
+
: "";
|
|
1877
|
+
return new Text(
|
|
1878
|
+
`${theme.fg("toolTitle", theme.bold("oracle "))}${theme.fg("accent", mode)}${override}${thinking}\n ${theme.fg("dim", task)}`,
|
|
1879
|
+
0,
|
|
1880
|
+
0,
|
|
1881
|
+
);
|
|
1882
|
+
},
|
|
1883
|
+
|
|
1884
|
+
renderResult(result, { expanded }, theme) {
|
|
1885
|
+
const details = result.details as OracleDetails | undefined;
|
|
1886
|
+
const body = result.content[0]?.type === "text" ? result.content[0].text : "(no output)";
|
|
1887
|
+
if (!details) return new Text(body, 0, 0);
|
|
1888
|
+
|
|
1889
|
+
const isError = (details.exitCode ?? 0) !== 0;
|
|
1890
|
+
const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
1891
|
+
const header = `${icon} ${theme.fg("toolTitle", theme.bold("oracle "))}${theme.fg("accent", details.modelRef || "(auto)")}`;
|
|
1892
|
+
const subheader = [
|
|
1893
|
+
details.thinkingLevel !== "off" ? details.thinkingLevel : undefined,
|
|
1894
|
+
details.includeBash ? "read-only+bash" : "read-only",
|
|
1895
|
+
formatDuration(details.durationMs),
|
|
1896
|
+
]
|
|
1897
|
+
.filter(Boolean)
|
|
1898
|
+
.join(" · ");
|
|
1899
|
+
const usage = formatUsage(details.usage);
|
|
1900
|
+
|
|
1901
|
+
if (!expanded) {
|
|
1902
|
+
let text = header;
|
|
1903
|
+
if (subheader) text += `\n${theme.fg("dim", subheader)}`;
|
|
1904
|
+
text += `\n\n${theme.fg("toolOutput", renderCollapsedText(body))}`;
|
|
1905
|
+
if (usage) text += `\n\n${theme.fg("dim", usage)}`;
|
|
1906
|
+
if (isError && details.stderr)
|
|
1907
|
+
text += `\n${theme.fg("error", renderCollapsedText(details.stderr, 4))}`;
|
|
1908
|
+
text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
1909
|
+
return new Text(text, 0, 0);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
const container = new Container();
|
|
1913
|
+
container.addChild(new Text(header, 0, 0));
|
|
1914
|
+
if (subheader) container.addChild(new Text(theme.fg("dim", subheader), 0, 0));
|
|
1915
|
+
container.addChild(new Spacer(1));
|
|
1916
|
+
container.addChild(new Text(theme.fg("muted", "Selection"), 0, 0));
|
|
1917
|
+
container.addChild(new Text(theme.fg("dim", details.selectionReason), 0, 0));
|
|
1918
|
+
container.addChild(new Spacer(1));
|
|
1919
|
+
container.addChild(new Text(theme.fg("muted", "Output"), 0, 0));
|
|
1920
|
+
container.addChild(new Markdown(body.trim(), 0, 0, getMarkdownTheme()));
|
|
1921
|
+
if (usage) {
|
|
1922
|
+
container.addChild(new Spacer(1));
|
|
1923
|
+
container.addChild(new Text(theme.fg("muted", "Usage"), 0, 0));
|
|
1924
|
+
container.addChild(new Text(theme.fg("dim", usage), 0, 0));
|
|
1925
|
+
}
|
|
1926
|
+
if (details.stderr) {
|
|
1927
|
+
container.addChild(new Spacer(1));
|
|
1928
|
+
container.addChild(new Text(theme.fg("muted", "stderr"), 0, 0));
|
|
1929
|
+
container.addChild(
|
|
1930
|
+
new Text(
|
|
1931
|
+
isError ? theme.fg("error", details.stderr) : theme.fg("dim", details.stderr),
|
|
1932
|
+
0,
|
|
1933
|
+
0,
|
|
1934
|
+
),
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
return container;
|
|
1938
|
+
},
|
|
1939
|
+
});
|
|
1834
1940
|
}
|