advisor-pi 1.0.0

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/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # advisor-pi
2
+
3
+ Advisor-style strategic guidance for Pi Coding Agent.
4
+
5
+ ![advisor-pi screenshot](../../assets/advisor-pi.png)
6
+
7
+ `advisor-pi` adds an LLM-callable `advisor` tool. During complex workflows, the
8
+ executor model can pause and consult a configured higher-capability advisor model
9
+ for planning, risk analysis, course correction, or review guidance.
10
+
11
+ The advisor receives the current conversation transcript plus the executor's
12
+ question. It has no tools and cannot modify files; it only returns strategic
13
+ advice to the executor.
14
+
15
+ ## Behavior
16
+
17
+ - Registers an `advisor` tool for complex or high-risk tasks.
18
+ - Lets the executor decide when a consultation is useful.
19
+ - Calls a configured advisor model through Pi's model registry and auth.
20
+ - Tracks advisor uses per session branch and stops after the configured limit.
21
+ - Passes a prompt-cache preference (`none`, `short`, or `long`) where the
22
+ selected provider supports it.
23
+ - Shows a compact `advisor:<remaining>` status in Pi's footer when UI is
24
+ available.
25
+
26
+ ## Commands
27
+
28
+ ```text
29
+ /advisor-pi status
30
+ /advisor-pi enable
31
+ /advisor-pi disable
32
+ /advisor-pi model <provider>/<model>
33
+ /advisor-pi max-uses <number>
34
+ /advisor-pi cache <none|short|long>
35
+ /advisor-pi reset
36
+ ```
37
+
38
+ Examples:
39
+
40
+ ```text
41
+ /advisor-pi model anthropic/claude-opus-4-5
42
+ /advisor-pi max-uses 5
43
+ /advisor-pi cache long
44
+ ```
45
+
46
+ ## CLI flags
47
+
48
+ ```bash
49
+ pi --advisor-model anthropic/claude-opus-4-5 \
50
+ --advisor-max-uses 3 \
51
+ --advisor-cache short
52
+ ```
53
+
54
+ Disable on startup:
55
+
56
+ ```bash
57
+ pi --advisor-enabled=false
58
+ ```
59
+
60
+ ## Install
61
+
62
+ From the repo root:
63
+
64
+ ```bash
65
+ cp -r extensions/advisor-pi ~/.pi/agent/extensions/
66
+ ```
67
+
68
+ Then run `/reload` in Pi.
69
+
70
+ ## Cost and latency
71
+
72
+ Each advisor consultation is a separate model call. That means:
73
+
74
+ - Advisor input and output are billed separately by the selected provider.
75
+ - Streaming from the executor pauses while the advisor call runs.
76
+ - Longer transcripts cost more because the advisor reads the conversation
77
+ context.
78
+ - The `max-uses` setting is a safety budget; raise it only when deeper review is
79
+ worth the extra cost.
80
+
81
+ ## Caching
82
+
83
+ `advisor-pi` passes the configured cache preference to Pi's AI provider layer:
84
+
85
+ - `none` — do not request prompt caching.
86
+ - `short` — request normal/short cache retention where supported.
87
+ - `long` — request long cache retention where supported.
88
+
89
+ Provider support varies. Some providers ignore cache preferences, some require
90
+ specific model compatibility, and cache reads/writes may still be billed.
91
+
92
+ ## Known limitations
93
+
94
+ - This is a portable Pi custom-tool approximation, not Anthropic's native beta
95
+ `advisor_20260301` server tool.
96
+ - Native provider-specific advisor usage fields may not appear in provider usage
97
+ reports.
98
+ - The advisor has no tools and cannot inspect files beyond what appears in the
99
+ transcript.
100
+ - Very long conversations can make advisor calls slower and more expensive.
101
+ - `max-uses` is tracked per Pi session branch from extension state and tool
102
+ result details; manual session editing can affect reconstruction.
@@ -0,0 +1,3 @@
1
+ import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ export default function advisorPiExtension(pi: ExtensionAPI): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAIN,KAAK,YAAY,EAEjB,MAAM,iCAAiC,CAAC;AAgEzC,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EAAE,EAAE,YAAY,QAkW1D"}
package/dist/index.js ADDED
@@ -0,0 +1,489 @@
1
+ import { complete, StringEnum } from "@earendil-works/pi-ai";
2
+ import { buildSessionContext, convertToLlm, serializeConversation, } from "@earendil-works/pi-coding-agent";
3
+ import { Type } from "typebox";
4
+ const STATE_ENTRY = "advisor-pi-state";
5
+ const TOOL_NAME = "advisor";
6
+ const DEFAULT_ADVISOR_MODEL = "openai-codex/gpt-5.5";
7
+ const DEFAULT_MAX_USES = 3;
8
+ const DEFAULT_CACHE_RETENTION = "short";
9
+ const DEFAULT_TIMEOUT_MS = 600_000;
10
+ const DEFAULT_MAX_TOKENS = 4_000;
11
+ const advisorToolSchema = Type.Object({
12
+ question: Type.String({
13
+ description: "Strategic question for the advisor. Ask for planning, risk analysis, course correction, or review guidance.",
14
+ }),
15
+ phase: Type.Optional(StringEnum(["planning", "course_correction", "review", "stuck", "other"], {
16
+ description: "Why the executor is consulting the advisor.",
17
+ default: "other",
18
+ })),
19
+ context: Type.Optional(Type.String({
20
+ description: "Optional extra context that is not obvious from the transcript, such as constraints, current plan, or failed attempts.",
21
+ })),
22
+ });
23
+ export default function advisorPiExtension(pi) {
24
+ let config = defaultConfig();
25
+ let useCount = 0;
26
+ pi.registerFlag("advisor-model", {
27
+ description: "Advisor model as provider/model, e.g. openai-codex/gpt-5.5",
28
+ type: "string",
29
+ });
30
+ pi.registerFlag("advisor-max-uses", {
31
+ description: "Maximum advisor calls per session branch",
32
+ type: "string",
33
+ });
34
+ pi.registerFlag("advisor-cache", {
35
+ description: "Advisor prompt-cache preference: none, short, or long",
36
+ type: "string",
37
+ });
38
+ pi.registerFlag("advisor-enabled", {
39
+ description: "Enable the advisor tool on startup",
40
+ type: "boolean",
41
+ default: true,
42
+ });
43
+ pi.registerTool({
44
+ name: TOOL_NAME,
45
+ label: "Advisor",
46
+ description: "Consult a configured higher-capability advisor model for strategic guidance during complex agent tasks. The advisor reads the current conversation transcript and returns planning, risk, or course-correction advice. It has no tools and does not modify files.",
47
+ promptSnippet: "Consult a higher-capability advisor model for strategic planning, risk checks, and course correction on complex tasks.",
48
+ promptGuidelines: [
49
+ "Use advisor before starting a complex, multi-step, or high-risk task when strategic planning would reduce mistakes.",
50
+ "Use advisor when stuck, when tests keep failing, or when choosing between competing implementation approaches.",
51
+ "Do not use advisor for simple lookups, trivial edits, or when the user explicitly wants the fastest possible answer.",
52
+ ],
53
+ parameters: advisorToolSchema,
54
+ executionMode: "sequential",
55
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
56
+ refreshStateFromBranch(ctx);
57
+ if (!config.enabled) {
58
+ return advisorDisabledResult(config, useCount, params);
59
+ }
60
+ if (useCount >= config.maxUses) {
61
+ return advisorLimitResult(config, useCount, params);
62
+ }
63
+ const model = ctx.modelRegistry.find(config.provider, config.modelId);
64
+ if (!model) {
65
+ throw new Error(`Advisor model not found: ${config.provider}/${config.modelId}. Run /advisor-pi model <provider>/<model>.`);
66
+ }
67
+ onUpdate?.({
68
+ content: [
69
+ {
70
+ type: "text",
71
+ text: `Consulting advisor ${config.provider}/${config.modelId}...`,
72
+ },
73
+ ],
74
+ details: makeSkippedDetails(config, useCount, params),
75
+ });
76
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
77
+ if (!auth.ok)
78
+ throw new Error(auth.error);
79
+ const startedAt = Date.now();
80
+ const conversationText = serializeCurrentConversation(ctx);
81
+ const userMessage = {
82
+ role: "user",
83
+ content: [
84
+ {
85
+ type: "text",
86
+ text: buildAdvisorUserPrompt(params, conversationText),
87
+ },
88
+ ],
89
+ timestamp: Date.now(),
90
+ };
91
+ const response = await complete(model, {
92
+ systemPrompt: ADVISOR_SYSTEM_PROMPT,
93
+ messages: [userMessage],
94
+ }, {
95
+ apiKey: auth.apiKey,
96
+ headers: auth.headers,
97
+ signal,
98
+ cacheRetention: config.cacheRetention,
99
+ sessionId: `advisor-pi:${ctx.sessionManager.getSessionId()}`,
100
+ maxTokens: config.maxTokens,
101
+ timeoutMs: config.timeoutMs,
102
+ });
103
+ if (response.stopReason === "error") {
104
+ throw new Error(response.errorMessage ?? "Advisor model returned an error");
105
+ }
106
+ if (response.stopReason === "aborted") {
107
+ throw new Error("Advisor call aborted");
108
+ }
109
+ useCount += 1;
110
+ persistState(pi, config, useCount);
111
+ updateStatus(ctx);
112
+ const elapsedMs = Date.now() - startedAt;
113
+ const text = extractText(response.content) || "Advisor returned no text guidance.";
114
+ const details = {
115
+ advisor: {
116
+ provider: response.provider,
117
+ model: response.model,
118
+ phase: params.phase ?? "other",
119
+ useCount,
120
+ maxUses: config.maxUses,
121
+ cacheRetention: config.cacheRetention,
122
+ elapsedMs,
123
+ stopReason: response.stopReason,
124
+ usage: response.usage,
125
+ },
126
+ state: makeStateEntry(config, useCount),
127
+ };
128
+ return {
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: `Advisor guidance (${useCount}/${config.maxUses}, ${config.provider}/${config.modelId}):\n\n${text}`,
133
+ },
134
+ ],
135
+ details,
136
+ };
137
+ },
138
+ });
139
+ pi.registerCommand("advisor-pi", {
140
+ description: "Configure advisor-pi: status, enable, disable, model, max-uses, cache, reset",
141
+ handler: async (args, ctx) => {
142
+ const result = handleCommand(args.trim(), ctx);
143
+ if (result.persist)
144
+ persistState(pi, config, useCount);
145
+ if (result.updateToolState)
146
+ syncActiveTool(pi);
147
+ updateStatus(ctx);
148
+ ctx.ui.notify(result.message, result.level);
149
+ },
150
+ });
151
+ pi.on("session_start", async (_event, ctx) => {
152
+ refreshStateFromBranch(ctx);
153
+ });
154
+ pi.on("session_tree", async (_event, ctx) => {
155
+ refreshStateFromBranch(ctx);
156
+ });
157
+ pi.on("before_agent_start", async (event) => {
158
+ if (!config.enabled)
159
+ return;
160
+ return {
161
+ systemPrompt: `${event.systemPrompt}\n\n${buildExecutorGuidance(config, useCount)}`,
162
+ };
163
+ });
164
+ pi.on("turn_start", async () => {
165
+ persistState(pi, config, useCount);
166
+ });
167
+ function refreshStateFromBranch(ctx) {
168
+ config = defaultConfig();
169
+ useCount = 0;
170
+ const restoredFromBranch = restoreStateFromSession(ctx);
171
+ if (!restoredFromBranch)
172
+ applyStartupFlags(pi);
173
+ syncActiveTool(pi);
174
+ updateStatus(ctx);
175
+ }
176
+ function restoreStateFromSession(ctx) {
177
+ let restored = false;
178
+ const branch = ctx.sessionManager.getBranch();
179
+ for (const entry of branch) {
180
+ if (entry.type === "custom" && entry.customType === STATE_ENTRY) {
181
+ restored = true;
182
+ const data = entry.data;
183
+ if (data?.config)
184
+ config = normalizeConfig(data.config, config);
185
+ if (typeof data?.useCount === "number")
186
+ useCount = Math.max(0, data.useCount);
187
+ }
188
+ if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.toolName === TOOL_NAME) {
189
+ restored = true;
190
+ const details = entry.message.details;
191
+ if (details?.state?.config)
192
+ config = normalizeConfig(details.state.config, config);
193
+ if (typeof details?.state?.useCount === "number")
194
+ useCount = Math.max(useCount, details.state.useCount);
195
+ else if (details?.advisor?.useCount)
196
+ useCount = Math.max(useCount, details.advisor.useCount);
197
+ }
198
+ }
199
+ return restored;
200
+ }
201
+ function applyStartupFlags(api) {
202
+ const enabledFlag = api.getFlag("advisor-enabled");
203
+ if (typeof enabledFlag === "boolean")
204
+ config.enabled = enabledFlag;
205
+ const modelFlag = api.getFlag("advisor-model");
206
+ if (typeof modelFlag === "string" && modelFlag.trim()) {
207
+ const parsed = parseModelSpec(modelFlag.trim());
208
+ if (parsed) {
209
+ config.provider = parsed.provider;
210
+ config.modelId = parsed.modelId;
211
+ }
212
+ }
213
+ const maxUsesFlag = api.getFlag("advisor-max-uses");
214
+ if (typeof maxUsesFlag === "string") {
215
+ const maxUses = parsePositiveInt(maxUsesFlag);
216
+ if (maxUses !== undefined)
217
+ config.maxUses = maxUses;
218
+ }
219
+ const cacheFlag = api.getFlag("advisor-cache");
220
+ if (typeof cacheFlag === "string") {
221
+ const cacheRetention = parseCacheRetention(cacheFlag);
222
+ if (cacheRetention)
223
+ config.cacheRetention = cacheRetention;
224
+ }
225
+ }
226
+ function handleCommand(args, ctx) {
227
+ if (!args || args === "status") {
228
+ return {
229
+ message: formatStatus(ctx),
230
+ level: "info",
231
+ persist: false,
232
+ updateToolState: false,
233
+ };
234
+ }
235
+ const [command, ...rest] = args.split(/\s+/);
236
+ const value = rest.join(" ").trim();
237
+ switch (command) {
238
+ case "enable":
239
+ config.enabled = true;
240
+ return { message: "advisor-pi enabled", level: "info", persist: true, updateToolState: true };
241
+ case "disable":
242
+ config.enabled = false;
243
+ return { message: "advisor-pi disabled", level: "info", persist: true, updateToolState: true };
244
+ case "reset":
245
+ useCount = 0;
246
+ return { message: "advisor-pi use count reset", level: "info", persist: true, updateToolState: false };
247
+ case "model": {
248
+ const parsed = parseModelSpec(value);
249
+ if (!parsed) {
250
+ return {
251
+ message: "Usage: /advisor-pi model <provider>/<model>",
252
+ level: "error",
253
+ persist: false,
254
+ updateToolState: false,
255
+ };
256
+ }
257
+ const model = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
258
+ if (!model) {
259
+ return {
260
+ message: `Advisor model not found: ${parsed.provider}/${parsed.modelId}`,
261
+ level: "error",
262
+ persist: false,
263
+ updateToolState: false,
264
+ };
265
+ }
266
+ config.provider = parsed.provider;
267
+ config.modelId = parsed.modelId;
268
+ return {
269
+ message: `advisor-pi model set to ${config.provider}/${config.modelId}`,
270
+ level: "info",
271
+ persist: true,
272
+ updateToolState: false,
273
+ };
274
+ }
275
+ case "max-uses": {
276
+ const maxUses = parsePositiveInt(value);
277
+ if (maxUses === undefined) {
278
+ return {
279
+ message: "Usage: /advisor-pi max-uses <positive-number>",
280
+ level: "error",
281
+ persist: false,
282
+ updateToolState: false,
283
+ };
284
+ }
285
+ config.maxUses = maxUses;
286
+ return {
287
+ message: `advisor-pi max uses set to ${maxUses}`,
288
+ level: "info",
289
+ persist: true,
290
+ updateToolState: false,
291
+ };
292
+ }
293
+ case "cache": {
294
+ const cacheRetention = parseCacheRetention(value);
295
+ if (!cacheRetention) {
296
+ return {
297
+ message: "Usage: /advisor-pi cache <none|short|long>",
298
+ level: "error",
299
+ persist: false,
300
+ updateToolState: false,
301
+ };
302
+ }
303
+ config.cacheRetention = cacheRetention;
304
+ return {
305
+ message: `advisor-pi cache set to ${cacheRetention}`,
306
+ level: "info",
307
+ persist: true,
308
+ updateToolState: false,
309
+ };
310
+ }
311
+ default:
312
+ return {
313
+ message: "Usage: /advisor-pi [status|enable|disable|model <provider>/<model>|max-uses <n>|cache <none|short|long>|reset]",
314
+ level: "error",
315
+ persist: false,
316
+ updateToolState: false,
317
+ };
318
+ }
319
+ }
320
+ function syncActiveTool(api) {
321
+ const activeTools = api.getActiveTools();
322
+ const hasAdvisor = activeTools.includes(TOOL_NAME);
323
+ if (config.enabled && !hasAdvisor) {
324
+ api.setActiveTools([...activeTools, TOOL_NAME]);
325
+ }
326
+ else if (!config.enabled && hasAdvisor) {
327
+ api.setActiveTools(activeTools.filter((tool) => tool !== TOOL_NAME));
328
+ }
329
+ }
330
+ function updateStatus(ctx) {
331
+ if (!ctx.hasUI)
332
+ return;
333
+ if (!config.enabled) {
334
+ ctx.ui.setStatus("advisor-pi", undefined);
335
+ return;
336
+ }
337
+ const remaining = Math.max(0, config.maxUses - useCount);
338
+ ctx.ui.setStatus("advisor-pi", ctx.ui.theme.fg(remaining > 0 ? "accent" : "warning", `advisor:${remaining}`));
339
+ }
340
+ function formatStatus(ctx) {
341
+ const model = ctx.modelRegistry.find(config.provider, config.modelId);
342
+ const availability = model ? "available" : "not found";
343
+ return [
344
+ `advisor-pi ${config.enabled ? "enabled" : "disabled"}`,
345
+ `model: ${config.provider}/${config.modelId} (${availability})`,
346
+ `uses: ${useCount}/${config.maxUses}`,
347
+ `cache: ${config.cacheRetention}`,
348
+ ].join(" • ");
349
+ }
350
+ }
351
+ const ADVISOR_SYSTEM_PROMPT = `You are an advisor model for Pi Coding Agent.
352
+
353
+ Your role is strategic guidance only. You do not have tools and you do not make changes. Given the executor's question and conversation transcript:
354
+
355
+ - Identify the best next plan or course correction.
356
+ - Call out risks, missing information, edge cases, and test strategy.
357
+ - Prefer concise, actionable guidance the executor can apply immediately.
358
+ - Do not restate the full transcript.
359
+ - If the task is unsafe, impossible, or underspecified, say what must be clarified.
360
+
361
+ Return guidance in short sections with bullets when useful.`;
362
+ function defaultConfig() {
363
+ const parsed = parseModelSpec(DEFAULT_ADVISOR_MODEL) ?? { provider: "openai-codex", modelId: "gpt-5.5" };
364
+ return {
365
+ enabled: true,
366
+ provider: parsed.provider,
367
+ modelId: parsed.modelId,
368
+ maxUses: DEFAULT_MAX_USES,
369
+ cacheRetention: DEFAULT_CACHE_RETENTION,
370
+ maxTokens: DEFAULT_MAX_TOKENS,
371
+ timeoutMs: DEFAULT_TIMEOUT_MS,
372
+ };
373
+ }
374
+ function buildExecutorGuidance(config, useCount) {
375
+ const remaining = Math.max(0, config.maxUses - useCount);
376
+ return [
377
+ "Advisor-pi is enabled.",
378
+ `The advisor tool consults ${config.provider}/${config.modelId} for strategic guidance and has ${remaining}/${config.maxUses} uses remaining on this branch.`,
379
+ "Use advisor for complex planning, risky implementation choices, repeated failures, or course correction. Do not use advisor for trivial edits or simple facts.",
380
+ `Advisor cache preference is ${config.cacheRetention}; caching support is provider-dependent.`,
381
+ ].join("\n");
382
+ }
383
+ function serializeCurrentConversation(ctx) {
384
+ const entries = ctx.sessionManager.getEntries();
385
+ const leafId = ctx.sessionManager.getLeafId();
386
+ const sessionContext = buildSessionContext(entries, leafId);
387
+ const llmMessages = convertToLlm(sessionContext.messages);
388
+ return serializeConversation(llmMessages);
389
+ }
390
+ function buildAdvisorUserPrompt(params, conversationText) {
391
+ return [
392
+ "## Executor Question",
393
+ params.question,
394
+ "",
395
+ "## Consultation Phase",
396
+ params.phase ?? "other",
397
+ "",
398
+ ...(params.context ? ["## Extra Context", params.context, ""] : []),
399
+ "## Conversation Transcript",
400
+ conversationText,
401
+ ].join("\n");
402
+ }
403
+ function extractText(content) {
404
+ return content
405
+ .filter((part) => part.type === "text" && typeof part.text === "string")
406
+ .map((part) => part.text)
407
+ .join("\n")
408
+ .trim();
409
+ }
410
+ function advisorDisabledResult(config, useCount, params) {
411
+ return {
412
+ content: [
413
+ {
414
+ type: "text",
415
+ text: `advisor-pi is disabled. The executor should continue without advisor guidance for: ${params.question}`,
416
+ },
417
+ ],
418
+ details: makeSkippedDetails(config, useCount, params),
419
+ };
420
+ }
421
+ function advisorLimitResult(config, useCount, params) {
422
+ return {
423
+ content: [
424
+ {
425
+ type: "text",
426
+ text: `Advisor use limit reached. The executor should continue without another advisor call for: ${params.question}`,
427
+ },
428
+ ],
429
+ details: makeSkippedDetails(config, useCount, params),
430
+ };
431
+ }
432
+ function makeSkippedDetails(config, useCount, params) {
433
+ return {
434
+ advisor: {
435
+ provider: config.provider,
436
+ model: config.modelId,
437
+ phase: params.phase ?? "other",
438
+ useCount,
439
+ maxUses: config.maxUses,
440
+ cacheRetention: config.cacheRetention,
441
+ elapsedMs: 0,
442
+ stopReason: "skipped",
443
+ },
444
+ state: makeStateEntry(config, useCount),
445
+ };
446
+ }
447
+ function makeStateEntry(config, useCount) {
448
+ return {
449
+ version: 1,
450
+ config: { ...config },
451
+ useCount,
452
+ updatedAt: new Date().toISOString(),
453
+ };
454
+ }
455
+ function persistState(pi, config, useCount) {
456
+ pi.appendEntry(STATE_ENTRY, makeStateEntry(config, useCount));
457
+ }
458
+ function normalizeConfig(input, fallback) {
459
+ return {
460
+ enabled: typeof input.enabled === "boolean" ? input.enabled : fallback.enabled,
461
+ provider: typeof input.provider === "string" && input.provider ? input.provider : fallback.provider,
462
+ modelId: typeof input.modelId === "string" && input.modelId ? input.modelId : fallback.modelId,
463
+ maxUses: typeof input.maxUses === "number" && input.maxUses > 0 ? Math.floor(input.maxUses) : fallback.maxUses,
464
+ cacheRetention: parseCacheRetention(input.cacheRetention) ?? fallback.cacheRetention,
465
+ maxTokens: typeof input.maxTokens === "number" && input.maxTokens > 0 ? Math.floor(input.maxTokens) : fallback.maxTokens,
466
+ timeoutMs: typeof input.timeoutMs === "number" && input.timeoutMs > 0 ? Math.floor(input.timeoutMs) : fallback.timeoutMs,
467
+ };
468
+ }
469
+ function parseModelSpec(value) {
470
+ const slash = value.indexOf("/");
471
+ if (slash <= 0 || slash === value.length - 1)
472
+ return undefined;
473
+ return {
474
+ provider: value.slice(0, slash),
475
+ modelId: value.slice(slash + 1),
476
+ };
477
+ }
478
+ function parsePositiveInt(value) {
479
+ const parsed = Number.parseInt(value, 10);
480
+ if (!Number.isFinite(parsed) || parsed <= 0)
481
+ return undefined;
482
+ return parsed;
483
+ }
484
+ function parseCacheRetention(value) {
485
+ if (value === "none" || value === "short" || value === "long")
486
+ return value;
487
+ return undefined;
488
+ }
489
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "advisor-pi",
3
+ "version": "1.0.0",
4
+ "description": "Advisor-style strategic model guidance for Pi Coding Agent workflows",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "src",
11
+ "README.md"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "pi": {
24
+ "extensions": ["./src/index.ts"]
25
+ },
26
+ "keywords": [
27
+ "pi",
28
+ "advisor",
29
+ "strategic-guidance",
30
+ "model-delegation",
31
+ "agent-workflows"
32
+ ],
33
+ "author": "pi-extensions contributors",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/luongnv89/pi-extensions.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/luongnv89/pi-extensions/issues"
41
+ },
42
+ "homepage": "https://github.com/luongnv89/pi-extensions/tree/main/extensions/advisor-pi",
43
+ "peerDependencies": {
44
+ "@earendil-works/pi-agent-core": "^0.75.4",
45
+ "@earendil-works/pi-ai": "^0.75.4",
46
+ "@earendil-works/pi-coding-agent": "^0.75.4",
47
+ "typebox": "^1.0.55"
48
+ },
49
+ "devDependencies": {
50
+ "typescript": "^5.6.0"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ }
55
+ }
package/src/index.ts ADDED
@@ -0,0 +1,577 @@
1
+ import { complete, StringEnum, type CacheRetention, type Message, type Usage } from "@earendil-works/pi-ai";
2
+ import {
3
+ buildSessionContext,
4
+ convertToLlm,
5
+ serializeConversation,
6
+ type ExtensionAPI,
7
+ type ExtensionContext,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { Type, type Static } from "typebox";
10
+
11
+ const STATE_ENTRY = "advisor-pi-state";
12
+ const TOOL_NAME = "advisor";
13
+ const DEFAULT_ADVISOR_MODEL = "openai-codex/gpt-5.5";
14
+ const DEFAULT_MAX_USES = 3;
15
+ const DEFAULT_CACHE_RETENTION: CacheRetention = "short";
16
+ const DEFAULT_TIMEOUT_MS = 600_000;
17
+ const DEFAULT_MAX_TOKENS = 4_000;
18
+
19
+ const advisorToolSchema = Type.Object({
20
+ question: Type.String({
21
+ description:
22
+ "Strategic question for the advisor. Ask for planning, risk analysis, course correction, or review guidance.",
23
+ }),
24
+ phase: Type.Optional(
25
+ StringEnum(["planning", "course_correction", "review", "stuck", "other"] as const, {
26
+ description: "Why the executor is consulting the advisor.",
27
+ default: "other",
28
+ }),
29
+ ),
30
+ context: Type.Optional(
31
+ Type.String({
32
+ description:
33
+ "Optional extra context that is not obvious from the transcript, such as constraints, current plan, or failed attempts.",
34
+ }),
35
+ ),
36
+ });
37
+
38
+ type AdvisorToolInput = Static<typeof advisorToolSchema>;
39
+
40
+ type AdvisorConfig = {
41
+ enabled: boolean;
42
+ provider: string;
43
+ modelId: string;
44
+ maxUses: number;
45
+ cacheRetention: CacheRetention;
46
+ maxTokens: number;
47
+ timeoutMs: number;
48
+ };
49
+
50
+ type AdvisorStateEntry = {
51
+ version: 1;
52
+ config: AdvisorConfig;
53
+ useCount: number;
54
+ updatedAt: string;
55
+ };
56
+
57
+ type AdvisorToolDetails = {
58
+ advisor: {
59
+ provider: string;
60
+ model: string;
61
+ phase: string;
62
+ useCount: number;
63
+ maxUses: number;
64
+ cacheRetention: CacheRetention;
65
+ elapsedMs: number;
66
+ stopReason: string;
67
+ usage?: Usage;
68
+ };
69
+ state: AdvisorStateEntry;
70
+ };
71
+
72
+ export default function advisorPiExtension(pi: ExtensionAPI) {
73
+ let config = defaultConfig();
74
+ let useCount = 0;
75
+
76
+ pi.registerFlag("advisor-model", {
77
+ description: "Advisor model as provider/model, e.g. openai-codex/gpt-5.5",
78
+ type: "string",
79
+ });
80
+ pi.registerFlag("advisor-max-uses", {
81
+ description: "Maximum advisor calls per session branch",
82
+ type: "string",
83
+ });
84
+ pi.registerFlag("advisor-cache", {
85
+ description: "Advisor prompt-cache preference: none, short, or long",
86
+ type: "string",
87
+ });
88
+ pi.registerFlag("advisor-enabled", {
89
+ description: "Enable the advisor tool on startup",
90
+ type: "boolean",
91
+ default: true,
92
+ });
93
+
94
+ pi.registerTool<typeof advisorToolSchema, AdvisorToolDetails>({
95
+ name: TOOL_NAME,
96
+ label: "Advisor",
97
+ description:
98
+ "Consult a configured higher-capability advisor model for strategic guidance during complex agent tasks. The advisor reads the current conversation transcript and returns planning, risk, or course-correction advice. It has no tools and does not modify files.",
99
+ promptSnippet:
100
+ "Consult a higher-capability advisor model for strategic planning, risk checks, and course correction on complex tasks.",
101
+ promptGuidelines: [
102
+ "Use advisor before starting a complex, multi-step, or high-risk task when strategic planning would reduce mistakes.",
103
+ "Use advisor when stuck, when tests keep failing, or when choosing between competing implementation approaches.",
104
+ "Do not use advisor for simple lookups, trivial edits, or when the user explicitly wants the fastest possible answer.",
105
+ ],
106
+ parameters: advisorToolSchema,
107
+ executionMode: "sequential",
108
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
109
+ refreshStateFromBranch(ctx);
110
+
111
+ if (!config.enabled) {
112
+ return advisorDisabledResult(config, useCount, params);
113
+ }
114
+
115
+ if (useCount >= config.maxUses) {
116
+ return advisorLimitResult(config, useCount, params);
117
+ }
118
+
119
+ const model = ctx.modelRegistry.find(config.provider, config.modelId);
120
+ if (!model) {
121
+ throw new Error(
122
+ `Advisor model not found: ${config.provider}/${config.modelId}. Run /advisor-pi model <provider>/<model>.`,
123
+ );
124
+ }
125
+
126
+ onUpdate?.({
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: `Consulting advisor ${config.provider}/${config.modelId}...`,
131
+ },
132
+ ],
133
+ details: makeSkippedDetails(config, useCount, params),
134
+ });
135
+
136
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
137
+ if (!auth.ok) throw new Error(auth.error);
138
+
139
+ const startedAt = Date.now();
140
+ const conversationText = serializeCurrentConversation(ctx);
141
+ const userMessage: Message = {
142
+ role: "user",
143
+ content: [
144
+ {
145
+ type: "text",
146
+ text: buildAdvisorUserPrompt(params, conversationText),
147
+ },
148
+ ],
149
+ timestamp: Date.now(),
150
+ };
151
+
152
+ const response = await complete(
153
+ model,
154
+ {
155
+ systemPrompt: ADVISOR_SYSTEM_PROMPT,
156
+ messages: [userMessage],
157
+ },
158
+ {
159
+ apiKey: auth.apiKey,
160
+ headers: auth.headers,
161
+ signal,
162
+ cacheRetention: config.cacheRetention,
163
+ sessionId: `advisor-pi:${ctx.sessionManager.getSessionId()}`,
164
+ maxTokens: config.maxTokens,
165
+ timeoutMs: config.timeoutMs,
166
+ },
167
+ );
168
+
169
+ if (response.stopReason === "error") {
170
+ throw new Error(response.errorMessage ?? "Advisor model returned an error");
171
+ }
172
+ if (response.stopReason === "aborted") {
173
+ throw new Error("Advisor call aborted");
174
+ }
175
+
176
+ useCount += 1;
177
+ persistState(pi, config, useCount);
178
+ updateStatus(ctx);
179
+
180
+ const elapsedMs = Date.now() - startedAt;
181
+ const text = extractText(response.content) || "Advisor returned no text guidance.";
182
+ const details: AdvisorToolDetails = {
183
+ advisor: {
184
+ provider: response.provider,
185
+ model: response.model,
186
+ phase: params.phase ?? "other",
187
+ useCount,
188
+ maxUses: config.maxUses,
189
+ cacheRetention: config.cacheRetention,
190
+ elapsedMs,
191
+ stopReason: response.stopReason,
192
+ usage: response.usage,
193
+ },
194
+ state: makeStateEntry(config, useCount),
195
+ };
196
+
197
+ return {
198
+ content: [
199
+ {
200
+ type: "text",
201
+ text: `Advisor guidance (${useCount}/${config.maxUses}, ${config.provider}/${config.modelId}):\n\n${text}`,
202
+ },
203
+ ],
204
+ details,
205
+ };
206
+ },
207
+ });
208
+
209
+ pi.registerCommand("advisor-pi", {
210
+ description: "Configure advisor-pi: status, enable, disable, model, max-uses, cache, reset",
211
+ handler: async (args, ctx) => {
212
+ const result = handleCommand(args.trim(), ctx);
213
+ if (result.persist) persistState(pi, config, useCount);
214
+ if (result.updateToolState) syncActiveTool(pi);
215
+ updateStatus(ctx);
216
+ ctx.ui.notify(result.message, result.level);
217
+ },
218
+ });
219
+
220
+ pi.on("session_start", async (_event, ctx) => {
221
+ refreshStateFromBranch(ctx);
222
+ });
223
+
224
+ pi.on("session_tree", async (_event, ctx) => {
225
+ refreshStateFromBranch(ctx);
226
+ });
227
+
228
+ pi.on("before_agent_start", async (event) => {
229
+ if (!config.enabled) return;
230
+ return {
231
+ systemPrompt: `${event.systemPrompt}\n\n${buildExecutorGuidance(config, useCount)}`,
232
+ };
233
+ });
234
+
235
+ pi.on("turn_start", async () => {
236
+ persistState(pi, config, useCount);
237
+ });
238
+
239
+ function refreshStateFromBranch(ctx: ExtensionContext): void {
240
+ config = defaultConfig();
241
+ useCount = 0;
242
+ const restoredFromBranch = restoreStateFromSession(ctx);
243
+ if (!restoredFromBranch) applyStartupFlags(pi);
244
+ syncActiveTool(pi);
245
+ updateStatus(ctx);
246
+ }
247
+
248
+ function restoreStateFromSession(ctx: ExtensionContext): boolean {
249
+ let restored = false;
250
+ const branch = ctx.sessionManager.getBranch();
251
+ for (const entry of branch) {
252
+ if (entry.type === "custom" && entry.customType === STATE_ENTRY) {
253
+ restored = true;
254
+ const data = entry.data as Partial<AdvisorStateEntry> | undefined;
255
+ if (data?.config) config = normalizeConfig(data.config, config);
256
+ if (typeof data?.useCount === "number") useCount = Math.max(0, data.useCount);
257
+ }
258
+ if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.toolName === TOOL_NAME) {
259
+ restored = true;
260
+ const details = entry.message.details as Partial<AdvisorToolDetails> | undefined;
261
+ if (details?.state?.config) config = normalizeConfig(details.state.config, config);
262
+ if (typeof details?.state?.useCount === "number") useCount = Math.max(useCount, details.state.useCount);
263
+ else if (details?.advisor?.useCount) useCount = Math.max(useCount, details.advisor.useCount);
264
+ }
265
+ }
266
+ return restored;
267
+ }
268
+
269
+ function applyStartupFlags(api: ExtensionAPI): void {
270
+ const enabledFlag = api.getFlag("advisor-enabled");
271
+ if (typeof enabledFlag === "boolean") config.enabled = enabledFlag;
272
+
273
+ const modelFlag = api.getFlag("advisor-model");
274
+ if (typeof modelFlag === "string" && modelFlag.trim()) {
275
+ const parsed = parseModelSpec(modelFlag.trim());
276
+ if (parsed) {
277
+ config.provider = parsed.provider;
278
+ config.modelId = parsed.modelId;
279
+ }
280
+ }
281
+
282
+ const maxUsesFlag = api.getFlag("advisor-max-uses");
283
+ if (typeof maxUsesFlag === "string") {
284
+ const maxUses = parsePositiveInt(maxUsesFlag);
285
+ if (maxUses !== undefined) config.maxUses = maxUses;
286
+ }
287
+
288
+ const cacheFlag = api.getFlag("advisor-cache");
289
+ if (typeof cacheFlag === "string") {
290
+ const cacheRetention = parseCacheRetention(cacheFlag);
291
+ if (cacheRetention) config.cacheRetention = cacheRetention;
292
+ }
293
+ }
294
+
295
+ function handleCommand(
296
+ args: string,
297
+ ctx: ExtensionContext,
298
+ ): { message: string; level: "info" | "warning" | "error"; persist: boolean; updateToolState: boolean } {
299
+ if (!args || args === "status") {
300
+ return {
301
+ message: formatStatus(ctx),
302
+ level: "info",
303
+ persist: false,
304
+ updateToolState: false,
305
+ };
306
+ }
307
+
308
+ const [command, ...rest] = args.split(/\s+/);
309
+ const value = rest.join(" ").trim();
310
+
311
+ switch (command) {
312
+ case "enable":
313
+ config.enabled = true;
314
+ return { message: "advisor-pi enabled", level: "info", persist: true, updateToolState: true };
315
+ case "disable":
316
+ config.enabled = false;
317
+ return { message: "advisor-pi disabled", level: "info", persist: true, updateToolState: true };
318
+ case "reset":
319
+ useCount = 0;
320
+ return { message: "advisor-pi use count reset", level: "info", persist: true, updateToolState: false };
321
+ case "model": {
322
+ const parsed = parseModelSpec(value);
323
+ if (!parsed) {
324
+ return {
325
+ message: "Usage: /advisor-pi model <provider>/<model>",
326
+ level: "error",
327
+ persist: false,
328
+ updateToolState: false,
329
+ };
330
+ }
331
+ const model = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
332
+ if (!model) {
333
+ return {
334
+ message: `Advisor model not found: ${parsed.provider}/${parsed.modelId}`,
335
+ level: "error",
336
+ persist: false,
337
+ updateToolState: false,
338
+ };
339
+ }
340
+ config.provider = parsed.provider;
341
+ config.modelId = parsed.modelId;
342
+ return {
343
+ message: `advisor-pi model set to ${config.provider}/${config.modelId}`,
344
+ level: "info",
345
+ persist: true,
346
+ updateToolState: false,
347
+ };
348
+ }
349
+ case "max-uses": {
350
+ const maxUses = parsePositiveInt(value);
351
+ if (maxUses === undefined) {
352
+ return {
353
+ message: "Usage: /advisor-pi max-uses <positive-number>",
354
+ level: "error",
355
+ persist: false,
356
+ updateToolState: false,
357
+ };
358
+ }
359
+ config.maxUses = maxUses;
360
+ return {
361
+ message: `advisor-pi max uses set to ${maxUses}`,
362
+ level: "info",
363
+ persist: true,
364
+ updateToolState: false,
365
+ };
366
+ }
367
+ case "cache": {
368
+ const cacheRetention = parseCacheRetention(value);
369
+ if (!cacheRetention) {
370
+ return {
371
+ message: "Usage: /advisor-pi cache <none|short|long>",
372
+ level: "error",
373
+ persist: false,
374
+ updateToolState: false,
375
+ };
376
+ }
377
+ config.cacheRetention = cacheRetention;
378
+ return {
379
+ message: `advisor-pi cache set to ${cacheRetention}`,
380
+ level: "info",
381
+ persist: true,
382
+ updateToolState: false,
383
+ };
384
+ }
385
+ default:
386
+ return {
387
+ message:
388
+ "Usage: /advisor-pi [status|enable|disable|model <provider>/<model>|max-uses <n>|cache <none|short|long>|reset]",
389
+ level: "error",
390
+ persist: false,
391
+ updateToolState: false,
392
+ };
393
+ }
394
+ }
395
+
396
+ function syncActiveTool(api: ExtensionAPI): void {
397
+ const activeTools = api.getActiveTools();
398
+ const hasAdvisor = activeTools.includes(TOOL_NAME);
399
+ if (config.enabled && !hasAdvisor) {
400
+ api.setActiveTools([...activeTools, TOOL_NAME]);
401
+ } else if (!config.enabled && hasAdvisor) {
402
+ api.setActiveTools(activeTools.filter((tool) => tool !== TOOL_NAME));
403
+ }
404
+ }
405
+
406
+ function updateStatus(ctx: ExtensionContext): void {
407
+ if (!ctx.hasUI) return;
408
+ if (!config.enabled) {
409
+ ctx.ui.setStatus("advisor-pi", undefined);
410
+ return;
411
+ }
412
+ const remaining = Math.max(0, config.maxUses - useCount);
413
+ ctx.ui.setStatus("advisor-pi", ctx.ui.theme.fg(remaining > 0 ? "accent" : "warning", `advisor:${remaining}`));
414
+ }
415
+
416
+ function formatStatus(ctx: ExtensionContext): string {
417
+ const model = ctx.modelRegistry.find(config.provider, config.modelId);
418
+ const availability = model ? "available" : "not found";
419
+ return [
420
+ `advisor-pi ${config.enabled ? "enabled" : "disabled"}`,
421
+ `model: ${config.provider}/${config.modelId} (${availability})`,
422
+ `uses: ${useCount}/${config.maxUses}`,
423
+ `cache: ${config.cacheRetention}`,
424
+ ].join(" • ");
425
+ }
426
+ }
427
+
428
+ const ADVISOR_SYSTEM_PROMPT = `You are an advisor model for Pi Coding Agent.
429
+
430
+ Your role is strategic guidance only. You do not have tools and you do not make changes. Given the executor's question and conversation transcript:
431
+
432
+ - Identify the best next plan or course correction.
433
+ - Call out risks, missing information, edge cases, and test strategy.
434
+ - Prefer concise, actionable guidance the executor can apply immediately.
435
+ - Do not restate the full transcript.
436
+ - If the task is unsafe, impossible, or underspecified, say what must be clarified.
437
+
438
+ Return guidance in short sections with bullets when useful.`;
439
+
440
+ function defaultConfig(): AdvisorConfig {
441
+ const parsed = parseModelSpec(DEFAULT_ADVISOR_MODEL) ?? { provider: "openai-codex", modelId: "gpt-5.5" };
442
+ return {
443
+ enabled: true,
444
+ provider: parsed.provider,
445
+ modelId: parsed.modelId,
446
+ maxUses: DEFAULT_MAX_USES,
447
+ cacheRetention: DEFAULT_CACHE_RETENTION,
448
+ maxTokens: DEFAULT_MAX_TOKENS,
449
+ timeoutMs: DEFAULT_TIMEOUT_MS,
450
+ };
451
+ }
452
+
453
+ function buildExecutorGuidance(config: AdvisorConfig, useCount: number): string {
454
+ const remaining = Math.max(0, config.maxUses - useCount);
455
+ return [
456
+ "Advisor-pi is enabled.",
457
+ `The advisor tool consults ${config.provider}/${config.modelId} for strategic guidance and has ${remaining}/${config.maxUses} uses remaining on this branch.`,
458
+ "Use advisor for complex planning, risky implementation choices, repeated failures, or course correction. Do not use advisor for trivial edits or simple facts.",
459
+ `Advisor cache preference is ${config.cacheRetention}; caching support is provider-dependent.`,
460
+ ].join("\n");
461
+ }
462
+
463
+ function serializeCurrentConversation(ctx: ExtensionContext): string {
464
+ const entries = ctx.sessionManager.getEntries();
465
+ const leafId = ctx.sessionManager.getLeafId();
466
+ const sessionContext = buildSessionContext(entries, leafId);
467
+ const llmMessages = convertToLlm(sessionContext.messages);
468
+ return serializeConversation(llmMessages);
469
+ }
470
+
471
+ function buildAdvisorUserPrompt(params: AdvisorToolInput, conversationText: string): string {
472
+ return [
473
+ "## Executor Question",
474
+ params.question,
475
+ "",
476
+ "## Consultation Phase",
477
+ params.phase ?? "other",
478
+ "",
479
+ ...(params.context ? ["## Extra Context", params.context, ""] : []),
480
+ "## Conversation Transcript",
481
+ conversationText,
482
+ ].join("\n");
483
+ }
484
+
485
+ function extractText(content: Array<{ type: string; text?: string }>): string {
486
+ return content
487
+ .filter((part): part is { type: "text"; text: string } => part.type === "text" && typeof part.text === "string")
488
+ .map((part) => part.text)
489
+ .join("\n")
490
+ .trim();
491
+ }
492
+
493
+ function advisorDisabledResult(config: AdvisorConfig, useCount: number, params: AdvisorToolInput) {
494
+ return {
495
+ content: [
496
+ {
497
+ type: "text" as const,
498
+ text: `advisor-pi is disabled. The executor should continue without advisor guidance for: ${params.question}`,
499
+ },
500
+ ],
501
+ details: makeSkippedDetails(config, useCount, params),
502
+ };
503
+ }
504
+
505
+ function advisorLimitResult(config: AdvisorConfig, useCount: number, params: AdvisorToolInput) {
506
+ return {
507
+ content: [
508
+ {
509
+ type: "text" as const,
510
+ text: `Advisor use limit reached. The executor should continue without another advisor call for: ${params.question}`,
511
+ },
512
+ ],
513
+ details: makeSkippedDetails(config, useCount, params),
514
+ };
515
+ }
516
+
517
+ function makeSkippedDetails(config: AdvisorConfig, useCount: number, params: AdvisorToolInput): AdvisorToolDetails {
518
+ return {
519
+ advisor: {
520
+ provider: config.provider,
521
+ model: config.modelId,
522
+ phase: params.phase ?? "other",
523
+ useCount,
524
+ maxUses: config.maxUses,
525
+ cacheRetention: config.cacheRetention,
526
+ elapsedMs: 0,
527
+ stopReason: "skipped",
528
+ },
529
+ state: makeStateEntry(config, useCount),
530
+ };
531
+ }
532
+
533
+ function makeStateEntry(config: AdvisorConfig, useCount: number): AdvisorStateEntry {
534
+ return {
535
+ version: 1,
536
+ config: { ...config },
537
+ useCount,
538
+ updatedAt: new Date().toISOString(),
539
+ };
540
+ }
541
+
542
+ function persistState(pi: ExtensionAPI, config: AdvisorConfig, useCount: number): void {
543
+ pi.appendEntry(STATE_ENTRY, makeStateEntry(config, useCount));
544
+ }
545
+
546
+ function normalizeConfig(input: Partial<AdvisorConfig>, fallback: AdvisorConfig): AdvisorConfig {
547
+ return {
548
+ enabled: typeof input.enabled === "boolean" ? input.enabled : fallback.enabled,
549
+ provider: typeof input.provider === "string" && input.provider ? input.provider : fallback.provider,
550
+ modelId: typeof input.modelId === "string" && input.modelId ? input.modelId : fallback.modelId,
551
+ maxUses: typeof input.maxUses === "number" && input.maxUses > 0 ? Math.floor(input.maxUses) : fallback.maxUses,
552
+ cacheRetention: parseCacheRetention(input.cacheRetention) ?? fallback.cacheRetention,
553
+ maxTokens: typeof input.maxTokens === "number" && input.maxTokens > 0 ? Math.floor(input.maxTokens) : fallback.maxTokens,
554
+ timeoutMs: typeof input.timeoutMs === "number" && input.timeoutMs > 0 ? Math.floor(input.timeoutMs) : fallback.timeoutMs,
555
+ };
556
+ }
557
+
558
+ function parseModelSpec(value: string): { provider: string; modelId: string } | undefined {
559
+ const slash = value.indexOf("/");
560
+ if (slash <= 0 || slash === value.length - 1) return undefined;
561
+ return {
562
+ provider: value.slice(0, slash),
563
+ modelId: value.slice(slash + 1),
564
+ };
565
+ }
566
+
567
+ function parsePositiveInt(value: string): number | undefined {
568
+ const parsed = Number.parseInt(value, 10);
569
+ if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
570
+ return parsed;
571
+ }
572
+
573
+ function parseCacheRetention(value: unknown): CacheRetention | undefined {
574
+ if (value === "none" || value === "short" || value === "long") return value;
575
+ return undefined;
576
+ }
577
+