agents 0.28.1 → 0.29.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.
Files changed (112) hide show
  1. package/dist/{agent-routing-BMvWTFEp.d.ts → agent-routing-CDbpVjYq.d.ts} +7 -8
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-ohoewW-B.d.ts → agent-tools-C8DH8Yii.d.ts} +2 -2
  5. package/dist/agent-tools.d.ts +1 -1
  6. package/dist/{ai-ahruGw7e.d.ts → ai-iXw0uOGj.d.ts} +3 -3
  7. package/dist/{ai-sdk-C2iTAwUd.js → ai-sdk-DRPh2rRa.js} +185 -125
  8. package/dist/ai-sdk-DRPh2rRa.js.map +1 -0
  9. package/dist/browser/ai.d.ts +1 -1
  10. package/dist/browser/ai.js +2 -2
  11. package/dist/browser/index.d.ts +8 -8
  12. package/dist/browser/index.js +3 -3
  13. package/dist/browser/tools/ai-sdk.d.ts +2 -2
  14. package/dist/browser/tools/ai-sdk.js +1 -1
  15. package/dist/browser/tools/pi.d.ts +1 -1
  16. package/dist/browser/tools/pi.js +3 -3
  17. package/dist/browser/tools/pi.js.map +1 -1
  18. package/dist/browser/tools/tanstack-ai.d.ts +2 -2
  19. package/dist/browser/tools/tanstack-ai.js +1 -1
  20. package/dist/{browser-CEdNlsaD.js → browser-DccPw_wW.js} +130 -79
  21. package/dist/browser-DccPw_wW.js.map +1 -0
  22. package/dist/{browser-D30VlpcA.d.ts → browser-DlXhSAdf.d.ts} +41 -37
  23. package/dist/{browser-tool-4iomavs_.js → browser-tool-B1mhifin.js} +3 -3
  24. package/dist/{browser-tool-4iomavs_.js.map → browser-tool-B1mhifin.js.map} +1 -1
  25. package/dist/{browser-tool-vtrPOV8e.d.ts → browser-tool-Cxjk5sgG.d.ts} +2 -2
  26. package/dist/chat/index.d.ts +143 -71
  27. package/dist/chat/index.js +269 -110
  28. package/dist/chat/index.js.map +1 -1
  29. package/dist/chat-sdk/index.d.ts +1 -1
  30. package/dist/chat-sdk/index.js +1 -1
  31. package/dist/cli.js +33 -33
  32. package/dist/{client-Bp-JwL3p.d.ts → client-C27CtRZr.d.ts} +2 -2
  33. package/dist/client.d.ts +1 -1
  34. package/dist/{connector-DEXzVoaY.d.ts → connector-20DJ6p4k.d.ts} +2 -2
  35. package/dist/{connector-CtZylXdk.js → connector-DAXBOIIi.js} +2 -2
  36. package/dist/{connector-CtZylXdk.js.map → connector-DAXBOIIi.js.map} +1 -1
  37. package/dist/experimental/channels/web/index.js +1 -1
  38. package/dist/harness/container/index.js +1 -1
  39. package/dist/harness/container/index.js.map +1 -1
  40. package/dist/harness/opencode/index.js +1 -1
  41. package/dist/harness/pi/index.d.ts +13 -5
  42. package/dist/harness/pi/index.js +8 -214
  43. package/dist/harness/pi/index.js.map +1 -1
  44. package/dist/{images-C4hiIexM.js → images-xaDv2Fqe.js} +636 -168
  45. package/dist/images-xaDv2Fqe.js.map +1 -0
  46. package/dist/index-DCxk3OSJ.d.ts +378 -0
  47. package/dist/{index-kbSUIer3.d.ts → index-iv9FTXX9.d.ts} +80 -112
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/{input-schema-xBofjBKC.js → input-schema-B0Etuo-o.js} +2 -2
  51. package/dist/{input-schema-xBofjBKC.js.map → input-schema-B0Etuo-o.js.map} +1 -1
  52. package/dist/lifecycle/index.js +1 -1
  53. package/dist/{lifecycle-DGorpAmA.js → lifecycle-ClT4DmF2.js} +11 -3
  54. package/dist/lifecycle-ClT4DmF2.js.map +1 -0
  55. package/dist/mcp/client/index.d.ts +1 -1
  56. package/dist/mcp/index.d.ts +1 -1
  57. package/dist/mcp/index.js +1 -1
  58. package/dist/models/ai-sdk/index.d.ts +29 -352
  59. package/dist/models/ai-sdk/index.js +1 -1
  60. package/dist/models/opencode/index.d.ts +25 -13
  61. package/dist/models/opencode/index.js +16 -11
  62. package/dist/models/opencode/index.js.map +1 -1
  63. package/dist/models/pi-ai/index.d.ts +13 -14
  64. package/dist/models/pi-ai/index.js +85 -32
  65. package/dist/models/pi-ai/index.js.map +1 -1
  66. package/dist/react.d.ts +2 -2
  67. package/dist/routing/index.d.ts +1 -1
  68. package/dist/serializable.d.ts +1 -1
  69. package/dist/{session-store-DoPY12xM.d.ts → session-store-_IK6dV-w.d.ts} +14 -2
  70. package/dist/{session-store-Do4xkDcf.js → session-store-yvwq1f9x.js} +55 -2
  71. package/dist/session-store-yvwq1f9x.js.map +1 -0
  72. package/dist/{settings-BjAiPd6Z.js → settings-Dk2il2Hf.js} +4 -1
  73. package/dist/settings-Dk2il2Hf.js.map +1 -0
  74. package/dist/skills/index.d.ts +22 -28
  75. package/dist/skills/index.js +183 -143
  76. package/dist/skills/index.js.map +1 -1
  77. package/dist/{src-CQxJFv4l.js → src-6lr7MtM_.js} +3 -3
  78. package/dist/src-6lr7MtM_.js.map +1 -0
  79. package/dist/sub-routing.d.ts +1 -1
  80. package/dist/{tool-VF0qiO7q.js → tool-DkT_DJBm.js} +2 -2
  81. package/dist/tool-DkT_DJBm.js.map +1 -0
  82. package/dist/transport-CPh7YGiw.d.ts +623 -0
  83. package/dist/voice/index.d.ts +1 -1
  84. package/dist/web/fetch/tools/pi.js +2 -2
  85. package/dist/web/fetch/tools/pi.js.map +1 -1
  86. package/dist/web/search/index.js +1 -1
  87. package/dist/web/search/tools/ai-sdk.js +2 -2
  88. package/dist/web/search/tools/pi.js +3 -3
  89. package/dist/web/search/tools/pi.js.map +1 -1
  90. package/dist/web/search/tools/tanstack-ai.js +2 -2
  91. package/dist/websockets/index.js +1 -1
  92. package/dist/{websockets-C3zy8YR0.js → websockets-CqxnntmE.js} +2 -2
  93. package/dist/{websockets-C3zy8YR0.js.map → websockets-CqxnntmE.js.map} +1 -1
  94. package/dist/workflow-types.d.ts +1 -1
  95. package/dist/workflows.d.ts +1 -1
  96. package/dist/workflows.js +1 -1
  97. package/docs/browse-the-web.md +1 -1
  98. package/docs/chat-agents.md +19 -18
  99. package/docs/harnesses/pi.md +47 -18
  100. package/docs/models-pi-ai.md +62 -20
  101. package/docs/models.md +197 -172
  102. package/package.json +7 -7
  103. package/dist/ai-sdk-C2iTAwUd.js.map +0 -1
  104. package/dist/browser-CEdNlsaD.js.map +0 -1
  105. package/dist/images-C4hiIexM.js.map +0 -1
  106. package/dist/lifecycle-DGorpAmA.js.map +0 -1
  107. package/dist/session-store-Do4xkDcf.js.map +0 -1
  108. package/dist/settings-BjAiPd6Z.js.map +0 -1
  109. package/dist/settings-DnD__xuE.d.ts +0 -349
  110. package/dist/src-CQxJFv4l.js.map +0 -1
  111. package/dist/tool-VF0qiO7q.js.map +0 -1
  112. package/dist/transport-DCyarnwm.d.ts +0 -200
@@ -1,4 +1,6 @@
1
1
  import { isPositionlessAgentToolChunk } from "../agent-tool-types.js";
2
+ import { i as _classPrivateFieldInitSpec, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js";
3
+ import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js";
2
4
  import { isDurableObjectResetError, isPlatformFailure, tryN } from "../retries.js";
3
5
  import { a as isReplayChunk, i as isLateToolInputChunk, n as applyLateToolInput, o as lateToolInputForwardChunks, r as getPartialStreamText, s as normalizeToolInput, t as applyChunkToParts } from "../message-builder-DAl9AhHH.js";
4
6
  import { a as interceptAgentToolBroadcast, i as createAgentToolEventState, r as applyAgentToolEvent, s as storedChunkBytes, t as AgentToolProgressEmitter } from "../agent-tools-CdJC-8Dc.js";
@@ -3003,18 +3005,14 @@ const DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS = 10;
3003
3005
  */
3004
3006
  const DEFAULT_CHAT_RECOVERY_MAX_WORK = 1e4;
3005
3007
  /**
3006
- * Tight, OOM-specific retry budget (#1825). A Durable Object memory-limit reset
3007
- * (`isDurableObjectMemoryLimitReset`) is usually deterministic — the turn's
3008
- * working set no longer fits in the isolate's 128 MB — so re-running it re-OOMs.
3009
- * But a single OOM CAN be a transient spike (the isolate's 128 MB is shared
3010
- * across the global scope / noisy neighbors), so recovery retries a small number
3011
- * of times before sealing with `reason="out_of_memory"` rather than abandoning a
3012
- * turn that one more attempt might have completed. Far tighter than the generic
3013
- * `maxRecoveryWork` backstop because an OOM is attributable and re-running it is
3014
- * expensive (it re-runs the model). Counts attempts that ended in an OOM, not
3015
- * total attempts, so a turn interrupted by deploys (no OOM) is unaffected.
3008
+ * Default number of interrupted attempts a turn may accumulate before recovery
3009
+ * seals it with `reason="interrupted_attempts_exceeded"` (#2613). The original
3010
+ * turn counts, so a turn that dies every time runs the model at most five
3011
+ * times. A deterministic memory-limit reset dies every time; a deploy rarely
3012
+ * lands on the same turn five times in a row, so five tolerates churn while
3013
+ * still bounding a crash loop.
3016
3014
  */
3017
- const DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES = 3;
3015
+ const DEFAULT_CHAT_RECOVERY_MAX_INTERRUPTED_ATTEMPTS = 5;
3018
3016
  const DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS = 1e4;
3019
3017
  /**
3020
3018
  * Delay before retrying a recovery that timed out waiting for stable state.
@@ -3061,12 +3059,25 @@ function resolveChatRecoveryConfig(raw) {
3061
3059
  terminalMessage: custom?.terminalMessage ?? "The assistant was interrupted and could not recover. Please try again.",
3062
3060
  noProgressTimeoutMs: Math.max(0, Math.floor(custom?.noProgressTimeoutMs ?? 3e5)),
3063
3061
  maxRecoveryWork: typeof custom?.maxRecoveryWork === "number" && custom.maxRecoveryWork >= 0 ? custom.maxRecoveryWork : DEFAULT_CHAT_RECOVERY_MAX_WORK,
3064
- maxOomRetries: typeof custom?.maxOomRetries === "number" && custom.maxOomRetries >= 0 ? Math.floor(custom.maxOomRetries) : 3,
3062
+ maxInterruptedAttempts: resolveMaxInterruptedAttempts(custom),
3065
3063
  ...custom?.shouldKeepRecovering ? { shouldKeepRecovering: custom.shouldKeepRecovering } : {},
3066
3064
  ...custom?.onExhausted ? { onExhausted: custom.onExhausted } : {}
3067
3065
  };
3068
3066
  }
3069
3067
  /**
3068
+ * Read the interruption budget, honoring the deprecated `maxOomRetries` alias
3069
+ * as `maxOomRetries + 1` so an explicit retry count keeps its meaning. A
3070
+ * negative or non-numeric value falls back to the default; `Infinity` is kept
3071
+ * and disables the bound.
3072
+ */
3073
+ function resolveMaxInterruptedAttempts(custom) {
3074
+ const attempts = custom?.maxInterruptedAttempts;
3075
+ if (typeof attempts === "number" && attempts >= 0) return Math.max(1, Math.floor(attempts));
3076
+ const legacyRetries = custom?.maxOomRetries;
3077
+ if (typeof legacyRetries === "number" && legacyRetries >= 0) return Math.floor(legacyRetries) + 1;
3078
+ return 5;
3079
+ }
3080
+ /**
3070
3081
  * Stable identifier for a recovery incident.
3071
3082
  *
3072
3083
  * `recoveryKind` is intentionally NOT part of the identity: a single
@@ -3293,6 +3304,10 @@ async function setChatRecovering(active, requestId, deps) {
3293
3304
  * no-progress alarm loop.
3294
3305
  * - RUNAWAY — the work budget seals a loop that keeps emitting content but
3295
3306
  * never converges. Keyed to WORK done, not wall-clock. Defaults to no cap.
3307
+ * - CRASH-LOOP — the interruption budget counts attempts whose isolate died
3308
+ * before they finished, and never resets on progress. The death is
3309
+ * counted by whoever starts the next attempt, because the isolate that
3310
+ * died cannot record anything (#2613).
3296
3311
  * - CALLER — `shouldKeepRecovering` lets the integrator express a
3297
3312
  * token/cost/step budget the SDK should not hardcode. Consulted only when no
3298
3313
  * hard bound has already sealed the incident, and never on first detection.
@@ -3301,7 +3316,7 @@ async function setChatRecovering(active, requestId, deps) {
3301
3316
  * suppressed and the no-progress clock kept fresh.
3302
3317
  */
3303
3318
  async function evaluateChatRecoveryIncident(input) {
3304
- const { identity, config, existing, currentProgress, awaitingClientInteraction, now } = input;
3319
+ const { identity, trigger, config, existing, currentProgress, awaitingClientInteraction, now } = input;
3305
3320
  const incidentId = chatRecoveryIncidentId(identity);
3306
3321
  const recoveryRootRequestId = identity.recoveryRootRequestId ?? identity.requestId;
3307
3322
  const prevProgress = existing?.progress ?? 0;
@@ -3312,14 +3327,15 @@ async function evaluateChatRecoveryIncident(input) {
3312
3327
  const progress = Math.max(prevProgress, currentProgress);
3313
3328
  const work = progress - workBaseline;
3314
3329
  const workBudgetExceeded = existing != null && Number.isFinite(config.maxRecoveryWork) && work > config.maxRecoveryWork;
3315
- const oomAttempts = existing?.oomAttempts ?? 0;
3316
- const oomBudgetExceeded = existing != null && !awaitingClientInteraction && oomAttempts > config.maxOomRetries;
3330
+ const newlyInterrupted = trigger.kind !== "interrupted" ? 0 : existing == null ? 1 : deadIsolateCount(existing.attemptsInFlight, trigger.attempt);
3331
+ const interruptedAttempts = (existing?.interruptedAttempts ?? 0) + newlyInterrupted;
3332
+ const interruptedBudgetExceeded = !awaitingClientInteraction && interruptedAttempts >= config.maxInterruptedAttempts;
3317
3333
  const transientRetries = existing?.transientRetries ?? 0;
3318
3334
  const transientBudgetExceeded = existing != null && !awaitingClientInteraction && transientRetries >= config.maxAttempts;
3319
3335
  const debounced = existing != null && !madeProgress && now - existing.lastAttemptAt < 3e4;
3320
3336
  const attempt = madeProgress ? 1 : debounced ? existing?.attempt ?? 1 : (existing?.attempt ?? 0) + 1;
3321
3337
  let abortedByCaller = false;
3322
- if (existing != null && !awaitingClientInteraction && config.shouldKeepRecovering && !noProgressExceeded && !workBudgetExceeded && !oomBudgetExceeded && !transientBudgetExceeded && attempt <= config.maxAttempts) try {
3338
+ if (existing != null && !awaitingClientInteraction && config.shouldKeepRecovering && !noProgressExceeded && !workBudgetExceeded && !interruptedBudgetExceeded && !transientBudgetExceeded && attempt <= config.maxAttempts) try {
3323
3339
  const ctx = {
3324
3340
  incidentId,
3325
3341
  requestId: identity.requestId,
@@ -3334,7 +3350,7 @@ async function evaluateChatRecoveryIncident(input) {
3334
3350
  } catch (error) {
3335
3351
  input.onShouldKeepRecoveringError?.(error);
3336
3352
  }
3337
- const exhausted = !awaitingClientInteraction && (oomBudgetExceeded || noProgressExceeded || workBudgetExceeded || abortedByCaller || transientBudgetExceeded || attempt > config.maxAttempts);
3353
+ const exhausted = !awaitingClientInteraction && (interruptedBudgetExceeded || noProgressExceeded || workBudgetExceeded || abortedByCaller || transientBudgetExceeded || attempt > config.maxAttempts);
3338
3354
  const incident = {
3339
3355
  incidentId,
3340
3356
  requestId: identity.requestId,
@@ -3348,9 +3364,10 @@ async function evaluateChatRecoveryIncident(input) {
3348
3364
  lastProgressAt,
3349
3365
  progress,
3350
3366
  workBaseline,
3351
- ...oomAttempts > 0 ? { oomAttempts } : {},
3367
+ ...interruptedAttempts > 0 ? { interruptedAttempts } : {},
3368
+ ...attemptsInFlightAfter(trigger, existing, exhausted),
3352
3369
  ...transientRetries > 0 ? { transientRetries } : {},
3353
- ...exhausted ? { reason: oomBudgetExceeded ? "out_of_memory" : workBudgetExceeded ? "work_budget_exceeded" : noProgressExceeded ? "no_progress_timeout" : abortedByCaller ? "recovery_aborted" : "max_attempts_exceeded" } : {}
3370
+ ...exhausted ? { reason: interruptedBudgetExceeded ? "interrupted_attempts_exceeded" : workBudgetExceeded ? "work_budget_exceeded" : noProgressExceeded ? "no_progress_timeout" : abortedByCaller ? "recovery_aborted" : "max_attempts_exceeded" } : {}
3354
3371
  };
3355
3372
  const events = [];
3356
3373
  if (!existing) events.push({
@@ -3375,8 +3392,87 @@ async function evaluateChatRecoveryIncident(input) {
3375
3392
  events
3376
3393
  };
3377
3394
  }
3395
+ /**
3396
+ * How many other isolates left attempts in `recorded`. Each such isolate died
3397
+ * mid-attempt once, however many attempts it was running.
3398
+ */
3399
+ function deadIsolateCount(recorded, current) {
3400
+ const dead = /* @__PURE__ */ new Set();
3401
+ for (const attempt of recorded ?? []) if (attempt.isolate !== current.isolate) dead.add(attempt.isolate);
3402
+ return dead.size;
3403
+ }
3404
+ /** The attempts in `recorded` that are still running in `current`'s isolate. */
3405
+ function liveAttempts(recorded, current) {
3406
+ return (recorded ?? []).filter((attempt) => attempt.isolate === current.isolate);
3407
+ }
3408
+ /** Persist `attempts` only when there is at least one. */
3409
+ function withAttemptsInFlight(attempts) {
3410
+ return attempts.length > 0 ? { attemptsInFlight: attempts } : {};
3411
+ }
3412
+ /**
3413
+ * The `attemptsInFlight` an incident opening leaves behind. A replay drops
3414
+ * the dead isolates' entries it just counted and adds itself, unless the
3415
+ * incident is now sealed; a stall leaves the running attempts untouched.
3416
+ */
3417
+ function attemptsInFlightAfter(trigger, existing, exhausted) {
3418
+ if (trigger.kind === "interrupted") {
3419
+ const live = liveAttempts(existing?.attemptsInFlight, trigger.attempt);
3420
+ return withAttemptsInFlight(exhausted ? live : [...live, trigger.attempt]);
3421
+ }
3422
+ return withAttemptsInFlight([...existing?.attemptsInFlight ?? []]);
3423
+ }
3424
+ /**
3425
+ * Admit one recovery attempt (a `_chatRecoveryContinue` / `_chatRecoveryRetry`
3426
+ * callback and the turn it starts) before it does any heavy work (#2613).
3427
+ *
3428
+ * Entries in `attemptsInFlight` left by other isolates are attempts that
3429
+ * died, for example to a memory-limit reset before or during their turn.
3430
+ * Each such isolate counts as one interrupted attempt and its entries are
3431
+ * dropped. The returned incident adds `attempt` to the running attempts,
3432
+ * unless the budget is spent, in which case the caller seals the incident
3433
+ * instead of running anything. A parked turn is budget-free: it is counted
3434
+ * but never sealed.
3435
+ *
3436
+ * @param input - The incident, the attempt about to start, and the budget.
3437
+ * @returns The incident to persist, and whether the budget is spent.
3438
+ */
3439
+ function admitChatRecoveryAttempt(input) {
3440
+ const { existing, attempt, config, awaitingClientInteraction } = input;
3441
+ const interruptedAttempts = (existing.interruptedAttempts ?? 0) + deadIsolateCount(existing.attemptsInFlight, attempt);
3442
+ const exhausted = !awaitingClientInteraction && interruptedAttempts >= config.maxInterruptedAttempts;
3443
+ const live = liveAttempts(existing.attemptsInFlight, attempt);
3444
+ const { attemptsInFlight: _previous, ...rest } = existing;
3445
+ return {
3446
+ incident: {
3447
+ ...rest,
3448
+ ...interruptedAttempts > 0 ? { interruptedAttempts } : {},
3449
+ ...withAttemptsInFlight(exhausted ? live : [...live, attempt])
3450
+ },
3451
+ exhausted
3452
+ };
3453
+ }
3454
+ /**
3455
+ * Remove `attempt` from the running attempts once it finished in this
3456
+ * isolate. Other running attempts stay recorded.
3457
+ *
3458
+ * @param existing - The persisted incident.
3459
+ * @param attempt - The attempt that finished.
3460
+ * @returns The incident to persist, or `null` when `attempt` is not recorded.
3461
+ */
3462
+ function finishChatRecoveryAttempt(existing, attempt) {
3463
+ const recorded = existing.attemptsInFlight ?? [];
3464
+ const remaining = recorded.filter((entry) => entry.token !== attempt.token);
3465
+ if (remaining.length === recorded.length) return null;
3466
+ const { attemptsInFlight: _finished, ...rest } = existing;
3467
+ return {
3468
+ ...rest,
3469
+ ...withAttemptsInFlight(remaining)
3470
+ };
3471
+ }
3378
3472
  //#endregion
3379
3473
  //#region src/chat/recovery-engine.ts
3474
+ var _isolate = /* @__PURE__ */ new WeakMap();
3475
+ var _ChatRecoveryEngine_brand = /* @__PURE__ */ new WeakSet();
3380
3476
  /**
3381
3477
  * Drives the shared recovery orchestration over a {@link ChatRecoveryAdapter}.
3382
3478
  * The incident *budget math* lives in the pure `evaluateChatRecoveryIncident`;
@@ -3385,6 +3481,59 @@ async function evaluateChatRecoveryIncident(input) {
3385
3481
  var ChatRecoveryEngine = class {
3386
3482
  constructor(adapter) {
3387
3483
  this.adapter = adapter;
3484
+ _classPrivateMethodInitSpec(this, _ChatRecoveryEngine_brand);
3485
+ _classPrivateFieldInitSpec(this, _isolate, crypto.randomUUID());
3486
+ }
3487
+ /**
3488
+ * Run one recovery attempt under the interruption budget (#2613): a
3489
+ * `_chatRecoveryContinue` / `_chatRecoveryRetry` callback and the turn it
3490
+ * starts. Before `run` does anything heavy, the attempt counts any earlier
3491
+ * attempt that died (entries another isolate left in `attemptsInFlight`)
3492
+ * and records itself, confirmed with `syncStorage`. Once
3493
+ * `maxInterruptedAttempts` attempts have died the turn is sealed with
3494
+ * `interrupted_attempts_exceeded` and `run` is never called. The entry is
3495
+ * removed when `run` settles, whatever its outcome.
3496
+ *
3497
+ * This catches deaths the replay of an interrupted turn run cannot see, such
3498
+ * as a reset before the callback starts its turn, which leaves no turn run
3499
+ * to replay. A callback for a sealed incident does nothing: the turn was
3500
+ * already terminalized, and running it would re-run the model. A payload
3501
+ * without an incident, or one whose incident is gone, runs untracked.
3502
+ *
3503
+ * @param input - The callback being run, its payload, and the work.
3504
+ */
3505
+ async runRecoveryAttempt(input) {
3506
+ const { adapter } = this;
3507
+ const incidentId = typeof input.data?.incidentId === "string" ? input.data.incidentId : void 0;
3508
+ const existing = incidentId ? await adapter.getIncident(chatRecoveryIncidentKey(incidentId)) : null;
3509
+ if (existing?.status === "exhausted") return;
3510
+ if (!incidentId || !existing) {
3511
+ await input.run();
3512
+ return;
3513
+ }
3514
+ adapter.ensureInteractionStateLoaded?.();
3515
+ const attempt = _assertClassBrand(_ChatRecoveryEngine_brand, this, _newAttempt).call(this);
3516
+ const { incident, exhausted } = admitChatRecoveryAttempt({
3517
+ existing,
3518
+ attempt,
3519
+ config: adapter.resolveConfig(),
3520
+ awaitingClientInteraction: adapter.isAwaitingClientInteraction?.() ?? false
3521
+ });
3522
+ await adapter.putIncident(chatRecoveryIncidentKey(incidentId), incident);
3523
+ await adapter.syncStorage();
3524
+ if (exhausted) {
3525
+ await this.exhaustRecoveryGiveUp({
3526
+ callback: input.callback,
3527
+ data: { incidentId },
3528
+ reason: "interrupted_attempts_exceeded"
3529
+ });
3530
+ return;
3531
+ }
3532
+ try {
3533
+ await input.run();
3534
+ } finally {
3535
+ await _assertClassBrand(_ChatRecoveryEngine_brand, this, _finishAttempt).call(this, incidentId, attempt);
3536
+ }
3388
3537
  }
3389
3538
  /**
3390
3539
  * Open or re-evaluate the recovery incident for `input`, persist the result,
@@ -3447,61 +3596,32 @@ var ChatRecoveryEngine = class {
3447
3596
  streamStatus
3448
3597
  });
3449
3598
  const recoveryRootRequestId = snapshot?.recoveryRootRequestId ?? requestId;
3450
- const { incident, config, exhausted } = await this.beginIncident({
3599
+ const { incident, config, exhausted, attempt } = await this.beginIncident({
3451
3600
  requestId,
3452
3601
  recoveryRootRequestId,
3453
3602
  latestUserMessageId: snapshot?.latestUserMessageId,
3454
- recoveryKind
3603
+ recoveryKind,
3604
+ trigger: "interrupted"
3455
3605
  });
3456
- if (exhausted) {
3457
- if (await this._shouldPersistOrphanedPartial(wake, {
3458
- streamId,
3459
- streamStillActive,
3460
- streamStatus,
3461
- snapshot,
3462
- options: void 0,
3463
- partial
3464
- })) await wake.persistOrphanedStream(streamId);
3465
- await adapter.exhaustChatRecovery(incident, config, partial, streamId, ctx.createdAt, snapshot?.originMessageIds);
3466
- return true;
3467
- }
3468
3606
  try {
3469
- const options = await wake.invokeOnChatRecovery?.({
3607
+ return await _assertClassBrand(_ChatRecoveryEngine_brand, this, _replayRecoveredTurn).call(this, wake, {
3608
+ ctx,
3470
3609
  incident,
3610
+ config,
3611
+ exhausted,
3471
3612
  recoveryKind,
3472
- recoveryRootRequestId,
3613
+ detail,
3473
3614
  requestId,
3474
- streamId,
3475
- partial,
3615
+ recoveryRootRequestId,
3476
3616
  snapshot,
3477
3617
  recoveryData,
3478
- createdAt: ctx.createdAt
3479
- }) ?? {};
3480
- if (await this._shouldPersistOrphanedPartial(wake, {
3481
3618
  streamId,
3482
3619
  streamStillActive,
3483
3620
  streamStatus,
3484
- snapshot,
3485
- options,
3486
3621
  partial
3487
- })) await wake.persistOrphanedStream(streamId);
3488
- if (streamStillActive) await wake.completeRecoveredStream(streamId);
3489
- await wake.dispatchRecoveredTurn({
3490
- incident,
3491
- config,
3492
- recoveryKind,
3493
- options,
3494
- snapshot,
3495
- requestId,
3496
- recoveryRootRequestId,
3497
- streamId,
3498
- streamStatus,
3499
- detail
3500
3622
  });
3501
- return true;
3502
- } catch (error) {
3503
- await this.updateIncident(incident.incidentId, "failed", error instanceof Error ? error.message : String(error));
3504
- throw error;
3623
+ } finally {
3624
+ if (attempt) await _assertClassBrand(_ChatRecoveryEngine_brand, this, _finishAttempt).call(this, incident.incidentId, attempt);
3505
3625
  }
3506
3626
  }
3507
3627
  /**
@@ -3530,21 +3650,29 @@ var ChatRecoveryEngine = class {
3530
3650
  await adapter.sweepStaleIncidents(now);
3531
3651
  const existing = await adapter.getIncident(key);
3532
3652
  adapter.ensureInteractionStateLoaded?.();
3653
+ const currentProgress = await adapter.readProgress();
3654
+ const trigger = input.trigger === "interrupted" ? {
3655
+ kind: "interrupted",
3656
+ attempt: _assertClassBrand(_ChatRecoveryEngine_brand, this, _newAttempt).call(this)
3657
+ } : { kind: "stalled" };
3533
3658
  const { incident, exhausted, events } = await evaluateChatRecoveryIncident({
3534
3659
  identity: input,
3660
+ trigger,
3535
3661
  config,
3536
3662
  existing,
3537
- currentProgress: await adapter.readProgress(),
3663
+ currentProgress,
3538
3664
  awaitingClientInteraction: adapter.isAwaitingClientInteraction?.() ?? false,
3539
3665
  now,
3540
3666
  onShouldKeepRecoveringError: (error) => adapter.onShouldKeepRecoveringError?.(error)
3541
3667
  });
3542
3668
  await adapter.putIncident(key, incident);
3669
+ await adapter.syncStorage();
3543
3670
  for (const event of events) adapter.emitRecoveryEvent(event);
3544
3671
  return {
3545
3672
  incident,
3546
3673
  config,
3547
- exhausted
3674
+ exhausted,
3675
+ attempt: trigger.kind === "interrupted" && !exhausted ? trigger.attempt : null
3548
3676
  };
3549
3677
  }
3550
3678
  /**
@@ -3656,54 +3784,6 @@ var ChatRecoveryEngine = class {
3656
3784
  return incident?.status === "skipped" && incident.reason === "user_cancelled";
3657
3785
  }
3658
3786
  /**
3659
- * Record that a recovery callback observed a Durable Object memory-limit reset
3660
- * (the isolate exceeded its 128 MB limit — `isDurableObjectMemoryLimitReset`)
3661
- * and decide what to do next (#1825).
3662
- *
3663
- * Bumps the incident's durable `oomAttempts` counter, then:
3664
- * - if it is still within `maxOomRetries`, issues a delayed, NON-idempotent
3665
- * reschedule of the SAME callback (same machinery as
3666
- * {@link rescheduleAfterStableTimeout}: the executing attempt settles
3667
- * only after the callback returns, so an idempotent reschedule would
3668
- * dedup onto that doomed attempt) and returns `"rescheduled"`. The small
3669
- * delay lets a transient memory spike clear before the re-run;
3670
- * - otherwise leaves the incremented count persisted (so a begin-path
3671
- * re-evaluation agrees) and returns `"exhausted"` — the caller then
3672
- * terminalizes via the give-up path with `reason="out_of_memory"`.
3673
- *
3674
- * Returns `"exhausted"` when there is no incident to track against (no id /
3675
- * record gone): an OOM we cannot bound must seal rather than loop. Unlike a
3676
- * stable-state retry this is gated by the OOM-specific budget, NOT the generic
3677
- * attempt cap — re-running an OOM streams a little "progress" that would
3678
- * otherwise reset the attempt cap forever (the #1825 loop).
3679
- */
3680
- async recordOomAndDecide(input) {
3681
- const { adapter } = this;
3682
- if (!input.incidentId) return "exhausted";
3683
- const key = chatRecoveryIncidentKey(input.incidentId);
3684
- const incident = await adapter.getIncident(key);
3685
- if (!incident) return "exhausted";
3686
- const oomAttempts = (incident.oomAttempts ?? 0) + 1;
3687
- if (oomAttempts > input.maxOomRetries) {
3688
- await adapter.putIncident(key, {
3689
- ...incident,
3690
- oomAttempts,
3691
- lastAttemptAt: adapter.now(),
3692
- reason: "out_of_memory"
3693
- });
3694
- return "exhausted";
3695
- }
3696
- await adapter.putIncident(key, {
3697
- ...incident,
3698
- oomAttempts,
3699
- status: "scheduled",
3700
- lastAttemptAt: adapter.now(),
3701
- reason: "oom_retry"
3702
- });
3703
- await adapter.scheduleRecovery(input.callback, input.data ?? {}, "stable_timeout_retry", 3);
3704
- return "rescheduled";
3705
- }
3706
- /**
3707
3787
  * Give up on a recovery turn whose retry budget drained, terminalizing it so
3708
3788
  * it can never become an eternal spinner (#1645). The shared spine both
3709
3789
  * packages repeated verbatim:
@@ -3822,6 +3902,85 @@ var ChatRecoveryEngine = class {
3822
3902
  else if (status === "completed" || status === "skipped" || status === "failed") await adapter.setRecovering(false);
3823
3903
  }
3824
3904
  };
3905
+ function _newAttempt() {
3906
+ return {
3907
+ isolate: _classPrivateFieldGet2(_isolate, this),
3908
+ token: crypto.randomUUID()
3909
+ };
3910
+ }
3911
+ /**
3912
+ * Remove a finished attempt's entry and confirm the write, so a reset right
3913
+ * after a clean finish cannot make the attempt look interrupted.
3914
+ * Best-effort: in a reset isolate this fails too, and the entry it leaves
3915
+ * behind is exactly what tells the next isolate that this attempt died.
3916
+ */
3917
+ async function _finishAttempt(incidentId, attempt) {
3918
+ const { adapter } = this;
3919
+ const key = chatRecoveryIncidentKey(incidentId);
3920
+ try {
3921
+ const stored = await adapter.getIncident(key);
3922
+ const next = stored ? finishChatRecoveryAttempt(stored, attempt) : null;
3923
+ if (next) {
3924
+ await adapter.putIncident(key, next);
3925
+ await adapter.syncStorage();
3926
+ }
3927
+ } catch {}
3928
+ }
3929
+ /** The part of {@link handleChatFiberRecovery} that runs after the incident opened. */
3930
+ async function _replayRecoveredTurn(wake, input) {
3931
+ const { adapter } = this;
3932
+ const { ctx, incident, config, exhausted, recoveryKind, detail, requestId, recoveryRootRequestId, snapshot, recoveryData, streamId, streamStillActive, streamStatus, partial } = input;
3933
+ if (exhausted) {
3934
+ if (await this._shouldPersistOrphanedPartial(wake, {
3935
+ streamId,
3936
+ streamStillActive,
3937
+ streamStatus,
3938
+ snapshot,
3939
+ options: void 0,
3940
+ partial
3941
+ })) await wake.persistOrphanedStream(streamId);
3942
+ await adapter.exhaustChatRecovery(incident, config, partial, streamId, ctx.createdAt, snapshot?.originMessageIds);
3943
+ return true;
3944
+ }
3945
+ try {
3946
+ const options = await wake.invokeOnChatRecovery?.({
3947
+ incident,
3948
+ recoveryKind,
3949
+ recoveryRootRequestId,
3950
+ requestId,
3951
+ streamId,
3952
+ partial,
3953
+ snapshot,
3954
+ recoveryData,
3955
+ createdAt: ctx.createdAt
3956
+ }) ?? {};
3957
+ if (await this._shouldPersistOrphanedPartial(wake, {
3958
+ streamId,
3959
+ streamStillActive,
3960
+ streamStatus,
3961
+ snapshot,
3962
+ options,
3963
+ partial
3964
+ })) await wake.persistOrphanedStream(streamId);
3965
+ if (streamStillActive) await wake.completeRecoveredStream(streamId);
3966
+ await wake.dispatchRecoveredTurn({
3967
+ incident,
3968
+ config,
3969
+ recoveryKind,
3970
+ options,
3971
+ snapshot,
3972
+ requestId,
3973
+ recoveryRootRequestId,
3974
+ streamId,
3975
+ streamStatus,
3976
+ detail
3977
+ });
3978
+ return true;
3979
+ } catch (error) {
3980
+ await this.updateIncident(incident.incidentId, "failed", error instanceof Error ? error.message : String(error));
3981
+ throw error;
3982
+ }
3983
+ }
3825
3984
  /** `incident.reason` for a scheduled recovery the user cancelled. */
3826
3985
  const CHAT_RECOVERY_CANCELLED_REASON = "user_cancelled";
3827
3986
  /**
@@ -4159,6 +4318,6 @@ function isProviderExecuted(part) {
4159
4318
  return part.providerExecuted === true;
4160
4319
  }
4161
4320
  //#endregion
4162
- export { AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS, AbortRegistry, AgentToolProgressEmitter, AgentToolStreamProgressThrottle, AutoContinuationController, CHAT_LAST_TERMINAL_KEY, CHAT_MESSAGE_TYPES, CHAT_RECOVERING_FLAG_TTL_MS, CHAT_RECOVERING_KEY, CHAT_RECOVERY_ALARM_DEBOUNCE_MS, CHAT_RECOVERY_CANCELLED_REASON, CHAT_RECOVERY_INCIDENT_KEY_PREFIX, CHAT_RECOVERY_INCIDENT_TTL_MS, CHAT_RECOVERY_PROGRESS_KEY, CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS, CHAT_RECOVERY_TASK_NAME, CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS, ChatRecoveryEngine, ChatStreamStalledError, ContinuationState, DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS, DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES, DEFAULT_CHAT_RECOVERY_MAX_WORK, DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS, DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS, DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE, KV_DELETE_MAX_KEYS, MAX_BOUND_PARAMS, MessageType, PreStreamTurns, ROW_MAX_BYTES, ResumableStream, ResumeHandshake, STREAM_RESUME_NONE_REASONS, StreamAccumulator, StreamProgressCreditThrottle, SubmitConcurrencyController, TIMED_OUT, TextSegmentJoiner, TurnQueue, aiSdkRecoveryCodec, applyAgentToolEvent, applyChunkToParts, applyLateToolInput, applyToolUpdate, awaitWithDeadline, transition as broadcastTransition, buildChatRecoveringFrame, buildInClauseStrings, bumpChatRecoveryProgress, byteLength, chatRecoveryBackoffSeconds, chatRecoveryTaskRunOptions, classifyAgentToolChildRecovery, clearChatTerminal, clientResolvableToolNames, createAgentToolEventState, createChatFiberSnapshot, createChatRecoveryTaskDefinition, createChatStreams, createChatTurnTaskDefinition, createToolsFromClientSchemas, crossMessageToolResultUpdate, dispatchChatRecoveryToHandoff, drainInteractionApplies, enforceRowSizeLimit, hasIncompleteToolBatch, interceptAgentToolBroadcast, isDurableObjectResetError, isLateToolInputChunk, isPlatformFailure, isPositionlessAgentToolChunk, isReplayChunk, iterateWithStallWatchdog, lateToolInputForwardChunks, listActiveChatRecoveryIncidents, normalizeToolInput, originMessageIds, parseProtocolMessage, partAwaitsClientInteraction, partialHasSettledToolResults, pausedExecutionUpdate, pendingChatTerminal, persistReconstructedOrphan, readChatRecoveryProgress, reconcileMessages, reconcileOrphanPartial, recordChatTerminal, repairInterruptedToolParts, resolveChatRecoveryConfig, resolveToolMergeId, retryAfterSeconds, runChatRecoveryExhaustion, sanitizeMessage, sendIfOpen, setChatRecovering, shouldCreditStreamProgress, sweepStaleChatRecoveryIncidents, toolApprovalUpdate, toolPartHasSettledResult, toolResultUpdate, truncateOlderMessages, truncateOlderToolResults, unwrapChatFiberSnapshot, withOriginMessageIds, wrapChatFiberSnapshot };
4321
+ export { AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS, AbortRegistry, AgentToolProgressEmitter, AgentToolStreamProgressThrottle, AutoContinuationController, CHAT_LAST_TERMINAL_KEY, CHAT_MESSAGE_TYPES, CHAT_RECOVERING_FLAG_TTL_MS, CHAT_RECOVERING_KEY, CHAT_RECOVERY_ALARM_DEBOUNCE_MS, CHAT_RECOVERY_CANCELLED_REASON, CHAT_RECOVERY_INCIDENT_KEY_PREFIX, CHAT_RECOVERY_INCIDENT_TTL_MS, CHAT_RECOVERY_PROGRESS_KEY, CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS, CHAT_RECOVERY_TASK_NAME, CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS, ChatRecoveryEngine, ChatStreamStalledError, ContinuationState, DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS, DEFAULT_CHAT_RECOVERY_MAX_INTERRUPTED_ATTEMPTS, DEFAULT_CHAT_RECOVERY_MAX_WORK, DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS, DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS, DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE, KV_DELETE_MAX_KEYS, MAX_BOUND_PARAMS, MessageType, PreStreamTurns, ROW_MAX_BYTES, ResumableStream, ResumeHandshake, STREAM_RESUME_NONE_REASONS, StreamAccumulator, StreamProgressCreditThrottle, SubmitConcurrencyController, TIMED_OUT, TextSegmentJoiner, TurnQueue, aiSdkRecoveryCodec, applyAgentToolEvent, applyChunkToParts, applyLateToolInput, applyToolUpdate, awaitWithDeadline, transition as broadcastTransition, buildChatRecoveringFrame, buildInClauseStrings, bumpChatRecoveryProgress, byteLength, chatRecoveryBackoffSeconds, chatRecoveryTaskRunOptions, classifyAgentToolChildRecovery, clearChatTerminal, clientResolvableToolNames, createAgentToolEventState, createChatFiberSnapshot, createChatRecoveryTaskDefinition, createChatStreams, createChatTurnTaskDefinition, createToolsFromClientSchemas, crossMessageToolResultUpdate, dispatchChatRecoveryToHandoff, drainInteractionApplies, enforceRowSizeLimit, hasIncompleteToolBatch, interceptAgentToolBroadcast, isDurableObjectResetError, isLateToolInputChunk, isPlatformFailure, isPositionlessAgentToolChunk, isReplayChunk, iterateWithStallWatchdog, lateToolInputForwardChunks, listActiveChatRecoveryIncidents, normalizeToolInput, originMessageIds, parseProtocolMessage, partAwaitsClientInteraction, partialHasSettledToolResults, pausedExecutionUpdate, pendingChatTerminal, persistReconstructedOrphan, readChatRecoveryProgress, reconcileMessages, reconcileOrphanPartial, recordChatTerminal, repairInterruptedToolParts, resolveChatRecoveryConfig, resolveToolMergeId, retryAfterSeconds, runChatRecoveryExhaustion, sanitizeMessage, sendIfOpen, setChatRecovering, shouldCreditStreamProgress, sweepStaleChatRecoveryIncidents, toolApprovalUpdate, toolPartHasSettledResult, toolResultUpdate, truncateOlderMessages, truncateOlderToolResults, unwrapChatFiberSnapshot, withOriginMessageIds, wrapChatFiberSnapshot };
4163
4322
 
4164
4323
  //# sourceMappingURL=index.js.map