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