@monotykamary/pi-retry 0.6.1 → 0.6.3
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/package.json +11 -11
- package/retry.ts +40 -0
- package/src/error-patterns.ts +63 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monotykamary/pi-retry",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Extension suite for pi coding agent that handles 400/413 errors and connection errors with automatic retry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Tom X Nguyen",
|
|
@@ -32,16 +32,9 @@
|
|
|
32
32
|
"src/",
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
|
-
"scripts": {
|
|
36
|
-
"test": "vitest run",
|
|
37
|
-
"test:watch": "vitest",
|
|
38
|
-
"test:coverage": "vitest run --coverage",
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"lint:dead": "knip --no-gitignore"
|
|
41
|
-
},
|
|
42
35
|
"devDependencies": {
|
|
43
|
-
"@earendil-works/pi-agent-core": "^0.79.
|
|
44
|
-
"@earendil-works/pi-coding-agent": "0.79.
|
|
36
|
+
"@earendil-works/pi-agent-core": "^0.79.8",
|
|
37
|
+
"@earendil-works/pi-coding-agent": "^0.79.8",
|
|
45
38
|
"@types/node": "25.9.1",
|
|
46
39
|
"@vitest/coverage-v8": "4.1.7",
|
|
47
40
|
"knip": "6.14.1",
|
|
@@ -55,5 +48,12 @@
|
|
|
55
48
|
},
|
|
56
49
|
"overrides": {
|
|
57
50
|
"brace-expansion": "5.0.6"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"test:coverage": "vitest run --coverage",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"lint:dead": "knip --no-gitignore"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|
package/retry.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isNonRetryableError,
|
|
10
10
|
isSilencedError,
|
|
11
11
|
hasMaxTokensStop,
|
|
12
|
+
isContextOverflowError,
|
|
12
13
|
isAssistantMessage,
|
|
13
14
|
getLastAssistantMessage,
|
|
14
15
|
calculateDelay,
|
|
@@ -73,6 +74,10 @@ Agent.prototype.subscribe = function (this: Agent, ...args: any[]) {
|
|
|
73
74
|
// with our _continueInProgress mutex AND can convert the "Cannot continue
|
|
74
75
|
// from assistant" error into a prompt([]) call when the agent was mid-task
|
|
75
76
|
// (compaction, toolUse, length — but NOT error, which the loop handles).
|
|
77
|
+
//
|
|
78
|
+
// Note (pi 0.79+): Agent.continue() now drains queued steering/follow-up
|
|
79
|
+
// messages before throwing, so this throw path only fires when there are
|
|
80
|
+
// genuinely no queued messages — the prompt([]) fallback is still correct.
|
|
76
81
|
const _origContinue = Agent.prototype.continue as (this: Agent) => Promise<void>;
|
|
77
82
|
Agent.prototype.continue = function (this: Agent) {
|
|
78
83
|
const self = this;
|
|
@@ -286,6 +291,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
286
291
|
return;
|
|
287
292
|
}
|
|
288
293
|
|
|
294
|
+
// Context overflow: defer to compaction. Do NOT retry here.
|
|
295
|
+
//
|
|
296
|
+
// triggerInvisibleContinue() calls agent.prompt([]) directly on the core
|
|
297
|
+
// Agent, bypassing AgentSession._handlePostAgentRun → _checkCompaction, so
|
|
298
|
+
// a pi-retry retry loop gets NO compaction. Retrying an overflow would
|
|
299
|
+
// re-send the same oversized context → overflow again → infinite loop
|
|
300
|
+
// (pi-retry's error loop is uncapped). Meanwhile pi-retry's _continueInProgress
|
|
301
|
+
// mutex would block pi-core's own compaction-retry (agent.continue()), so
|
|
302
|
+
// pi-core never gets to compact either.
|
|
303
|
+
//
|
|
304
|
+
// Instead, return without firing triggerInvisibleContinue. pi-core's
|
|
305
|
+
// _checkCompaction (which runs in _handlePostAgentRun regardless of
|
|
306
|
+
// extensions) detects the same overflow, compacts (statically via pi-vcc
|
|
307
|
+
// when installed), and retries once via agent.continue() with the reduced
|
|
308
|
+
// context. _continueInProgress stays false, so pi-core's continue is
|
|
309
|
+
// unblocked.
|
|
310
|
+
if (isContextOverflowError(lastAssistant)) {
|
|
311
|
+
ctx.ui.notify(
|
|
312
|
+
"Context overflow — deferring to compaction (auto-retry after compact).",
|
|
313
|
+
"info",
|
|
314
|
+
);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
289
318
|
// Catch-all: retry ANY error except known permanent failures
|
|
290
319
|
if (hasRetryableError(lastAssistant)) {
|
|
291
320
|
const errorMsg = lastAssistant.errorMessage || "Unknown error";
|
|
@@ -425,6 +454,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
425
454
|
return;
|
|
426
455
|
}
|
|
427
456
|
|
|
457
|
+
// Context overflow: don't retry in place — reducing context is required.
|
|
458
|
+
// Compaction (pi-vcc / /compact) handles it and auto-retries. Retrying
|
|
459
|
+
// without compaction loops forever on a genuinely oversized payload.
|
|
460
|
+
if (isContextOverflowError(lastAssistant)) {
|
|
461
|
+
ctx.ui.notify(
|
|
462
|
+
"Context overflow — use /compact (or /pi-vcc) to reduce context. Compaction auto-retries.",
|
|
463
|
+
"info",
|
|
464
|
+
);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
428
468
|
// Auto-detect error type and trigger appropriate retry
|
|
429
469
|
if (has400or413Error(lastAssistant)) {
|
|
430
470
|
ctx.ui.notify("Manually retrying 400/413 error...", "info");
|
package/src/error-patterns.ts
CHANGED
|
@@ -64,6 +64,51 @@ const BUILTIN_HANDLED_PATTERNS = [
|
|
|
64
64
|
/retry\s*delay/i,
|
|
65
65
|
];
|
|
66
66
|
|
|
67
|
+
// Context-overflow error patterns. Mirrors pi-core's OVERFLOW_PATTERNS in
|
|
68
|
+
// @earendil-works/pi-ai/dist/utils/overflow.js so that pi-retry defers to
|
|
69
|
+
// compaction exactly when pi-core's _checkCompaction will detect overflow and
|
|
70
|
+
// compact + retry. kept in sync manually — pi-ai is not a direct dependency.
|
|
71
|
+
//
|
|
72
|
+
// Why these are NOT retried by pi-retry: retrying an overflow via prompt([])
|
|
73
|
+
// re-sends the same oversized context, so it overflows again → infinite loop
|
|
74
|
+
// (pi-retry's loop is uncapped for errors). pi-core instead compacts and
|
|
75
|
+
// retries once via agent.continue(); with static compaction (pi-vcc) that
|
|
76
|
+
// reliably reduces context, so the single retry succeeds.
|
|
77
|
+
const OVERFLOW_ERROR_PATTERNS = [
|
|
78
|
+
/prompt is too long/i,
|
|
79
|
+
/request_too_large/i,
|
|
80
|
+
/input is too long for requested model/i,
|
|
81
|
+
/exceeds the context window/i,
|
|
82
|
+
/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i,
|
|
83
|
+
/input token count.*exceeds the maximum/i,
|
|
84
|
+
/maximum prompt length is \d+/i,
|
|
85
|
+
/reduce the length of the messages/i,
|
|
86
|
+
/maximum context length is \d+ tokens/i,
|
|
87
|
+
/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i,
|
|
88
|
+
/input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i,
|
|
89
|
+
/exceeds the limit of \d+/i,
|
|
90
|
+
/exceeds the available context size/i,
|
|
91
|
+
/greater than the context length/i,
|
|
92
|
+
/context window exceeds limit/i,
|
|
93
|
+
/exceeded model token limit/i,
|
|
94
|
+
/too large for model with \d+ maximum context length/i,
|
|
95
|
+
/model_context_window_exceeded/i,
|
|
96
|
+
/prompt too long; exceeded (?:max )?context length/i,
|
|
97
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
98
|
+
/too many tokens/i,
|
|
99
|
+
/token limit exceeded/i,
|
|
100
|
+
/^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i,
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
// Patterns that look like overflow but are actually rate limiting / throttling.
|
|
104
|
+
// Mirrors pi-core's NON_OVERFLOW_PATTERNS. Excluded from overflow detection so
|
|
105
|
+
// throttling errors are still retried (they are not context-size problems).
|
|
106
|
+
const NON_OVERFLOW_PATTERNS = [
|
|
107
|
+
/^(Throttling error|Service unavailable):/i,
|
|
108
|
+
/rate limit/i,
|
|
109
|
+
/too many requests/i,
|
|
110
|
+
];
|
|
111
|
+
|
|
67
112
|
// ── Blacklist: errors that are truly permanent and should NOT be retried ──
|
|
68
113
|
|
|
69
114
|
const NON_RETRYABLE_PATTERNS = [
|
|
@@ -109,6 +154,24 @@ export function hasConnectionError(message: AgentMessage): boolean {
|
|
|
109
154
|
return CONNECTION_ERROR_PATTERNS.some(p => p.test(message.errorMessage!));
|
|
110
155
|
}
|
|
111
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Returns true for an error assistant message whose errorMessage indicates a
|
|
159
|
+
* context-overflow (input exceeded the model's context window).
|
|
160
|
+
*
|
|
161
|
+
* Mirrors pi-core's isContextOverflow Case 1 (error-message patterns). The
|
|
162
|
+
* silent-overflow cases (stopReason "stop"/"length") are not errors and are
|
|
163
|
+
* never seen here — pi-core handles those in _checkCompaction directly.
|
|
164
|
+
*
|
|
165
|
+
* Callers should treat a true result as "defer to compaction, do NOT retry" —
|
|
166
|
+
* see OVERFLOW_ERROR_PATTERNS for rationale.
|
|
167
|
+
*/
|
|
168
|
+
export function isContextOverflowError(message: AgentMessage): boolean {
|
|
169
|
+
if (!isAssistantMessage(message)) return false;
|
|
170
|
+
if (message.stopReason !== "error" || !message.errorMessage) return false;
|
|
171
|
+
if (NON_OVERFLOW_PATTERNS.some(p => p.test(message.errorMessage!))) return false;
|
|
172
|
+
return OVERFLOW_ERROR_PATTERNS.some(p => p.test(message.errorMessage!));
|
|
173
|
+
}
|
|
174
|
+
|
|
112
175
|
// ── Universal retry check ──
|
|
113
176
|
|
|
114
177
|
/**
|