@oh-my-pi/pi-coding-agent 10.5.0 → 10.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/package.json +7 -7
- package/src/commit/model-selection.ts +18 -39
- package/src/config/model-registry.ts +19 -0
- package/src/config/model-resolver.ts +92 -1
- package/src/config/settings-schema.ts +22 -2
- package/src/config/settings.ts +23 -2
- package/src/cursor.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/loader.ts +1 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +4 -4
- package/src/extensibility/hooks/runner.ts +2 -2
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/main.ts +1 -2
- package/src/modes/components/model-selector.ts +43 -94
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +4 -2
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/rpc/rpc-types.ts +4 -4
- package/src/prompts/tools/grep.md +4 -8
- package/src/sdk.ts +2 -2
- package/src/session/agent-session.ts +26 -27
- package/src/session/compaction/branch-summarization.ts +1 -1
- package/src/session/compaction/compaction.ts +4 -4
- package/src/task/executor.ts +1 -35
- package/src/task/index.ts +1 -10
- package/src/tools/grep.ts +111 -107
- package/src/web/search/index.ts +15 -4
- package/src/web/search/providers/jina.ts +76 -0
- package/src/web/search/render.ts +3 -1
- package/src/web/search/types.ts +2 -2
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type TUI,
|
|
12
12
|
visibleWidth,
|
|
13
13
|
} from "@oh-my-pi/pi-tui";
|
|
14
|
-
import type
|
|
14
|
+
import { MODEL_ROLE_IDS, MODEL_ROLES, type ModelRegistry, type ModelRole } from "../../config/model-registry";
|
|
15
15
|
import { parseModelString } from "../../config/model-resolver";
|
|
16
16
|
import type { Settings } from "../../config/settings";
|
|
17
17
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
@@ -27,27 +27,20 @@ function makeInvertedBadge(label: string, color: ThemeColor): string {
|
|
|
27
27
|
interface ModelItem {
|
|
28
28
|
provider: string;
|
|
29
29
|
id: string;
|
|
30
|
-
model: Model
|
|
30
|
+
model: Model;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
interface ScopedModelItem {
|
|
34
|
-
model: Model
|
|
34
|
+
model: Model;
|
|
35
35
|
thinkingLevel: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
type ModelRole = "default" | "smol" | "slow" | "plan" | "temporary";
|
|
39
|
-
|
|
40
38
|
interface MenuAction {
|
|
41
39
|
label: string;
|
|
42
40
|
role: ModelRole;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
const MENU_ACTIONS: MenuAction[] = [
|
|
46
|
-
{ label: "Set as Default", role: "default" },
|
|
47
|
-
{ label: "Set as Smol (Fast)", role: "smol" },
|
|
48
|
-
{ label: "Set as Slow (Thinking)", role: "slow" },
|
|
49
|
-
{ label: "Set as Plan (Architect)", role: "plan" },
|
|
50
|
-
];
|
|
43
|
+
const MENU_ACTIONS: MenuAction[] = MODEL_ROLE_IDS.map(role => ({ label: `Set as ${MODEL_ROLES[role].name}`, role }));
|
|
51
44
|
|
|
52
45
|
const ALL_TAB = "ALL";
|
|
53
46
|
|
|
@@ -76,13 +69,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
76
69
|
private allModels: ModelItem[] = [];
|
|
77
70
|
private filteredModels: ModelItem[] = [];
|
|
78
71
|
private selectedIndex: number = 0;
|
|
79
|
-
private
|
|
80
|
-
private smolModel?: Model<any>;
|
|
81
|
-
private slowModel?: Model<any>;
|
|
82
|
-
private planModel?: Model<any>;
|
|
72
|
+
private roles: { [key in ModelRole]?: Model } = {};
|
|
83
73
|
private settings: Settings;
|
|
84
74
|
private modelRegistry: ModelRegistry;
|
|
85
|
-
private onSelectCallback: (model: Model
|
|
75
|
+
private onSelectCallback: (model: Model, role: ModelRole | null) => void;
|
|
86
76
|
private onCancelCallback: () => void;
|
|
87
77
|
private errorMessage?: string;
|
|
88
78
|
private tui: TUI;
|
|
@@ -99,11 +89,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
99
89
|
|
|
100
90
|
constructor(
|
|
101
91
|
tui: TUI,
|
|
102
|
-
_currentModel: Model
|
|
92
|
+
_currentModel: Model | undefined,
|
|
103
93
|
settings: Settings,
|
|
104
94
|
modelRegistry: ModelRegistry,
|
|
105
95
|
scopedModels: ReadonlyArray<ScopedModelItem>,
|
|
106
|
-
onSelect: (model: Model
|
|
96
|
+
onSelect: (model: Model, role: ModelRole | null) => void,
|
|
107
97
|
onCancel: () => void,
|
|
108
98
|
options?: { temporaryOnly?: boolean; initialSearchInput?: string },
|
|
109
99
|
) {
|
|
@@ -182,42 +172,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
182
172
|
}
|
|
183
173
|
|
|
184
174
|
private _loadRoleModels(): void {
|
|
185
|
-
const roles = this.settings.get("modelRoles") as Record<string, string>;
|
|
186
175
|
const allModels = this.modelRegistry.getAll();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const parsed = parseModelString(defaultStr);
|
|
192
|
-
if (parsed) {
|
|
193
|
-
this.defaultModel = allModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Load smol model
|
|
198
|
-
const smolStr = roles.smol;
|
|
199
|
-
if (smolStr) {
|
|
200
|
-
const parsed = parseModelString(smolStr);
|
|
201
|
-
if (parsed) {
|
|
202
|
-
this.smolModel = allModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Load slow model
|
|
207
|
-
const slowStr = roles.slow;
|
|
208
|
-
if (slowStr) {
|
|
209
|
-
const parsed = parseModelString(slowStr);
|
|
210
|
-
if (parsed) {
|
|
211
|
-
this.slowModel = allModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Load plan model
|
|
216
|
-
const planStr = roles.plan;
|
|
217
|
-
if (planStr) {
|
|
218
|
-
const parsed = parseModelString(planStr);
|
|
176
|
+
for (const role of MODEL_ROLE_IDS) {
|
|
177
|
+
const modelId = this.settings.getModelRole(role);
|
|
178
|
+
if (!modelId) continue;
|
|
179
|
+
const parsed = parseModelString(modelId);
|
|
219
180
|
if (parsed) {
|
|
220
|
-
|
|
181
|
+
const model = allModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
182
|
+
if (model) {
|
|
183
|
+
this.roles[role] = model;
|
|
184
|
+
}
|
|
221
185
|
}
|
|
222
186
|
}
|
|
223
187
|
}
|
|
@@ -227,30 +191,25 @@ export class ModelSelectorComponent extends Container {
|
|
|
227
191
|
const mruOrder = this.settings.getStorage()?.getModelUsageOrder() ?? [];
|
|
228
192
|
const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
|
|
229
193
|
|
|
194
|
+
const modelRank = (model: ModelItem) => {
|
|
195
|
+
let i = 0;
|
|
196
|
+
while (i < MODEL_ROLE_IDS.length) {
|
|
197
|
+
const role = MODEL_ROLE_IDS[i];
|
|
198
|
+
if (this.roles[role] && modelsAreEqual(this.roles[role], model.model)) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
i++;
|
|
202
|
+
}
|
|
203
|
+
return i;
|
|
204
|
+
};
|
|
205
|
+
|
|
230
206
|
models.sort((a, b) => {
|
|
231
207
|
const aKey = `${a.provider}/${a.id}`;
|
|
232
208
|
const bKey = `${b.provider}/${b.id}`;
|
|
233
209
|
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
: modelsAreEqual(this.smolModel, a.model)
|
|
238
|
-
? 1
|
|
239
|
-
: modelsAreEqual(this.slowModel, a.model)
|
|
240
|
-
? 2
|
|
241
|
-
: modelsAreEqual(this.planModel, a.model)
|
|
242
|
-
? 3
|
|
243
|
-
: 4;
|
|
244
|
-
const bTag = modelsAreEqual(this.defaultModel, b.model)
|
|
245
|
-
? 0
|
|
246
|
-
: modelsAreEqual(this.smolModel, b.model)
|
|
247
|
-
? 1
|
|
248
|
-
: modelsAreEqual(this.slowModel, b.model)
|
|
249
|
-
? 2
|
|
250
|
-
: modelsAreEqual(this.planModel, b.model)
|
|
251
|
-
? 3
|
|
252
|
-
: 4;
|
|
253
|
-
if (aTag !== bTag) return aTag - bTag;
|
|
210
|
+
const aRank = modelRank(a);
|
|
211
|
+
const bRank = modelRank(b);
|
|
212
|
+
if (aRank !== bRank) return aRank - bRank;
|
|
254
213
|
|
|
255
214
|
// Then MRU order (models in mruIndex come before those not in it)
|
|
256
215
|
const aMru = mruIndex.get(aKey) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -287,7 +246,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
287
246
|
// Load available models (built-in models still work even if models.json failed)
|
|
288
247
|
try {
|
|
289
248
|
const availableModels = this.modelRegistry.getAvailable();
|
|
290
|
-
models = availableModels.map((model: Model
|
|
249
|
+
models = availableModels.map((model: Model) => ({
|
|
291
250
|
provider: model.provider,
|
|
292
251
|
id: model.id,
|
|
293
252
|
model,
|
|
@@ -392,17 +351,15 @@ export class ModelSelectorComponent extends Container {
|
|
|
392
351
|
if (!item) continue;
|
|
393
352
|
|
|
394
353
|
const isSelected = i === this.selectedIndex;
|
|
395
|
-
const isDefault = modelsAreEqual(this.defaultModel, item.model);
|
|
396
|
-
const isSmol = modelsAreEqual(this.smolModel, item.model);
|
|
397
|
-
const isSlow = modelsAreEqual(this.slowModel, item.model);
|
|
398
|
-
const isPlan = modelsAreEqual(this.planModel, item.model);
|
|
399
354
|
|
|
400
355
|
// Build role badges (inverted: color as background, black text)
|
|
401
356
|
const badges: string[] = [];
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
357
|
+
for (const role of MODEL_ROLE_IDS) {
|
|
358
|
+
const { tag, color } = MODEL_ROLES[role];
|
|
359
|
+
if (tag && modelsAreEqual(this.roles[role], item.model)) {
|
|
360
|
+
badges.push(makeInvertedBadge(tag, color ?? "success"));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
406
363
|
const badgeText = badges.length > 0 ? ` ${badges.join(" ")}` : "";
|
|
407
364
|
|
|
408
365
|
let line = "";
|
|
@@ -533,7 +490,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
533
490
|
if (selectedModel) {
|
|
534
491
|
if (this.temporaryOnly) {
|
|
535
492
|
// In temporary mode, skip menu and select directly
|
|
536
|
-
this.handleSelect(selectedModel.model,
|
|
493
|
+
this.handleSelect(selectedModel.model, null);
|
|
537
494
|
} else {
|
|
538
495
|
this.openMenu();
|
|
539
496
|
}
|
|
@@ -585,10 +542,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
585
542
|
}
|
|
586
543
|
}
|
|
587
544
|
|
|
588
|
-
private handleSelect(model: Model
|
|
545
|
+
private handleSelect(model: Model, role: ModelRole | null): void {
|
|
589
546
|
// For temporary role, don't save to settings - just notify caller
|
|
590
|
-
if (role ===
|
|
591
|
-
this.onSelectCallback(model,
|
|
547
|
+
if (role === null) {
|
|
548
|
+
this.onSelectCallback(model, null);
|
|
592
549
|
return;
|
|
593
550
|
}
|
|
594
551
|
|
|
@@ -596,15 +553,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
596
553
|
this.settings.setModelRole(role, `${model.provider}/${model.id}`);
|
|
597
554
|
|
|
598
555
|
// Update local state for UI
|
|
599
|
-
|
|
600
|
-
this.defaultModel = model;
|
|
601
|
-
} else if (role === "smol") {
|
|
602
|
-
this.smolModel = model;
|
|
603
|
-
} else if (role === "slow") {
|
|
604
|
-
this.slowModel = model;
|
|
605
|
-
} else if (role === "plan") {
|
|
606
|
-
this.planModel = model;
|
|
607
|
-
}
|
|
556
|
+
this.roles[role] = model;
|
|
608
557
|
|
|
609
558
|
// Notify caller (for updating agent state if needed)
|
|
610
559
|
this.onSelectCallback(model, role);
|
|
@@ -649,7 +649,7 @@ export class InputController {
|
|
|
649
649
|
|
|
650
650
|
async cycleRoleModel(options?: { temporary?: boolean }): Promise<void> {
|
|
651
651
|
try {
|
|
652
|
-
const roleOrder = ["slow", "default", "smol"];
|
|
652
|
+
const roleOrder = ["slow", "default", "smol"] as const;
|
|
653
653
|
const result = await this.ctx.session.cycleRoleModels(roleOrder, options);
|
|
654
654
|
if (!result) {
|
|
655
655
|
this.ctx.showStatus("Only one role model available");
|
|
@@ -3,6 +3,7 @@ import type { OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { getAgentDbPath } from "../../config";
|
|
6
|
+
import { MODEL_ROLES } from "../../config/model-registry";
|
|
6
7
|
import { settings } from "../../config/settings";
|
|
7
8
|
import { DebugSelectorComponent } from "../../debug";
|
|
8
9
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
@@ -277,7 +278,7 @@ export class SelectorController {
|
|
|
277
278
|
this.ctx.session.scopedModels,
|
|
278
279
|
async (model, role) => {
|
|
279
280
|
try {
|
|
280
|
-
if (role ===
|
|
281
|
+
if (role === null) {
|
|
281
282
|
// Temporary: update agent state but don't persist to settings
|
|
282
283
|
await this.ctx.session.setModelTemporary(model);
|
|
283
284
|
this.ctx.statusLine.invalidate();
|
|
@@ -294,7 +295,8 @@ export class SelectorController {
|
|
|
294
295
|
// Don't call done() - selector stays open for role assignment
|
|
295
296
|
} else {
|
|
296
297
|
// Other roles (smol, slow): just update settings, not current model
|
|
297
|
-
const
|
|
298
|
+
const roleInfo = MODEL_ROLES[role];
|
|
299
|
+
const roleLabel = roleInfo?.name ?? role;
|
|
298
300
|
this.ctx.showStatus(`${roleLabel} model: ${model.id}`);
|
|
299
301
|
// Don't call done() - selector stays open
|
|
300
302
|
}
|
|
@@ -137,7 +137,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
137
137
|
private readonly version: string;
|
|
138
138
|
private readonly changelogMarkdown: string | undefined;
|
|
139
139
|
private planModePreviousTools: string[] | undefined;
|
|
140
|
-
private planModePreviousModel: Model
|
|
140
|
+
private planModePreviousModel: Model | undefined;
|
|
141
141
|
private planModeHasEntered = false;
|
|
142
142
|
public readonly lspServers:
|
|
143
143
|
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>
|
|
@@ -68,7 +68,7 @@ export type RpcCommand =
|
|
|
68
68
|
// ============================================================================
|
|
69
69
|
|
|
70
70
|
export interface RpcSessionState {
|
|
71
|
-
model?: Model
|
|
71
|
+
model?: Model;
|
|
72
72
|
thinkingLevel: ThinkingLevel;
|
|
73
73
|
isStreaming: boolean;
|
|
74
74
|
isCompacting: boolean;
|
|
@@ -105,21 +105,21 @@ export type RpcResponse =
|
|
|
105
105
|
type: "response";
|
|
106
106
|
command: "set_model";
|
|
107
107
|
success: true;
|
|
108
|
-
data: Model
|
|
108
|
+
data: Model;
|
|
109
109
|
}
|
|
110
110
|
| {
|
|
111
111
|
id?: string;
|
|
112
112
|
type: "response";
|
|
113
113
|
command: "cycle_model";
|
|
114
114
|
success: true;
|
|
115
|
-
data: { model: Model
|
|
115
|
+
data: { model: Model; thinkingLevel: ThinkingLevel; isScoped: boolean } | null;
|
|
116
116
|
}
|
|
117
117
|
| {
|
|
118
118
|
id?: string;
|
|
119
119
|
type: "response";
|
|
120
120
|
command: "get_available_models";
|
|
121
121
|
success: true;
|
|
122
|
-
data: { models: Model
|
|
122
|
+
data: { models: Model[] };
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
// Thinking
|
|
@@ -6,17 +6,13 @@ Powerful search tool built on ripgrep.
|
|
|
6
6
|
- Supports full regex syntax (e.g., `log.*Error`, `function\\s+\\w+`)
|
|
7
7
|
- Filter files with `glob` (e.g., `*.js`, `**/*.tsx`) or `type` (e.g., `js`, `py`, `rust`)
|
|
8
8
|
- Pattern syntax uses ripgrep—literal braces need escaping (`interface\\{\\}` to find `interface{}` in Go)
|
|
9
|
-
- For cross-line patterns like `struct \\{[\\s\\S]*?field`,
|
|
9
|
+
- For cross-line patterns like `struct \\{[\\s\\S]*?field`, set `multiline: true` if needed
|
|
10
|
+
- If the pattern contains a literal `\n`, multiline defaults to true
|
|
10
11
|
</instruction>
|
|
11
12
|
|
|
12
13
|
<output>
|
|
13
|
-
Results
|
|
14
|
-
|
|
15
|
-
- `files_with_matches`: File paths only (one per line)
|
|
16
|
-
- `count`: Match counts per file
|
|
17
|
-
|
|
18
|
-
In `content` mode, truncated at 100 matches default (configurable via `limit`).
|
|
19
|
-
For `files_with_matches` and `count` modes, use `limit` truncate results.
|
|
14
|
+
Results are always content mode: matching lines with file paths and line numbers.
|
|
15
|
+
Truncated at 100 matches by default (configurable via `limit`).
|
|
20
16
|
</output>
|
|
21
17
|
|
|
22
18
|
<critical>
|
package/src/sdk.ts
CHANGED
|
@@ -131,11 +131,11 @@ export interface CreateAgentSessionOptions {
|
|
|
131
131
|
modelRegistry?: ModelRegistry;
|
|
132
132
|
|
|
133
133
|
/** Model to use. Default: from settings, else first available */
|
|
134
|
-
model?: Model
|
|
134
|
+
model?: Model;
|
|
135
135
|
/** Thinking level. Default: from settings, else 'off' (clamped to model capabilities) */
|
|
136
136
|
thinkingLevel?: ThinkingLevel;
|
|
137
137
|
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
138
|
-
scopedModels?: Array<{ model: Model
|
|
138
|
+
scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingLevel }>;
|
|
139
139
|
|
|
140
140
|
/** System prompt. String replaces default, function receives default and returns final. */
|
|
141
141
|
systemPrompt?: string | ((defaultPrompt: string) => string);
|
|
@@ -32,7 +32,7 @@ import { abortableSleep, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
|
32
32
|
import { YAML } from "bun";
|
|
33
33
|
import type { Rule } from "../capability/rule";
|
|
34
34
|
import { getAgentDbPath } from "../config";
|
|
35
|
-
import type
|
|
35
|
+
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "../config/model-registry";
|
|
36
36
|
import { parseModelString } from "../config/model-resolver";
|
|
37
37
|
import {
|
|
38
38
|
expandPromptTemplate,
|
|
@@ -127,7 +127,7 @@ export interface AgentSessionConfig {
|
|
|
127
127
|
sessionManager: SessionManager;
|
|
128
128
|
settings: Settings;
|
|
129
129
|
/** Models to cycle through with Ctrl+P (from --models flag) */
|
|
130
|
-
scopedModels?: Array<{ model: Model
|
|
130
|
+
scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingLevel }>;
|
|
131
131
|
/** Prompt templates for expansion */
|
|
132
132
|
promptTemplates?: PromptTemplate[];
|
|
133
133
|
/** File-based slash commands for expansion */
|
|
@@ -167,7 +167,7 @@ export interface PromptOptions {
|
|
|
167
167
|
|
|
168
168
|
/** Result from cycleModel() */
|
|
169
169
|
export interface ModelCycleResult {
|
|
170
|
-
model: Model
|
|
170
|
+
model: Model;
|
|
171
171
|
thinkingLevel: ThinkingLevel;
|
|
172
172
|
/** Whether cycling through scoped models (--models flag) or all available */
|
|
173
173
|
isScoped: boolean;
|
|
@@ -175,9 +175,9 @@ export interface ModelCycleResult {
|
|
|
175
175
|
|
|
176
176
|
/** Result from cycleRoleModels() */
|
|
177
177
|
export interface RoleModelCycleResult {
|
|
178
|
-
model: Model
|
|
178
|
+
model: Model;
|
|
179
179
|
thinkingLevel: ThinkingLevel;
|
|
180
|
-
role:
|
|
180
|
+
role: ModelRole;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/** Session statistics for /session command */
|
|
@@ -257,7 +257,7 @@ export class AgentSession {
|
|
|
257
257
|
readonly sessionManager: SessionManager;
|
|
258
258
|
readonly settings: Settings;
|
|
259
259
|
|
|
260
|
-
private _scopedModels: Array<{ model: Model
|
|
260
|
+
private _scopedModels: Array<{ model: Model; thinkingLevel: ThinkingLevel }>;
|
|
261
261
|
private _promptTemplates: PromptTemplate[];
|
|
262
262
|
private _slashCommands: FileSlashCommand[];
|
|
263
263
|
|
|
@@ -887,7 +887,7 @@ export class AgentSession {
|
|
|
887
887
|
}
|
|
888
888
|
|
|
889
889
|
/** Current model (may be undefined if not yet selected) */
|
|
890
|
-
get model(): Model
|
|
890
|
+
get model(): Model | undefined {
|
|
891
891
|
return this.agent.state.model;
|
|
892
892
|
}
|
|
893
893
|
|
|
@@ -994,7 +994,7 @@ export class AgentSession {
|
|
|
994
994
|
}
|
|
995
995
|
|
|
996
996
|
/** Scoped models for cycling (from --models flag) */
|
|
997
|
-
get scopedModels(): ReadonlyArray<{ model: Model
|
|
997
|
+
get scopedModels(): ReadonlyArray<{ model: Model; thinkingLevel: ThinkingLevel }> {
|
|
998
998
|
return this._scopedModels;
|
|
999
999
|
}
|
|
1000
1000
|
|
|
@@ -1014,7 +1014,7 @@ export class AgentSession {
|
|
|
1014
1014
|
this._planReferenceSent = true;
|
|
1015
1015
|
}
|
|
1016
1016
|
|
|
1017
|
-
resolveRoleModel(role:
|
|
1017
|
+
resolveRoleModel(role: ModelRole): Model | undefined {
|
|
1018
1018
|
return this._resolveRoleModel(role, this._modelRegistry.getAvailable(), this.model);
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
@@ -1800,7 +1800,7 @@ export class AgentSession {
|
|
|
1800
1800
|
* Validates API key, saves to session and settings.
|
|
1801
1801
|
* @throws Error if no API key available for the model
|
|
1802
1802
|
*/
|
|
1803
|
-
async setModel(model: Model
|
|
1803
|
+
async setModel(model: Model, role: ModelRole = "default"): Promise<void> {
|
|
1804
1804
|
const apiKey = await this._modelRegistry.getApiKey(model, this.sessionId);
|
|
1805
1805
|
if (!apiKey) {
|
|
1806
1806
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
@@ -1820,7 +1820,7 @@ export class AgentSession {
|
|
|
1820
1820
|
* Validates API key, saves to session log but NOT to settings.
|
|
1821
1821
|
* @throws Error if no API key available for the model
|
|
1822
1822
|
*/
|
|
1823
|
-
async setModelTemporary(model: Model
|
|
1823
|
+
async setModelTemporary(model: Model): Promise<void> {
|
|
1824
1824
|
const apiKey = await this._modelRegistry.getApiKey(model, this.sessionId);
|
|
1825
1825
|
if (!apiKey) {
|
|
1826
1826
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
@@ -1854,7 +1854,7 @@ export class AgentSession {
|
|
|
1854
1854
|
* @param options - Optional settings: `temporary` to not persist to settings
|
|
1855
1855
|
*/
|
|
1856
1856
|
async cycleRoleModels(
|
|
1857
|
-
roleOrder:
|
|
1857
|
+
roleOrder: readonly ModelRole[],
|
|
1858
1858
|
options?: { temporary?: boolean },
|
|
1859
1859
|
): Promise<RoleModelCycleResult | undefined> {
|
|
1860
1860
|
const availableModels = this._modelRegistry.getAvailable();
|
|
@@ -1862,7 +1862,7 @@ export class AgentSession {
|
|
|
1862
1862
|
|
|
1863
1863
|
const currentModel = this.model;
|
|
1864
1864
|
if (!currentModel) return undefined;
|
|
1865
|
-
const roleModels: Array<{ role:
|
|
1865
|
+
const roleModels: Array<{ role: ModelRole; model: Model }> = [];
|
|
1866
1866
|
|
|
1867
1867
|
for (const role of roleOrder) {
|
|
1868
1868
|
const roleModelStr =
|
|
@@ -1872,7 +1872,7 @@ export class AgentSession {
|
|
|
1872
1872
|
if (!roleModelStr) continue;
|
|
1873
1873
|
|
|
1874
1874
|
const parsed = parseModelString(roleModelStr);
|
|
1875
|
-
let match: Model
|
|
1875
|
+
let match: Model | undefined;
|
|
1876
1876
|
if (parsed) {
|
|
1877
1877
|
match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
1878
1878
|
}
|
|
@@ -1964,7 +1964,7 @@ export class AgentSession {
|
|
|
1964
1964
|
/**
|
|
1965
1965
|
* Get all available models with valid API keys.
|
|
1966
1966
|
*/
|
|
1967
|
-
getAvailableModels(): Model
|
|
1967
|
+
getAvailableModels(): Model[] {
|
|
1968
1968
|
return this._modelRegistry.getAvailable();
|
|
1969
1969
|
}
|
|
1970
1970
|
|
|
@@ -2530,15 +2530,15 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2530
2530
|
this.agent.continue().catch(() => {});
|
|
2531
2531
|
}
|
|
2532
2532
|
|
|
2533
|
-
private _getModelKey(model: Model
|
|
2533
|
+
private _getModelKey(model: Model): string {
|
|
2534
2534
|
return `${model.provider}/${model.id}`;
|
|
2535
2535
|
}
|
|
2536
2536
|
|
|
2537
2537
|
private _resolveRoleModel(
|
|
2538
|
-
role:
|
|
2539
|
-
availableModels: Model
|
|
2540
|
-
currentModel: Model
|
|
2541
|
-
): Model
|
|
2538
|
+
role: ModelRole,
|
|
2539
|
+
availableModels: Model[],
|
|
2540
|
+
currentModel: Model | undefined,
|
|
2541
|
+
): Model | undefined {
|
|
2542
2542
|
const roleModelStr =
|
|
2543
2543
|
role === "default"
|
|
2544
2544
|
? (this.settings.getModelRole("default") ??
|
|
@@ -2555,11 +2555,11 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2555
2555
|
return availableModels.find(m => m.id.toLowerCase() === roleLower);
|
|
2556
2556
|
}
|
|
2557
2557
|
|
|
2558
|
-
private _getCompactionModelCandidates(availableModels: Model
|
|
2559
|
-
const candidates: Model
|
|
2558
|
+
private _getCompactionModelCandidates(availableModels: Model[]): Model[] {
|
|
2559
|
+
const candidates: Model[] = [];
|
|
2560
2560
|
const seen = new Set<string>();
|
|
2561
2561
|
|
|
2562
|
-
const addCandidate = (model: Model
|
|
2562
|
+
const addCandidate = (model: Model | undefined): void => {
|
|
2563
2563
|
if (!model) return;
|
|
2564
2564
|
const key = this._getModelKey(model);
|
|
2565
2565
|
if (seen.has(key)) return;
|
|
@@ -2568,10 +2568,9 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2568
2568
|
};
|
|
2569
2569
|
|
|
2570
2570
|
const currentModel = this.model;
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
addCandidate(this._resolveRoleModel("smol", availableModels, currentModel));
|
|
2571
|
+
for (const role of MODEL_ROLE_IDS) {
|
|
2572
|
+
addCandidate(this._resolveRoleModel(role, availableModels, currentModel));
|
|
2573
|
+
}
|
|
2575
2574
|
|
|
2576
2575
|
const sortedByContext = [...availableModels].sort((a, b) => b.contextWindow - a.contextWindow);
|
|
2577
2576
|
for (const model of sortedByContext) {
|
|
@@ -66,7 +66,7 @@ export interface CollectEntriesResult {
|
|
|
66
66
|
|
|
67
67
|
export interface GenerateBranchSummaryOptions {
|
|
68
68
|
/** Model to use for summarization */
|
|
69
|
-
model: Model
|
|
69
|
+
model: Model;
|
|
70
70
|
/** API key for the model */
|
|
71
71
|
apiKey: string;
|
|
72
72
|
/** Abort signal for cancellation */
|
|
@@ -478,7 +478,7 @@ export interface SummaryOptions {
|
|
|
478
478
|
|
|
479
479
|
export async function generateSummary(
|
|
480
480
|
currentMessages: AgentMessage[],
|
|
481
|
-
model: Model
|
|
481
|
+
model: Model,
|
|
482
482
|
reserveTokens: number,
|
|
483
483
|
apiKey: string,
|
|
484
484
|
signal?: AbortSignal,
|
|
@@ -547,7 +547,7 @@ export async function generateSummary(
|
|
|
547
547
|
async function generateShortSummary(
|
|
548
548
|
recentMessages: AgentMessage[],
|
|
549
549
|
historySummary: string | undefined,
|
|
550
|
-
model: Model
|
|
550
|
+
model: Model,
|
|
551
551
|
reserveTokens: number,
|
|
552
552
|
apiKey: string,
|
|
553
553
|
signal?: AbortSignal,
|
|
@@ -724,7 +724,7 @@ const TURN_PREFIX_SUMMARIZATION_PROMPT = renderPromptTemplate(compactionTurnPref
|
|
|
724
724
|
*/
|
|
725
725
|
export async function compact(
|
|
726
726
|
preparation: CompactionPreparation,
|
|
727
|
-
model: Model
|
|
727
|
+
model: Model,
|
|
728
728
|
apiKey: string,
|
|
729
729
|
customInstructions?: string,
|
|
730
730
|
signal?: AbortSignal,
|
|
@@ -822,7 +822,7 @@ export async function compact(
|
|
|
822
822
|
*/
|
|
823
823
|
async function generateTurnPrefixSummary(
|
|
824
824
|
messages: AgentMessage[],
|
|
825
|
-
model: Model
|
|
825
|
+
model: Model,
|
|
826
826
|
reserveTokens: number,
|
|
827
827
|
apiKey: string,
|
|
828
828
|
signal?: AbortSignal,
|
package/src/task/executor.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
10
10
|
import type { TSchema } from "@sinclair/typebox";
|
|
11
11
|
import Ajv, { type ValidateFunction } from "ajv";
|
|
12
12
|
import type { ModelRegistry } from "../config/model-registry";
|
|
13
|
-
import {
|
|
13
|
+
import { resolveModelOverride } from "../config/model-resolver";
|
|
14
14
|
import { type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
|
|
15
15
|
import { Settings } from "../config/settings";
|
|
16
16
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
TASK_SUBAGENT_PROGRESS_CHANNEL,
|
|
40
40
|
} from "./types";
|
|
41
41
|
|
|
42
|
-
const DEFAULT_MODEL_ALIASES = new Set(["default", "pi/default", "omp/default"]);
|
|
43
42
|
const MCP_CALL_TIMEOUT_MS = 60_000;
|
|
44
43
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
45
44
|
|
|
@@ -129,39 +128,6 @@ function getReportFindingKey(value: unknown): string | null {
|
|
|
129
128
|
return `${filePath}:${lineStart}:${lineEnd}:${priority ?? ""}:${title}`;
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
function resolveModelOverride(
|
|
133
|
-
modelPatterns: string[],
|
|
134
|
-
modelRegistry: ModelRegistry,
|
|
135
|
-
settings?: Settings,
|
|
136
|
-
): { model?: Model<Api>; thinkingLevel?: ThinkingLevel } {
|
|
137
|
-
if (modelPatterns.length === 0) return {};
|
|
138
|
-
const matchPreferences = { usageOrder: settings?.getStorage()?.getModelUsageOrder() };
|
|
139
|
-
const roles = settings?.getGroup("modelRoles");
|
|
140
|
-
for (const pattern of modelPatterns) {
|
|
141
|
-
const normalized = pattern.trim().toLowerCase();
|
|
142
|
-
if (!normalized || DEFAULT_MODEL_ALIASES.has(normalized)) {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
let effectivePattern = pattern;
|
|
146
|
-
if (normalized.startsWith("omp/") || normalized.startsWith("pi/")) {
|
|
147
|
-
const role = normalized.startsWith("omp/") ? pattern.slice(4) : pattern.slice(3);
|
|
148
|
-
const configured = roles?.[role] ?? roles?.[role.toLowerCase()];
|
|
149
|
-
if (configured) {
|
|
150
|
-
effectivePattern = configured;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const { model, thinkingLevel } = parseModelPattern(
|
|
154
|
-
effectivePattern,
|
|
155
|
-
modelRegistry.getAvailable(),
|
|
156
|
-
matchPreferences,
|
|
157
|
-
);
|
|
158
|
-
if (model) {
|
|
159
|
-
return { model, thinkingLevel: thinkingLevel !== "off" ? thinkingLevel : undefined };
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return {};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
131
|
function buildSubmitResultToolChoice(model?: Model<Api>): ToolChoice | undefined {
|
|
166
132
|
if (!model) return undefined;
|
|
167
133
|
if (
|
package/src/task/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type { Usage } from "@oh-my-pi/pi-ai";
|
|
|
20
20
|
import { $ } from "bun";
|
|
21
21
|
import { nanoid } from "nanoid";
|
|
22
22
|
import type { ToolSession } from "..";
|
|
23
|
+
import { isDefaultModelAlias } from "../config/model-resolver";
|
|
23
24
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
24
25
|
import type { Theme } from "../modes/theme/theme";
|
|
25
26
|
import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
|
|
@@ -165,16 +166,6 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
|
|
|
165
166
|
const { agent: agentName, context, schema: outputSchema, isolated } = params;
|
|
166
167
|
const isIsolated = isolated === true;
|
|
167
168
|
|
|
168
|
-
const isDefaultModelAlias = (value: string | string[] | undefined): boolean => {
|
|
169
|
-
if (!value) return true;
|
|
170
|
-
const values = Array.isArray(value) ? value : [value];
|
|
171
|
-
if (values.length === 0) return true;
|
|
172
|
-
return values.every(entry => {
|
|
173
|
-
const normalized = entry.trim().toLowerCase();
|
|
174
|
-
return normalized === "default" || normalized === "pi/default" || normalized === "omp/default";
|
|
175
|
-
});
|
|
176
|
-
};
|
|
177
|
-
|
|
178
169
|
// Validate agent exists
|
|
179
170
|
const agent = getAgent(agents, agentName);
|
|
180
171
|
if (!agent) {
|