@madgagarin/pi-agentrouter 1.1.1 → 1.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/README.md +3 -0
- package/index.ts +158 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,6 +120,9 @@ AgentRouter performs client fingerprint verification. Raw summarization requests
|
|
|
120
120
|
#### Q: Does the 2.5s pacing delay affect local or other cloud models?
|
|
121
121
|
No. The pacing logic specifically filters for AgentRouter endpoints (`isAgentRouter`). Native OpenAI, Anthropic, Gemini, or local models run at full speed without delay.
|
|
122
122
|
|
|
123
|
+
#### Q: How to use custom subagents with AgentRouter?
|
|
124
|
+
AgentRouter strictly verifies client authenticity (`pi-code` / `claude-code` prompt signature). If you define custom subagents in extensions like `pi-subagents`, make sure to specify `systemPromptMode: append` in your agent definition frontmatter so the base Pi system prompt identity is preserved.
|
|
125
|
+
|
|
123
126
|
---
|
|
124
127
|
|
|
125
128
|
## 📄 License
|
package/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as fs from "fs";
|
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
|
|
6
6
|
const CONFIG_FILE = path.join(process.env.HOME || "", ".pi/agent/agentrouter.json");
|
|
7
|
+
const SETTINGS_FILE = path.join(process.env.HOME || "", ".pi/agent/settings.json");
|
|
7
8
|
|
|
8
9
|
export interface AgentRouterConfig {
|
|
9
10
|
apiKey?: string;
|
|
@@ -34,10 +35,82 @@ export function normalizeApiKey(key?: string): string {
|
|
|
34
35
|
return key.trim().replace(/^["']|["']$/g, "").trim();
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
export interface PackageOrderState {
|
|
39
|
+
agentRouterIndex: number;
|
|
40
|
+
cacheOptimizerIndex: number;
|
|
41
|
+
needsFix: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getPackageOrderState(): PackageOrderState {
|
|
45
|
+
try {
|
|
46
|
+
if (!fs.existsSync(SETTINGS_FILE)) {
|
|
47
|
+
return { agentRouterIndex: -1, cacheOptimizerIndex: -1, needsFix: false };
|
|
48
|
+
}
|
|
49
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf-8"));
|
|
50
|
+
if (!Array.isArray(settings.packages)) {
|
|
51
|
+
return { agentRouterIndex: -1, cacheOptimizerIndex: -1, needsFix: false };
|
|
52
|
+
}
|
|
53
|
+
const arIdx = settings.packages.findIndex((p: string) =>
|
|
54
|
+
typeof p === "string" && (p.includes("@madgagarin/pi-agentrouter") || p.includes("pi-agentrouter"))
|
|
55
|
+
);
|
|
56
|
+
const cacheIdx = settings.packages.findIndex((p: string) =>
|
|
57
|
+
typeof p === "string" && p.includes("pi-cache-optimizer")
|
|
58
|
+
);
|
|
59
|
+
const needsFix = cacheIdx !== -1 && arIdx !== -1 && arIdx > cacheIdx;
|
|
60
|
+
return { agentRouterIndex: arIdx, cacheOptimizerIndex: cacheIdx, needsFix };
|
|
61
|
+
} catch {
|
|
62
|
+
return { agentRouterIndex: -1, cacheOptimizerIndex: -1, needsFix: false };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function fixPackagePriorityInSettings(): boolean {
|
|
67
|
+
try {
|
|
68
|
+
if (!fs.existsSync(SETTINGS_FILE)) return false;
|
|
69
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf-8"));
|
|
70
|
+
if (!Array.isArray(settings.packages)) return false;
|
|
71
|
+
const arIdx = settings.packages.findIndex((p: string) =>
|
|
72
|
+
typeof p === "string" && (p.includes("@madgagarin/pi-agentrouter") || p.includes("pi-agentrouter"))
|
|
73
|
+
);
|
|
74
|
+
const cacheIdx = settings.packages.findIndex((p: string) =>
|
|
75
|
+
typeof p === "string" && p.includes("pi-cache-optimizer")
|
|
76
|
+
);
|
|
77
|
+
if (cacheIdx !== -1 && arIdx !== -1 && arIdx > cacheIdx) {
|
|
78
|
+
const pkg = settings.packages.splice(arIdx, 1)[0];
|
|
79
|
+
const targetCacheIdx = settings.packages.findIndex((p: string) =>
|
|
80
|
+
typeof p === "string" && p.includes("pi-cache-optimizer")
|
|
81
|
+
);
|
|
82
|
+
settings.packages.splice(targetCacheIdx, 0, pkg);
|
|
83
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), "utf-8");
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
} catch {}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
37
90
|
const initialConfig = loadConfig();
|
|
38
91
|
let currentApiKey = normalizeApiKey(process.env.AGENTROUTER_API_KEY || initialConfig.apiKey || "");
|
|
39
92
|
let minIntervalMs = initialConfig.minIntervalMs ?? 2500;
|
|
40
|
-
|
|
93
|
+
const PACING_FILE = path.join(process.env.HOME || "", ".pi/agent/.agentrouter-pacing");
|
|
94
|
+
|
|
95
|
+
export function getLastRequestEndTime(): number {
|
|
96
|
+
try {
|
|
97
|
+
if (fs.existsSync(PACING_FILE)) {
|
|
98
|
+
const val = parseInt(fs.readFileSync(PACING_FILE, "utf-8").trim(), 10);
|
|
99
|
+
if (!isNaN(val)) return val;
|
|
100
|
+
}
|
|
101
|
+
} catch {}
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function setLastRequestEndTime(ts: number): void {
|
|
106
|
+
try {
|
|
107
|
+
const dir = path.dirname(PACING_FILE);
|
|
108
|
+
if (!fs.existsSync(dir)) {
|
|
109
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
fs.writeFileSync(PACING_FILE, String(ts), "utf-8");
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
41
114
|
|
|
42
115
|
export function isAgentRouter(providerName?: string, baseUrl?: string): boolean {
|
|
43
116
|
if (providerName && providerName.toLowerCase().includes("agentrouter")) return true;
|
|
@@ -76,8 +149,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
76
149
|
baseUrl: "https://agentrouter.org",
|
|
77
150
|
apiKey,
|
|
78
151
|
api: "anthropic-messages",
|
|
152
|
+
headers: {
|
|
153
|
+
"User-Agent": "claude-cli/1.0.108 (external, cli)",
|
|
154
|
+
"anthropic-beta": "claude-code-20250219,interleaved-thinking-2025-05-14,context-management-2025-06-27",
|
|
155
|
+
"x-stainless-lang": "js",
|
|
156
|
+
"x-stainless-package-version": "0.38.0",
|
|
157
|
+
"x-stainless-os": "Linux",
|
|
158
|
+
"x-stainless-arch": "x64",
|
|
159
|
+
"x-stainless-runtime": "node"
|
|
160
|
+
},
|
|
79
161
|
compat: {
|
|
80
162
|
forceAdaptiveThinking: true,
|
|
163
|
+
sendSessionAffinityHeaders: true,
|
|
81
164
|
},
|
|
82
165
|
models: [
|
|
83
166
|
{
|
|
@@ -90,6 +173,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
90
173
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
91
174
|
compat: {
|
|
92
175
|
forceAdaptiveThinking: true,
|
|
176
|
+
sendSessionAffinityHeaders: true,
|
|
93
177
|
},
|
|
94
178
|
},
|
|
95
179
|
{
|
|
@@ -102,6 +186,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
102
186
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
103
187
|
compat: {
|
|
104
188
|
forceAdaptiveThinking: true,
|
|
189
|
+
sendSessionAffinityHeaders: true,
|
|
105
190
|
},
|
|
106
191
|
},
|
|
107
192
|
],
|
|
@@ -120,7 +205,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
120
205
|
|
|
121
206
|
pi.on("session_start", async (_event, ctx) => {
|
|
122
207
|
updatePromptRewriteEnvForModel(ctx.model);
|
|
123
|
-
|
|
208
|
+
setLastRequestEndTime(Date.now());
|
|
209
|
+
|
|
210
|
+
const order = getPackageOrderState();
|
|
211
|
+
if (order.needsFix && ctx.hasUI && typeof (ctx.ui as any).confirm === "function") {
|
|
212
|
+
try {
|
|
213
|
+
const confirmed = await (ctx.ui as any).confirm(
|
|
214
|
+
"Pi AgentRouter Package Priority",
|
|
215
|
+
"@madgagarin/pi-agentrouter is listed AFTER pi-cache-optimizer in settings.json packages.\n\n" +
|
|
216
|
+
"It must be placed before pi-cache-optimizer so prompt cache bypass takes effect before cache-optimizer transforms the prompt.\n\n" +
|
|
217
|
+
"Move @madgagarin/pi-agentrouter directly above pi-cache-optimizer in settings.json?"
|
|
218
|
+
);
|
|
219
|
+
if (confirmed) {
|
|
220
|
+
const success = fixPackagePriorityInSettings();
|
|
221
|
+
if (success) {
|
|
222
|
+
ctx.ui.notify("@madgagarin/pi-agentrouter moved above pi-cache-optimizer in settings.json. Please restart Pi for changes to take full effect.", "info");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch {}
|
|
226
|
+
}
|
|
124
227
|
});
|
|
125
228
|
|
|
126
229
|
pi.on("model_select", async (event) => {
|
|
@@ -138,15 +241,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
138
241
|
return;
|
|
139
242
|
}
|
|
140
243
|
|
|
244
|
+
const lastEnd = getLastRequestEndTime();
|
|
141
245
|
const now = Date.now();
|
|
142
|
-
const elapsed = now -
|
|
246
|
+
const elapsed = now - lastEnd;
|
|
143
247
|
|
|
144
|
-
if (
|
|
248
|
+
if (lastEnd > 0 && elapsed < minIntervalMs) {
|
|
145
249
|
const waitMs = minIntervalMs - elapsed;
|
|
146
250
|
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
147
251
|
}
|
|
252
|
+
});
|
|
148
253
|
|
|
149
|
-
|
|
254
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
255
|
+
const model = ctx.model;
|
|
256
|
+
if (isAgentRouter(model?.provider, (model as any)?.baseUrl)) {
|
|
257
|
+
setLastRequestEndTime(Date.now());
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
262
|
+
const model = ctx.model;
|
|
263
|
+
if (isAgentRouter(model?.provider, (model as any)?.baseUrl)) {
|
|
264
|
+
setLastRequestEndTime(Date.now());
|
|
265
|
+
}
|
|
150
266
|
});
|
|
151
267
|
|
|
152
268
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
@@ -193,6 +309,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
193
309
|
signal,
|
|
194
310
|
}
|
|
195
311
|
);
|
|
312
|
+
setLastRequestEndTime(Date.now());
|
|
196
313
|
|
|
197
314
|
const summary = response.content
|
|
198
315
|
.filter((c: any): c is { type: "text"; text: string } => c.type === "text")
|
|
@@ -241,6 +358,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
241
358
|
return;
|
|
242
359
|
}
|
|
243
360
|
|
|
361
|
+
if (action === "fix-order" || action === "order") {
|
|
362
|
+
const order = getPackageOrderState();
|
|
363
|
+
if (!order.needsFix) {
|
|
364
|
+
if (order.cacheOptimizerIndex === -1) {
|
|
365
|
+
ctx.ui.notify("pi-cache-optimizer is not installed in settings.json. Package order is optimal.", "info");
|
|
366
|
+
} else if (order.agentRouterIndex !== -1 && order.agentRouterIndex < order.cacheOptimizerIndex) {
|
|
367
|
+
ctx.ui.notify("@madgagarin/pi-agentrouter is already placed before pi-cache-optimizer (Optimal).", "info");
|
|
368
|
+
} else {
|
|
369
|
+
ctx.ui.notify("@madgagarin/pi-agentrouter was not found in settings.json packages.", "warning");
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const success = fixPackagePriorityInSettings();
|
|
374
|
+
if (success) {
|
|
375
|
+
ctx.ui.notify("@madgagarin/pi-agentrouter moved directly above pi-cache-optimizer in settings.json. Please restart Pi for changes to take full effect.", "info");
|
|
376
|
+
} else {
|
|
377
|
+
ctx.ui.notify("Failed to update settings.json.", "error");
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
244
382
|
if (action === "pacing" || action === "throttle") {
|
|
245
383
|
const val = parseInt(parts[1], 10);
|
|
246
384
|
if (isNaN(val) || val < 0) {
|
|
@@ -256,16 +394,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
256
394
|
const maskedKey = currentApiKey.length > 8 ? `${currentApiKey.slice(0, 7)}...${currentApiKey.slice(-4)}` : "not set";
|
|
257
395
|
const activeModel = ctx.model;
|
|
258
396
|
const isAR = isAgentRouter(activeModel?.provider, (activeModel as any)?.baseUrl);
|
|
397
|
+
const order = getPackageOrderState();
|
|
398
|
+
let priorityStatus = "Optimal";
|
|
399
|
+
if (order.needsFix) {
|
|
400
|
+
priorityStatus = "Listed AFTER pi-cache-optimizer (Run: /agentrouter fix-order)";
|
|
401
|
+
} else if (order.cacheOptimizerIndex !== -1 && order.agentRouterIndex < order.cacheOptimizerIndex) {
|
|
402
|
+
priorityStatus = "Before pi-cache-optimizer (Optimal)";
|
|
403
|
+
} else if (order.agentRouterIndex !== -1) {
|
|
404
|
+
priorityStatus = "Active (Optimal)";
|
|
405
|
+
} else {
|
|
406
|
+
priorityStatus = "Not in packages";
|
|
407
|
+
}
|
|
259
408
|
|
|
260
409
|
ctx.ui.notify(
|
|
261
410
|
`[AgentRouter Plugin]\n` +
|
|
262
411
|
`- Active model: ${activeModel?.id || "none"} (${isAR ? "AgentRouter [yes]" : "Other Provider"})\n` +
|
|
412
|
+
`- Package Priority: ${priorityStatus}\n` +
|
|
263
413
|
`- API Key: ${maskedKey}\n` +
|
|
264
414
|
`- Caching: Enabled (Prompt Cache + Session Affinity + Adaptive Thinking)\n` +
|
|
265
|
-
`- Pacing Interval: ${minIntervalMs} ms\n` +
|
|
415
|
+
`- Pacing Interval: ${minIntervalMs} ms (Measured from turn completion)\n` +
|
|
266
416
|
`- Commands:\n` +
|
|
267
417
|
` /agentrouter key <key> (update API key)\n` +
|
|
268
|
-
` /agentrouter pacing <ms> (set
|
|
418
|
+
` /agentrouter pacing <ms> (set request delay after completion)\n` +
|
|
419
|
+
` /agentrouter fix-order (move plugin above pi-cache-optimizer in settings.json)`,
|
|
269
420
|
"info"
|
|
270
421
|
);
|
|
271
422
|
},
|
package/package.json
CHANGED