@pi-kaush/pi-agent-mode 0.1.1 → 0.2.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 +10 -0
- package/README.md +21 -4
- package/package.json +4 -1
- package/src/agent-discovery.ts +7 -0
- package/src/index.ts +108 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Refresh Pi's local model catalog before `/agent` model selection and baseline
|
|
6
|
+
restoration, so a long-lived session sees newly configured profile candidates
|
|
7
|
+
without using the parent's model-cycling scope. Report refresh failures without
|
|
8
|
+
changing the active agent.
|
|
9
|
+
- Add `profile` frontmatter: activate a named model profile from the shared machine-local `~/.pi/agent/profiles.yaml` (`@pi-kaush/pi-model-profiles`), walking candidates in order and applying the first available model and thinking level.
|
|
10
|
+
- Reject agents declaring both `profile` and `model`; precedence is explicit: profile first, then model, then the session default.
|
|
11
|
+
- Load `@pi-kaush/pi-model-profiles` lazily so a missing library degrades only profile support; `/agent` remains fully functional without it.
|
|
12
|
+
|
|
3
13
|
## 0.1.0
|
|
4
14
|
|
|
5
15
|
- Add the `/agent` command for activating a configured Pi agent as a persistent mode in the current session.
|
package/README.md
CHANGED
|
@@ -5,14 +5,13 @@ the session to the agent's model, thinking level, and tool set, appends the agen
|
|
|
5
5
|
prompt to Pi's base instructions, and restores the pre-activation baseline when cleared.
|
|
6
6
|
It does not spawn children and provides no delegated subagents.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
### Install
|
|
9
9
|
|
|
10
|
-
```
|
|
10
|
+
```fish
|
|
11
11
|
pi install npm:@pi-kaush/pi-agent-mode
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Restart Pi or run `/reload`.
|
|
15
|
-
`@0.1.0`.
|
|
14
|
+
Restart Pi or run `/reload`.
|
|
16
15
|
|
|
17
16
|
## Usage
|
|
18
17
|
|
|
@@ -37,11 +36,29 @@ description: ... # required
|
|
|
37
36
|
emoji: 🔍 # optional, shown in the footer status
|
|
38
37
|
tools: read, grep # optional allowlist; unavailable tools are reported and omitted
|
|
39
38
|
model: provider/model:high # optional; thinking suffix may be :off/:low/:medium/:high/:max
|
|
39
|
+
profile: coder # optional; named model candidates from ~/.pi/agent/profiles.yaml
|
|
40
40
|
confirmProjectAgents: false # project agents default to requiring confirmation
|
|
41
41
|
---
|
|
42
42
|
Prompt body appended to Pi's base instructions while the agent is active.
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
## Model selection precedence
|
|
46
|
+
|
|
47
|
+
Agent frontmatter may declare a `model` (exact model, optional thinking
|
|
48
|
+
suffix) or a `profile` — a named entry in the shared machine-local
|
|
49
|
+
`~/.pi/agent/profiles.yaml` maintained by
|
|
50
|
+
[@pi-kaush/pi-model-profiles](../pi-model-profiles). An agent must not declare
|
|
51
|
+
both. Before selecting a model, `/agent` refreshes Pi's local model catalog so
|
|
52
|
+
models added during a long-lived session can be used; the parent session's
|
|
53
|
+
`enabledModels` / `--models` cycling scope does not limit profile candidates.
|
|
54
|
+
Profile candidates are walked in order at activation and the first model that
|
|
55
|
+
exists and is authenticated wins; once the session is running, models never
|
|
56
|
+
switch mid-request. Agents without either keep the current session model.
|
|
57
|
+
|
|
58
|
+
`pi-agent-mode` loads `@pi-kaush/pi-model-profiles` lazily: if the library is
|
|
59
|
+
missing, only agents that declare `profile:` fail (with an install hint) and
|
|
60
|
+
every other `/agent` capability is unencumbered.
|
|
61
|
+
|
|
45
62
|
## Behavior
|
|
46
63
|
|
|
47
64
|
- The first activation captures the current model, thinking level, and active
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-kaush/pi-agent-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Activate a configured Pi agent as a persistent mode in the current session.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kaushik Gopal",
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
"./src/index.ts"
|
|
34
34
|
]
|
|
35
35
|
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@pi-kaush/pi-model-profiles": "^0.1.0"
|
|
38
|
+
},
|
|
36
39
|
"peerDependencies": {
|
|
37
40
|
"@earendil-works/pi-coding-agent": ">=0.80.6",
|
|
38
41
|
"@earendil-works/pi-tui": ">=0.80.6"
|
package/src/agent-discovery.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface AgentConfig {
|
|
|
23
23
|
emoji?: string;
|
|
24
24
|
tools?: string[];
|
|
25
25
|
model?: string;
|
|
26
|
+
profile?: string;
|
|
26
27
|
confirmProjectAgents?: boolean;
|
|
27
28
|
systemPrompt: string;
|
|
28
29
|
source: "user" | "project";
|
|
@@ -35,6 +36,7 @@ interface AgentFrontmatter extends Record<string, unknown> {
|
|
|
35
36
|
emoji?: string;
|
|
36
37
|
tools?: string;
|
|
37
38
|
model?: string;
|
|
39
|
+
profile?: string;
|
|
38
40
|
confirmProjectAgents?: boolean;
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -105,6 +107,10 @@ function loadAgentsFromDir(
|
|
|
105
107
|
: undefined;
|
|
106
108
|
const model =
|
|
107
109
|
typeof frontmatter.model === "string" ? frontmatter.model : undefined;
|
|
110
|
+
const profile =
|
|
111
|
+
typeof frontmatter.profile === "string"
|
|
112
|
+
? frontmatter.profile.trim()
|
|
113
|
+
: undefined;
|
|
108
114
|
|
|
109
115
|
agents.push({
|
|
110
116
|
name,
|
|
@@ -112,6 +118,7 @@ function loadAgentsFromDir(
|
|
|
112
118
|
...(emoji ? { emoji } : {}),
|
|
113
119
|
...(tools && tools.length > 0 ? { tools } : {}),
|
|
114
120
|
...(model ? { model } : {}),
|
|
121
|
+
...(profile ? { profile } : {}),
|
|
115
122
|
...(typeof frontmatter.confirmProjectAgents === "boolean"
|
|
116
123
|
? { confirmProjectAgents: frontmatter.confirmProjectAgents }
|
|
117
124
|
: {}),
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
ExtensionAPI,
|
|
15
15
|
ExtensionContext,
|
|
16
16
|
} from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import type { ModelProfilesConfig } from "@pi-kaush/pi-model-profiles";
|
|
17
19
|
import {
|
|
18
20
|
discoverAgents,
|
|
19
21
|
formatAgentDisplayName,
|
|
@@ -32,7 +34,7 @@ interface ModelReference {
|
|
|
32
34
|
id: string;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
export { parseAgentModelSpec };
|
|
37
|
+
export { isModuleUnavailable, parseAgentModelSpec };
|
|
36
38
|
export type { AgentModelSpec } from "./model-spec.ts";
|
|
37
39
|
|
|
38
40
|
// Durable session state is namespaced so it cannot collide with other
|
|
@@ -99,6 +101,15 @@ function isActiveAgentState(value: unknown): value is ActiveAgentState {
|
|
|
99
101
|
return true;
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
function isModuleUnavailable(error: unknown): boolean {
|
|
105
|
+
const code = (error as { code?: string } | undefined)?.code;
|
|
106
|
+
return (
|
|
107
|
+
code === "MODULE_NOT_FOUND" ||
|
|
108
|
+
code === "ERR_MODULE_NOT_FOUND" ||
|
|
109
|
+
(error instanceof Error && error.message.includes("Cannot find module"))
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
102
113
|
export function registerAgentMode(pi: ExtensionAPI) {
|
|
103
114
|
let activeAgent: ActiveAgent | undefined;
|
|
104
115
|
|
|
@@ -137,6 +148,26 @@ export function registerAgentMode(pi: ExtensionAPI) {
|
|
|
137
148
|
pi.appendEntry(PI_AGENT_MODE_STATE_TYPE, state);
|
|
138
149
|
}
|
|
139
150
|
|
|
151
|
+
async function refreshModelCatalog(ctx: ExtensionContext): Promise<boolean> {
|
|
152
|
+
try {
|
|
153
|
+
// Activation may happen after models.json changes in a long-lived session.
|
|
154
|
+
// Pi 0.80 accepts no options; newer Pi can suppress remote catalog fetches.
|
|
155
|
+
// The cycling scope is independent of the models an agent may select.
|
|
156
|
+
await (
|
|
157
|
+
ctx.modelRegistry.refresh as (options: {
|
|
158
|
+
allowNetwork: boolean;
|
|
159
|
+
}) => void | Promise<unknown>
|
|
160
|
+
)({ allowNetwork: false });
|
|
161
|
+
return true;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
ctx.ui.notify(
|
|
164
|
+
`Could not refresh model catalog: ${error instanceof Error ? error.message : error}`,
|
|
165
|
+
"error",
|
|
166
|
+
);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
140
171
|
function findModel(modelSpec: string, ctx: ExtensionContext) {
|
|
141
172
|
const spec = modelSpec.trim();
|
|
142
173
|
const separator = spec.indexOf("/");
|
|
@@ -164,6 +195,7 @@ export function registerAgentMode(pi: ExtensionAPI) {
|
|
|
164
195
|
if (ctx.model?.provider === model.provider && ctx.model.id === model.id)
|
|
165
196
|
return true;
|
|
166
197
|
|
|
198
|
+
if (!(await refreshModelCatalog(ctx))) return false;
|
|
167
199
|
const resolved = ctx.modelRegistry.find(model.provider, model.id);
|
|
168
200
|
if (!resolved) {
|
|
169
201
|
ctx.ui.notify(
|
|
@@ -200,6 +232,67 @@ export function registerAgentMode(pi: ExtensionAPI) {
|
|
|
200
232
|
pi.setActiveTools(valid);
|
|
201
233
|
}
|
|
202
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Walk the agent's profile candidates in order and switch to the first
|
|
237
|
+
* model that exists and is authenticated. Fallback happens only during
|
|
238
|
+
* activation; running sessions never switch models mid-request.
|
|
239
|
+
*/
|
|
240
|
+
async function applyProfileModels(
|
|
241
|
+
agent: AgentConfig,
|
|
242
|
+
ctx: ExtensionContext,
|
|
243
|
+
): Promise<{ thinkingLevel?: ThinkingLevel } | false> {
|
|
244
|
+
let profiles: ModelProfilesConfig;
|
|
245
|
+
try {
|
|
246
|
+
// Runtime-only import: a missing @pi-kaush/pi-model-profiles degrades
|
|
247
|
+
// just profile support instead of failing the whole extension load.
|
|
248
|
+
const profilesModule = await import("@pi-kaush/pi-model-profiles");
|
|
249
|
+
profiles = profilesModule.loadModelProfiles(
|
|
250
|
+
profilesModule.resolveProfilesPath(getAgentDir()),
|
|
251
|
+
);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (isModuleUnavailable(error)) {
|
|
254
|
+
ctx.ui.notify(
|
|
255
|
+
`Agent ${formatAgentDisplayName(agent)}: profile support unavailable; install it with pi install npm:@pi-kaush/pi-model-profiles.`,
|
|
256
|
+
"warning",
|
|
257
|
+
);
|
|
258
|
+
} else {
|
|
259
|
+
ctx.ui.notify(
|
|
260
|
+
`Agent ${formatAgentDisplayName(agent)}: ${error instanceof Error ? error.message : error}`,
|
|
261
|
+
"error",
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const profileName = agent.profile!.trim();
|
|
268
|
+
const profile = profiles.profiles[profileName];
|
|
269
|
+
if (!profile) {
|
|
270
|
+
ctx.ui.notify(
|
|
271
|
+
`Agent ${formatAgentDisplayName(agent)}: unknown profile "${profileName}". Available: ${Object.keys(profiles.profiles).join(", ")}.`,
|
|
272
|
+
"error",
|
|
273
|
+
);
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const candidate of profile.candidates) {
|
|
278
|
+
const model = findModel(candidate.model, ctx);
|
|
279
|
+
if (!model) continue;
|
|
280
|
+
if (await pi.setModel(model)) {
|
|
281
|
+
return {
|
|
282
|
+
...(candidate.thinkingLevel !== undefined
|
|
283
|
+
? { thinkingLevel: candidate.thinkingLevel }
|
|
284
|
+
: {}),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
ctx.ui.notify(
|
|
290
|
+
`Agent ${formatAgentDisplayName(agent)}: profile "${profileName}" has no available candidate models. Configured: ${profile.candidates.map((candidate) => candidate.model).join(", ")}.`,
|
|
291
|
+
"error",
|
|
292
|
+
);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
203
296
|
async function activateAgent(
|
|
204
297
|
agent: AgentConfig,
|
|
205
298
|
ctx: ExtensionContext,
|
|
@@ -211,7 +304,20 @@ export function registerAgentMode(pi: ExtensionAPI) {
|
|
|
211
304
|
thinkingLevel: baseline.thinkingLevel ?? pi.getThinkingLevel(),
|
|
212
305
|
};
|
|
213
306
|
let thinkingLevel = baseline.thinkingLevel;
|
|
214
|
-
if (agent.model) {
|
|
307
|
+
if (agent.profile && agent.model) {
|
|
308
|
+
ctx.ui.notify(
|
|
309
|
+
`Agent ${formatAgentDisplayName(agent)}: declare either "profile" or "model" in frontmatter, not both.`,
|
|
310
|
+
"error",
|
|
311
|
+
);
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
if ((agent.profile || agent.model) && !(await refreshModelCatalog(ctx)))
|
|
315
|
+
return false;
|
|
316
|
+
if (agent.profile) {
|
|
317
|
+
const applied = await applyProfileModels(agent, ctx);
|
|
318
|
+
if (!applied) return false;
|
|
319
|
+
thinkingLevel = applied.thinkingLevel ?? baseline.thinkingLevel;
|
|
320
|
+
} else if (agent.model) {
|
|
215
321
|
const modelSpec = parseAgentModelSpec(agent.model);
|
|
216
322
|
const model = findModel(modelSpec.model, ctx);
|
|
217
323
|
if (!model) {
|