@madgagarin/pi-agentrouter 1.2.1 β 1.4.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 +22 -12
- package/index.ts +123 -24
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@madgagarin/pi-agentrouter)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://pi.dev)
|
|
6
|
+
**`pi-agentrouter`** seamlessly connects 4 powerful flagship AI coding models (**GPT-5.6 Sol**, **Claude Opus 4.8**, **Claude Opus 5**, and **DeepSeek V4F**) to your [Pi Coding Agent](https://pi.dev) using a single, unified API key from [AgentRouter](https://agentrouter.org).
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
### π‘ Why use this plugin?
|
|
9
|
+
* π **All 4 Top Models in One Place:** Instantly switch between GPT-5.6 Sol, Claude Opus 4.8, Claude Opus 5, and DeepSeek V4F without managing separate subscriptions or multiple API keys.
|
|
10
|
+
* π° **Save up to 80% on Tokens:** Smart prompt caching and session affinity drastically cut token usage and speed up responses.
|
|
11
|
+
* β‘ **Zero-Config Setup:** Installs in seconds β all models are automatically registered with native reasoning, tool calling, and thinking support.
|
|
12
|
+
* π‘οΈ **Built-in Stability:** Automatic cross-process pacing and authentication guards eliminate 401 and 429 errors during long coding sessions and multi-agent tasks.
|
|
8
13
|
|
|
9
14
|
---
|
|
10
15
|
|
|
@@ -12,7 +17,7 @@ A native, zero-config extension for the [Pi Coding Agent](https://pi.dev) that c
|
|
|
12
17
|
|
|
13
18
|
AgentRouter is a non-profit AI API gateway providing unified access to cutting-edge models:
|
|
14
19
|
|
|
15
|
-
* **Increased Trial Credits:** By signing up through the referral link below, you unlock **increased trial credits (up to $175 / extra +$50 bonus)** to explore GPT-5.6 Sol
|
|
20
|
+
* **Increased Trial Credits:** By signing up through the referral link below, you unlock **increased trial credits (up to $175 / extra +$50 bonus)** to explore GPT-5.6 Sol, Claude Opus, and DeepSeek models.
|
|
16
21
|
* **Support the Author:** Using this link directly supports the author and the ongoing maintenance of this open-source plugin!
|
|
17
22
|
|
|
18
23
|
π **[Sign up on AgentRouter (Referral Link with Bonus)](https://agentrouter.org/register?aff=34dc)** π
|
|
@@ -23,15 +28,18 @@ AgentRouter is a non-profit AI API gateway providing unified access to cutting-e
|
|
|
23
28
|
|
|
24
29
|
## β¨ Features
|
|
25
30
|
|
|
26
|
-
- π **Unified API Key**: Use a single `sk-...` (or custom token) for all modelsβboth OpenAI GPT and Anthropic Claude endpoints are authenticated seamlessly.
|
|
31
|
+
- π **Unified API Key**: Use a single `sk-...` (or custom token) for all modelsβboth OpenAI GPT, DeepSeek, and Anthropic Claude endpoints are authenticated seamlessly.
|
|
27
32
|
- π **Zero-Config Model Auto-Registration**: Automatically registers:
|
|
28
33
|
- `agentrouter-openai/gpt-5.6-sol` (1M Context Window, Native Reasoning, Session Affinity).
|
|
29
|
-
- `agentrouter-
|
|
30
|
-
- `agentrouter-clode/claude-opus-
|
|
31
|
-
-
|
|
32
|
-
- π‘οΈ **
|
|
34
|
+
- `agentrouter-openai/deepseek-v4f` (128K Context Window, Reasoning Support, Session Affinity).
|
|
35
|
+
- `agentrouter-clode/claude-opus-4-8` (512K Context Window, Adaptive Thinking, Empty Signature Compat).
|
|
36
|
+
- `agentrouter-clode/claude-opus-5` (1M Context Window, Adaptive Thinking, Empty Signature Compat).
|
|
37
|
+
- π‘οΈ **Cross-Process Request Pacing**: Shared file-based rate limiter (`~/.pi/agent/.agentrouter-pacing`) synchronizing delays across concurrent subagents, background workers, and the main Pi process to prevent WAF burst blocks.
|
|
38
|
+
- π **Transport-Level Root Prompt Guard**: Enforces canonical `pi-code` harness header at `index: 0` on every outbound request (`before_provider_request`), preventing 401 unauthorized client errors across multi-turn tool executions.
|
|
39
|
+
- β‘ **Optimized Prompt Caching & Dynamic Bypass**: Automatically disables aggressive prompt rewriting on AgentRouter routes (`PI_CACHE_OPTIMIZER_NO_PROMPT_REWRITE=1`) while preserving session affinity headers, achieving **>80% cache hit rates** on consecutive turns.
|
|
40
|
+
- π **Package Priority Guard & Auto-Fix**: Automatically checks package order in `settings.json` on startup and offers interactive one-click placement directly above `pi-cache-optimizer`.
|
|
33
41
|
- π¦ **Seamless Compaction (Fixes 401)**: Solves the `401 unauthorized client` error during `/compact` by ensuring valid Pi client fingerprint headers are passed during summarization.
|
|
34
|
-
- β¨οΈ **Interactive Terminal Controls**: Change API keys, adjust throttle pacing, and cycle through models or thinking depths directly in the TUI.
|
|
42
|
+
- β¨οΈ **Interactive Terminal Controls**: Change API keys, adjust throttle pacing, fix package priority, and cycle through models or thinking depths directly in the TUI.
|
|
35
43
|
|
|
36
44
|
---
|
|
37
45
|
|
|
@@ -83,6 +91,7 @@ To enable quick model cycling with `Ctrl+P` and set default thinking levels, add
|
|
|
83
91
|
"defaultThinkingLevel": "medium",
|
|
84
92
|
"enabledModels": [
|
|
85
93
|
"agentrouter-openai/gpt-5.6-sol",
|
|
94
|
+
"agentrouter-openai/deepseek-v4f",
|
|
86
95
|
"agentrouter-clode/claude-opus-4-8",
|
|
87
96
|
"agentrouter-clode/claude-opus-5"
|
|
88
97
|
]
|
|
@@ -95,7 +104,7 @@ To enable quick model cycling with `Ctrl+P` and set default thinking levels, add
|
|
|
95
104
|
|
|
96
105
|
| Shortcut | Action | Description |
|
|
97
106
|
| :--- | :--- | :--- |
|
|
98
|
-
| `Ctrl + P` | π **Next Model** | Cycles to the next model (`gpt-5.6-sol` β `claude-opus-4-8` β `claude-opus-5`). |
|
|
107
|
+
| `Ctrl + P` | π **Next Model** | Cycles to the next model (`gpt-5.6-sol` β `deepseek-v4f` β `claude-opus-4-8` β `claude-opus-5`). |
|
|
99
108
|
| `Shift + Ctrl + P` | π **Previous Model** | Cycles to the previous model. |
|
|
100
109
|
| `Shift + Tab` | π§ **Cycle Thinking Level** | Toggles reasoning depth: `off` β `minimal` β `low` β `medium` β `high`. |
|
|
101
110
|
| `Ctrl + T` | π **Toggle Thinking Visibility** | Collapses or expands thinking/reasoning blocks on screen. |
|
|
@@ -105,9 +114,10 @@ To enable quick model cycling with `Ctrl+P` and set default thinking levels, add
|
|
|
105
114
|
|
|
106
115
|
## π οΈ In-Chat Commands
|
|
107
116
|
|
|
108
|
-
* `/agentrouter` β View current plugin status, active model, masked key, and pacing interval.
|
|
117
|
+
* `/agentrouter` β View current plugin status, active model, priority position, masked key, and pacing interval.
|
|
109
118
|
* `/agentrouter key <your-key>` β Update API key for all AgentRouter models on the fly.
|
|
110
|
-
* `/agentrouter pacing <ms>` β Adjust the minimum delay between consecutive requests (default: `
|
|
119
|
+
* `/agentrouter pacing <ms>` β Adjust the minimum delay between consecutive requests (default: `3500` ms).
|
|
120
|
+
* `/agentrouter fix-order` β Automatically reorder `settings.json` packages to place this plugin directly before `pi-cache-optimizer`.
|
|
111
121
|
* `/compact` β Compress conversation history safely without 401 authorization errors.
|
|
112
122
|
|
|
113
123
|
---
|
|
@@ -117,7 +127,7 @@ To enable quick model cycling with `Ctrl+P` and set default thinking levels, add
|
|
|
117
127
|
#### Q: Why does `/compact` fail on raw proxy configurations?
|
|
118
128
|
AgentRouter performs client fingerprint verification. Raw summarization requests without Pi's standard prompt signatures get rejected with `401 unauthorized client`. This plugin intercepts the `session_before_compact` event and automatically injects proper authentication signatures.
|
|
119
129
|
|
|
120
|
-
#### Q: Does the
|
|
130
|
+
#### Q: Does the 3.5s pacing delay affect local or other cloud models?
|
|
121
131
|
No. The pacing logic specifically filters for AgentRouter endpoints (`isAgentRouter`). Native OpenAI, Anthropic, Gemini, or local models run at full speed without delay.
|
|
122
132
|
|
|
123
133
|
#### Q: How to use custom subagents with AgentRouter?
|
package/index.ts
CHANGED
|
@@ -89,7 +89,7 @@ export function fixPackagePriorityInSettings(): boolean {
|
|
|
89
89
|
|
|
90
90
|
const initialConfig = loadConfig();
|
|
91
91
|
let currentApiKey = normalizeApiKey(process.env.AGENTROUTER_API_KEY || initialConfig.apiKey || "");
|
|
92
|
-
let minIntervalMs = initialConfig.minIntervalMs ??
|
|
92
|
+
let minIntervalMs = initialConfig.minIntervalMs ?? 3500;
|
|
93
93
|
const PACING_FILE = path.join(process.env.HOME || "", ".pi/agent/.agentrouter-pacing");
|
|
94
94
|
|
|
95
95
|
export function getLastRequestEndTime(): number {
|
|
@@ -118,6 +118,53 @@ export function isAgentRouter(providerName?: string, baseUrl?: string): boolean
|
|
|
118
118
|
return false;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
export const CANONICAL_PI_HEADER =
|
|
122
|
+
"You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.";
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Enforces that the canonical pi-code system prompt signature is strictly at index 0.
|
|
126
|
+
* If another plugin or wrapper prepended text before the canonical header, it reorders
|
|
127
|
+
* the header to the very top and shifts the injected prefix right after it.
|
|
128
|
+
* If the header is missing entirely (e.g. from replace mode), it prepends the canonical header.
|
|
129
|
+
* This guarantees both WAF client authentication and stable prompt cache prefix matching.
|
|
130
|
+
*/
|
|
131
|
+
export function enforceCanonicalRootPrompt(systemPrompt: string | any[] | undefined): string | any[] {
|
|
132
|
+
if (!systemPrompt) {
|
|
133
|
+
return CANONICAL_PI_HEADER;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof systemPrompt === "string") {
|
|
137
|
+
const text = systemPrompt.trim();
|
|
138
|
+
const piHeaderRegex = /(?:You are [^\n\r]*operating inside pi[^\n\r]*\n?|You are (?:pi|Pi)[^\n\r]*\n?)/i;
|
|
139
|
+
|
|
140
|
+
if (piHeaderRegex.test(text)) {
|
|
141
|
+
const match = text.match(piHeaderRegex);
|
|
142
|
+
if (match && match.index !== undefined && match.index > 0) {
|
|
143
|
+
const header = match[0].trim();
|
|
144
|
+
const prefix = text.slice(0, match.index).trim();
|
|
145
|
+
const rest = text.slice(match.index + match[0].length).trim();
|
|
146
|
+
return `${header}\n\n${prefix}${rest ? "\n\n" + rest : ""}`;
|
|
147
|
+
}
|
|
148
|
+
return text;
|
|
149
|
+
} else {
|
|
150
|
+
return `${CANONICAL_PI_HEADER}\n\n${text}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (Array.isArray(systemPrompt)) {
|
|
155
|
+
if (systemPrompt.length === 0) {
|
|
156
|
+
return [{ type: "text", text: CANONICAL_PI_HEADER }];
|
|
157
|
+
}
|
|
158
|
+
const firstBlock = systemPrompt[0];
|
|
159
|
+
if (firstBlock && typeof firstBlock.text === "string") {
|
|
160
|
+
firstBlock.text = enforceCanonicalRootPrompt(firstBlock.text) as string;
|
|
161
|
+
}
|
|
162
|
+
return systemPrompt;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return systemPrompt;
|
|
166
|
+
}
|
|
167
|
+
|
|
121
168
|
export default function (pi: ExtensionAPI) {
|
|
122
169
|
function registerAgentRouterProviders(apiKey: string): void {
|
|
123
170
|
pi.registerProvider("agentrouter-openai", {
|
|
@@ -141,6 +188,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
141
188
|
sendSessionAffinityHeaders: true,
|
|
142
189
|
},
|
|
143
190
|
},
|
|
191
|
+
{
|
|
192
|
+
id: "deepseek-v4f",
|
|
193
|
+
name: "deepseek-v4f",
|
|
194
|
+
reasoning: true,
|
|
195
|
+
input: ["text"],
|
|
196
|
+
contextWindow: 131072,
|
|
197
|
+
maxTokens: 65536,
|
|
198
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
199
|
+
compat: {
|
|
200
|
+
sendSessionAffinityHeaders: true,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
144
203
|
],
|
|
145
204
|
});
|
|
146
205
|
|
|
@@ -149,18 +208,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
149
208
|
baseUrl: "https://agentrouter.org",
|
|
150
209
|
apiKey,
|
|
151
210
|
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
|
-
},
|
|
161
211
|
compat: {
|
|
162
212
|
forceAdaptiveThinking: true,
|
|
213
|
+
allowEmptySignature: true,
|
|
163
214
|
sendSessionAffinityHeaders: true,
|
|
215
|
+
supportsEagerToolInputStreaming: false,
|
|
164
216
|
},
|
|
165
217
|
models: [
|
|
166
218
|
{
|
|
@@ -173,7 +225,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
173
225
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
174
226
|
compat: {
|
|
175
227
|
forceAdaptiveThinking: true,
|
|
228
|
+
allowEmptySignature: true,
|
|
176
229
|
sendSessionAffinityHeaders: true,
|
|
230
|
+
supportsEagerToolInputStreaming: false,
|
|
177
231
|
},
|
|
178
232
|
},
|
|
179
233
|
{
|
|
@@ -186,7 +240,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
186
240
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
187
241
|
compat: {
|
|
188
242
|
forceAdaptiveThinking: true,
|
|
243
|
+
allowEmptySignature: true,
|
|
189
244
|
sendSessionAffinityHeaders: true,
|
|
245
|
+
supportsEagerToolInputStreaming: false,
|
|
190
246
|
},
|
|
191
247
|
},
|
|
192
248
|
],
|
|
@@ -203,6 +259,63 @@ export default function (pi: ExtensionAPI) {
|
|
|
203
259
|
}
|
|
204
260
|
}
|
|
205
261
|
|
|
262
|
+
// Inter-request pacing to respect AgentRouter WAF rate limits and enforce canonical root prompt
|
|
263
|
+
pi.on("before_provider_request", async (event, ctx) => {
|
|
264
|
+
const provider = ((event as any)?.model?.provider || ctx?.model?.provider || "").toLowerCase();
|
|
265
|
+
const baseUrl = ((event as any)?.model?.baseUrl || (ctx?.model as any)?.baseUrl || "");
|
|
266
|
+
|
|
267
|
+
if (isAgentRouter(provider, baseUrl)) {
|
|
268
|
+
// 1. Cross-process request pacing
|
|
269
|
+
const lastEnd = getLastRequestEndTime();
|
|
270
|
+
const now = Date.now();
|
|
271
|
+
const elapsed = now - lastEnd;
|
|
272
|
+
if (lastEnd > 0 && elapsed < minIntervalMs) {
|
|
273
|
+
const waitMs = minIntervalMs - elapsed;
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 2. Enforce canonical root system prompt in payload (Anthropic & OpenAI formats)
|
|
278
|
+
const payload = event.payload;
|
|
279
|
+
if (payload) {
|
|
280
|
+
if (payload.system !== undefined) {
|
|
281
|
+
payload.system = enforceCanonicalRootPrompt(payload.system);
|
|
282
|
+
}
|
|
283
|
+
if (Array.isArray(payload.messages) && payload.messages.length > 0) {
|
|
284
|
+
const firstMsg = payload.messages[0];
|
|
285
|
+
if (firstMsg && firstMsg.role === "system") {
|
|
286
|
+
if (typeof firstMsg.content === "string") {
|
|
287
|
+
firstMsg.content = enforceCanonicalRootPrompt(firstMsg.content);
|
|
288
|
+
} else if (Array.isArray(firstMsg.content)) {
|
|
289
|
+
firstMsg.content = enforceCanonicalRootPrompt(firstMsg.content);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
pi.on("message_end", async (_event, ctx) => {
|
|
299
|
+
const model = ctx?.model;
|
|
300
|
+
if (isAgentRouter(model?.provider, (model as any)?.baseUrl)) {
|
|
301
|
+
setLastRequestEndTime(Date.now());
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
306
|
+
const model = ctx?.model;
|
|
307
|
+
if (isAgentRouter(model?.provider, (model as any)?.baseUrl)) {
|
|
308
|
+
setLastRequestEndTime(Date.now());
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
313
|
+
const model = ctx?.model;
|
|
314
|
+
if (isAgentRouter(model?.provider, (model as any)?.baseUrl)) {
|
|
315
|
+
setLastRequestEndTime(Date.now());
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
206
319
|
pi.on("session_start", async (_event, ctx) => {
|
|
207
320
|
updatePromptRewriteEnvForModel(ctx.model);
|
|
208
321
|
setLastRequestEndTime(Date.now());
|
|
@@ -251,20 +364,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
251
364
|
}
|
|
252
365
|
});
|
|
253
366
|
|
|
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
|
-
}
|
|
266
|
-
});
|
|
267
|
-
|
|
268
367
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
269
368
|
const model = ctx.model;
|
|
270
369
|
if (!model || !isAgentRouter(model.provider, (model as any).baseUrl)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madgagarin/pi-agentrouter",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Pi coding agent extension for AgentRouter (GPT-5.6 Sol
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Pi coding agent extension for AgentRouter (GPT-5.6 Sol, Claude Opus 5, DeepSeek V4F) with rate-limit pacing, prompt caching, and WAF protection.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"gpt-5.6-sol",
|
|
18
18
|
"claude-opus-5",
|
|
19
19
|
"claude-opus-4-8",
|
|
20
|
+
"deepseek-v4f",
|
|
21
|
+
"deepseek",
|
|
20
22
|
"prompt-caching",
|
|
21
23
|
"openai",
|
|
22
24
|
"claude",
|