@caupulican/pi-agent-core 0.81.2 → 0.81.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.
Files changed (44) hide show
  1. package/dist/compaction/compaction.d.ts +11 -2
  2. package/dist/compaction/compaction.d.ts.map +1 -1
  3. package/dist/compaction/compaction.js +258 -106
  4. package/dist/compaction/compaction.js.map +1 -1
  5. package/dist/compaction/extraction.d.ts +15 -0
  6. package/dist/compaction/extraction.d.ts.map +1 -0
  7. package/dist/compaction/extraction.js +342 -0
  8. package/dist/compaction/extraction.js.map +1 -0
  9. package/dist/compaction/index.d.ts +4 -0
  10. package/dist/compaction/index.d.ts.map +1 -1
  11. package/dist/compaction/index.js +4 -0
  12. package/dist/compaction/index.js.map +1 -1
  13. package/dist/compaction/loop.d.ts +52 -0
  14. package/dist/compaction/loop.d.ts.map +1 -0
  15. package/dist/compaction/loop.js +154 -0
  16. package/dist/compaction/loop.js.map +1 -0
  17. package/dist/compaction/token-budget.d.ts +12 -0
  18. package/dist/compaction/token-budget.d.ts.map +1 -0
  19. package/dist/compaction/token-budget.js +52 -0
  20. package/dist/compaction/token-budget.js.map +1 -0
  21. package/dist/compaction/utils.d.ts +1 -1
  22. package/dist/compaction/utils.d.ts.map +1 -1
  23. package/dist/compaction/utils.js +45 -2
  24. package/dist/compaction/utils.js.map +1 -1
  25. package/dist/compaction/verification.d.ts +20 -0
  26. package/dist/compaction/verification.d.ts.map +1 -0
  27. package/dist/compaction/verification.js +169 -0
  28. package/dist/compaction/verification.js.map +1 -0
  29. package/dist/reliability/classifier.d.ts +2 -0
  30. package/dist/reliability/classifier.d.ts.map +1 -1
  31. package/dist/reliability/classifier.js +18 -1
  32. package/dist/reliability/classifier.js.map +1 -1
  33. package/dist/reliability/index.d.ts +1 -0
  34. package/dist/reliability/index.d.ts.map +1 -1
  35. package/dist/reliability/index.js +1 -0
  36. package/dist/reliability/index.js.map +1 -1
  37. package/dist/reliability/provider-signatures.d.ts +10 -0
  38. package/dist/reliability/provider-signatures.d.ts.map +1 -0
  39. package/dist/reliability/provider-signatures.js +11 -0
  40. package/dist/reliability/provider-signatures.js.map +1 -0
  41. package/dist/reliability/retry-controller.d.ts.map +1 -1
  42. package/dist/reliability/retry-controller.js +1 -0
  43. package/dist/reliability/retry-controller.js.map +1 -1
  44. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../src/reliability/classifier.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure provider-failure classifier.\n *\n * One classification produces four independent action booleans (hermes-derived design):\n * the retry loop, compaction, credential rotation, and provider failover each read their\n * own flag, so one pipeline can route a 429 to rotation, an overflow to compaction, and a\n * billing error to failover without re-parsing error text at each site.\n *\n * Pattern sources: AgentSession._isRetryableError / _isNonRetryableProviderLimitError\n * (the live, battle-tested regexes), split by reason so each maps to distinct actions.\n * Detection of context overflow stays in @caupulican/pi-ai (needs model state); hosts pass\n * `contextOverflow` in.\n */\n\nexport type FailureReason =\n\t| \"overloaded\"\n\t| \"rate_limit\"\n\t| \"server_error\"\n\t| \"network\"\n\t| \"stream_stall\"\n\t| \"context_overflow\"\n\t| \"auth\"\n\t| \"billing_or_quota\"\n\t| \"aborted\"\n\t| \"unknown\";\n\nexport interface ClassifiedError {\n\treason: FailureReason;\n\tretryable: boolean;\n\tshouldCompact: boolean;\n\tshouldRotateCredential: boolean;\n\tshouldFallback: boolean;\n\t/** Provider-suggested delay parsed from the message, capped by the retry policy at use site. */\n\tretryAfterMs?: number;\n\tmessage: string;\n}\n\nexport interface ClassifyFailureInput {\n\tmessage: string;\n\t/** Host-computed via pi-ai isContextOverflow(message, contextWindow). */\n\tcontextOverflow?: boolean;\n\t/** True when the failure came from an intentional abort (stopReason \"aborted\"). */\n\taborted?: boolean;\n}\n\nconst BILLING_OR_QUOTA =\n\t/GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i;\nconst AUTH = /\\b401\\b|unauthorized|invalid.?api.?key|authentication.?error|forbidden|permission.?denied/i;\nconst RATE_LIMIT = /rate.?limit|too many requests|429/i;\nconst OVERLOADED = /overloaded/i;\nconst STREAM_STALL = /stream stalled|ended without|stream ended before message_stop|reset before headers/i;\nconst SERVER_ERROR =\n\t/500|502|503|504|service.?unavailable|server.?error|internal.?error|provider.?returned.?error|upstream.?connect|http2 request did not get a response|retry delay/i;\nconst NETWORK =\n\t/network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|socket hang up|timed? out|timeout|terminated/i;\n\nconst RETRY_AFTER_S = /retry.?(?:after|in)\\s+(\\d+(?:\\.\\d+)?)\\s*s\\b|\"retryDelay\"\\s*:\\s*\"(\\d+(?:\\.\\d+)?)s\"/i;\nconst RETRY_AFTER_MS = /retry.?(?:after|in)\\s+(\\d+)\\s*ms\\b/i;\n\nfunction parseRetryAfterMs(message: string): number | undefined {\n\tconst ms = RETRY_AFTER_MS.exec(message);\n\tif (ms) return Number(ms[1]);\n\tconst s = RETRY_AFTER_S.exec(message);\n\tif (s) return Math.round(Number(s[1] ?? s[2]) * 1000);\n\treturn undefined;\n}\n\nexport function classifyFailure(input: ClassifyFailureInput): ClassifiedError {\n\tconst message = input.message;\n\tconst retryAfterMs = parseRetryAfterMs(message);\n\n\tconst base = {\n\t\tretryable: false,\n\t\tshouldCompact: false,\n\t\tshouldRotateCredential: false,\n\t\tshouldFallback: false,\n\t\tmessage,\n\t};\n\n\tconst withRetry = <T extends { reason: FailureReason }>(obj: T): T | (T & { retryAfterMs: number }) =>\n\t\tretryAfterMs !== undefined ? { ...obj, retryAfterMs } : obj;\n\n\tif (input.aborted) return withRetry({ ...base, reason: \"aborted\" });\n\tif (input.contextOverflow) return withRetry({ ...base, reason: \"context_overflow\", shouldCompact: true });\n\tif (BILLING_OR_QUOTA.test(message)) return withRetry({ ...base, reason: \"billing_or_quota\", shouldFallback: true });\n\tif (AUTH.test(message))\n\t\treturn withRetry({ ...base, reason: \"auth\", shouldRotateCredential: true, shouldFallback: true });\n\n\tconst isRateLimit = RATE_LIMIT.test(message);\n\tif (isRateLimit || OVERLOADED.test(message)) {\n\t\treturn withRetry({\n\t\t\t...base,\n\t\t\treason: isRateLimit ? \"rate_limit\" : \"overloaded\",\n\t\t\tretryable: true,\n\t\t\tshouldRotateCredential: true,\n\t\t\tshouldFallback: true,\n\t\t});\n\t}\n\tif (STREAM_STALL.test(message))\n\t\treturn withRetry({ ...base, reason: \"stream_stall\", retryable: true, shouldFallback: true });\n\tif (SERVER_ERROR.test(message))\n\t\treturn withRetry({ ...base, reason: \"server_error\", retryable: true, shouldFallback: true });\n\tif (NETWORK.test(message)) return withRetry({ ...base, reason: \"network\", retryable: true, shouldFallback: true });\n\n\treturn withRetry({ ...base, reason: \"unknown\" });\n}\n"]}
1
+ {"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../src/reliability/classifier.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure provider-failure classifier.\n *\n * One classification produces four independent action booleans (hermes-derived design):\n * the retry loop, compaction, credential rotation, and provider failover each read their\n * own flag, so one pipeline can route a 429 to rotation, an overflow to compaction, and a\n * billing error to failover without re-parsing error text at each site.\n *\n * Pattern sources: AgentSession._isRetryableError / _isNonRetryableProviderLimitError\n * (the live, battle-tested regexes), split by reason so each maps to distinct actions.\n * Detection of context overflow stays in @caupulican/pi-ai (needs model state); hosts pass\n * `contextOverflow` in.\n */\n\nimport { PROVIDER_FAILURE_SIGNATURES } from \"./provider-signatures.ts\";\n\nexport type FailureReason =\n\t| \"overloaded\"\n\t| \"rate_limit\"\n\t| \"server_error\"\n\t| \"network\"\n\t| \"stream_stall\"\n\t| \"context_overflow\"\n\t| \"auth\"\n\t| \"billing_or_quota\"\n\t| \"aborted\"\n\t| \"unknown\";\n\nexport interface ClassifiedError {\n\treason: FailureReason;\n\tretryable: boolean;\n\tshouldCompact: boolean;\n\tshouldRotateCredential: boolean;\n\tshouldFallback: boolean;\n\t/** Provider-suggested delay parsed from the message, capped by the retry policy at use site. */\n\tretryAfterMs?: number;\n\tmessage: string;\n}\n\nexport interface ClassifyFailureInput {\n\tmessage: string;\n\t/** Host-computed via pi-ai isContextOverflow(message, contextWindow). */\n\tcontextOverflow?: boolean;\n\t/** True when the failure came from an intentional abort (stopReason \"aborted\"). */\n\taborted?: boolean;\n\t/** Provider id; provider-specific signatures are checked before generic patterns. */\n\tprovider?: string;\n}\n\nconst BILLING_OR_QUOTA =\n\t/GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing|usage.?limit(?:s)?\\s*(?:reached|exceeded|hit)|usage_limit_reached|hit your usage limit|hit your ChatGPT usage limit/i;\nconst AUTH = /\\b401\\b|unauthorized|invalid.?api.?key|authentication.?error|forbidden|permission.?denied/i;\nconst RATE_LIMIT = /rate.?limit|too many requests|429/i;\nconst OVERLOADED = /overloaded/i;\nconst STREAM_STALL = /stream stalled|ended without|stream ended before message_stop|reset before headers/i;\nconst SERVER_ERROR =\n\t/500|502|503|504|service.?unavailable|server.?error|internal.?error|provider.?returned.?error|upstream.?connect|http2 request did not get a response|retry delay/i;\nconst NETWORK =\n\t/network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|socket hang up|timed? out|timeout|terminated/i;\n\nconst RETRY_AFTER_S = /retry.?(?:after|in)\\s+(\\d+(?:\\.\\d+)?)\\s*s\\b|\"retryDelay\"\\s*:\\s*\"(\\d+(?:\\.\\d+)?)s\"/i;\nconst RETRY_AFTER_MS = /retry.?(?:after|in)\\s+(\\d+)\\s*ms\\b/i;\n\nfunction parseRetryAfterMs(message: string): number | undefined {\n\tconst ms = RETRY_AFTER_MS.exec(message);\n\tif (ms) return Number(ms[1]);\n\tconst s = RETRY_AFTER_S.exec(message);\n\tif (s) return Math.round(Number(s[1] ?? s[2]) * 1000);\n\treturn undefined;\n}\n\nexport function classifyFailure(input: ClassifyFailureInput): ClassifiedError {\n\tconst message = input.message;\n\tconst retryAfterMs = parseRetryAfterMs(message);\n\n\tconst base = {\n\t\tretryable: false,\n\t\tshouldCompact: false,\n\t\tshouldRotateCredential: false,\n\t\tshouldFallback: false,\n\t\tmessage,\n\t};\n\n\tconst withRetry = <T extends { reason: FailureReason }>(obj: T): T | (T & { retryAfterMs: number }) =>\n\t\tretryAfterMs !== undefined ? { ...obj, retryAfterMs } : obj;\n\n\tif (input.aborted) return withRetry({ ...base, reason: \"aborted\" });\n\tif (input.contextOverflow) return withRetry({ ...base, reason: \"context_overflow\", shouldCompact: true });\n\tconst providerSignatures = input.provider ? (PROVIDER_FAILURE_SIGNATURES[input.provider] ?? []) : [];\n\tfor (const signature of providerSignatures) {\n\t\tif (signature.pattern.test(message)) {\n\t\t\treturn withRetry({\n\t\t\t\t...base,\n\t\t\t\treason: signature.reason,\n\t\t\t\tshouldFallback: signature.reason === \"billing_or_quota\" || signature.reason === \"auth\",\n\t\t\t\tshouldRotateCredential: signature.reason === \"auth\",\n\t\t\t\tretryable:\n\t\t\t\t\tsignature.reason === \"rate_limit\" ||\n\t\t\t\t\tsignature.reason === \"overloaded\" ||\n\t\t\t\t\tsignature.reason === \"server_error\" ||\n\t\t\t\t\tsignature.reason === \"network\" ||\n\t\t\t\t\tsignature.reason === \"stream_stall\",\n\t\t\t});\n\t\t}\n\t}\n\tif (BILLING_OR_QUOTA.test(message)) return withRetry({ ...base, reason: \"billing_or_quota\", shouldFallback: true });\n\tif (AUTH.test(message))\n\t\treturn withRetry({ ...base, reason: \"auth\", shouldRotateCredential: true, shouldFallback: true });\n\n\tconst isRateLimit = RATE_LIMIT.test(message);\n\tif (isRateLimit || OVERLOADED.test(message)) {\n\t\treturn withRetry({\n\t\t\t...base,\n\t\t\treason: isRateLimit ? \"rate_limit\" : \"overloaded\",\n\t\t\tretryable: true,\n\t\t\tshouldRotateCredential: true,\n\t\t\tshouldFallback: true,\n\t\t});\n\t}\n\tif (STREAM_STALL.test(message))\n\t\treturn withRetry({ ...base, reason: \"stream_stall\", retryable: true, shouldFallback: true });\n\tif (SERVER_ERROR.test(message))\n\t\treturn withRetry({ ...base, reason: \"server_error\", retryable: true, shouldFallback: true });\n\tif (NETWORK.test(message)) return withRetry({ ...base, reason: \"network\", retryable: true, shouldFallback: true });\n\n\treturn withRetry({ ...base, reason: \"unknown\" });\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  export * from "./classifier.ts";
2
+ export * from "./provider-signatures.ts";
2
3
  export * from "./retry.ts";
3
4
  export * from "./retry-controller.ts";
4
5
  export * from "./watchdogs.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reliability/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC","sourcesContent":["export * from \"./classifier.ts\";\nexport * from \"./retry.ts\";\nexport * from \"./retry-controller.ts\";\nexport * from \"./watchdogs.ts\";\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reliability/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC","sourcesContent":["export * from \"./classifier.ts\";\nexport * from \"./provider-signatures.ts\";\nexport * from \"./retry.ts\";\nexport * from \"./retry-controller.ts\";\nexport * from \"./watchdogs.ts\";\n"]}
@@ -1,4 +1,5 @@
1
1
  export * from "./classifier.js";
2
+ export * from "./provider-signatures.js";
2
3
  export * from "./retry.js";
3
4
  export * from "./retry-controller.js";
4
5
  export * from "./watchdogs.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/reliability/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC","sourcesContent":["export * from \"./classifier.ts\";\nexport * from \"./retry.ts\";\nexport * from \"./retry-controller.ts\";\nexport * from \"./watchdogs.ts\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/reliability/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC","sourcesContent":["export * from \"./classifier.ts\";\nexport * from \"./provider-signatures.ts\";\nexport * from \"./retry.ts\";\nexport * from \"./retry-controller.ts\";\nexport * from \"./watchdogs.ts\";\n"]}
@@ -0,0 +1,10 @@
1
+ import type { FailureReason } from "./classifier.ts";
2
+ export interface ProviderSignature {
3
+ reason: FailureReason;
4
+ pattern: RegExp;
5
+ /** Where this pattern was observed: adapter file:line, or "incident YYYY-MM-DD". */
6
+ source: string;
7
+ }
8
+ /** Provider-thrown-message signatures checked before the generic ladder. */
9
+ export declare const PROVIDER_FAILURE_SIGNATURES: Record<string, readonly ProviderSignature[]>;
10
+ //# sourceMappingURL=provider-signatures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-signatures.d.ts","sourceRoot":"","sources":["../../src/reliability/provider-signatures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;CACf;AAED,4EAA4E;AAC5E,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAQpF,CAAC","sourcesContent":["import type { FailureReason } from \"./classifier.ts\";\n\nexport interface ProviderSignature {\n\treason: FailureReason;\n\tpattern: RegExp;\n\t/** Where this pattern was observed: adapter file:line, or \"incident YYYY-MM-DD\". */\n\tsource: string;\n}\n\n/** Provider-thrown-message signatures checked before the generic ladder. */\nexport const PROVIDER_FAILURE_SIGNATURES: Record<string, readonly ProviderSignature[]> = {\n\t\"openai-codex\": [\n\t\t{\n\t\t\treason: \"billing_or_quota\",\n\t\t\tpattern: /You have hit your ChatGPT usage limit/i,\n\t\t\tsource: \"packages/ai/src/providers/openai-codex-responses.ts:1402\",\n\t\t},\n\t],\n};\n"]}
@@ -0,0 +1,11 @@
1
+ /** Provider-thrown-message signatures checked before the generic ladder. */
2
+ export const PROVIDER_FAILURE_SIGNATURES = {
3
+ "openai-codex": [
4
+ {
5
+ reason: "billing_or_quota",
6
+ pattern: /You have hit your ChatGPT usage limit/i,
7
+ source: "packages/ai/src/providers/openai-codex-responses.ts:1402",
8
+ },
9
+ ],
10
+ };
11
+ //# sourceMappingURL=provider-signatures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-signatures.js","sourceRoot":"","sources":["../../src/reliability/provider-signatures.ts"],"names":[],"mappings":"AASA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,2BAA2B,GAAiD;IACxF,cAAc,EAAE;QACf;YACC,MAAM,EAAE,kBAAkB;YAC1B,OAAO,EAAE,wCAAwC;YACjD,MAAM,EAAE,0DAA0D;SAClE;KACD;CACD,CAAC","sourcesContent":["import type { FailureReason } from \"./classifier.ts\";\n\nexport interface ProviderSignature {\n\treason: FailureReason;\n\tpattern: RegExp;\n\t/** Where this pattern was observed: adapter file:line, or \"incident YYYY-MM-DD\". */\n\tsource: string;\n}\n\n/** Provider-thrown-message signatures checked before the generic ladder. */\nexport const PROVIDER_FAILURE_SIGNATURES: Record<string, readonly ProviderSignature[]> = {\n\t\"openai-codex\": [\n\t\t{\n\t\t\treason: \"billing_or_quota\",\n\t\t\tpattern: /You have hit your ChatGPT usage limit/i,\n\t\t\tsource: \"packages/ai/src/providers/openai-codex-responses.ts:1402\",\n\t\t},\n\t],\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"retry-controller.d.ts","sourceRoot":"","sources":["../../src/reliability/retry-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,gBAAgB,EAAqB,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAuB,KAAK,WAAW,EAAkB,MAAM,YAAY,CAAC;AAEnF,0FAA0F;AAC1F,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC3B,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;CACrC;AAED,iGAAiG;AACjG,MAAM,MAAM,qBAAqB,GAAG,WAAW,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvE,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAe;IAEhD,YACC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,qBAAqB,EACtC,MAAM,EAAE,WAAW,EACnB,gBAAgB,EAAE,MAAM,MAAM,EAM9B;IAED,0EAA0E;IAC1E,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,+FAA+F;IAC/F,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,oGAAkG;IAClG,KAAK,IAAI,IAAI,CAEZ;IAED,8EAA8E;IAC9E,KAAK,IAAI,IAAI,CAEZ;IAED;;;;OAIG;IACG,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAwD9D;CACD","sourcesContent":["/**\n * Host-agnostic auto-retry driver.\n *\n * Owns the retry attempt counter and the abortable backoff for one agent run. It reads the\n * failure verdict from {@link classifyFailure} (fed a host-computed context-overflow flag), so\n * billing/auth are terminal and overflow routes to compaction — never to a pointless retry.\n * The controller only ever touches `agent.state.messages` (it drops the trailing assistant error\n * before retrying); durable history stays the host session's responsibility.\n *\n * Ported from AgentSession._prepareRetry / _isRetryableError so the exact event ordering and\n * exhaustion semantics carry over: the retry window is marked active (isRetrying) before the\n * start event fires, so prompts arriving inside start handlers queue as steering instead of\n * racing the retry continuation.\n */\n\nimport { type AssistantMessage, isContextOverflow } from \"@caupulican/pi-ai\";\nimport type { AgentMessage } from \"../types.ts\";\nimport { classifyFailure } from \"./classifier.ts\";\nimport { computeRetryDelayMs, type RetryPolicy, sleepAbortable } from \"./retry.ts\";\n\n/** The slice of an Agent the retry driver reads and mutates: just the live transcript. */\nexport interface RetryAgent {\n\treadonly state: { messages: AgentMessage[] };\n}\n\nexport interface RetryStartInfo {\n\tattempt: number;\n\tmaxAttempts: number;\n\tdelayMs: number;\n\terrorMessage: string;\n}\n\nexport interface RetryEndInfo {\n\tsuccess: boolean;\n\tattempt: number;\n\tfinalError?: string;\n}\n\nexport interface RetryEvents {\n\tonRetryStart(info: RetryStartInfo): void;\n\tonRetryEnd(info: RetryEndInfo): void;\n}\n\n/** Runtime retry policy: the backoff shape plus the on/off switch the host resolves per call. */\nexport type RetryControllerPolicy = RetryPolicy & { enabled: boolean };\n\nexport class RetryController {\n\tprivate _attempt = 0;\n\tprivate _abortController: AbortController | undefined;\n\tprivate readonly agent: RetryAgent;\n\tprivate readonly getPolicy: () => RetryControllerPolicy;\n\tprivate readonly events: RetryEvents;\n\tprivate readonly getContextWindow: () => number;\n\n\tconstructor(\n\t\tagent: RetryAgent,\n\t\tgetPolicy: () => RetryControllerPolicy,\n\t\tevents: RetryEvents,\n\t\tgetContextWindow: () => number,\n\t) {\n\t\tthis.agent = agent;\n\t\tthis.getPolicy = getPolicy;\n\t\tthis.events = events;\n\t\tthis.getContextWindow = getContextWindow;\n\t}\n\n\t/** Completed retry attempts for the current run (0 when not retrying). */\n\tget attempt(): number {\n\t\treturn this._attempt;\n\t}\n\n\t/** True from the instant onRetryStart fires until the backoff sleep resolves or is aborted. */\n\tget isRetrying(): boolean {\n\t\treturn this._abortController !== undefined;\n\t}\n\n\t/** Clear the attempt counter — the host calls this after a successful turn or a final failure. */\n\treset(): void {\n\t\tthis._attempt = 0;\n\t}\n\n\t/** Cancel an in-progress backoff; the pending prepareRetry resolves false. */\n\tabort(): void {\n\t\tthis._abortController?.abort();\n\t}\n\n\t/**\n\t * Classify `message`; if it is retryable and attempts remain, drop the trailing assistant\n\t * error from agent state, emit the start event, and wait out the backoff (abortable).\n\t * @returns true if the caller should continue the agent, false otherwise.\n\t */\n\tasync prepareRetry(message: AssistantMessage): Promise<boolean> {\n\t\tconst policy = this.getPolicy();\n\t\tif (!policy.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// The classifier is the single source of the retry verdict: context overflow (host-computed\n\t\t// from the live window) routes to compaction, billing/auth are terminal, transient failures retry.\n\t\tconst classified = classifyFailure({\n\t\t\tmessage: message.errorMessage ?? \"\",\n\t\t\tcontextOverflow: isContextOverflow(message, this.getContextWindow()),\n\t\t});\n\t\tif (!classified.retryable) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._attempt++;\n\t\tif (this._attempt > policy.maxAttempts) {\n\t\t\t// Preserve the completed attempt count so the host can emit the final failure.\n\t\t\tthis._attempt--;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst delayMs = computeRetryDelayMs(policy, this._attempt);\n\n\t\t// The retry window counts as active work from the instant listeners hear about it:\n\t\t// isRetrying must already be true inside onRetryStart handlers so prompts arriving there\n\t\t// queue as steering instead of racing the retry continuation.\n\t\tthis._abortController = new AbortController();\n\n\t\tthis.events.onRetryStart({\n\t\t\tattempt: this._attempt,\n\t\t\tmaxAttempts: policy.maxAttempts,\n\t\t\tdelayMs,\n\t\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t\t});\n\n\t\t// Remove the trailing assistant error from live agent state (the host session keeps it in history).\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\n\t\ttry {\n\t\t\tawait sleepAbortable(delayMs, this._abortController.signal);\n\t\t} catch {\n\t\t\t// Aborted mid-backoff: report the cancellation and reset so the next turn starts clean.\n\t\t\tconst attempt = this._attempt;\n\t\t\tthis._attempt = 0;\n\t\t\tthis.events.onRetryEnd({ success: false, attempt, finalError: \"Retry cancelled\" });\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._abortController = undefined;\n\t\t}\n\n\t\treturn true;\n\t}\n}\n"]}
1
+ {"version":3,"file":"retry-controller.d.ts","sourceRoot":"","sources":["../../src/reliability/retry-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,gBAAgB,EAAqB,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAuB,KAAK,WAAW,EAAkB,MAAM,YAAY,CAAC;AAEnF,0FAA0F;AAC1F,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC3B,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;CACrC;AAED,iGAAiG;AACjG,MAAM,MAAM,qBAAqB,GAAG,WAAW,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvE,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAe;IAEhD,YACC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,qBAAqB,EACtC,MAAM,EAAE,WAAW,EACnB,gBAAgB,EAAE,MAAM,MAAM,EAM9B;IAED,0EAA0E;IAC1E,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,+FAA+F;IAC/F,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,oGAAkG;IAClG,KAAK,IAAI,IAAI,CAEZ;IAED,8EAA8E;IAC9E,KAAK,IAAI,IAAI,CAEZ;IAED;;;;OAIG;IACG,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAyD9D;CACD","sourcesContent":["/**\n * Host-agnostic auto-retry driver.\n *\n * Owns the retry attempt counter and the abortable backoff for one agent run. It reads the\n * failure verdict from {@link classifyFailure} (fed a host-computed context-overflow flag), so\n * billing/auth are terminal and overflow routes to compaction — never to a pointless retry.\n * The controller only ever touches `agent.state.messages` (it drops the trailing assistant error\n * before retrying); durable history stays the host session's responsibility.\n *\n * Ported from AgentSession._prepareRetry / _isRetryableError so the exact event ordering and\n * exhaustion semantics carry over: the retry window is marked active (isRetrying) before the\n * start event fires, so prompts arriving inside start handlers queue as steering instead of\n * racing the retry continuation.\n */\n\nimport { type AssistantMessage, isContextOverflow } from \"@caupulican/pi-ai\";\nimport type { AgentMessage } from \"../types.ts\";\nimport { classifyFailure } from \"./classifier.ts\";\nimport { computeRetryDelayMs, type RetryPolicy, sleepAbortable } from \"./retry.ts\";\n\n/** The slice of an Agent the retry driver reads and mutates: just the live transcript. */\nexport interface RetryAgent {\n\treadonly state: { messages: AgentMessage[] };\n}\n\nexport interface RetryStartInfo {\n\tattempt: number;\n\tmaxAttempts: number;\n\tdelayMs: number;\n\terrorMessage: string;\n}\n\nexport interface RetryEndInfo {\n\tsuccess: boolean;\n\tattempt: number;\n\tfinalError?: string;\n}\n\nexport interface RetryEvents {\n\tonRetryStart(info: RetryStartInfo): void;\n\tonRetryEnd(info: RetryEndInfo): void;\n}\n\n/** Runtime retry policy: the backoff shape plus the on/off switch the host resolves per call. */\nexport type RetryControllerPolicy = RetryPolicy & { enabled: boolean };\n\nexport class RetryController {\n\tprivate _attempt = 0;\n\tprivate _abortController: AbortController | undefined;\n\tprivate readonly agent: RetryAgent;\n\tprivate readonly getPolicy: () => RetryControllerPolicy;\n\tprivate readonly events: RetryEvents;\n\tprivate readonly getContextWindow: () => number;\n\n\tconstructor(\n\t\tagent: RetryAgent,\n\t\tgetPolicy: () => RetryControllerPolicy,\n\t\tevents: RetryEvents,\n\t\tgetContextWindow: () => number,\n\t) {\n\t\tthis.agent = agent;\n\t\tthis.getPolicy = getPolicy;\n\t\tthis.events = events;\n\t\tthis.getContextWindow = getContextWindow;\n\t}\n\n\t/** Completed retry attempts for the current run (0 when not retrying). */\n\tget attempt(): number {\n\t\treturn this._attempt;\n\t}\n\n\t/** True from the instant onRetryStart fires until the backoff sleep resolves or is aborted. */\n\tget isRetrying(): boolean {\n\t\treturn this._abortController !== undefined;\n\t}\n\n\t/** Clear the attempt counter — the host calls this after a successful turn or a final failure. */\n\treset(): void {\n\t\tthis._attempt = 0;\n\t}\n\n\t/** Cancel an in-progress backoff; the pending prepareRetry resolves false. */\n\tabort(): void {\n\t\tthis._abortController?.abort();\n\t}\n\n\t/**\n\t * Classify `message`; if it is retryable and attempts remain, drop the trailing assistant\n\t * error from agent state, emit the start event, and wait out the backoff (abortable).\n\t * @returns true if the caller should continue the agent, false otherwise.\n\t */\n\tasync prepareRetry(message: AssistantMessage): Promise<boolean> {\n\t\tconst policy = this.getPolicy();\n\t\tif (!policy.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// The classifier is the single source of the retry verdict: context overflow (host-computed\n\t\t// from the live window) routes to compaction, billing/auth are terminal, transient failures retry.\n\t\tconst classified = classifyFailure({\n\t\t\tmessage: message.errorMessage ?? \"\",\n\t\t\tcontextOverflow: isContextOverflow(message, this.getContextWindow()),\n\t\t\tprovider: message.provider,\n\t\t});\n\t\tif (!classified.retryable) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._attempt++;\n\t\tif (this._attempt > policy.maxAttempts) {\n\t\t\t// Preserve the completed attempt count so the host can emit the final failure.\n\t\t\tthis._attempt--;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst delayMs = computeRetryDelayMs(policy, this._attempt);\n\n\t\t// The retry window counts as active work from the instant listeners hear about it:\n\t\t// isRetrying must already be true inside onRetryStart handlers so prompts arriving there\n\t\t// queue as steering instead of racing the retry continuation.\n\t\tthis._abortController = new AbortController();\n\n\t\tthis.events.onRetryStart({\n\t\t\tattempt: this._attempt,\n\t\t\tmaxAttempts: policy.maxAttempts,\n\t\t\tdelayMs,\n\t\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t\t});\n\n\t\t// Remove the trailing assistant error from live agent state (the host session keeps it in history).\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\n\t\ttry {\n\t\t\tawait sleepAbortable(delayMs, this._abortController.signal);\n\t\t} catch {\n\t\t\t// Aborted mid-backoff: report the cancellation and reset so the next turn starts clean.\n\t\t\tconst attempt = this._attempt;\n\t\t\tthis._attempt = 0;\n\t\t\tthis.events.onRetryEnd({ success: false, attempt, finalError: \"Retry cancelled\" });\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._abortController = undefined;\n\t\t}\n\n\t\treturn true;\n\t}\n}\n"]}
@@ -59,6 +59,7 @@ export class RetryController {
59
59
  const classified = classifyFailure({
60
60
  message: message.errorMessage ?? "",
61
61
  contextOverflow: isContextOverflow(message, this.getContextWindow()),
62
+ provider: message.provider,
62
63
  });
63
64
  if (!classified.retryable) {
64
65
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"retry-controller.js","sourceRoot":"","sources":["../../src/reliability/retry-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Host-agnostic auto-retry driver.\n *\n * Owns the retry attempt counter and the abortable backoff for one agent run. It reads the\n * failure verdict from {@link classifyFailure} (fed a host-computed context-overflow flag), so\n * billing/auth are terminal and overflow routes to compaction — never to a pointless retry.\n * The controller only ever touches `agent.state.messages` (it drops the trailing assistant error\n * before retrying); durable history stays the host session's responsibility.\n *\n * Ported from AgentSession._prepareRetry / _isRetryableError so the exact event ordering and\n * exhaustion semantics carry over: the retry window is marked active (isRetrying) before the\n * start event fires, so prompts arriving inside start handlers queue as steering instead of\n * racing the retry continuation.\n */\n\nimport { type AssistantMessage, isContextOverflow } from \"@caupulican/pi-ai\";\nimport type { AgentMessage } from \"../types.ts\";\nimport { classifyFailure } from \"./classifier.ts\";\nimport { computeRetryDelayMs, type RetryPolicy, sleepAbortable } from \"./retry.ts\";\n\n/** The slice of an Agent the retry driver reads and mutates: just the live transcript. */\nexport interface RetryAgent {\n\treadonly state: { messages: AgentMessage[] };\n}\n\nexport interface RetryStartInfo {\n\tattempt: number;\n\tmaxAttempts: number;\n\tdelayMs: number;\n\terrorMessage: string;\n}\n\nexport interface RetryEndInfo {\n\tsuccess: boolean;\n\tattempt: number;\n\tfinalError?: string;\n}\n\nexport interface RetryEvents {\n\tonRetryStart(info: RetryStartInfo): void;\n\tonRetryEnd(info: RetryEndInfo): void;\n}\n\n/** Runtime retry policy: the backoff shape plus the on/off switch the host resolves per call. */\nexport type RetryControllerPolicy = RetryPolicy & { enabled: boolean };\n\nexport class RetryController {\n\tprivate _attempt = 0;\n\tprivate _abortController: AbortController | undefined;\n\tprivate readonly agent: RetryAgent;\n\tprivate readonly getPolicy: () => RetryControllerPolicy;\n\tprivate readonly events: RetryEvents;\n\tprivate readonly getContextWindow: () => number;\n\n\tconstructor(\n\t\tagent: RetryAgent,\n\t\tgetPolicy: () => RetryControllerPolicy,\n\t\tevents: RetryEvents,\n\t\tgetContextWindow: () => number,\n\t) {\n\t\tthis.agent = agent;\n\t\tthis.getPolicy = getPolicy;\n\t\tthis.events = events;\n\t\tthis.getContextWindow = getContextWindow;\n\t}\n\n\t/** Completed retry attempts for the current run (0 when not retrying). */\n\tget attempt(): number {\n\t\treturn this._attempt;\n\t}\n\n\t/** True from the instant onRetryStart fires until the backoff sleep resolves or is aborted. */\n\tget isRetrying(): boolean {\n\t\treturn this._abortController !== undefined;\n\t}\n\n\t/** Clear the attempt counter — the host calls this after a successful turn or a final failure. */\n\treset(): void {\n\t\tthis._attempt = 0;\n\t}\n\n\t/** Cancel an in-progress backoff; the pending prepareRetry resolves false. */\n\tabort(): void {\n\t\tthis._abortController?.abort();\n\t}\n\n\t/**\n\t * Classify `message`; if it is retryable and attempts remain, drop the trailing assistant\n\t * error from agent state, emit the start event, and wait out the backoff (abortable).\n\t * @returns true if the caller should continue the agent, false otherwise.\n\t */\n\tasync prepareRetry(message: AssistantMessage): Promise<boolean> {\n\t\tconst policy = this.getPolicy();\n\t\tif (!policy.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// The classifier is the single source of the retry verdict: context overflow (host-computed\n\t\t// from the live window) routes to compaction, billing/auth are terminal, transient failures retry.\n\t\tconst classified = classifyFailure({\n\t\t\tmessage: message.errorMessage ?? \"\",\n\t\t\tcontextOverflow: isContextOverflow(message, this.getContextWindow()),\n\t\t});\n\t\tif (!classified.retryable) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._attempt++;\n\t\tif (this._attempt > policy.maxAttempts) {\n\t\t\t// Preserve the completed attempt count so the host can emit the final failure.\n\t\t\tthis._attempt--;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst delayMs = computeRetryDelayMs(policy, this._attempt);\n\n\t\t// The retry window counts as active work from the instant listeners hear about it:\n\t\t// isRetrying must already be true inside onRetryStart handlers so prompts arriving there\n\t\t// queue as steering instead of racing the retry continuation.\n\t\tthis._abortController = new AbortController();\n\n\t\tthis.events.onRetryStart({\n\t\t\tattempt: this._attempt,\n\t\t\tmaxAttempts: policy.maxAttempts,\n\t\t\tdelayMs,\n\t\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t\t});\n\n\t\t// Remove the trailing assistant error from live agent state (the host session keeps it in history).\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\n\t\ttry {\n\t\t\tawait sleepAbortable(delayMs, this._abortController.signal);\n\t\t} catch {\n\t\t\t// Aborted mid-backoff: report the cancellation and reset so the next turn starts clean.\n\t\t\tconst attempt = this._attempt;\n\t\t\tthis._attempt = 0;\n\t\t\tthis.events.onRetryEnd({ success: false, attempt, finalError: \"Retry cancelled\" });\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._abortController = undefined;\n\t\t}\n\n\t\treturn true;\n\t}\n}\n"]}
1
+ {"version":3,"file":"retry-controller.js","sourceRoot":"","sources":["../../src/reliability/retry-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Host-agnostic auto-retry driver.\n *\n * Owns the retry attempt counter and the abortable backoff for one agent run. It reads the\n * failure verdict from {@link classifyFailure} (fed a host-computed context-overflow flag), so\n * billing/auth are terminal and overflow routes to compaction — never to a pointless retry.\n * The controller only ever touches `agent.state.messages` (it drops the trailing assistant error\n * before retrying); durable history stays the host session's responsibility.\n *\n * Ported from AgentSession._prepareRetry / _isRetryableError so the exact event ordering and\n * exhaustion semantics carry over: the retry window is marked active (isRetrying) before the\n * start event fires, so prompts arriving inside start handlers queue as steering instead of\n * racing the retry continuation.\n */\n\nimport { type AssistantMessage, isContextOverflow } from \"@caupulican/pi-ai\";\nimport type { AgentMessage } from \"../types.ts\";\nimport { classifyFailure } from \"./classifier.ts\";\nimport { computeRetryDelayMs, type RetryPolicy, sleepAbortable } from \"./retry.ts\";\n\n/** The slice of an Agent the retry driver reads and mutates: just the live transcript. */\nexport interface RetryAgent {\n\treadonly state: { messages: AgentMessage[] };\n}\n\nexport interface RetryStartInfo {\n\tattempt: number;\n\tmaxAttempts: number;\n\tdelayMs: number;\n\terrorMessage: string;\n}\n\nexport interface RetryEndInfo {\n\tsuccess: boolean;\n\tattempt: number;\n\tfinalError?: string;\n}\n\nexport interface RetryEvents {\n\tonRetryStart(info: RetryStartInfo): void;\n\tonRetryEnd(info: RetryEndInfo): void;\n}\n\n/** Runtime retry policy: the backoff shape plus the on/off switch the host resolves per call. */\nexport type RetryControllerPolicy = RetryPolicy & { enabled: boolean };\n\nexport class RetryController {\n\tprivate _attempt = 0;\n\tprivate _abortController: AbortController | undefined;\n\tprivate readonly agent: RetryAgent;\n\tprivate readonly getPolicy: () => RetryControllerPolicy;\n\tprivate readonly events: RetryEvents;\n\tprivate readonly getContextWindow: () => number;\n\n\tconstructor(\n\t\tagent: RetryAgent,\n\t\tgetPolicy: () => RetryControllerPolicy,\n\t\tevents: RetryEvents,\n\t\tgetContextWindow: () => number,\n\t) {\n\t\tthis.agent = agent;\n\t\tthis.getPolicy = getPolicy;\n\t\tthis.events = events;\n\t\tthis.getContextWindow = getContextWindow;\n\t}\n\n\t/** Completed retry attempts for the current run (0 when not retrying). */\n\tget attempt(): number {\n\t\treturn this._attempt;\n\t}\n\n\t/** True from the instant onRetryStart fires until the backoff sleep resolves or is aborted. */\n\tget isRetrying(): boolean {\n\t\treturn this._abortController !== undefined;\n\t}\n\n\t/** Clear the attempt counter — the host calls this after a successful turn or a final failure. */\n\treset(): void {\n\t\tthis._attempt = 0;\n\t}\n\n\t/** Cancel an in-progress backoff; the pending prepareRetry resolves false. */\n\tabort(): void {\n\t\tthis._abortController?.abort();\n\t}\n\n\t/**\n\t * Classify `message`; if it is retryable and attempts remain, drop the trailing assistant\n\t * error from agent state, emit the start event, and wait out the backoff (abortable).\n\t * @returns true if the caller should continue the agent, false otherwise.\n\t */\n\tasync prepareRetry(message: AssistantMessage): Promise<boolean> {\n\t\tconst policy = this.getPolicy();\n\t\tif (!policy.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// The classifier is the single source of the retry verdict: context overflow (host-computed\n\t\t// from the live window) routes to compaction, billing/auth are terminal, transient failures retry.\n\t\tconst classified = classifyFailure({\n\t\t\tmessage: message.errorMessage ?? \"\",\n\t\t\tcontextOverflow: isContextOverflow(message, this.getContextWindow()),\n\t\t\tprovider: message.provider,\n\t\t});\n\t\tif (!classified.retryable) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._attempt++;\n\t\tif (this._attempt > policy.maxAttempts) {\n\t\t\t// Preserve the completed attempt count so the host can emit the final failure.\n\t\t\tthis._attempt--;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst delayMs = computeRetryDelayMs(policy, this._attempt);\n\n\t\t// The retry window counts as active work from the instant listeners hear about it:\n\t\t// isRetrying must already be true inside onRetryStart handlers so prompts arriving there\n\t\t// queue as steering instead of racing the retry continuation.\n\t\tthis._abortController = new AbortController();\n\n\t\tthis.events.onRetryStart({\n\t\t\tattempt: this._attempt,\n\t\t\tmaxAttempts: policy.maxAttempts,\n\t\t\tdelayMs,\n\t\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t\t});\n\n\t\t// Remove the trailing assistant error from live agent state (the host session keeps it in history).\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\n\t\ttry {\n\t\t\tawait sleepAbortable(delayMs, this._abortController.signal);\n\t\t} catch {\n\t\t\t// Aborted mid-backoff: report the cancellation and reset so the next turn starts clean.\n\t\t\tconst attempt = this._attempt;\n\t\t\tthis._attempt = 0;\n\t\t\tthis.events.onRetryEnd({ success: false, attempt, finalError: \"Retry cancelled\" });\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._abortController = undefined;\n\t\t}\n\n\t\treturn true;\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caupulican/pi-agent-core",
3
- "version": "0.81.2",
3
+ "version": "0.81.3",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "prepublishOnly": "npm run clean && npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@caupulican/pi-ai": "^0.81.2",
36
+ "@caupulican/pi-ai": "^0.81.3",
37
37
  "ignore": "7.0.5",
38
38
  "typebox": "1.1.38",
39
39
  "yaml": "2.9.0"