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