agents 0.17.0 → 0.17.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.
@@ -1,5 +1,5 @@
1
1
  import { i as normalizeToolInput, n as getPartialStreamText, r as isReplayChunk, t as applyChunkToParts } from "../message-builder-BymO4N_D.js";
2
- import { i as interceptAgentToolBroadcast, n as applyAgentToolEvent, r as createAgentToolEventState, t as AgentToolProgressEmitter } from "../agent-tools-BXlsuX0d.js";
2
+ import { i as interceptAgentToolBroadcast, n as applyAgentToolEvent, r as createAgentToolEventState, t as AgentToolProgressEmitter } from "../agent-tools-y7zLfw4Q.js";
3
3
  import { t as truncateToolOutput } from "../tool-output-truncation-CNnnGZQ3.js";
4
4
  import { n as transition, r as StreamAccumulator, t as MessageType } from "../wire-types-nflOzNuU.js";
5
5
  import { jsonSchema, tool } from "ai";
@@ -2445,12 +2445,42 @@ const CHAT_LAST_TERMINAL_KEY = "cf:chat:last-terminal";
2445
2445
  */
2446
2446
  const DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS = 10;
2447
2447
  /**
2448
- * Runaway-loop guard default. `Infinity` = no SDK-imposed work cap: a turn that
2449
- * keeps making forward progress is never terminated by the framework on its own
2450
- * (rfc-chat-recovery-work-budget). Integrators bound a content-emitting runaway
2451
- * by setting `maxRecoveryWork` or a `shouldKeepRecovering` predicate.
2452
- */
2453
- const DEFAULT_CHAT_RECOVERY_MAX_WORK = Number.POSITIVE_INFINITY;
2448
+ * Runaway-loop guard default the framework-imposed backstop on cumulative
2449
+ * recovery WORK (produced content/tool units) since an incident opened.
2450
+ *
2451
+ * Originally `Infinity` (rfc-chat-recovery-work-budget): the SDK shipped the
2452
+ * *mechanism* but no default cap, so a progressing turn was never terminated on
2453
+ * its own. Production issue #1825 showed that this is a footgun: an isolate that
2454
+ * OOMs mid-stream still credits a little progress before it dies, which resets
2455
+ * BOTH progress-keyed bounds (the attempt cap and the no-progress window) on
2456
+ * every wake — and a fast crash loop (each attempt inside the alarm-debounce
2457
+ * window) pins the attempt counter too. With `maxRecoveryWork = Infinity` the
2458
+ * ONLY instrument whose meter still climbs across such a loop is disabled, so
2459
+ * recovery re-runs the turn (and its LLM calls) forever.
2460
+ *
2461
+ * A finite default closes that loop out of the box: work climbs regardless of
2462
+ * debounce/progress resets, so a content-emitting runaway is always sealed with
2463
+ * `reason="work_budget_exceeded"`. The value is deliberately generous — it
2464
+ * bounds wasted re-run cost without clipping a normal interrupted turn (work
2465
+ * only accrues from the first interruption until the turn completes, after which
2466
+ * the incident is deleted). A very long agentic turn under heavy interruption
2467
+ * that legitimately needs more should raise `maxRecoveryWork` (or set it to
2468
+ * `Infinity` to restore the pre-#1825 unbounded behavior).
2469
+ */
2470
+ const DEFAULT_CHAT_RECOVERY_MAX_WORK = 1e3;
2471
+ /**
2472
+ * Tight, OOM-specific retry budget (#1825). A Durable Object memory-limit reset
2473
+ * (`isDurableObjectMemoryLimitReset`) is usually deterministic — the turn's
2474
+ * working set no longer fits in the isolate's 128 MB — so re-running it re-OOMs.
2475
+ * But a single OOM CAN be a transient spike (the isolate's 128 MB is shared
2476
+ * across the global scope / noisy neighbors), so recovery retries a small number
2477
+ * of times before sealing with `reason="out_of_memory"` rather than abandoning a
2478
+ * turn that one more attempt might have completed. Far tighter than the generic
2479
+ * `maxRecoveryWork` backstop because an OOM is attributable and re-running it is
2480
+ * expensive (it re-runs the model). Counts attempts that ended in an OOM, not
2481
+ * total attempts, so a turn interrupted by deploys (no OOM) is unaffected.
2482
+ */
2483
+ const DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES = 3;
2454
2484
  const DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS = 1e4;
2455
2485
  /**
2456
2486
  * Delay before retrying a recovery that timed out waiting for stable state.
@@ -2497,6 +2527,7 @@ function resolveChatRecoveryConfig(raw) {
2497
2527
  terminalMessage: custom?.terminalMessage ?? "The assistant was interrupted and could not recover. Please try again.",
2498
2528
  noProgressTimeoutMs: Math.max(0, Math.floor(custom?.noProgressTimeoutMs ?? 3e5)),
2499
2529
  maxRecoveryWork: typeof custom?.maxRecoveryWork === "number" && custom.maxRecoveryWork >= 0 ? custom.maxRecoveryWork : DEFAULT_CHAT_RECOVERY_MAX_WORK,
2530
+ maxOomRetries: typeof custom?.maxOomRetries === "number" && custom.maxOomRetries >= 0 ? Math.floor(custom.maxOomRetries) : 3,
2500
2531
  ...custom?.shouldKeepRecovering ? { shouldKeepRecovering: custom.shouldKeepRecovering } : {},
2501
2532
  ...custom?.onExhausted ? { onExhausted: custom.onExhausted } : {}
2502
2533
  };
@@ -2539,6 +2570,22 @@ async function sweepStaleChatRecoveryIncidents(storage, now) {
2539
2570
  for (let i = 0; i < staleKeys.length; i += 128) await storage.delete(staleKeys.slice(i, i + 128));
2540
2571
  }
2541
2572
  /**
2573
+ * List the persisted recovery incidents that are still live (status
2574
+ * `detected` / `scheduled` / `attempting`) — i.e. NOT yet terminalized
2575
+ * (`exhausted` / `failed`). Used by the alarm-boundary OOM circuit breaker
2576
+ * (#1825) to find the incident(s) it must seal when the in-DO budgets could not.
2577
+ * Lists by the incident key prefix so the storage layout stays encapsulated.
2578
+ */
2579
+ async function listActiveChatRecoveryIncidents(storage) {
2580
+ const entries = await storage.list({ prefix: CHAT_RECOVERY_INCIDENT_KEY_PREFIX });
2581
+ const active = [];
2582
+ for (const [key, incident] of entries) if (incident && (incident.status === "detected" || incident.status === "scheduled" || incident.status === "attempting")) active.push({
2583
+ key,
2584
+ incident
2585
+ });
2586
+ return active;
2587
+ }
2588
+ /**
2542
2589
  * Summarize a child agent's persisted recovery incidents for the parent's
2543
2590
  * agent-tool reattach decision: `"in-progress"` if any incident is still live
2544
2591
  * (detected/scheduled/attempting), else `"failed"` if any terminalized
@@ -2725,10 +2772,12 @@ async function evaluateChatRecoveryIncident(input) {
2725
2772
  const progress = Math.max(prevProgress, currentProgress);
2726
2773
  const work = progress - workBaseline;
2727
2774
  const workBudgetExceeded = existing != null && Number.isFinite(config.maxRecoveryWork) && work > config.maxRecoveryWork;
2775
+ const oomAttempts = existing?.oomAttempts ?? 0;
2776
+ const oomBudgetExceeded = existing != null && !awaitingClientInteraction && oomAttempts > config.maxOomRetries;
2728
2777
  const debounced = existing != null && !madeProgress && now - existing.lastAttemptAt < 3e4;
2729
2778
  const attempt = madeProgress ? 1 : debounced ? existing?.attempt ?? 1 : (existing?.attempt ?? 0) + 1;
2730
2779
  let abortedByCaller = false;
2731
- if (existing != null && !awaitingClientInteraction && config.shouldKeepRecovering && !noProgressExceeded && !workBudgetExceeded && attempt <= config.maxAttempts) try {
2780
+ if (existing != null && !awaitingClientInteraction && config.shouldKeepRecovering && !noProgressExceeded && !workBudgetExceeded && !oomBudgetExceeded && attempt <= config.maxAttempts) try {
2732
2781
  const ctx = {
2733
2782
  incidentId,
2734
2783
  requestId: identity.requestId,
@@ -2743,7 +2792,7 @@ async function evaluateChatRecoveryIncident(input) {
2743
2792
  } catch (error) {
2744
2793
  input.onShouldKeepRecoveringError?.(error);
2745
2794
  }
2746
- const exhausted = !awaitingClientInteraction && (noProgressExceeded || workBudgetExceeded || abortedByCaller || attempt > config.maxAttempts);
2795
+ const exhausted = !awaitingClientInteraction && (oomBudgetExceeded || noProgressExceeded || workBudgetExceeded || abortedByCaller || attempt > config.maxAttempts);
2747
2796
  const incident = {
2748
2797
  incidentId,
2749
2798
  requestId: identity.requestId,
@@ -2757,7 +2806,8 @@ async function evaluateChatRecoveryIncident(input) {
2757
2806
  lastProgressAt,
2758
2807
  progress,
2759
2808
  workBaseline,
2760
- ...exhausted ? { reason: workBudgetExceeded ? "work_budget_exceeded" : noProgressExceeded ? "no_progress_timeout" : abortedByCaller ? "recovery_aborted" : "max_attempts_exceeded" } : {}
2809
+ ...oomAttempts > 0 ? { oomAttempts } : {},
2810
+ ...exhausted ? { reason: oomBudgetExceeded ? "out_of_memory" : workBudgetExceeded ? "work_budget_exceeded" : noProgressExceeded ? "no_progress_timeout" : abortedByCaller ? "recovery_aborted" : "max_attempts_exceeded" } : {}
2761
2811
  };
2762
2812
  const events = [];
2763
2813
  if (!existing) events.push({
@@ -3031,6 +3081,54 @@ var ChatRecoveryEngine = class {
3031
3081
  return true;
3032
3082
  }
3033
3083
  /**
3084
+ * Record that a recovery callback observed a Durable Object memory-limit reset
3085
+ * (the isolate exceeded its 128 MB limit — `isDurableObjectMemoryLimitReset`)
3086
+ * and decide what to do next (#1825).
3087
+ *
3088
+ * Bumps the incident's durable `oomAttempts` counter, then:
3089
+ * - if it is still within `maxOomRetries`, issues a delayed, NON-idempotent
3090
+ * reschedule of the SAME callback (same machinery as
3091
+ * {@link rescheduleAfterStableTimeout}: the executing one-shot row is
3092
+ * deleted only after the callback returns, so an idempotent reschedule
3093
+ * would dedup onto that doomed row) and returns `"rescheduled"`. The small
3094
+ * delay lets a transient memory spike clear before the re-run;
3095
+ * - otherwise leaves the incremented count persisted (so a begin-path
3096
+ * re-evaluation agrees) and returns `"exhausted"` — the caller then
3097
+ * terminalizes via the give-up path with `reason="out_of_memory"`.
3098
+ *
3099
+ * Returns `"exhausted"` when there is no incident to track against (no id /
3100
+ * record gone): an OOM we cannot bound must seal rather than loop. Unlike a
3101
+ * stable-state retry this is gated by the OOM-specific budget, NOT the generic
3102
+ * attempt cap — re-running an OOM streams a little "progress" that would
3103
+ * otherwise reset the attempt cap forever (the #1825 loop).
3104
+ */
3105
+ async recordOomAndDecide(input) {
3106
+ const { adapter } = this;
3107
+ if (!input.incidentId) return "exhausted";
3108
+ const key = chatRecoveryIncidentKey(input.incidentId);
3109
+ const incident = await adapter.getIncident(key);
3110
+ if (!incident) return "exhausted";
3111
+ const oomAttempts = (incident.oomAttempts ?? 0) + 1;
3112
+ if (oomAttempts > input.maxOomRetries) {
3113
+ await adapter.putIncident(key, {
3114
+ ...incident,
3115
+ oomAttempts,
3116
+ lastAttemptAt: adapter.now(),
3117
+ reason: "out_of_memory"
3118
+ });
3119
+ return "exhausted";
3120
+ }
3121
+ await adapter.putIncident(key, {
3122
+ ...incident,
3123
+ oomAttempts,
3124
+ status: "scheduled",
3125
+ lastAttemptAt: adapter.now(),
3126
+ reason: "oom_retry"
3127
+ });
3128
+ await adapter.scheduleRecovery(input.callback, input.data ?? {}, "stable_timeout_retry", 3);
3129
+ return "rescheduled";
3130
+ }
3131
+ /**
3034
3132
  * Give up on a recovery turn whose retry budget drained, terminalizing it so
3035
3133
  * it can never become an eternal spinner (#1645). The shared spine both
3036
3134
  * packages repeated verbatim:
@@ -3305,6 +3403,6 @@ async function* iterateWithStallWatchdog(source, timeoutMs, onStall) {
3305
3403
  }
3306
3404
  }
3307
3405
  //#endregion
3308
- 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_INCIDENT_KEY_PREFIX, CHAT_RECOVERY_INCIDENT_TTL_MS, CHAT_RECOVERY_PROGRESS_KEY, CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS, CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS, ChatRecoveryEngine, ChatStreamStalledError, ContinuationState, DEFAULT_CHAT_RECOVERY_MAX_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_CLEANUP_DELAY_SECONDS, StreamAccumulator, StreamProgressCreditThrottle, SubmitConcurrencyController, TIMED_OUT, TurnQueue, aiSdkRecoveryCodec, applyAgentToolEvent, applyChunkToParts, applyToolUpdate, awaitWithDeadline, transition as broadcastTransition, buildChatRecoveringFrame, buildInClauseStrings, bumpChatRecoveryProgress, byteLength, chatRecoverySchedulePolicy, classifyAgentToolChildRecovery, cleanupStreamBuffers, clearChatTerminal, clientResolvableToolNames, createAgentToolEventState, createChatFiberSnapshot, createToolsFromClientSchemas, crossMessageToolResultUpdate, drainInteractionApplies, enforceRowSizeLimit, hasIncompleteToolBatch, interceptAgentToolBroadcast, isReplayChunk, iterateWithStallWatchdog, normalizeToolInput, parseProtocolMessage, partAwaitsClientInteraction, pausedExecutionUpdate, pendingChatTerminal, persistReconstructedOrphan, readChatRecoveryProgress, reconcileMessages, reconcileOrphanPartial, recordChatTerminal, repairInterruptedToolParts, resolveChatRecoveryConfig, resolveToolMergeId, runChatRecoveryExhaustion, sanitizeMessage, sendIfOpen, setChatRecovering, shouldCreditStreamProgress, sweepStaleChatRecoveryIncidents, toolApprovalUpdate, toolPartHasSettledResult, toolResultUpdate, unwrapChatFiberSnapshot, wrapChatFiberSnapshot };
3406
+ 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_INCIDENT_KEY_PREFIX, CHAT_RECOVERY_INCIDENT_TTL_MS, CHAT_RECOVERY_PROGRESS_KEY, CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS, 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_CLEANUP_DELAY_SECONDS, StreamAccumulator, StreamProgressCreditThrottle, SubmitConcurrencyController, TIMED_OUT, TurnQueue, aiSdkRecoveryCodec, applyAgentToolEvent, applyChunkToParts, applyToolUpdate, awaitWithDeadline, transition as broadcastTransition, buildChatRecoveringFrame, buildInClauseStrings, bumpChatRecoveryProgress, byteLength, chatRecoverySchedulePolicy, classifyAgentToolChildRecovery, cleanupStreamBuffers, clearChatTerminal, clientResolvableToolNames, createAgentToolEventState, createChatFiberSnapshot, createToolsFromClientSchemas, crossMessageToolResultUpdate, drainInteractionApplies, enforceRowSizeLimit, hasIncompleteToolBatch, interceptAgentToolBroadcast, isReplayChunk, iterateWithStallWatchdog, listActiveChatRecoveryIncidents, normalizeToolInput, parseProtocolMessage, partAwaitsClientInteraction, pausedExecutionUpdate, pendingChatTerminal, persistReconstructedOrphan, readChatRecoveryProgress, reconcileMessages, reconcileOrphanPartial, recordChatTerminal, repairInterruptedToolParts, resolveChatRecoveryConfig, resolveToolMergeId, runChatRecoveryExhaustion, sanitizeMessage, sendIfOpen, setChatRecovering, shouldCreditStreamProgress, sweepStaleChatRecoveryIncidents, toolApprovalUpdate, toolPartHasSettledResult, toolResultUpdate, unwrapChatFiberSnapshot, wrapChatFiberSnapshot };
3309
3407
 
3310
3408
  //# sourceMappingURL=index.js.map