@monotykamary/pi-retry 0.6.2 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monotykamary/pi-retry",
3
- "version": "0.6.2",
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,13 +32,6 @@
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
36
  "@earendil-works/pi-agent-core": "^0.79.8",
44
37
  "@earendil-works/pi-coding-agent": "^0.79.8",
@@ -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,
@@ -290,6 +291,30 @@ export default function (pi: ExtensionAPI) {
290
291
  return;
291
292
  }
292
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
+
293
318
  // Catch-all: retry ANY error except known permanent failures
294
319
  if (hasRetryableError(lastAssistant)) {
295
320
  const errorMsg = lastAssistant.errorMessage || "Unknown error";
@@ -429,6 +454,17 @@ export default function (pi: ExtensionAPI) {
429
454
  return;
430
455
  }
431
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
+
432
468
  // Auto-detect error type and trigger appropriate retry
433
469
  if (has400or413Error(lastAssistant)) {
434
470
  ctx.ui.notify("Manually retrying 400/413 error...", "info");
@@ -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
  /**