@mastra/memory 1.39.0-alpha.3 → 1.39.0-alpha.4

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 (27) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/docs/references/reference-memory-settled.md +13 -5
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.d.ts +4 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/processors/index.cjs +1 -1
  9. package/dist/processors/index.js +1 -1
  10. package/dist/processors/observational-memory/extraction-runner.d.ts.map +1 -1
  11. package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
  12. package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
  13. package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
  14. package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
  15. package/dist/processors/observational-memory/observational-memory.d.ts +17 -3
  16. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  17. package/dist/processors/observational-memory/observer-runner.d.ts.map +1 -1
  18. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  19. package/dist/processors/observational-memory/reflector-runner.d.ts +1 -1
  20. package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
  21. package/dist/processors/observational-memory/retry.d.ts +28 -1
  22. package/dist/processors/observational-memory/retry.d.ts.map +1 -1
  23. package/dist/{src-Dg8w4Ud-.js → src--zkaCof1.js} +134 -38
  24. package/dist/{src-Dg8w4Ud-.js.map → src--zkaCof1.js.map} +1 -1
  25. package/dist/{src-GHUZVCL8.cjs → src-BGgY5kgl.cjs} +134 -38
  26. package/dist/{src-GHUZVCL8.cjs.map → src-BGgY5kgl.cjs.map} +1 -1
  27. package/package.json +3 -3
@@ -18685,7 +18685,15 @@ const RETRY_CONFIG = {
18685
18685
  /** Cap on per-attempt delay (ms). */
18686
18686
  maxDelayMs: 12e4,
18687
18687
  /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
18688
- jitter: .2
18688
+ jitter: .2,
18689
+ /**
18690
+ * How long one observer, reflector, or extraction model call may go without streaming any
18691
+ * output before it is aborted and retried. Without it a provider that accepts the request but
18692
+ * never answers stalls the call forever, and the retry schedule above never gets a chance to run.
18693
+ * It measures inactivity, not total duration, so a long reply that keeps streaming is never cut
18694
+ * off. It is generous because reasoning models can think silently before their first token.
18695
+ */
18696
+ modelCallIdleTimeoutMs: 3e5
18689
18697
  };
18690
18698
  const TRANSIENT_MESSAGE_SUBSTRINGS = [
18691
18699
  "terminated",
@@ -18704,6 +18712,19 @@ const TRANSIENT_MESSAGE_SUBSTRINGS = [
18704
18712
  ];
18705
18713
  const INCOMPLETE_FINISH_REASONS = /* @__PURE__ */ new Set(["other", "unknown"]);
18706
18714
  /**
18715
+ * A model call that streamed nothing for `RETRY_CONFIG.modelCallIdleTimeoutMs`. Retryable, and
18716
+ * deliberately not an AbortError so `withRetry` doesn't treat it as cancellation.
18717
+ *
18718
+ * @internal
18719
+ */
18720
+ var OmModelCallTimeoutError = class extends Error {
18721
+ isRetryable = true;
18722
+ constructor(label, idleTimeoutMs) {
18723
+ super(`${label} call produced no output for ${idleTimeoutMs}ms`);
18724
+ this.name = "OmModelCallTimeoutError";
18725
+ }
18726
+ };
18727
+ /**
18707
18728
  * A model reply that ended before the model finished. Carries the AI SDK's
18708
18729
  * `isRetryable` flag, which `isTransientLLMError` and core's error processors
18709
18730
  * already recognize.
@@ -18846,6 +18867,51 @@ function sleep(ms, abortSignal) {
18846
18867
  });
18847
18868
  }
18848
18869
  /**
18870
+ * Run one attempt, failing with a retryable `OmModelCallTimeoutError` if it
18871
+ * goes `idleTimeoutMs` without calling `onActivity`. `fn` gets a signal that
18872
+ * aborts on timeout or on the caller's signal, so the underlying request is
18873
+ * cancelled too.
18874
+ */
18875
+ async function attemptWithIdleTimeout(fn, label, idleTimeoutMs, abortSignal) {
18876
+ const controller = new AbortController();
18877
+ let rejectOnAbort;
18878
+ const callerAborted = new Promise((_, reject) => {
18879
+ rejectOnAbort = reject;
18880
+ });
18881
+ const onAbort = () => {
18882
+ controller.abort(abortSignal?.reason);
18883
+ rejectOnAbort(abortSignal?.reason ?? /* @__PURE__ */ new Error("The operation was aborted."));
18884
+ };
18885
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
18886
+ let timer;
18887
+ let rejectOnIdle;
18888
+ const idle = new Promise((_, reject) => {
18889
+ rejectOnIdle = reject;
18890
+ });
18891
+ const armIdleTimer = () => {
18892
+ clearTimeout(timer);
18893
+ timer = setTimeout(() => {
18894
+ const error = new OmModelCallTimeoutError(label, idleTimeoutMs);
18895
+ rejectOnIdle(error);
18896
+ controller.abort(error);
18897
+ }, idleTimeoutMs);
18898
+ };
18899
+ const onActivity = () => {
18900
+ if (!controller.signal.aborted) armIdleTimer();
18901
+ };
18902
+ armIdleTimer();
18903
+ try {
18904
+ return await Promise.race([
18905
+ fn(controller.signal, onActivity),
18906
+ idle,
18907
+ callerAborted
18908
+ ]);
18909
+ } finally {
18910
+ clearTimeout(timer);
18911
+ abortSignal?.removeEventListener("abort", onAbort);
18912
+ }
18913
+ }
18914
+ /**
18849
18915
  * Run `fn` with retries on transient transport-class errors.
18850
18916
  *
18851
18917
  * Non-transient errors (auth, validation, schema, etc.) are rethrown
@@ -18854,14 +18920,16 @@ function sleep(ms, abortSignal) {
18854
18920
  * @internal
18855
18921
  */
18856
18922
  async function withRetry(fn, opts) {
18857
- const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries } = opts;
18923
+ const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries, idleTimeoutMs } = opts;
18858
18924
  let attempt = 0;
18859
18925
  while (true) {
18860
18926
  if (abortSignal?.aborted) throw new Error("The operation was aborted.");
18861
18927
  try {
18862
- return await fn();
18928
+ if (idleTimeoutMs === void 0) return await fn(abortSignal ?? new AbortController().signal, () => {});
18929
+ return await attemptWithIdleTimeout(fn, label, idleTimeoutMs, abortSignal);
18863
18930
  } catch (error) {
18864
- if (hasAbortInChain(error) || abortSignal?.aborted) throw error;
18931
+ if (abortSignal?.aborted) throw error;
18932
+ if (!(error instanceof OmModelCallTimeoutError) && hasAbortInChain(error)) throw error;
18865
18933
  if (attempt >= maxRetries || !isTransientLLMError(error)) {
18866
18934
  if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
18867
18935
  throw error;
@@ -18981,19 +19049,21 @@ If a prior value is still applicable and carry-forward is enabled, return that p
18981
19049
  ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values\n\n${priorLines.join("\n\n")}` : ""}`;
18982
19050
  const values = {};
18983
19051
  const failures = [];
18984
- const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry(() => streamOnce(jsonPromptInjection), {
19052
+ const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry((attemptSignal, onActivity) => streamOnce(jsonPromptInjection, attemptSignal, onActivity), {
18985
19053
  label: `om-${opts.source}-structured-extraction`,
18986
19054
  abortSignal: opts.abortSignal,
18987
- maxRetries: 1
19055
+ maxRetries: 1,
19056
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs
18988
19057
  });
18989
- const streamOnce = async (jsonPromptInjection) => {
19058
+ const streamOnce = async (jsonPromptInjection, abortSignal, onActivity) => {
18990
19059
  const object = await (await opts.agent.stream(prompt, {
18991
19060
  structuredOutput: {
18992
19061
  schema,
18993
19062
  ...jsonPromptInjection ? { jsonPromptInjection } : {}
18994
19063
  },
18995
19064
  ...opts.memory ? { memory: opts.memory } : {},
18996
- ...opts.abortSignal ? { abortSignal: opts.abortSignal } : {},
19065
+ abortSignal,
19066
+ onChunk: onActivity,
18997
19067
  ...opts.requestContext ? { requestContext: opts.requestContext } : {},
18998
19068
  ...opts.observabilityContext
18999
19069
  })).object;
@@ -21761,7 +21831,7 @@ var ObserverRunner = class {
21761
21831
  timeZone: options?.timeZone
21762
21832
  })];
21763
21833
  const doGenerate = async () => {
21764
- return withRetry(() => withOmTracingSpan({
21834
+ return withRetry((attemptSignal, onActivity) => withOmTracingSpan({
21765
21835
  phase: "observer",
21766
21836
  model: resolvedModel.model,
21767
21837
  inputTokens,
@@ -21784,7 +21854,8 @@ var ObserverRunner = class {
21784
21854
  modelSettings: { ...this.observationConfig.modelSettings },
21785
21855
  providerOptions: this.observationConfig.providerOptions,
21786
21856
  ...attemptMemory ? { memory: attemptMemory } : {},
21787
- ...abortSignal ? { abortSignal } : {},
21857
+ abortSignal: attemptSignal,
21858
+ onChunk: onActivity,
21788
21859
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
21789
21860
  ...childObservabilityContext
21790
21861
  })).getFullOutput(), "OM observer");
@@ -21806,6 +21877,7 @@ var ObserverRunner = class {
21806
21877
  }), {
21807
21878
  label: "observer",
21808
21879
  abortSignal,
21880
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
21809
21881
  maxRetries: this.observationConfig.maxRetries
21810
21882
  });
21811
21883
  };
@@ -21974,7 +22046,7 @@ var ObserverRunner = class {
21974
22046
  timeZone
21975
22047
  })];
21976
22048
  const doGenerate = async () => {
21977
- return withRetry(() => withOmTracingSpan({
22049
+ return withRetry((attemptSignal, onActivity) => withOmTracingSpan({
21978
22050
  phase: "observer-multi-thread",
21979
22051
  model: resolvedModel.model,
21980
22052
  inputTokens,
@@ -21995,7 +22067,8 @@ var ObserverRunner = class {
21995
22067
  maxSteps: 1,
21996
22068
  modelSettings: { ...this.observationConfig.modelSettings },
21997
22069
  providerOptions: this.observationConfig.providerOptions,
21998
- ...abortSignal ? { abortSignal } : {},
22070
+ abortSignal: attemptSignal,
22071
+ onChunk: onActivity,
21999
22072
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
22000
22073
  ...childObservabilityContext
22001
22074
  })).getFullOutput(), "OM multi-thread observer");
@@ -22019,6 +22092,7 @@ var ObserverRunner = class {
22019
22092
  }), {
22020
22093
  label: "observer-multi-thread",
22021
22094
  abortSignal,
22095
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
22022
22096
  maxRetries: this.observationConfig.maxRetries
22023
22097
  });
22024
22098
  };
@@ -28700,7 +28774,10 @@ var ObservationStep = class {
28700
28774
  observabilityContext: this.turn.observabilityContext
28701
28775
  }).catch((err) => {
28702
28776
  omDebug(`[OM:buffer] fire-and-forget buffer failed: ${err?.message}`);
28703
- }));
28777
+ }), {
28778
+ threadId,
28779
+ resourceId
28780
+ });
28704
28781
  buffered = true;
28705
28782
  }
28706
28783
  }
@@ -29253,7 +29330,10 @@ var ObservationTurn = class {
29253
29330
  skipMinimumTokenCheck: true
29254
29331
  }).catch((err) => {
29255
29332
  omDebug(`[OM:turn.end] idle buffer failed: ${err?.message}`);
29256
- }));
29333
+ }), {
29334
+ threadId: this.threadId,
29335
+ resourceId: this.resourceId
29336
+ });
29257
29337
  }
29258
29338
  return { record: this._record };
29259
29339
  }
@@ -29811,7 +29891,7 @@ var ReflectorRunner = class {
29811
29891
  const prompt = buildReflectorPrompt(observations, manualPrompt, currentLevel, skipContinuationHints, activeExtractors, priorExtractedValues);
29812
29892
  omDebug(`[OM:callReflector] ${isRetry ? `retry #${attemptNumber - 1}` : "first attempt"}: level=${currentLevel}, originalTokens=${originalTokens}, targetThreshold=${targetThreshold}, promptLen=${prompt.length}, skipContinuationHints=${skipContinuationHints}`);
29813
29893
  let chunkCount = 0;
29814
- const result = await withRetry(() => withOmTracingSpan({
29894
+ const result = await withRetry((attemptSignal, onActivity) => withOmTracingSpan({
29815
29895
  phase: "reflector",
29816
29896
  model: resolvedModel.model,
29817
29897
  inputTokens: originalTokens,
@@ -29835,17 +29915,19 @@ var ReflectorRunner = class {
29835
29915
  modelSettings: { ...this.reflectionConfig.modelSettings },
29836
29916
  providerOptions: this.reflectionConfig.providerOptions,
29837
29917
  ...attemptMemory ? { memory: attemptMemory } : {},
29838
- ...abortSignal ? { abortSignal } : {},
29918
+ abortSignal: attemptSignal,
29839
29919
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
29840
29920
  ...childObservabilityContext,
29921
+ onChunk(chunk) {
29922
+ onActivity();
29923
+ if (attemptNumber !== 1) return;
29924
+ chunkCount++;
29925
+ if (chunkCount === 1 || chunkCount % 50 === 0) {
29926
+ const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
29927
+ omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
29928
+ }
29929
+ },
29841
29930
  ...attemptNumber === 1 ? {
29842
- onChunk(chunk) {
29843
- chunkCount++;
29844
- if (chunkCount === 1 || chunkCount % 50 === 0) {
29845
- const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
29846
- omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
29847
- }
29848
- },
29849
29931
  onFinish(event) {
29850
29932
  omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
29851
29933
  },
@@ -29865,6 +29947,7 @@ var ReflectorRunner = class {
29865
29947
  }), {
29866
29948
  label: "reflector",
29867
29949
  abortSignal,
29950
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
29868
29951
  maxRetries: this.reflectionConfig.maxRetries
29869
29952
  });
29870
29953
  omDebug(`[OM:callReflector] attempt #${attemptNumber} returned: textLen=${result.text?.length}, textPreview="${result.text?.slice(0, 120)}...", inputTokens=${result.usage?.inputTokens ?? result.totalUsage?.inputTokens}, outputTokens=${result.usage?.outputTokens ?? result.totalUsage?.outputTokens}`);
@@ -30023,7 +30106,7 @@ var ReflectorRunner = class {
30023
30106
  }
30024
30107
  })();
30025
30108
  BufferingCoordinator.asyncBufferingOps.set(bufferKey, asyncOp);
30026
- this.trackBackgroundWork?.(asyncOp);
30109
+ this.trackBackgroundWork?.(asyncOp, lockKey);
30027
30110
  }
30028
30111
  /**
30029
30112
  * Perform async buffered reflection — reflects observations and stores to bufferedReflection.
@@ -31051,32 +31134,44 @@ var ObservationalMemory = class ObservationalMemory {
31051
31134
  * Tracked so callers can join them via `settled()` before closing a storage
31052
31135
  * connection they own — otherwise a background cycle's tail statements race the close.
31053
31136
  */
31054
- pendingBackgroundWork = /* @__PURE__ */ new Set();
31137
+ pendingBackgroundWork = /* @__PURE__ */ new Map();
31055
31138
  /**
31056
31139
  * Register fire-and-forget background work so `settled()` can join it.
31140
+ * `scope` is the conversation the work belongs to (thread/resource ids, or a lock key from
31141
+ * `buffering.getLockKey`),
31142
+ * so a scoped `settled()` can wait for one conversation without waiting for every other.
31057
31143
  * Returns the original promise so callers keep their own rejection handling.
31058
31144
  */
31059
- trackBackgroundWork(work) {
31145
+ trackBackgroundWork(work, scope) {
31146
+ const lockKey = typeof scope === "string" ? scope : this.buffering.getLockKey(scope.threadId, scope.resourceId);
31060
31147
  const tracked = work.catch(() => {}).finally(() => {
31061
31148
  this.pendingBackgroundWork.delete(tracked);
31062
31149
  });
31063
- this.pendingBackgroundWork.add(tracked);
31150
+ this.pendingBackgroundWork.set(tracked, lockKey);
31064
31151
  return work;
31065
31152
  }
31066
31153
  /**
31067
- * Resolve once all background observational-memory work started so far has finished.
31154
+ * Resolve once background observational-memory work started so far has finished.
31155
+ *
31156
+ * Pass `threadId`/`resourceId` to wait only for that conversation's work. An engine shared by
31157
+ * many busy threads may never be fully idle, so an unscoped wait can last indefinitely there.
31158
+ * A scope without the id this engine keys work on (`resourceId` for resource scope, `threadId`
31159
+ * for thread scope) can't be matched to its work, so it falls back to waiting for all work.
31068
31160
  *
31069
31161
  * Background cycles enqueue further background work (a buffered observation can
31070
31162
  * trigger a reflection, which runs nested agent streams), so this drains repeatedly
31071
31163
  * until nothing is left rather than joining a single snapshot.
31072
31164
  */
31073
- async settled() {
31165
+ async settled(scope) {
31166
+ const lockKey = (this.scope === "resource" ? scope?.resourceId : scope?.threadId) ? this.buffering.getLockKey(scope.threadId, scope.resourceId) : void 0;
31167
+ const pending = () => [...this.pendingBackgroundWork].filter(([, key]) => lockKey === void 0 || key === lockKey).map(([w]) => w);
31074
31168
  const maxDrainRounds = 100;
31075
31169
  for (let round = 0; round < maxDrainRounds; round++) {
31076
- if (this.pendingBackgroundWork.size === 0) return;
31077
- await Promise.allSettled([...this.pendingBackgroundWork]);
31170
+ const work = pending();
31171
+ if (work.length === 0) return;
31172
+ await Promise.allSettled(work);
31078
31173
  }
31079
- if (this.pendingBackgroundWork.size === 0) return;
31174
+ if (pending().length === 0) return;
31080
31175
  omError(`[OM:settled] background work still pending after ${maxDrainRounds} drain rounds; giving up waiting`);
31081
31176
  }
31082
31177
  /**
@@ -31233,7 +31328,7 @@ var ObservationalMemory = class ObservationalMemory {
31233
31328
  memory: this.memory,
31234
31329
  onReflectionCommitted: config.onReflectionCommitted,
31235
31330
  hooks: this.hooks,
31236
- trackBackgroundWork: (work) => this.trackBackgroundWork(work)
31331
+ trackBackgroundWork: (work, lockKey) => this.trackBackgroundWork(work, lockKey)
31237
31332
  });
31238
31333
  this.validateBufferConfig();
31239
31334
  if (this.scope === "resource" && !hasWarnedResourceScopeDeprecation) {
@@ -32089,11 +32184,12 @@ ${formattedMessages}
32089
32184
  }).finally(() => {
32090
32185
  if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === asyncOp) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
32091
32186
  unregisterOp(record.id, "bufferingObservation");
32092
- runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
32187
+ return runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
32093
32188
  omError("[OM] Failed to clear buffering observation flag", err);
32094
32189
  });
32095
32190
  });
32096
32191
  BufferingCoordinator.asyncBufferingOps.set(bufferKey, asyncOp);
32192
+ return { op: asyncOp };
32097
32193
  }
32098
32194
  /**
32099
32195
  * Internal method that waits for existing buffering operation and then runs new buffering.
@@ -32193,7 +32289,7 @@ ${formattedMessages}
32193
32289
  if (!this.buffering.isAsyncObservationEnabled()) return false;
32194
32290
  const lockKey = this.buffering.getLockKey(opts.threadId, opts.resourceId);
32195
32291
  const shouldTrigger = this.buffering.shouldTriggerAsyncObservation(opts.pendingTokens, lockKey, opts.record, this.storage, opts.threshold);
32196
- if (shouldTrigger) this.trackBackgroundWork(this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext, opts.observabilityContext));
32292
+ if (shouldTrigger) this.trackBackgroundWork(this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext, opts.observabilityContext).then(({ op }) => op), lockKey);
32197
32293
  return shouldTrigger;
32198
32294
  }
32199
32295
  isMessageList(value) {
@@ -34245,9 +34341,9 @@ var Memory = class Memory extends _mastra_core_memory.MastraMemory {
34245
34341
  * await store.close();
34246
34342
  * ```
34247
34343
  */
34248
- async settled() {
34344
+ async settled(scope) {
34249
34345
  await this.pendingVectorCleanup;
34250
- await (this._omEngine ? await this._omEngine : this._omEngineInstance)?.settled();
34346
+ await (this._omEngine ? await this._omEngine : this._omEngineInstance)?.settled(scope);
34251
34347
  await this.pendingVectorCleanup;
34252
34348
  }
34253
34349
  /** The shared ObservationalMemory engine. Lazily created on first access. */
@@ -36841,4 +36937,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
36841
36937
  }
36842
36938
  });
36843
36939
 
36844
- //# sourceMappingURL=src-GHUZVCL8.cjs.map
36940
+ //# sourceMappingURL=src-BGgY5kgl.cjs.map