@monotykamary/pi-retry 0.2.0 → 0.3.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/package.json +1 -1
- package/retry.ts +33 -59
- package/src/error-patterns.ts +0 -11
package/package.json
CHANGED
package/retry.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Agent } from "@earendil-works/pi-agent-core";
|
|
2
3
|
import {
|
|
3
|
-
RETRY_TRIGGER_CUSTOM_TYPE,
|
|
4
|
-
CONTINUATION_CUSTOM_TYPE,
|
|
5
4
|
has400or413Error,
|
|
6
5
|
hasCreditError,
|
|
7
6
|
hasConnectionError,
|
|
@@ -31,16 +30,27 @@ import {
|
|
|
31
30
|
* - Automatic detection and retry for ALL errors (catch-all)
|
|
32
31
|
* - Indefinite retry with exponential backoff (capped at 60s)
|
|
33
32
|
* - Auto-continuation when model hits max output tokens (stopReason "length")
|
|
34
|
-
* - ALL triggers are invisible —
|
|
33
|
+
* - ALL triggers are invisible — agent.prompt([]) resumes the loop with no new message
|
|
35
34
|
* - Unified manual controls via /retry command
|
|
36
35
|
*
|
|
37
|
-
*
|
|
38
|
-
* -
|
|
39
|
-
* -
|
|
40
|
-
* -
|
|
41
|
-
* - No
|
|
36
|
+
* Invisibility mechanism:
|
|
37
|
+
* - Agent.prototype.subscribe monkey-patch captures the Agent instance
|
|
38
|
+
* - agent.prompt([]) starts a fresh agent loop with an empty prompt array
|
|
39
|
+
* - No message injected into context — LLM sees the exact same message list
|
|
40
|
+
* - No convertToLlm involvement, no filter needed, no session artifact
|
|
42
41
|
*/
|
|
43
42
|
|
|
43
|
+
// Capture the live Agent instance when AgentSession subscribes to it.
|
|
44
|
+
// subscribe() is called during AgentSession construction — fires on both
|
|
45
|
+
// fresh sessions and session resumes.
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
let _agent: Agent | null = null;
|
|
48
|
+
const _origSubscribe = Agent.prototype.subscribe as (...args: any[]) => any;
|
|
49
|
+
Agent.prototype.subscribe = function (this: Agent, ...args: any[]) {
|
|
50
|
+
_agent = this;
|
|
51
|
+
return _origSubscribe.apply(this, args);
|
|
52
|
+
};
|
|
53
|
+
|
|
44
54
|
// Per-category retry state (for diagnostics / messaging)
|
|
45
55
|
const state400 = new RetryState();
|
|
46
56
|
const stateCredit = new RetryState();
|
|
@@ -93,14 +103,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
93
103
|
return;
|
|
94
104
|
}
|
|
95
105
|
|
|
96
|
-
// Check for max_tokens stop — auto-continue (
|
|
106
|
+
// Check for max_tokens stop — auto-continue (invisible to LLM)
|
|
97
107
|
if (hasMaxTokensStop(lastAssistant) && !stateContinuation.getIsContinuing()) {
|
|
98
108
|
stateContinuation.startContinuation();
|
|
99
109
|
ctx.ui.notify(
|
|
100
110
|
`Max tokens reached — auto-continuing (continuation ${stateContinuation.getCount()})...`,
|
|
101
111
|
"info",
|
|
102
112
|
);
|
|
103
|
-
|
|
113
|
+
triggerInvisibleContinue();
|
|
104
114
|
stateContinuation.endContinuation();
|
|
105
115
|
return;
|
|
106
116
|
}
|
|
@@ -133,7 +143,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
133
143
|
const delay = calculateDelay(state.getAttempt());
|
|
134
144
|
|
|
135
145
|
await sleep(delay);
|
|
136
|
-
|
|
146
|
+
triggerInvisibleContinue();
|
|
137
147
|
state.endRetry();
|
|
138
148
|
return;
|
|
139
149
|
}
|
|
@@ -147,23 +157,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
147
157
|
|
|
148
158
|
|
|
149
159
|
|
|
150
|
-
// Strip hidden retry/continuation markers from context before each LLM call.
|
|
151
|
-
// This is insurance — convertToLlm already filters custom roles, but a
|
|
152
|
-
// custom convertToLlm override could leak them. Clean proactively.
|
|
153
|
-
pi.on("context", async (event) => {
|
|
154
|
-
const cleaned = event.messages.filter(
|
|
155
|
-
(msg: any) =>
|
|
156
|
-
!(
|
|
157
|
-
msg.role === "custom" &&
|
|
158
|
-
(msg.customType === RETRY_TRIGGER_CUSTOM_TYPE ||
|
|
159
|
-
msg.customType === CONTINUATION_CUSTOM_TYPE)
|
|
160
|
-
),
|
|
161
|
-
);
|
|
162
|
-
if (cleaned.length !== event.messages.length) {
|
|
163
|
-
return { messages: cleaned };
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
160
|
// Unified /retry command with subcommands
|
|
168
161
|
pi.registerCommand("retry", {
|
|
169
162
|
description: "Unified retry controls: /retry (manual trigger), /retry status (diagnostics), /retry reset (clear state)",
|
|
@@ -205,14 +198,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
205
198
|
status += "Max Tokens Continuation:\n";
|
|
206
199
|
status += ` Continuations used: ${stateContinuation.getCount()}\n`;
|
|
207
200
|
status += ` Is continuing: ${stateContinuation.getIsContinuing()}\n`;
|
|
208
|
-
status += ` Trigger: invisible (
|
|
201
|
+
status += ` Trigger: invisible (agent.prompt([]), LLM never sees a prompt)\n\n`;
|
|
209
202
|
|
|
210
203
|
// Config
|
|
211
204
|
status += "Configuration:\n";
|
|
212
205
|
status += ` Base delay: 2000ms\n`;
|
|
213
206
|
status += ` Max delay: 60000ms\n`;
|
|
214
207
|
status += ` Backoff multiplier: 2\n`;
|
|
215
|
-
status += ` Continuation: invisible
|
|
208
|
+
status += ` Continuation: invisible (agent.prompt([]))\n\n`;
|
|
216
209
|
|
|
217
210
|
// Last assistant info
|
|
218
211
|
if (lastAssistant && isAssistantMessage(lastAssistant)) {
|
|
@@ -251,7 +244,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
251
244
|
// Auto-detect: max_tokens continuation takes priority
|
|
252
245
|
if (hasMaxTokensStop(lastAssistant)) {
|
|
253
246
|
ctx.ui.notify("Manually continuing after max_tokens...", "info");
|
|
254
|
-
|
|
247
|
+
triggerInvisibleContinue();
|
|
255
248
|
return;
|
|
256
249
|
}
|
|
257
250
|
|
|
@@ -259,21 +252,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
259
252
|
if (has400or413Error(lastAssistant)) {
|
|
260
253
|
ctx.ui.notify("Manually retrying 400/413 error...", "info");
|
|
261
254
|
state400.reset();
|
|
262
|
-
|
|
255
|
+
triggerInvisibleContinue();
|
|
263
256
|
return;
|
|
264
257
|
}
|
|
265
258
|
|
|
266
259
|
if (hasCreditError(lastAssistant)) {
|
|
267
260
|
ctx.ui.notify("Manually retrying credit error...", "info");
|
|
268
261
|
stateCredit.reset();
|
|
269
|
-
|
|
262
|
+
triggerInvisibleContinue();
|
|
270
263
|
return;
|
|
271
264
|
}
|
|
272
265
|
|
|
273
266
|
if (hasConnectionError(lastAssistant)) {
|
|
274
267
|
ctx.ui.notify("Manually retrying connection error...", "info");
|
|
275
268
|
stateConnection.reset();
|
|
276
|
-
|
|
269
|
+
triggerInvisibleContinue();
|
|
277
270
|
return;
|
|
278
271
|
}
|
|
279
272
|
|
|
@@ -281,7 +274,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
281
274
|
if (hasRetryableError(lastAssistant)) {
|
|
282
275
|
ctx.ui.notify("Manually retrying error...", "info");
|
|
283
276
|
stateOther.reset();
|
|
284
|
-
|
|
277
|
+
triggerInvisibleContinue();
|
|
285
278
|
return;
|
|
286
279
|
}
|
|
287
280
|
|
|
@@ -299,29 +292,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
299
292
|
stateContinuation.reset();
|
|
300
293
|
});
|
|
301
294
|
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
content: "",
|
|
308
|
-
display: false,
|
|
309
|
-
details: {},
|
|
310
|
-
},
|
|
311
|
-
{ triggerTurn: true },
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Helper: send the hidden continuation trigger (silent — LLM never sees a prompt)
|
|
316
|
-
function triggerContinuation(pi: ExtensionAPI) {
|
|
317
|
-
pi.sendMessage(
|
|
318
|
-
{
|
|
319
|
-
customType: CONTINUATION_CUSTOM_TYPE,
|
|
320
|
-
content: "",
|
|
321
|
-
display: false,
|
|
322
|
-
details: {},
|
|
323
|
-
},
|
|
324
|
-
{ triggerTurn: true },
|
|
325
|
-
);
|
|
295
|
+
// Resume the agent loop invisibly — no message injected into context.
|
|
296
|
+
// The LLM sees the exact same message list it had before.
|
|
297
|
+
function triggerInvisibleContinue() {
|
|
298
|
+
if (!_agent) return;
|
|
299
|
+
_agent.prompt([]);
|
|
326
300
|
}
|
|
327
301
|
}
|
package/src/error-patterns.ts
CHANGED
|
@@ -9,17 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
11
11
|
|
|
12
|
-
// Custom message types for invisible triggers.
|
|
13
|
-
// These are sent with role="custom" and display=false, so pi's default
|
|
14
|
-
// convertToLlm filters them out. The context event handler also strips
|
|
15
|
-
// them as insurance.
|
|
16
|
-
|
|
17
|
-
/** Custom type used for the invisible error-retry trigger. */
|
|
18
|
-
export const RETRY_TRIGGER_CUSTOM_TYPE = "__retry_trigger";
|
|
19
|
-
|
|
20
|
-
/** Custom type used for the invisible max_tokens continuation trigger. */
|
|
21
|
-
export const CONTINUATION_CUSTOM_TYPE = "__retry_continuation";
|
|
22
|
-
|
|
23
12
|
// ── Specific pattern groups (used for categorisation / messaging) ──
|
|
24
13
|
|
|
25
14
|
const ERROR_400_413_PATTERNS = [
|