@oh-my-pi/pi-coding-agent 13.8.0 → 13.9.2
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/CHANGELOG.md +52 -0
- package/package.json +7 -7
- package/src/capability/rule.ts +0 -4
- package/src/cli/agents-cli.ts +1 -1
- package/src/cli/args.ts +7 -12
- package/src/commands/launch.ts +3 -2
- package/src/config/model-resolver.ts +106 -33
- package/src/config/settings-schema.ts +14 -2
- package/src/config/settings.ts +1 -17
- package/src/discovery/helpers.ts +10 -17
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +37 -15
- package/src/extensibility/extensions/loader.ts +1 -2
- package/src/extensibility/extensions/types.ts +2 -1
- package/src/main.ts +20 -13
- package/src/modes/components/agent-dashboard.ts +12 -13
- package/src/modes/components/model-selector.ts +157 -59
- package/src/modes/components/read-tool-group.ts +36 -2
- package/src/modes/components/settings-defs.ts +11 -8
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/thinking-selector.ts +3 -15
- package/src/modes/controllers/selector-controller.ts +21 -7
- package/src/modes/rpc/rpc-client.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +2 -2
- package/src/modes/theme/theme.ts +2 -1
- package/src/patch/hashline.ts +26 -3
- package/src/patch/index.ts +14 -16
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +21 -29
- package/src/session/agent-session.ts +44 -37
- package/src/task/executor.ts +10 -8
- package/src/task/types.ts +1 -2
- package/src/tools/read.ts +88 -264
- package/src/utils/frontmatter.ts +25 -4
- package/src/web/scrapers/choosealicense.ts +1 -1
|
@@ -63,6 +63,7 @@ interface DashboardAgent extends AgentDefinition {
|
|
|
63
63
|
interface ModelResolution {
|
|
64
64
|
resolved: string;
|
|
65
65
|
thinkingLevel?: string;
|
|
66
|
+
explicitThinkingLevel: boolean;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
interface GeneratedAgentSpec {
|
|
@@ -109,6 +110,12 @@ function joinPatterns(patterns: string[]): string {
|
|
|
109
110
|
return patterns.join(", ");
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
function formatResolution(resolution: ModelResolution): string {
|
|
114
|
+
const resolved = theme.fg("success", resolution.resolved);
|
|
115
|
+
if (!resolution.explicitThinkingLevel || !resolution.thinkingLevel) return resolved;
|
|
116
|
+
return `${resolved} ${theme.fg("dim", `(${resolution.thinkingLevel})`)}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
112
119
|
function matchAgent(agent: DashboardAgent, query: string): boolean {
|
|
113
120
|
const q = query.toLowerCase();
|
|
114
121
|
if (agent.name.toLowerCase().includes(q)) return true;
|
|
@@ -289,10 +296,7 @@ class AgentInspectorPane implements Component {
|
|
|
289
296
|
}
|
|
290
297
|
|
|
291
298
|
#formatResolution(resolution: ModelResolution): string {
|
|
292
|
-
|
|
293
|
-
return `${theme.fg("success", resolution.resolved)} ${theme.fg("dim", `(${resolution.thinkingLevel})`)}`;
|
|
294
|
-
}
|
|
295
|
-
return theme.fg("success", resolution.resolved);
|
|
299
|
+
return formatResolution(resolution);
|
|
296
300
|
}
|
|
297
301
|
|
|
298
302
|
invalidate(): void {}
|
|
@@ -770,7 +774,7 @@ export class AgentDashboard extends Container {
|
|
|
770
774
|
#resolvePatterns(patterns: string[]): ModelResolution | undefined {
|
|
771
775
|
const modelRegistry = this.modelContext.modelRegistry;
|
|
772
776
|
if (!modelRegistry || patterns.length === 0) return undefined;
|
|
773
|
-
const { model, thinkingLevel } = resolveModelOverride(
|
|
777
|
+
const { model, thinkingLevel, explicitThinkingLevel } = resolveModelOverride(
|
|
774
778
|
patterns,
|
|
775
779
|
modelRegistry,
|
|
776
780
|
this.#settingsManager ?? undefined,
|
|
@@ -779,6 +783,7 @@ export class AgentDashboard extends Container {
|
|
|
779
783
|
return {
|
|
780
784
|
resolved: formatModelString(model),
|
|
781
785
|
thinkingLevel,
|
|
786
|
+
explicitThinkingLevel,
|
|
782
787
|
};
|
|
783
788
|
}
|
|
784
789
|
|
|
@@ -930,20 +935,14 @@ export class AgentDashboard extends Container {
|
|
|
930
935
|
);
|
|
931
936
|
this.addChild(
|
|
932
937
|
new Text(
|
|
933
|
-
theme.fg(
|
|
934
|
-
"muted",
|
|
935
|
-
`Default resolves: ${defaultResolution ? defaultResolution.resolved : "(unresolved)"}`,
|
|
936
|
-
),
|
|
938
|
+
`${theme.fg("muted", "Default resolves:")} ${defaultResolution ? formatResolution(defaultResolution) : theme.fg("dim", "(unresolved)")}`,
|
|
937
939
|
0,
|
|
938
940
|
0,
|
|
939
941
|
),
|
|
940
942
|
);
|
|
941
943
|
this.addChild(
|
|
942
944
|
new Text(
|
|
943
|
-
theme.fg(
|
|
944
|
-
"muted",
|
|
945
|
-
`Preview effective: ${previewResolution ? previewResolution.resolved : "(unresolved)"}`,
|
|
946
|
-
),
|
|
945
|
+
`${theme.fg("muted", "Preview effective:")} ${previewResolution ? formatResolution(previewResolution) : theme.fg("dim", "(unresolved)")}`,
|
|
947
946
|
0,
|
|
948
947
|
0,
|
|
949
948
|
),
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getAvailableThinkingLevels,
|
|
3
|
+
getThinkingMetadata,
|
|
4
|
+
type Model,
|
|
5
|
+
modelsAreEqual,
|
|
6
|
+
supportsXhigh,
|
|
7
|
+
type ThinkingMode,
|
|
8
|
+
} from "@oh-my-pi/pi-ai";
|
|
2
9
|
import { Container, Input, matchesKey, Spacer, type Tab, TabBar, Text, type TUI, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
3
10
|
import { MODEL_ROLE_IDS, MODEL_ROLES, type ModelRegistry, type ModelRole } from "../../config/model-registry";
|
|
4
|
-
import {
|
|
11
|
+
import { resolveModelRoleValue } from "../../config/model-resolver";
|
|
5
12
|
import type { Settings } from "../../config/settings";
|
|
6
13
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
7
14
|
import { fuzzyFilter } from "../../utils/fuzzy";
|
|
@@ -25,12 +32,26 @@ interface ScopedModelItem {
|
|
|
25
32
|
thinkingLevel: string;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
interface
|
|
35
|
+
interface RoleAssignment {
|
|
36
|
+
model: Model;
|
|
37
|
+
thinkingMode: ThinkingMode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type RoleSelectCallback = (model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode) => void;
|
|
41
|
+
type CancelCallback = () => void;
|
|
42
|
+
interface MenuRoleAction {
|
|
29
43
|
label: string;
|
|
30
44
|
role: ModelRole;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
const
|
|
47
|
+
const MENU_ROLE_ACTIONS: MenuRoleAction[] = MODEL_ROLE_IDS.map(role => {
|
|
48
|
+
const roleInfo = MODEL_ROLES[role];
|
|
49
|
+
const roleLabel = roleInfo.tag ? `${roleInfo.tag} (${roleInfo.name})` : roleInfo.name;
|
|
50
|
+
return {
|
|
51
|
+
label: `Set as ${roleLabel}`,
|
|
52
|
+
role,
|
|
53
|
+
};
|
|
54
|
+
});
|
|
34
55
|
|
|
35
56
|
const ALL_TAB = "ALL";
|
|
36
57
|
|
|
@@ -50,11 +71,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
50
71
|
#allModels: ModelItem[] = [];
|
|
51
72
|
#filteredModels: ModelItem[] = [];
|
|
52
73
|
#selectedIndex: number = 0;
|
|
53
|
-
#roles
|
|
54
|
-
#settings
|
|
55
|
-
#modelRegistry
|
|
56
|
-
#onSelectCallback
|
|
57
|
-
#onCancelCallback
|
|
74
|
+
#roles = {} as Record<ModelRole, RoleAssignment | undefined>;
|
|
75
|
+
#settings = null as unknown as Settings;
|
|
76
|
+
#modelRegistry = null as unknown as ModelRegistry;
|
|
77
|
+
#onSelectCallback = (() => {}) as RoleSelectCallback;
|
|
78
|
+
#onCancelCallback = (() => {}) as CancelCallback;
|
|
58
79
|
#errorMessage?: unknown;
|
|
59
80
|
#tui: TUI;
|
|
60
81
|
#scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
@@ -67,6 +88,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
67
88
|
// Context menu state
|
|
68
89
|
#isMenuOpen: boolean = false;
|
|
69
90
|
#menuSelectedIndex: number = 0;
|
|
91
|
+
#menuStep: "role" | "thinking" = "role";
|
|
92
|
+
#menuSelectedRole: ModelRole | null = null;
|
|
70
93
|
|
|
71
94
|
constructor(
|
|
72
95
|
tui: TUI,
|
|
@@ -74,7 +97,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
74
97
|
settings: Settings,
|
|
75
98
|
modelRegistry: ModelRegistry,
|
|
76
99
|
scopedModels: ReadonlyArray<ScopedModelItem>,
|
|
77
|
-
onSelect: (model: Model, role: ModelRole | null) => void,
|
|
100
|
+
onSelect: (model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode) => void,
|
|
78
101
|
onCancel: () => void,
|
|
79
102
|
options?: { temporaryOnly?: boolean; initialSearchInput?: string },
|
|
80
103
|
) {
|
|
@@ -157,15 +180,20 @@ export class ModelSelectorComponent extends Container {
|
|
|
157
180
|
|
|
158
181
|
#loadRoleModels(): void {
|
|
159
182
|
const allModels = this.#modelRegistry.getAll();
|
|
183
|
+
const matchPreferences = { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() };
|
|
160
184
|
for (const role of MODEL_ROLE_IDS) {
|
|
161
|
-
const
|
|
162
|
-
if (!
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
185
|
+
const roleValue = this.#settings.getModelRole(role);
|
|
186
|
+
if (!roleValue) continue;
|
|
187
|
+
|
|
188
|
+
const { model, thinkingLevel, explicitThinkingLevel } = resolveModelRoleValue(roleValue, allModels, {
|
|
189
|
+
settings: this.#settings,
|
|
190
|
+
matchPreferences,
|
|
191
|
+
});
|
|
192
|
+
if (model) {
|
|
193
|
+
this.#roles[role] = {
|
|
194
|
+
model,
|
|
195
|
+
thinkingMode: explicitThinkingLevel && thinkingLevel !== undefined ? thinkingLevel : "inherit",
|
|
196
|
+
};
|
|
169
197
|
}
|
|
170
198
|
}
|
|
171
199
|
}
|
|
@@ -179,7 +207,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
179
207
|
let i = 0;
|
|
180
208
|
while (i < MODEL_ROLE_IDS.length) {
|
|
181
209
|
const role = MODEL_ROLE_IDS[i];
|
|
182
|
-
|
|
210
|
+
const assigned = this.#roles[role];
|
|
211
|
+
if (assigned && modelsAreEqual(assigned.model, model.model)) {
|
|
183
212
|
break;
|
|
184
213
|
}
|
|
185
214
|
i++;
|
|
@@ -373,14 +402,17 @@ export class ModelSelectorComponent extends Container {
|
|
|
373
402
|
const isSelected = i === this.#selectedIndex;
|
|
374
403
|
|
|
375
404
|
// Build role badges (inverted: color as background, black text)
|
|
376
|
-
const
|
|
405
|
+
const roleBadgeTokens: string[] = [];
|
|
377
406
|
for (const role of MODEL_ROLE_IDS) {
|
|
378
407
|
const { tag, color } = MODEL_ROLES[role];
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
408
|
+
const assigned = this.#roles[role];
|
|
409
|
+
if (!tag || !assigned || !modelsAreEqual(assigned.model, item.model)) continue;
|
|
410
|
+
|
|
411
|
+
const badge = makeInvertedBadge(tag, color ?? "success");
|
|
412
|
+
const thinkingLabel = getThinkingMetadata(assigned.thinkingMode).label;
|
|
413
|
+
roleBadgeTokens.push(`${badge} ${theme.fg("dim", `(${thinkingLabel})`)}`);
|
|
382
414
|
}
|
|
383
|
-
const badgeText =
|
|
415
|
+
const badgeText = roleBadgeTokens.length > 0 ? ` ${roleBadgeTokens.join(" ")}` : "";
|
|
384
416
|
|
|
385
417
|
let line = "";
|
|
386
418
|
if (isSelected) {
|
|
@@ -424,17 +456,36 @@ export class ModelSelectorComponent extends Container {
|
|
|
424
456
|
this.#listContainer.addChild(new Text(theme.fg("muted", ` Model Name: ${selected.model.name}`), 0, 0));
|
|
425
457
|
}
|
|
426
458
|
}
|
|
459
|
+
#getThinkingModesForModel(model: Model): ReadonlyArray<ThinkingMode> {
|
|
460
|
+
return ["inherit", ...getAvailableThinkingLevels(supportsXhigh(model))];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
#getCurrentRoleThinkingMode(role: ModelRole): ThinkingMode {
|
|
464
|
+
return this.#roles[role]?.thinkingMode ?? "inherit";
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
#getThinkingPreselectIndex(role: ModelRole, model: Model): number {
|
|
468
|
+
const options = this.#getThinkingModesForModel(model);
|
|
469
|
+
const currentMode = this.#getCurrentRoleThinkingMode(role);
|
|
470
|
+
const preferredMode = currentMode === "xhigh" && !options.includes("xhigh") ? "high" : currentMode;
|
|
471
|
+
const foundIndex = options.indexOf(preferredMode);
|
|
472
|
+
return foundIndex >= 0 ? foundIndex : 0;
|
|
473
|
+
}
|
|
427
474
|
|
|
428
475
|
#openMenu(): void {
|
|
429
476
|
if (this.#filteredModels.length === 0) return;
|
|
430
477
|
|
|
431
478
|
this.#isMenuOpen = true;
|
|
479
|
+
this.#menuStep = "role";
|
|
480
|
+
this.#menuSelectedRole = null;
|
|
432
481
|
this.#menuSelectedIndex = 0;
|
|
433
482
|
this.#updateMenu();
|
|
434
483
|
}
|
|
435
484
|
|
|
436
485
|
#closeMenu(): void {
|
|
437
486
|
this.#isMenuOpen = false;
|
|
487
|
+
this.#menuStep = "role";
|
|
488
|
+
this.#menuSelectedRole = null;
|
|
438
489
|
this.#menuContainer.clear();
|
|
439
490
|
}
|
|
440
491
|
|
|
@@ -444,35 +495,53 @@ export class ModelSelectorComponent extends Container {
|
|
|
444
495
|
const selectedModel = this.#filteredModels[this.#selectedIndex];
|
|
445
496
|
if (!selectedModel) return;
|
|
446
497
|
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
498
|
+
const showingThinking = this.#menuStep === "thinking" && this.#menuSelectedRole !== null;
|
|
499
|
+
const thinkingOptions = showingThinking ? this.#getThinkingModesForModel(selectedModel.model) : [];
|
|
500
|
+
const optionLines = showingThinking
|
|
501
|
+
? thinkingOptions.map((thinkingMode, index) => {
|
|
502
|
+
const prefix = index === this.#menuSelectedIndex ? ` ${theme.nav.cursor} ` : " ";
|
|
503
|
+
const label = getThinkingMetadata(thinkingMode).label;
|
|
504
|
+
return `${prefix}${label}`;
|
|
505
|
+
})
|
|
506
|
+
: MENU_ROLE_ACTIONS.map((action, index) => {
|
|
507
|
+
const prefix = index === this.#menuSelectedIndex ? ` ${theme.nav.cursor} ` : " ";
|
|
508
|
+
return `${prefix}${action.label}`;
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const selectedRoleName = this.#menuSelectedRole ? MODEL_ROLES[this.#menuSelectedRole].name : "";
|
|
512
|
+
const headerText =
|
|
513
|
+
showingThinking && this.#menuSelectedRole
|
|
514
|
+
? ` Thinking for: ${selectedRoleName} (${selectedModel.id})`
|
|
515
|
+
: ` Action for: ${selectedModel.id}`;
|
|
516
|
+
const hintText = showingThinking ? " Enter: confirm Esc: back" : " Enter: continue Esc: cancel";
|
|
453
517
|
const menuWidth = Math.max(
|
|
454
518
|
visibleWidth(headerText),
|
|
455
519
|
visibleWidth(hintText),
|
|
456
|
-
...
|
|
520
|
+
...optionLines.map(line => visibleWidth(line)),
|
|
457
521
|
);
|
|
458
522
|
|
|
459
|
-
// Menu header
|
|
460
523
|
this.#menuContainer.addChild(new Spacer(1));
|
|
461
524
|
this.#menuContainer.addChild(new Text(theme.fg("border", theme.boxSharp.horizontal.repeat(menuWidth)), 0, 0));
|
|
462
|
-
|
|
525
|
+
if (showingThinking && this.#menuSelectedRole) {
|
|
526
|
+
this.#menuContainer.addChild(
|
|
527
|
+
new Text(
|
|
528
|
+
theme.fg("text", ` Thinking for: ${theme.bold(selectedRoleName)} (${theme.bold(selectedModel.id)})`),
|
|
529
|
+
0,
|
|
530
|
+
0,
|
|
531
|
+
),
|
|
532
|
+
);
|
|
533
|
+
} else {
|
|
534
|
+
this.#menuContainer.addChild(
|
|
535
|
+
new Text(theme.fg("text", ` Action for: ${theme.bold(selectedModel.id)}`), 0, 0),
|
|
536
|
+
);
|
|
537
|
+
}
|
|
463
538
|
this.#menuContainer.addChild(new Spacer(1));
|
|
464
539
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
540
|
+
for (let i = 0; i < optionLines.length; i++) {
|
|
541
|
+
const lineText = optionLines[i];
|
|
542
|
+
if (!lineText) continue;
|
|
468
543
|
const isSelected = i === this.#menuSelectedIndex;
|
|
469
|
-
|
|
470
|
-
let line: string;
|
|
471
|
-
if (isSelected) {
|
|
472
|
-
line = theme.fg("accent", ` ${theme.nav.cursor} ${action.label}`);
|
|
473
|
-
} else {
|
|
474
|
-
line = theme.fg("muted", ` ${action.label}`);
|
|
475
|
-
}
|
|
544
|
+
const line = isSelected ? theme.fg("accent", lineText) : theme.fg("muted", lineText);
|
|
476
545
|
this.#menuContainer.addChild(new Text(line, 0, 0));
|
|
477
546
|
}
|
|
478
547
|
|
|
@@ -532,55 +601,84 @@ export class ModelSelectorComponent extends Container {
|
|
|
532
601
|
this.#searchInput.handleInput(keyData);
|
|
533
602
|
this.#filterModels(this.#searchInput.getValue());
|
|
534
603
|
}
|
|
535
|
-
|
|
536
604
|
#handleMenuInput(keyData: string): void {
|
|
537
|
-
|
|
605
|
+
const selectedModel = this.#filteredModels[this.#selectedIndex];
|
|
606
|
+
if (!selectedModel) return;
|
|
607
|
+
|
|
608
|
+
const optionCount =
|
|
609
|
+
this.#menuStep === "thinking" && this.#menuSelectedRole !== null
|
|
610
|
+
? this.#getThinkingModesForModel(selectedModel.model).length
|
|
611
|
+
: MENU_ROLE_ACTIONS.length;
|
|
612
|
+
if (optionCount === 0) return;
|
|
613
|
+
|
|
538
614
|
if (matchesKey(keyData, "up")) {
|
|
539
|
-
this.#menuSelectedIndex = (this.#menuSelectedIndex - 1 +
|
|
615
|
+
this.#menuSelectedIndex = (this.#menuSelectedIndex - 1 + optionCount) % optionCount;
|
|
540
616
|
this.#updateMenu();
|
|
541
617
|
return;
|
|
542
618
|
}
|
|
543
619
|
|
|
544
|
-
// Down arrow - navigate menu
|
|
545
620
|
if (matchesKey(keyData, "down")) {
|
|
546
|
-
this.#menuSelectedIndex = (this.#menuSelectedIndex + 1) %
|
|
621
|
+
this.#menuSelectedIndex = (this.#menuSelectedIndex + 1) % optionCount;
|
|
547
622
|
this.#updateMenu();
|
|
548
623
|
return;
|
|
549
624
|
}
|
|
550
625
|
|
|
551
|
-
// Enter - confirm selection
|
|
552
626
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
this.#
|
|
557
|
-
this.#
|
|
627
|
+
if (this.#menuStep === "role") {
|
|
628
|
+
const action = MENU_ROLE_ACTIONS[this.#menuSelectedIndex];
|
|
629
|
+
if (!action) return;
|
|
630
|
+
this.#menuSelectedRole = action.role;
|
|
631
|
+
this.#menuStep = "thinking";
|
|
632
|
+
this.#menuSelectedIndex = this.#getThinkingPreselectIndex(action.role, selectedModel.model);
|
|
633
|
+
this.#updateMenu();
|
|
634
|
+
return;
|
|
558
635
|
}
|
|
636
|
+
|
|
637
|
+
if (!this.#menuSelectedRole) return;
|
|
638
|
+
const thinkingOptions = this.#getThinkingModesForModel(selectedModel.model);
|
|
639
|
+
const thinkingMode = thinkingOptions[this.#menuSelectedIndex];
|
|
640
|
+
if (!thinkingMode) return;
|
|
641
|
+
this.#handleSelect(selectedModel.model, this.#menuSelectedRole, thinkingMode);
|
|
642
|
+
this.#closeMenu();
|
|
559
643
|
return;
|
|
560
644
|
}
|
|
561
645
|
|
|
562
|
-
// Escape or Ctrl+C - close menu only
|
|
563
646
|
if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc") || matchesKey(keyData, "ctrl+c")) {
|
|
647
|
+
if (this.#menuStep === "thinking" && this.#menuSelectedRole !== null) {
|
|
648
|
+
this.#menuStep = "role";
|
|
649
|
+
const roleIndex = MENU_ROLE_ACTIONS.findIndex(action => action.role === this.#menuSelectedRole);
|
|
650
|
+
this.#menuSelectedRole = null;
|
|
651
|
+
this.#menuSelectedIndex = roleIndex >= 0 ? roleIndex : 0;
|
|
652
|
+
this.#updateMenu();
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
564
655
|
this.#closeMenu();
|
|
565
656
|
return;
|
|
566
657
|
}
|
|
567
658
|
}
|
|
568
659
|
|
|
569
|
-
#
|
|
660
|
+
#formatRoleModelValue(model: Model, thinkingMode: ThinkingMode): string {
|
|
661
|
+
const modelKey = `${model.provider}/${model.id}`;
|
|
662
|
+
if (thinkingMode === "inherit") return modelKey;
|
|
663
|
+
return `${modelKey}:${thinkingMode}`;
|
|
664
|
+
}
|
|
665
|
+
#handleSelect(model: Model, role: ModelRole | null, thinkingMode?: ThinkingMode): void {
|
|
570
666
|
// For temporary role, don't save to settings - just notify caller
|
|
571
667
|
if (role === null) {
|
|
572
668
|
this.#onSelectCallback(model, null);
|
|
573
669
|
return;
|
|
574
670
|
}
|
|
575
671
|
|
|
672
|
+
const selectedThinkingMode = thinkingMode ?? this.#getCurrentRoleThinkingMode(role);
|
|
673
|
+
|
|
576
674
|
// Save to settings
|
|
577
|
-
this.#settings.setModelRole(role,
|
|
675
|
+
this.#settings.setModelRole(role, this.#formatRoleModelValue(model, selectedThinkingMode));
|
|
578
676
|
|
|
579
677
|
// Update local state for UI
|
|
580
|
-
this.#roles[role] = model;
|
|
678
|
+
this.#roles[role] = { model, thinkingMode: selectedThinkingMode };
|
|
581
679
|
|
|
582
680
|
// Notify caller (for updating agent state if needed)
|
|
583
|
-
this.#onSelectCallback(model, role);
|
|
681
|
+
this.#onSelectCallback(model, role, selectedThinkingMode);
|
|
584
682
|
|
|
585
683
|
// Update list to show new badges
|
|
586
684
|
this.#updateList();
|
|
@@ -11,12 +11,32 @@ type ReadRenderArgs = {
|
|
|
11
11
|
limit?: number;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
type ReadToolSuffixResolution = {
|
|
15
|
+
from: string;
|
|
16
|
+
to: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type ReadToolResultDetails = {
|
|
20
|
+
suffixResolution?: {
|
|
21
|
+
from?: string;
|
|
22
|
+
to?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function getSuffixResolution(details: ReadToolResultDetails | undefined): ReadToolSuffixResolution | undefined {
|
|
27
|
+
if (typeof details?.suffixResolution?.from !== "string" || typeof details.suffixResolution.to !== "string") {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return { from: details.suffixResolution.from, to: details.suffixResolution.to };
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
type ReadEntry = {
|
|
15
34
|
toolCallId: string;
|
|
16
35
|
path: string;
|
|
17
36
|
offset?: number;
|
|
18
37
|
limit?: number;
|
|
19
|
-
status: "pending" | "success" | "error";
|
|
38
|
+
status: "pending" | "success" | "warning" | "error";
|
|
39
|
+
correctedFrom?: string;
|
|
20
40
|
};
|
|
21
41
|
|
|
22
42
|
export class ReadToolGroupComponent extends Container implements ToolExecutionHandle {
|
|
@@ -56,7 +76,15 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
56
76
|
const entry = this.#entries.get(toolCallId);
|
|
57
77
|
if (!entry) return;
|
|
58
78
|
if (isPartial) return;
|
|
59
|
-
|
|
79
|
+
const details = result.details as ReadToolResultDetails | undefined;
|
|
80
|
+
const suffixResolution = getSuffixResolution(details);
|
|
81
|
+
if (suffixResolution) {
|
|
82
|
+
entry.path = suffixResolution.to;
|
|
83
|
+
entry.correctedFrom = suffixResolution.from;
|
|
84
|
+
} else {
|
|
85
|
+
entry.correctedFrom = undefined;
|
|
86
|
+
}
|
|
87
|
+
entry.status = result.isError ? "error" : suffixResolution ? "warning" : "success";
|
|
60
88
|
this.#updateDisplay();
|
|
61
89
|
}
|
|
62
90
|
|
|
@@ -109,6 +137,9 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
109
137
|
const endLine = entry.limit !== undefined ? startLine + entry.limit - 1 : "";
|
|
110
138
|
pathDisplay += theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
|
111
139
|
}
|
|
140
|
+
if (entry.correctedFrom) {
|
|
141
|
+
pathDisplay += theme.fg("dim", ` (corrected from ${shortenPath(entry.correctedFrom)})`);
|
|
142
|
+
}
|
|
112
143
|
return pathDisplay;
|
|
113
144
|
}
|
|
114
145
|
|
|
@@ -116,6 +147,9 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
116
147
|
if (status === "success") {
|
|
117
148
|
return theme.fg("success", theme.status.success);
|
|
118
149
|
}
|
|
150
|
+
if (status === "warning") {
|
|
151
|
+
return theme.fg("warning", theme.status.warning);
|
|
152
|
+
}
|
|
119
153
|
if (status === "error") {
|
|
120
154
|
return theme.fg("error", theme.status.error);
|
|
121
155
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* 1. Add it to settings-schema.ts with a `ui` field
|
|
7
7
|
* 2. That's it - it appears in the UI automatically
|
|
8
8
|
*/
|
|
9
|
+
|
|
10
|
+
import { getAvailableThinkingLevels, getThinkingMetadata } from "@oh-my-pi/pi-ai";
|
|
9
11
|
import { TERMINAL } from "@oh-my-pi/pi-tui";
|
|
10
12
|
import {
|
|
11
13
|
getDefault,
|
|
@@ -167,6 +169,14 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
167
169
|
{ value: "300", label: "5 minutes" },
|
|
168
170
|
{ value: "600", label: "10 minutes" },
|
|
169
171
|
],
|
|
172
|
+
// Read line limit
|
|
173
|
+
"read.defaultLimit": [
|
|
174
|
+
{ value: "200", label: "200 lines" },
|
|
175
|
+
{ value: "300", label: "300 lines" },
|
|
176
|
+
{ value: "500", label: "500 lines" },
|
|
177
|
+
{ value: "1000", label: "1000 lines" },
|
|
178
|
+
{ value: "5000", label: "5000 lines" },
|
|
179
|
+
],
|
|
170
180
|
// Edit fuzzy threshold
|
|
171
181
|
"edit.fuzzyThreshold": [
|
|
172
182
|
{ value: "0.85", label: "0.85", description: "Lenient" },
|
|
@@ -221,14 +231,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
221
231
|
{ value: "on", label: "On", description: "Force websockets for OpenAI Codex models" },
|
|
222
232
|
],
|
|
223
233
|
// Default thinking level
|
|
224
|
-
defaultThinkingLevel: [
|
|
225
|
-
{ value: "off", label: "off", description: "No reasoning" },
|
|
226
|
-
{ value: "minimal", label: "minimal", description: "Very brief (~1k tokens)" },
|
|
227
|
-
{ value: "low", label: "low", description: "Light (~2k tokens)" },
|
|
228
|
-
{ value: "medium", label: "medium", description: "Moderate (~8k tokens)" },
|
|
229
|
-
{ value: "high", label: "high", description: "Deep (~16k tokens)" },
|
|
230
|
-
{ value: "xhigh", label: "xhigh", description: "Maximum (~32k tokens)" },
|
|
231
|
-
],
|
|
234
|
+
defaultThinkingLevel: [...getAvailableThinkingLevels().map(getThinkingMetadata)],
|
|
232
235
|
// Temperature
|
|
233
236
|
temperature: [
|
|
234
237
|
{ value: "-1", label: "Default", description: "Use provider default" },
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { getThinkingMetadata, type ThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
2
|
+
|
|
2
3
|
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
3
4
|
import { getSelectListTheme } from "../../modes/theme/theme";
|
|
4
5
|
import { DynamicBorder } from "./dynamic-border";
|
|
5
6
|
|
|
6
|
-
const LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {
|
|
7
|
-
off: "No reasoning",
|
|
8
|
-
minimal: "Very brief reasoning (~1k tokens)",
|
|
9
|
-
low: "Light reasoning (~2k tokens)",
|
|
10
|
-
medium: "Moderate reasoning (~8k tokens)",
|
|
11
|
-
high: "Deep reasoning (~16k tokens)",
|
|
12
|
-
xhigh: "Maximum reasoning (~32k tokens)",
|
|
13
|
-
};
|
|
14
|
-
|
|
15
7
|
/**
|
|
16
8
|
* Component that renders a thinking level selector with borders
|
|
17
9
|
*/
|
|
@@ -26,11 +18,7 @@ export class ThinkingSelectorComponent extends Container {
|
|
|
26
18
|
) {
|
|
27
19
|
super();
|
|
28
20
|
|
|
29
|
-
const thinkingLevels: SelectItem[] = availableLevels.map(
|
|
30
|
-
value: level,
|
|
31
|
-
label: level,
|
|
32
|
-
description: LEVEL_DESCRIPTIONS[level],
|
|
33
|
-
}));
|
|
21
|
+
const thinkingLevels: SelectItem[] = availableLevels.map(getThinkingMetadata);
|
|
34
22
|
|
|
35
23
|
// Add top border
|
|
36
24
|
this.addChild(new DynamicBorder());
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import { getOAuthProviders, type OAuthProvider, type ThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
3
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
3
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
4
|
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
@@ -76,7 +75,7 @@ export class SelectorController {
|
|
|
76
75
|
this.showSelector(done => {
|
|
77
76
|
const selector = new SettingsSelectorComponent(
|
|
78
77
|
{
|
|
79
|
-
availableThinkingLevels: this.ctx.session.getAvailableThinkingLevels(),
|
|
78
|
+
availableThinkingLevels: [...this.ctx.session.getAvailableThinkingLevels()],
|
|
80
79
|
thinkingLevel: this.ctx.session.thinkingLevel,
|
|
81
80
|
availableThemes,
|
|
82
81
|
cwd: getProjectDir(),
|
|
@@ -342,12 +341,24 @@ export class SelectorController {
|
|
|
342
341
|
}
|
|
343
342
|
|
|
344
343
|
// Provider settings - update runtime preferences
|
|
345
|
-
case "
|
|
344
|
+
case "providers.webSearch":
|
|
346
345
|
setPreferredSearchProvider(
|
|
347
|
-
value as
|
|
346
|
+
value as
|
|
347
|
+
| "auto"
|
|
348
|
+
| "exa"
|
|
349
|
+
| "brave"
|
|
350
|
+
| "jina"
|
|
351
|
+
| "kimi"
|
|
352
|
+
| "zai"
|
|
353
|
+
| "perplexity"
|
|
354
|
+
| "anthropic"
|
|
355
|
+
| "gemini"
|
|
356
|
+
| "codex"
|
|
357
|
+
| "kagi"
|
|
358
|
+
| "synthetic",
|
|
348
359
|
);
|
|
349
360
|
break;
|
|
350
|
-
case "
|
|
361
|
+
case "providers.image":
|
|
351
362
|
setPreferredImageProvider(value as "auto" | "gemini" | "openrouter");
|
|
352
363
|
break;
|
|
353
364
|
|
|
@@ -369,7 +380,7 @@ export class SelectorController {
|
|
|
369
380
|
this.ctx.settings,
|
|
370
381
|
this.ctx.session.modelRegistry,
|
|
371
382
|
this.ctx.session.scopedModels,
|
|
372
|
-
async (model, role) => {
|
|
383
|
+
async (model, role, thinkingMode) => {
|
|
373
384
|
try {
|
|
374
385
|
if (role === null) {
|
|
375
386
|
// Temporary: update agent state but don't persist to settings
|
|
@@ -382,6 +393,9 @@ export class SelectorController {
|
|
|
382
393
|
} else if (role === "default") {
|
|
383
394
|
// Default: update agent state and persist
|
|
384
395
|
await this.ctx.session.setModel(model, role);
|
|
396
|
+
if (thinkingMode && thinkingMode !== "inherit") {
|
|
397
|
+
this.ctx.session.setThinkingLevel(thinkingMode as ThinkingLevel);
|
|
398
|
+
}
|
|
385
399
|
this.ctx.statusLine.invalidate();
|
|
386
400
|
this.ctx.updateEditorBorderColor();
|
|
387
401
|
this.ctx.showStatus(`Default model: ${model.id}`);
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Spawns the agent in RPC mode and provides a typed API for all operations.
|
|
5
5
|
*/
|
|
6
|
-
import type { AgentEvent, AgentMessage
|
|
7
|
-
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
6
|
+
import type { AgentEvent, AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
7
|
+
import type { ImageContent, ThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { isRecord, ptree, readJsonl } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { BashResult } from "../../exec/bash-executor";
|
|
10
10
|
import type { SessionStats } from "../../session/agent-session";
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Commands are sent as JSON lines on stdin.
|
|
5
5
|
* Responses and events are emitted as JSON lines on stdout.
|
|
6
6
|
*/
|
|
7
|
-
import type { AgentMessage
|
|
8
|
-
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { ImageContent, Model, ThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import type { BashResult } from "../../exec/bash-executor";
|
|
10
10
|
import type { SessionStats } from "../../session/agent-session";
|
|
11
11
|
import type { CompactionResult } from "../../session/compaction";
|