@mastra/memory 1.39.1-alpha.2 → 1.39.1

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.
@@ -27126,15 +27126,16 @@ var OmIncompleteResponseError = class extends Error {
27126
27126
  }
27127
27127
  };
27128
27128
  /**
27129
- * A model reply that finished but contains no text, or only whitespace.
27129
+ * A model reply that finished but carries nothing usable: no text, only whitespace,
27130
+ * or text with no recognised Observer sections (e.g. a refusal).
27130
27131
  * Retryable, so an empty reply is retried like a transport error.
27131
27132
  *
27132
27133
  * @internal
27133
27134
  */
27134
27135
  var OmEmptyResponseError = class extends Error {
27135
27136
  isRetryable = true;
27136
- constructor(label) {
27137
- super(`${label} returned empty output`);
27137
+ constructor(label, what = "empty output") {
27138
+ super(`${label} returned ${what}`);
27138
27139
  this.name = "OmEmptyResponseError";
27139
27140
  }
27140
27141
  };
@@ -27677,7 +27678,7 @@ function formatGapBetweenDates(prevDate, currDate) {
27677
27678
  else if (diffDays < 14) return `[1 week later]`;
27678
27679
  else if (diffDays < 30) return `[${Math.floor(diffDays / 7)} weeks later]`;
27679
27680
  else if (diffDays < 60) return `[1 month later]`;
27680
- else return `[${Math.floor(diffDays / 30)} months later]`;
27681
+ return `[${Math.floor(diffDays / 30)} months later]`;
27681
27682
  }
27682
27683
  const MONTH_INDEX = {
27683
27684
  jan: 0,
@@ -29665,13 +29666,15 @@ function parseMemorySectionXml(content) {
29665
29666
  suggestedResponse: "",
29666
29667
  threadTitle: ""
29667
29668
  };
29668
- const observationsMatches = [...content.matchAll(/^[ \t]*<observations>([\s\S]*?)^[ \t]*<\/observations>/gim)];
29669
- if (observationsMatches.length > 0) result.observations = observationsMatches.map((m) => m[1]?.trim() ?? "").filter(Boolean).join("\n");
29669
+ const observationsMatches = [...content.matchAll(/^[ \t]*<observations>(?:([^\r\n]*?)<\/observations>[ \t]*$|([\s\S]*?)^[ \t]*<\/observations>)/gim)];
29670
+ if (observationsMatches.length > 0) result.observations = observationsMatches.map((m) => (m[1] ?? m[2])?.trim() ?? "").filter(Boolean).join("\n");
29670
29671
  else result.observations = extractListItemsOnly(content);
29671
- const currentTaskMatch = content.match(/^[ \t]*<current-task>([\s\S]*?)^[ \t]*<\/current-task>/im);
29672
- if (currentTaskMatch?.[1]) result.currentTask = currentTaskMatch[1].trim();
29673
- const suggestedResponseMatch = content.match(/^[ \t]*<suggested-response>([\s\S]*?)^[ \t]*<\/suggested-response>/im);
29674
- if (suggestedResponseMatch?.[1]) result.suggestedResponse = suggestedResponseMatch[1].trim();
29672
+ const currentTaskMatch = content.match(/^[ \t]*<current-task>(?:([^\r\n]*?)<\/current-task>[ \t]*$|([\s\S]*?)^[ \t]*<\/current-task>)/im);
29673
+ const currentTask = currentTaskMatch?.[1] ?? currentTaskMatch?.[2];
29674
+ if (currentTask) result.currentTask = currentTask.trim();
29675
+ const suggestedResponseMatch = content.match(/^[ \t]*<suggested-response>(?:([^\r\n]*?)<\/suggested-response>[ \t]*$|([\s\S]*?)^[ \t]*<\/suggested-response>)/im);
29676
+ const suggestedResponse = suggestedResponseMatch?.[1] ?? suggestedResponseMatch?.[2];
29677
+ if (suggestedResponse) result.suggestedResponse = suggestedResponse.trim();
29675
29678
  const threadTitleMatch = content.match(/^[ \t]*<thread-title>([\s\S]*?)<\/thread-title>/im);
29676
29679
  if (threadTitleMatch?.[1]) result.threadTitle = threadTitleMatch[1].trim();
29677
29680
  return result;
@@ -30244,7 +30247,7 @@ var ObserverRunner = class {
30244
30247
  callback: (childObservabilityContext) => this.withAbortCheck(async () => {
30245
30248
  try {
30246
30249
  attemptMemory = temporaryMemory?.newThread();
30247
- return assertNonEmptyObserverOutput(assertCompleteModelResponse(await (await agent.stream(observerMessages, {
30250
+ return assertRecognizedObserverOutput(assertNonEmptyObserverOutput(assertCompleteModelResponse(await (await agent.stream(observerMessages, {
30248
30251
  maxSteps: 1,
30249
30252
  modelSettings: { ...this.observationConfig.modelSettings },
30250
30253
  providerOptions: this.observationConfig.providerOptions,
@@ -30253,7 +30256,7 @@ var ObserverRunner = class {
30253
30256
  onChunk: onActivity,
30254
30257
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
30255
30258
  ...childObservabilityContext
30256
- })).getFullOutput(), "OM observer"), "OM observer");
30259
+ })).getFullOutput(), "OM observer"), "OM observer"), "OM observer", (text) => hasSingleThreadObserverContent(text, activeExtractors));
30257
30260
  } catch (error) {
30258
30261
  this.mastra?.getLogger?.().error("OM observer provider call failed", {
30259
30262
  diagnostic: formatOmError(error),
@@ -30356,7 +30359,7 @@ var ObserverRunner = class {
30356
30359
  for (const threadId of allThreadOrder) {
30357
30360
  const threadResult = output.results.get(threadId);
30358
30361
  if (!threadResult) {
30359
- output.results.set(threadId, { observations: "" });
30362
+ if (!messagesByThread.has(threadId)) output.results.set(threadId, { observations: "" });
30360
30363
  continue;
30361
30364
  }
30362
30365
  threadResult.observations = await applyTextTransform(this.hooks, "afterObservation", threadResult.observations, contextFor(threadId));
@@ -30458,7 +30461,7 @@ var ObserverRunner = class {
30458
30461
  },
30459
30462
  callback: (childObservabilityContext) => this.withAbortCheck(async () => {
30460
30463
  try {
30461
- return assertNonEmptyObserverOutput(assertCompleteModelResponse(await (await agent.stream(observerMessages, {
30464
+ return assertRecognizedObserverOutput(assertNonEmptyObserverOutput(assertCompleteModelResponse(await (await agent.stream(observerMessages, {
30462
30465
  maxSteps: 1,
30463
30466
  modelSettings: { ...this.observationConfig.modelSettings },
30464
30467
  providerOptions: this.observationConfig.providerOptions,
@@ -30466,7 +30469,7 @@ var ObserverRunner = class {
30466
30469
  onChunk: onActivity,
30467
30470
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
30468
30471
  ...childObservabilityContext
30469
- })).getFullOutput(), "OM multi-thread observer"), "OM multi-thread observer");
30472
+ })).getFullOutput(), "OM multi-thread observer"), "OM multi-thread observer"), "OM multi-thread observer", (text) => hasMultiThreadObserverContent(text, activeExtractors, threadOrder));
30470
30473
  } catch (error) {
30471
30474
  this.mastra?.getLogger?.().error("OM multi-thread observer provider call failed", {
30472
30475
  diagnostic: formatOmError(error),
@@ -30543,8 +30546,7 @@ var ObserverRunner = class {
30543
30546
  extractors: activeExtractors
30544
30547
  });
30545
30548
  }
30546
- for (const threadId of threadOrder) if (!results.has(threadId)) results.set(threadId, { observations: "" });
30547
- for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
30549
+ for (const threadId of results.keys()) for (const msg of messagesByThread.get(threadId) ?? []) this.observedMessageIds.add(msg.id);
30548
30550
  const usage = result.totalUsage ?? result.usage;
30549
30551
  return {
30550
30552
  results,
@@ -30567,6 +30569,29 @@ function assertNonEmptyObserverOutput(output, label) {
30567
30569
  if (!output.text?.trim()) throw new OmEmptyResponseError(label);
30568
30570
  return output;
30569
30571
  }
30572
+ /**
30573
+ * A reply with text but nothing the parser recognises (e.g. a refusal like
30574
+ * "I can't help with that") parses to zero observations. Committing it would drop
30575
+ * the observed messages the same way an empty reply does, so it gets the same retry.
30576
+ * Degenerate replies pass through; the degenerate retry handles them.
30577
+ */
30578
+ function assertRecognizedObserverOutput(output, label, isRecognized) {
30579
+ if (!isRecognized(output.text)) throw new OmEmptyResponseError(label, "no observations");
30580
+ return output;
30581
+ }
30582
+ function hasSingleThreadObserverContent(text, extractors) {
30583
+ if (/^[ \t]*<observations>/im.test(text)) return true;
30584
+ const parsed = parseObserverOutput(text, extractors);
30585
+ return Boolean(parsed.degenerate || parsed.observations || parsed.currentTask || parsed.suggestedContinuation || parsed.threadTitle || Object.keys(parsed.extractedValues ?? {}).length || parsed.extractionFailures?.length);
30586
+ }
30587
+ /**
30588
+ * Multi-thread replies carry every recognised section inside `<thread>` blocks, so a reply
30589
+ * with no block for any requested thread has nothing to record.
30590
+ */
30591
+ function hasMultiThreadObserverContent(text, extractors, threadOrder) {
30592
+ const parsed = parseMultiThreadObserverOutput(text, extractors);
30593
+ return Boolean(parsed.degenerate || threadOrder.some((threadId) => parsed.threads.has(threadId)));
30594
+ }
30570
30595
  // src/processors/observational-memory/measure-image-buffer.ts
30571
30596
  /**
30572
30597
  * Synchronous image dimension lookup for in-memory image buffers.
@@ -42101,54 +42126,6 @@ ${formattedMessages}
42101
42126
  });
42102
42127
  }
42103
42128
  };
42104
- // src/processors/observational-memory/context-overflow.ts
42105
- /**
42106
- * Detects provider errors that reject a request for exceeding the model's context window.
42107
- *
42108
- * Providers report this with different codes and wording, so this matches the known shapes
42109
- * (OpenAI and OpenAI-compatible APIs, Anthropic, Google, Bedrock, xAI, Mistral, local servers)
42110
- * against the error's message, code, response body and causes.
42111
- */
42112
- const CONTEXT_OVERFLOW_PATTERNS = [
42113
- /context[_ ]length[_ ]exceeded/i,
42114
- /maximum context length/i,
42115
- /maximum prompt length/i,
42116
- /prompt is too long/i,
42117
- /input is too long/i,
42118
- /exceed\w*\b[^.]{0,40}\bcontext (?:window|length|limit|size)/i,
42119
- /input token count[^.]{0,40}exceeds the maximum/i,
42120
- /reduce the length of the (?:messages|prompt|input)/i
42121
- ];
42122
- const MAX_CAUSE_DEPTH = 4;
42123
- function collectErrorText(error, depth = 0) {
42124
- if (depth > MAX_CAUSE_DEPTH || error == null) return [];
42125
- if (typeof error === "string") return [error];
42126
- if (typeof error !== "object") return [];
42127
- const record = error;
42128
- const texts = [];
42129
- for (const key of [
42130
- "message",
42131
- "code",
42132
- "type",
42133
- "responseBody"
42134
- ]) {
42135
- const value = record[key];
42136
- if (typeof value === "string") texts.push(value);
42137
- }
42138
- if (record.data && typeof record.data === "object") texts.push(...collectErrorText(record.data.error ?? record.data, depth + 1));
42139
- if (record.error && typeof record.error === "object") texts.push(...collectErrorText(record.error, depth + 1));
42140
- texts.push(...collectErrorText(record.cause, depth + 1));
42141
- return texts;
42142
- }
42143
- function isRateLimited(error, depth = 0) {
42144
- if (depth > MAX_CAUSE_DEPTH || error == null || typeof error !== "object") return false;
42145
- const record = error;
42146
- return record.statusCode === 429 || isRateLimited(record.cause, depth + 1);
42147
- }
42148
- function isContextOverflowError(error) {
42149
- if (isRateLimited(error)) return false;
42150
- return collectErrorText(error).some((text) => CONTEXT_OVERFLOW_PATTERNS.some((pattern) => pattern.test(text)));
42151
- }
42152
42129
  // src/processors/observational-memory/temporal-markers.ts
42153
42130
  const TEMPORAL_GAP_REMINDER_TYPE = "temporal-gap";
42154
42131
  function getTemporalGapReminderText(gapText, timestamp) {
@@ -42550,7 +42527,7 @@ var ObservationalMemoryProcessor = class {
42550
42527
  if (!this.engine.getObservationConfig().observeOnContextOverflow) return;
42551
42528
  if (state[GATEWAY_STATE_KEY]) return;
42552
42529
  if (state[CONTEXT_OVERFLOW_STEP_KEY] === stepNumber) return;
42553
- if (!isContextOverflowError(error)) return;
42530
+ if ((0, _mastra_core_error.classifyProviderError)(error) !== "context_overflow") return;
42554
42531
  const context = this.engine.getThreadContext(requestContext, messageList);
42555
42532
  if (!context) return;
42556
42533
  if ((0, _mastra_core_memory.parseMemoryRequestContext)(requestContext)?.memoryConfig?.readOnly) return;
@@ -44707,7 +44684,7 @@ Notes:
44707
44684
  messageIds = input.map((item) => {
44708
44685
  if (typeof item === "string") return item;
44709
44686
  else if (item && typeof item === "object" && "id" in item) return item.id;
44710
- else throw new Error("Invalid input: array items must be strings or objects with an id property");
44687
+ throw new Error("Invalid input: array items must be strings or objects with an id property");
44711
44688
  });
44712
44689
  if (messageIds.filter((id) => !id || typeof id !== "string").length > 0) throw new Error("All message IDs must be non-empty strings");
44713
44690
  const span = this.createMemorySpan("delete", observabilityContext, void 0, { messageCount: messageIds.length });
@@ -45667,4 +45644,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
45667
45644
  }
45668
45645
  });
45669
45646
 
45670
- //# sourceMappingURL=src-CwOBNoJs.cjs.map
45647
+ //# sourceMappingURL=src-DnCjNqwE.cjs.map