@mastra/memory 1.32.2-alpha.0 → 1.33.0-alpha.2
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.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-evals-evals-with-memory.md +3 -1
- package/dist/docs/references/docs-memory-observational-memory.md +34 -0
- package/dist/docs/references/reference-memory-observational-memory.md +8 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/processors/index.cjs +1 -1
- package/dist/processors/index.js +1 -1
- package/dist/processors/observational-memory/date-utils.d.ts +36 -15
- package/dist/processors/observational-memory/date-utils.d.ts.map +1 -1
- package/dist/processors/observational-memory/error.d.ts +12 -0
- package/dist/processors/observational-memory/error.d.ts.map +1 -1
- package/dist/processors/observational-memory/extraction-runner.d.ts.map +1 -1
- package/dist/processors/observational-memory/markers.d.ts +3 -0
- package/dist/processors/observational-memory/markers.d.ts.map +1 -1
- package/dist/processors/observational-memory/message-utils.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-groups.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
- package/dist/processors/observational-memory/observational-memory.d.ts +17 -8
- package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-agent.d.ts +2 -0
- package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-runner.d.ts +3 -1
- package/dist/processors/observational-memory/observer-runner.d.ts.map +1 -1
- package/dist/processors/observational-memory/processor.d.ts +8 -0
- package/dist/processors/observational-memory/processor.d.ts.map +1 -1
- package/dist/processors/observational-memory/reflector-runner.d.ts +1 -1
- package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
- package/dist/processors/observational-memory/retry.d.ts +10 -0
- package/dist/processors/observational-memory/retry.d.ts.map +1 -1
- package/dist/processors/observational-memory/summarize.d.ts.map +1 -1
- package/dist/processors/observational-memory/types.d.ts +25 -1
- package/dist/processors/observational-memory/types.d.ts.map +1 -1
- package/dist/{src-B-_pkR2t.js → src-BikfoIdS.js} +747 -368
- package/dist/{src-B-_pkR2t.js.map → src-BikfoIdS.js.map} +1 -1
- package/dist/{src-CBxKXo2w.cjs → src-CeFdO1YZ.cjs} +747 -368
- package/dist/{src-CBxKXo2w.cjs.map → src-CeFdO1YZ.cjs.map} +1 -1
- package/package.json +5 -5
|
@@ -18718,10 +18718,227 @@ async function applyExtractorHooks(opts) {
|
|
|
18718
18718
|
};
|
|
18719
18719
|
}
|
|
18720
18720
|
//#endregion
|
|
18721
|
-
//#region src/processors/observational-memory/
|
|
18721
|
+
//#region src/processors/observational-memory/retry.ts
|
|
18722
|
+
/**
|
|
18723
|
+
* Retry knobs for the internal OM transport-error retry wrapper.
|
|
18724
|
+
* Exported as a mutable object so tests can shrink the backoff schedule
|
|
18725
|
+
* without changing public API.
|
|
18726
|
+
*
|
|
18727
|
+
* With the defaults the per-retry pre-jitter backoff schedule is:
|
|
18728
|
+
* 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
|
|
18729
|
+
* giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
|
|
18730
|
+
* before the final attempt fails. Designed to ride out short provider /
|
|
18731
|
+
* network blips without holding the actor turn for much longer than that.
|
|
18732
|
+
*
|
|
18733
|
+
* @internal
|
|
18734
|
+
*/
|
|
18735
|
+
const RETRY_CONFIG = {
|
|
18736
|
+
/** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
|
|
18737
|
+
maxRetries: 8,
|
|
18738
|
+
/** Initial backoff delay in milliseconds. */
|
|
18739
|
+
initialDelayMs: 1e3,
|
|
18740
|
+
/** Multiplier applied to the delay after each failed attempt. */
|
|
18741
|
+
backoffFactor: 2,
|
|
18742
|
+
/** Cap on per-attempt delay (ms). */
|
|
18743
|
+
maxDelayMs: 12e4,
|
|
18744
|
+
/** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
|
|
18745
|
+
jitter: .2
|
|
18746
|
+
};
|
|
18747
|
+
const TRANSIENT_MESSAGE_SUBSTRINGS = [
|
|
18748
|
+
"terminated",
|
|
18749
|
+
"fetch failed",
|
|
18750
|
+
"econnreset",
|
|
18751
|
+
"econnrefused",
|
|
18752
|
+
"enotfound",
|
|
18753
|
+
"eai_again",
|
|
18754
|
+
"socket hang up",
|
|
18755
|
+
"network error",
|
|
18756
|
+
"request timed out",
|
|
18757
|
+
"request timeout",
|
|
18758
|
+
"connection reset",
|
|
18759
|
+
"connection closed"
|
|
18760
|
+
];
|
|
18722
18761
|
function isRecord$5(value) {
|
|
18723
18762
|
return typeof value === "object" && value !== null;
|
|
18724
18763
|
}
|
|
18764
|
+
function isAbortError$1(error) {
|
|
18765
|
+
if (!isRecord$5(error)) return false;
|
|
18766
|
+
if (error.name === "AbortError") return true;
|
|
18767
|
+
if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
|
|
18768
|
+
return false;
|
|
18769
|
+
}
|
|
18770
|
+
function hasTransientMessage(value) {
|
|
18771
|
+
if (!isRecord$5(value)) return false;
|
|
18772
|
+
const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
|
|
18773
|
+
if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
|
|
18774
|
+
if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
|
|
18775
|
+
return false;
|
|
18776
|
+
}
|
|
18777
|
+
function isRetryableHttpStatus(status) {
|
|
18778
|
+
if (status === 408 || status === 425 || status === 429) return true;
|
|
18779
|
+
return status >= 500 && status <= 599;
|
|
18780
|
+
}
|
|
18781
|
+
function hasRetryableHttpStatus(value) {
|
|
18782
|
+
if (!isRecord$5(value)) return false;
|
|
18783
|
+
if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
|
|
18784
|
+
if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
|
|
18785
|
+
return false;
|
|
18786
|
+
}
|
|
18787
|
+
function hasIsRetryableFlag(value) {
|
|
18788
|
+
if (!isRecord$5(value)) return false;
|
|
18789
|
+
return value.isRetryable === true;
|
|
18790
|
+
}
|
|
18791
|
+
/**
|
|
18792
|
+
* Returns true when a user-initiated cancellation appears anywhere in the
|
|
18793
|
+
* error's `cause`/`error` wrapper chain, so a wrapped abort can never be
|
|
18794
|
+
* mistaken for a retryable or survivable provider failure.
|
|
18795
|
+
*
|
|
18796
|
+
* @internal
|
|
18797
|
+
*/
|
|
18798
|
+
function hasAbortInChain(error) {
|
|
18799
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18800
|
+
function visit(candidate) {
|
|
18801
|
+
if (isAbortError$1(candidate)) return true;
|
|
18802
|
+
if (!isRecord$5(candidate)) return false;
|
|
18803
|
+
if (seen.has(candidate)) return false;
|
|
18804
|
+
seen.add(candidate);
|
|
18805
|
+
return visit(candidate.cause) || visit(candidate.error);
|
|
18806
|
+
}
|
|
18807
|
+
return visit(error);
|
|
18808
|
+
}
|
|
18809
|
+
/**
|
|
18810
|
+
* Returns true when the given error looks like a transient transport-class
|
|
18811
|
+
* failure that's worth retrying — undici `terminated`, `fetch failed`,
|
|
18812
|
+
* `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
|
|
18813
|
+
* common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
|
|
18814
|
+
* wrapper errors don't hide the real failure.
|
|
18815
|
+
*
|
|
18816
|
+
* Never retries on user-initiated aborts.
|
|
18817
|
+
*
|
|
18818
|
+
* @internal
|
|
18819
|
+
*/
|
|
18820
|
+
function isTransientLLMError(error) {
|
|
18821
|
+
if (hasAbortInChain(error)) return false;
|
|
18822
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
18823
|
+
function visit(candidate) {
|
|
18824
|
+
if (isRecord$5(candidate)) {
|
|
18825
|
+
if (visited.has(candidate)) return false;
|
|
18826
|
+
visited.add(candidate);
|
|
18827
|
+
}
|
|
18828
|
+
if (hasTransientMessage(candidate)) return true;
|
|
18829
|
+
if (hasRetryableHttpStatus(candidate)) return true;
|
|
18830
|
+
if (hasIsRetryableFlag(candidate)) return true;
|
|
18831
|
+
if (isRecord$5(candidate)) {
|
|
18832
|
+
if (visit(candidate.cause)) return true;
|
|
18833
|
+
if (visit(candidate.error)) return true;
|
|
18834
|
+
}
|
|
18835
|
+
return false;
|
|
18836
|
+
}
|
|
18837
|
+
return visit(error);
|
|
18838
|
+
}
|
|
18839
|
+
/**
|
|
18840
|
+
* Compute the backoff delay (ms) for the Nth retry (0-indexed).
|
|
18841
|
+
*
|
|
18842
|
+
* Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
|
|
18843
|
+
* `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
|
|
18844
|
+
* tests that lock the schedule against drift.
|
|
18845
|
+
*
|
|
18846
|
+
* @internal
|
|
18847
|
+
*/
|
|
18848
|
+
function computeDelay(attempt) {
|
|
18849
|
+
const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
|
|
18850
|
+
const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
|
|
18851
|
+
if (RETRY_CONFIG.jitter <= 0) return capped;
|
|
18852
|
+
const jitterRange = capped * RETRY_CONFIG.jitter;
|
|
18853
|
+
const offset = (Math.random() * 2 - 1) * jitterRange;
|
|
18854
|
+
return Math.max(0, Math.round(capped + offset));
|
|
18855
|
+
}
|
|
18856
|
+
function sleep(ms, abortSignal) {
|
|
18857
|
+
if (ms <= 0) return Promise.resolve();
|
|
18858
|
+
return new Promise((resolve, reject) => {
|
|
18859
|
+
if (abortSignal?.aborted) {
|
|
18860
|
+
reject(/* @__PURE__ */ new Error("The operation was aborted."));
|
|
18861
|
+
return;
|
|
18862
|
+
}
|
|
18863
|
+
const timer = setTimeout(() => {
|
|
18864
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
18865
|
+
resolve();
|
|
18866
|
+
}, ms);
|
|
18867
|
+
const onAbort = () => {
|
|
18868
|
+
clearTimeout(timer);
|
|
18869
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
18870
|
+
reject(/* @__PURE__ */ new Error("The operation was aborted."));
|
|
18871
|
+
};
|
|
18872
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
18873
|
+
});
|
|
18874
|
+
}
|
|
18875
|
+
/**
|
|
18876
|
+
* Run `fn` with retries on transient transport-class errors.
|
|
18877
|
+
*
|
|
18878
|
+
* Non-transient errors (auth, validation, schema, etc.) are rethrown
|
|
18879
|
+
* immediately. User-initiated aborts are rethrown without delay.
|
|
18880
|
+
*
|
|
18881
|
+
* @internal
|
|
18882
|
+
*/
|
|
18883
|
+
async function withRetry(fn, opts) {
|
|
18884
|
+
const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries } = opts;
|
|
18885
|
+
let attempt = 0;
|
|
18886
|
+
while (true) {
|
|
18887
|
+
if (abortSignal?.aborted) throw new Error("The operation was aborted.");
|
|
18888
|
+
try {
|
|
18889
|
+
return await fn();
|
|
18890
|
+
} catch (error) {
|
|
18891
|
+
if (hasAbortInChain(error) || abortSignal?.aborted) throw error;
|
|
18892
|
+
if (attempt >= maxRetries || !isTransientLLMError(error)) {
|
|
18893
|
+
if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
|
|
18894
|
+
throw error;
|
|
18895
|
+
}
|
|
18896
|
+
const delay = computeDelay(attempt);
|
|
18897
|
+
attempt++;
|
|
18898
|
+
omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
|
|
18899
|
+
await sleep(delay, abortSignal);
|
|
18900
|
+
}
|
|
18901
|
+
}
|
|
18902
|
+
}
|
|
18903
|
+
//#endregion
|
|
18904
|
+
//#region src/processors/observational-memory/error.ts
|
|
18905
|
+
const AI_API_CALL_ERROR_MARKER = Symbol.for("vercel.ai.error.AI_APICallError");
|
|
18906
|
+
function isRecord$4(value) {
|
|
18907
|
+
return typeof value === "object" && value !== null;
|
|
18908
|
+
}
|
|
18909
|
+
function isOmModelExecutionFailure(error) {
|
|
18910
|
+
if (hasAbortInChain(error)) return false;
|
|
18911
|
+
if (isTransientLLMError(error)) return true;
|
|
18912
|
+
const visited = /* @__PURE__ */ new Set();
|
|
18913
|
+
let current = error;
|
|
18914
|
+
while (isRecord$4(current) && !visited.has(current)) {
|
|
18915
|
+
visited.add(current);
|
|
18916
|
+
if (Object.prototype.hasOwnProperty.call(current, AI_API_CALL_ERROR_MARKER)) return Reflect.get(current, AI_API_CALL_ERROR_MARKER) === true;
|
|
18917
|
+
current = current.cause ?? current.error;
|
|
18918
|
+
}
|
|
18919
|
+
return false;
|
|
18920
|
+
}
|
|
18921
|
+
var OmModelExecutionError = class extends Error {
|
|
18922
|
+
failureKind;
|
|
18923
|
+
constructor(failureKind, cause) {
|
|
18924
|
+
super(formatOmError(cause), { cause });
|
|
18925
|
+
this.failureKind = failureKind;
|
|
18926
|
+
this.name = "OmModelExecutionError";
|
|
18927
|
+
}
|
|
18928
|
+
};
|
|
18929
|
+
function isOmModelExecutionError(error) {
|
|
18930
|
+
try {
|
|
18931
|
+
return error instanceof OmModelExecutionError;
|
|
18932
|
+
} catch {
|
|
18933
|
+
return false;
|
|
18934
|
+
}
|
|
18935
|
+
}
|
|
18936
|
+
function getOmFailureMetadata(error, failurePolicy) {
|
|
18937
|
+
return {
|
|
18938
|
+
failurePolicy,
|
|
18939
|
+
...isOmModelExecutionError(error) ? { failureKind: error.failureKind } : {}
|
|
18940
|
+
};
|
|
18941
|
+
}
|
|
18725
18942
|
/** Keep provider diagnostics in streamed/persisted markers, not whole API request/response objects. */
|
|
18726
18943
|
function formatOmError(error) {
|
|
18727
18944
|
const details = /* @__PURE__ */ new Set();
|
|
@@ -18731,7 +18948,7 @@ function formatOmError(error) {
|
|
|
18731
18948
|
};
|
|
18732
18949
|
function collectErrorDetails(value, depth) {
|
|
18733
18950
|
if (depth > 5) return;
|
|
18734
|
-
if (!isRecord$
|
|
18951
|
+
if (!isRecord$4(value)) {
|
|
18735
18952
|
if (value !== void 0) add(String(value));
|
|
18736
18953
|
return;
|
|
18737
18954
|
}
|
|
@@ -18741,10 +18958,10 @@ function formatOmError(error) {
|
|
|
18741
18958
|
if (typeof value.statusCode === "number") add(`HTTP ${value.statusCode}`);
|
|
18742
18959
|
if (typeof value.responseBody === "string") try {
|
|
18743
18960
|
const body = JSON.parse(value.responseBody);
|
|
18744
|
-
if (isRecord$
|
|
18961
|
+
if (isRecord$4(body)) {
|
|
18745
18962
|
add(body.message);
|
|
18746
18963
|
add(body.detail);
|
|
18747
|
-
if (isRecord$
|
|
18964
|
+
if (isRecord$4(body.error)) add(body.error.message);
|
|
18748
18965
|
else add(body.error);
|
|
18749
18966
|
}
|
|
18750
18967
|
} catch {}
|
|
@@ -18759,8 +18976,8 @@ function formatOmError(error) {
|
|
|
18759
18976
|
}
|
|
18760
18977
|
//#endregion
|
|
18761
18978
|
//#region src/processors/observational-memory/extraction-runner.ts
|
|
18762
|
-
function isAbortError
|
|
18763
|
-
return abortSignal?.aborted === true || error
|
|
18979
|
+
function isAbortError(error, abortSignal) {
|
|
18980
|
+
return abortSignal?.aborted === true || hasAbortInChain(error);
|
|
18764
18981
|
}
|
|
18765
18982
|
function shouldRetryEmptyStructuredObject(object, extractors) {
|
|
18766
18983
|
return Object.keys(object).length === 0 && extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject);
|
|
@@ -18786,7 +19003,12 @@ If a prior value is still applicable and carry-forward is enabled, return that p
|
|
|
18786
19003
|
${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values\n\n${priorLines.join("\n\n")}` : ""}`;
|
|
18787
19004
|
const values = {};
|
|
18788
19005
|
const failures = [];
|
|
18789
|
-
const streamWithStructuredOutput = async (jsonPromptInjection) => {
|
|
19006
|
+
const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry(() => streamOnce(jsonPromptInjection), {
|
|
19007
|
+
label: `om-${opts.source}-structured-extraction`,
|
|
19008
|
+
abortSignal: opts.abortSignal,
|
|
19009
|
+
maxRetries: 1
|
|
19010
|
+
});
|
|
19011
|
+
const streamOnce = async (jsonPromptInjection) => {
|
|
18790
19012
|
const object = await (await opts.agent.stream(prompt, {
|
|
18791
19013
|
structuredOutput: {
|
|
18792
19014
|
schema,
|
|
@@ -18806,11 +19028,21 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
|
|
|
18806
19028
|
object = await streamWithStructuredOutput();
|
|
18807
19029
|
retryEmptyObject = shouldRetryEmptyStructuredObject(object, structuredExtractors);
|
|
18808
19030
|
} catch (error) {
|
|
18809
|
-
if (isAbortError
|
|
19031
|
+
if (isAbortError(error, opts.abortSignal)) throw error;
|
|
19032
|
+
if (isTransientLLMError(error)) {
|
|
19033
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19034
|
+
return {
|
|
19035
|
+
values,
|
|
19036
|
+
failures: structuredExtractors.map((extractor) => ({
|
|
19037
|
+
slug: extractor.slug,
|
|
19038
|
+
error: message
|
|
19039
|
+
}))
|
|
19040
|
+
};
|
|
19041
|
+
}
|
|
18810
19042
|
try {
|
|
18811
19043
|
object = await streamWithStructuredOutput(_mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
|
|
18812
19044
|
} catch (fallbackError) {
|
|
18813
|
-
if (isAbortError
|
|
19045
|
+
if (isAbortError(fallbackError, opts.abortSignal)) throw fallbackError;
|
|
18814
19046
|
const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
18815
19047
|
return {
|
|
18816
19048
|
values,
|
|
@@ -18824,7 +19056,7 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
|
|
|
18824
19056
|
if (retryEmptyObject) try {
|
|
18825
19057
|
object = await streamWithStructuredOutput(_mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
|
|
18826
19058
|
} catch (fallbackError) {
|
|
18827
|
-
if (isAbortError
|
|
19059
|
+
if (isAbortError(fallbackError, opts.abortSignal)) throw fallbackError;
|
|
18828
19060
|
const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
18829
19061
|
return {
|
|
18830
19062
|
values,
|
|
@@ -18852,15 +19084,15 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
|
|
|
18852
19084
|
//#endregion
|
|
18853
19085
|
//#region src/system-reminders.ts
|
|
18854
19086
|
const LEGACY_SYSTEM_REMINDER_METADATA_KEY = "dynamicAgentsMdReminder";
|
|
18855
|
-
function isRecord$
|
|
19087
|
+
function isRecord$3(value) {
|
|
18856
19088
|
return typeof value === "object" && value !== null;
|
|
18857
19089
|
}
|
|
18858
19090
|
function isSystemReminderMessage(message) {
|
|
18859
|
-
if (!isRecord$
|
|
19091
|
+
if (!isRecord$3(message.content)) return false;
|
|
18860
19092
|
const metadata = message.content.metadata;
|
|
18861
|
-
if (message.role === "signal") return isRecord$
|
|
19093
|
+
if (message.role === "signal") return isRecord$3(metadata) && isRecord$3(metadata.signal) && (metadata.signal.type === "system-reminder" || metadata.signal.type === "reactive");
|
|
18862
19094
|
if (message.role !== "user") return false;
|
|
18863
|
-
if (isRecord$
|
|
19095
|
+
if (isRecord$3(metadata) && (isRecord$3(metadata.systemReminder) || LEGACY_SYSTEM_REMINDER_METADATA_KEY in metadata)) return true;
|
|
18864
19096
|
const firstTextPart = message.content.parts.find((part) => part.type === "text");
|
|
18865
19097
|
return typeof firstTextPart?.text === "string" && firstTextPart.text.startsWith("<system-reminder");
|
|
18866
19098
|
}
|
|
@@ -18923,12 +19155,41 @@ function stripEphemeralAnchorIds(observations) {
|
|
|
18923
19155
|
* Date/time utility functions for Observational Memory.
|
|
18924
19156
|
* Pure functions for formatting relative timestamps and annotating observations.
|
|
18925
19157
|
*/
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
18931
|
-
|
|
19158
|
+
const DAY_MS = 1440 * 60 * 1e3;
|
|
19159
|
+
/** Day number of the calendar date a parsed date names. Parsed dates are built from local-time fields. */
|
|
19160
|
+
function calendarDay(date) {
|
|
19161
|
+
return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / DAY_MS;
|
|
19162
|
+
}
|
|
19163
|
+
const knownTimeZones = /* @__PURE__ */ new Map();
|
|
19164
|
+
/** `timeZone` when this runtime recognises it; otherwise undefined, which means the process time zone. */
|
|
19165
|
+
function resolveTimeZone(timeZone) {
|
|
19166
|
+
if (!timeZone) return void 0;
|
|
19167
|
+
let known = knownTimeZones.get(timeZone);
|
|
19168
|
+
if (known === void 0) {
|
|
19169
|
+
try {
|
|
19170
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
19171
|
+
known = true;
|
|
19172
|
+
} catch {
|
|
19173
|
+
known = false;
|
|
19174
|
+
}
|
|
19175
|
+
knownTimeZones.set(timeZone, known);
|
|
19176
|
+
}
|
|
19177
|
+
return known ? timeZone : void 0;
|
|
19178
|
+
}
|
|
19179
|
+
/** Day number of the calendar date `instant` falls on in `timeZone` (the process time zone when unset). */
|
|
19180
|
+
function calendarDayIn(instant, timeZone) {
|
|
19181
|
+
const zone = resolveTimeZone(timeZone);
|
|
19182
|
+
if (!zone) return calendarDay(instant);
|
|
19183
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
19184
|
+
timeZone: zone,
|
|
19185
|
+
year: "numeric",
|
|
19186
|
+
month: "numeric",
|
|
19187
|
+
day: "numeric"
|
|
19188
|
+
}).formatToParts(instant);
|
|
19189
|
+
const part = (type) => Number(parts.find((p) => p.type === type)?.value);
|
|
19190
|
+
return Date.UTC(part("year"), part("month") - 1, part("day")) / DAY_MS;
|
|
19191
|
+
}
|
|
19192
|
+
function formatRelativeDays(diffDays) {
|
|
18932
19193
|
if (diffDays < 0) {
|
|
18933
19194
|
const futureDays = Math.abs(diffDays);
|
|
18934
19195
|
if (futureDays === 1) return "tomorrow";
|
|
@@ -18954,8 +19215,7 @@ function formatRelativeTime(date, currentDate) {
|
|
|
18954
19215
|
* Returns null for consecutive days (no gap marker needed).
|
|
18955
19216
|
*/
|
|
18956
19217
|
function formatGapBetweenDates(prevDate, currDate) {
|
|
18957
|
-
const
|
|
18958
|
-
const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
19218
|
+
const diffDays = calendarDay(currDate) - calendarDay(prevDate);
|
|
18959
19219
|
if (diffDays <= 1) return null;
|
|
18960
19220
|
else if (diffDays < 7) return `[${diffDays} days later]`;
|
|
18961
19221
|
else if (diffDays < 14) return `[1 week later]`;
|
|
@@ -18963,45 +19223,156 @@ function formatGapBetweenDates(prevDate, currDate) {
|
|
|
18963
19223
|
else if (diffDays < 60) return `[1 month later]`;
|
|
18964
19224
|
else return `[${Math.floor(diffDays / 30)} months later]`;
|
|
18965
19225
|
}
|
|
19226
|
+
const MONTH_INDEX = {
|
|
19227
|
+
jan: 0,
|
|
19228
|
+
feb: 1,
|
|
19229
|
+
mar: 2,
|
|
19230
|
+
apr: 3,
|
|
19231
|
+
may: 4,
|
|
19232
|
+
jun: 5,
|
|
19233
|
+
jul: 6,
|
|
19234
|
+
aug: 7,
|
|
19235
|
+
sep: 8,
|
|
19236
|
+
oct: 9,
|
|
19237
|
+
nov: 10,
|
|
19238
|
+
dec: 11
|
|
19239
|
+
};
|
|
19240
|
+
const MONTH = String.raw`(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\.?`;
|
|
19241
|
+
const DAY = String.raw`\d{1,2}(?:st|nd|rd|th)?`;
|
|
19242
|
+
const YEAR = String.raw`(?:19|20|21)\d{2}(?!\d)`;
|
|
19243
|
+
const MERIDIEM = String.raw`(?:[AaPp]\.[Mm]\.|[AaPp][Mm]\b)`;
|
|
19244
|
+
const TIME = String.raw`(?:,?\s+(?:at\s+)?\d{1,2}:\d{2}(?:\s*${MERIDIEM})?|\s+at\s+\d{1,2}\s*${MERIDIEM})`;
|
|
19245
|
+
const QUALIFIER = String.raw`(?:(?:early|mid|late)[- ](?:to[- ](?:early|mid|late)[- ])?)`;
|
|
19246
|
+
const RANGE_SEP = String.raw`(?:\s*[–—]\s*|\s*-\s*|\s+(?:to|through|until)\s+)`;
|
|
19247
|
+
/**
|
|
19248
|
+
* Dates written into observation text that carry their own year, longest forms first:
|
|
19249
|
+
* "Mar 1 - Mar 18, 2025", "May 27-28, 2023", "January 10, 2024 at 3:00 PM",
|
|
19250
|
+
* "June–July 2022", "Dec 2021 – June 2023", "late April 2023".
|
|
19251
|
+
* Bare years, dates without a year, and ISO dates (often part of model IDs, API versions,
|
|
19252
|
+
* paths and branch names) are deliberately not matched.
|
|
19253
|
+
*/
|
|
19254
|
+
const FREE_TEXT_DATE = new RegExp([
|
|
19255
|
+
String.raw`\b${MONTH}\s+${DAY}(?:,?\s+${YEAR})?${RANGE_SEP}${MONTH}\s+${DAY},?\s+${YEAR}${TIME}?`,
|
|
19256
|
+
String.raw`\b${MONTH}\s+${DAY}${RANGE_SEP}${DAY},?\s+${YEAR}`,
|
|
19257
|
+
String.raw`\b${MONTH}\s+${DAY},?\s+${YEAR}${TIME}?`,
|
|
19258
|
+
String.raw`\b${QUALIFIER}?${MONTH}(?:\s+${YEAR})?${RANGE_SEP}${QUALIFIER}?${MONTH}\s+${YEAR}`,
|
|
19259
|
+
String.raw`\b${QUALIFIER}?${MONTH}\s+${YEAR}`
|
|
19260
|
+
].join("|"), "g");
|
|
19261
|
+
const ENDPOINT = new RegExp(String.raw`^(?:(early|mid|late)\s*)?(?:(${MONTH})\s*)?(?:(\d{1,2})(?:st|nd|rd|th)?)?,?\s*(?:(${YEAR}))?$`, "i");
|
|
19262
|
+
function parseEndpoint(text) {
|
|
19263
|
+
const match = ENDPOINT.exec(text.trim());
|
|
19264
|
+
if (!match || !match[0]) return null;
|
|
19265
|
+
const [, qualifier, month, day, year] = match;
|
|
19266
|
+
return {
|
|
19267
|
+
...qualifier ? { qualifier: qualifier.toLowerCase() } : {},
|
|
19268
|
+
...month ? { month: MONTH_INDEX[month.slice(0, 3).toLowerCase()] } : {},
|
|
19269
|
+
...day ? { day: Number(day) } : {},
|
|
19270
|
+
...year ? { year: Number(year) } : {}
|
|
19271
|
+
};
|
|
19272
|
+
}
|
|
19273
|
+
const QUALIFIER_DAY = {
|
|
19274
|
+
early: 7,
|
|
19275
|
+
mid: 15,
|
|
19276
|
+
late: 23
|
|
19277
|
+
};
|
|
19278
|
+
const QUALIFIER_MONTH = {
|
|
19279
|
+
early: [1, 15],
|
|
19280
|
+
mid: [6, 1],
|
|
19281
|
+
late: [10, 15]
|
|
19282
|
+
};
|
|
19283
|
+
/** Earliest and latest local day an endpoint can mean; null when it has no year or is not a real date. */
|
|
19284
|
+
function endpointBounds(date) {
|
|
19285
|
+
const { qualifier, month, day, year } = date;
|
|
19286
|
+
if (year === void 0) return null;
|
|
19287
|
+
if (month === void 0) {
|
|
19288
|
+
if (day !== void 0) return null;
|
|
19289
|
+
if (qualifier) {
|
|
19290
|
+
const [m, d] = QUALIFIER_MONTH[qualifier];
|
|
19291
|
+
return [new Date(year, m, d), new Date(year, m, d)];
|
|
19292
|
+
}
|
|
19293
|
+
return [new Date(year, 0, 1), new Date(year, 11, 31)];
|
|
19294
|
+
}
|
|
19295
|
+
if (day !== void 0) {
|
|
19296
|
+
const point = new Date(year, month, day);
|
|
19297
|
+
if (point.getMonth() !== month) return null;
|
|
19298
|
+
return [point, point];
|
|
19299
|
+
}
|
|
19300
|
+
if (qualifier) {
|
|
19301
|
+
const point = new Date(year, month, QUALIFIER_DAY[qualifier]);
|
|
19302
|
+
return [point, point];
|
|
19303
|
+
}
|
|
19304
|
+
return [new Date(year, month, 1), new Date(year, month + 1, 0)];
|
|
19305
|
+
}
|
|
19306
|
+
/** Parses one alternative: a single date or a two-ended range, filling each end's missing parts from the other. */
|
|
19307
|
+
function parseSpanAlternative(text) {
|
|
19308
|
+
const ends = text.split(new RegExp(`${RANGE_SEP}(?=\\S)`)).filter(Boolean);
|
|
19309
|
+
if (ends.length === 0 || ends.length > 2) return null;
|
|
19310
|
+
const parsed = ends.map(parseEndpoint);
|
|
19311
|
+
if (parsed.some((end) => end === null)) return null;
|
|
19312
|
+
const [first, second = first] = parsed;
|
|
19313
|
+
const from = { ...first };
|
|
19314
|
+
const to = { ...second };
|
|
19315
|
+
const fromYearInferred = from.year === void 0;
|
|
19316
|
+
const toYearInferred = to.year === void 0;
|
|
19317
|
+
from.year ??= to.year;
|
|
19318
|
+
to.year ??= from.year;
|
|
19319
|
+
if (from.month === void 0 && (from.day !== void 0 || from.qualifier) && to.month !== void 0) from.month = to.month;
|
|
19320
|
+
if (to.month === void 0 && to.day !== void 0 && from.month !== void 0) to.month = from.month;
|
|
19321
|
+
if (from.month !== void 0 && to.month !== void 0 && from.month > to.month && from.year === to.year) {
|
|
19322
|
+
if (fromYearInferred) from.year -= 1;
|
|
19323
|
+
else if (toYearInferred) to.year += 1;
|
|
19324
|
+
}
|
|
19325
|
+
const fromBounds = endpointBounds(from);
|
|
19326
|
+
const toBounds = endpointBounds(to);
|
|
19327
|
+
if (!fromBounds || !toBounds) return null;
|
|
19328
|
+
const span = {
|
|
19329
|
+
start: fromBounds[0],
|
|
19330
|
+
end: toBounds[1]
|
|
19331
|
+
};
|
|
19332
|
+
return span.start <= span.end ? span : null;
|
|
19333
|
+
}
|
|
19334
|
+
function normalizeDateText(text) {
|
|
19335
|
+
return text.replace(/\s+/g, " ").trim().replace(/^(?:approx(?:\.|imately)?|about|around|circa|c\.|~)\s*/i, "").replace(/^(?:by|in|until|before|after)\s+/i, "").replace(/\b(?:the\s+)?end\s+of\s+/gi, "late ").replace(/\b(?:the\s+)?(?:start|beginning)\s+of\s+/gi, "early ").replace(/\b(\d{4})-(\d{2})-(\d{2})\b/g, (whole, y, m, d) => {
|
|
19336
|
+
const month = Object.keys(MONTH_INDEX)[Number(m) - 1];
|
|
19337
|
+
return month ? `${month} ${Number(d)}, ${y}` : whole;
|
|
19338
|
+
}).replace(new RegExp(TIME, "g"), "").replace(/\b(early|mid|late|to)-/gi, "$1 ").replace(/ {2,}/g, " ").trim();
|
|
19339
|
+
}
|
|
19340
|
+
/**
|
|
19341
|
+
* Parses the whole of `text` as a date or date range. Accepts "May 30, 2023",
|
|
19342
|
+
* "Mar 22, 2025 at 18:08", "May 27-28, 2023", "Dec 27–31, 2021", "Mar 1 - Mar 18, 2025",
|
|
19343
|
+
* "Aug 1, 2024 - Feb 28, 2025", "August 2024", "June–July 2022", "late April 2023",
|
|
19344
|
+
* "mid-to-late May 2023", "late 2023", "2035", "2021–2026", an "approx." prefix, and
|
|
19345
|
+
* "A or B" alternatives (their union). Returns null when no year is known.
|
|
19346
|
+
*/
|
|
19347
|
+
function parseDateSpan(text) {
|
|
19348
|
+
const alternatives = normalizeDateText(text).split(/\s+or\s+/i).map(parseSpanAlternative);
|
|
19349
|
+
if (alternatives.length === 0 || alternatives.some((span) => span === null)) return null;
|
|
19350
|
+
const spans = alternatives;
|
|
19351
|
+
return {
|
|
19352
|
+
start: new Date(Math.min(...spans.map((span) => span.start.getTime()))),
|
|
19353
|
+
end: new Date(Math.max(...spans.map((span) => span.end.getTime())))
|
|
19354
|
+
};
|
|
19355
|
+
}
|
|
19356
|
+
const QUALIFIED_YEAR = new RegExp(String.raw`\b(?:by|in|until|before|after|(?:the\s+)?(?:early|mid|late|end\s+of|start\s+of|beginning\s+of))\s+${YEAR}`, "gi");
|
|
18966
19357
|
/**
|
|
18967
|
-
*
|
|
18968
|
-
*
|
|
19358
|
+
* Like `parseDateSpan`, but falls back to the first dated expression inside longer text,
|
|
19359
|
+
* then to a year on its own ("by late 2027", "$500 in 2024"). Only for text known to be a date.
|
|
18969
19360
|
*/
|
|
18970
|
-
function
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
const
|
|
18975
|
-
if (
|
|
18976
|
-
}
|
|
18977
|
-
|
|
18978
|
-
|
|
18979
|
-
|
|
18980
|
-
|
|
18981
|
-
|
|
18982
|
-
|
|
18983
|
-
}
|
|
18984
|
-
|
|
18985
|
-
const vagueMatch = dateContent.match(/(late|early|mid)[- ]?(?:to[- ]?(?:late|early|mid)[- ]?)?([A-Z][a-z]+)\s+(\d{4})/i);
|
|
18986
|
-
if (vagueMatch) {
|
|
18987
|
-
const month = vagueMatch[2];
|
|
18988
|
-
const year = vagueMatch[3];
|
|
18989
|
-
const modifier = vagueMatch[1].toLowerCase();
|
|
18990
|
-
let day = 15;
|
|
18991
|
-
if (modifier === "early") day = 7;
|
|
18992
|
-
if (modifier === "late") day = 23;
|
|
18993
|
-
const parsed = /* @__PURE__ */ new Date(`${month} ${day}, ${year}`);
|
|
18994
|
-
if (!isNaN(parsed.getTime())) targetDate = parsed;
|
|
18995
|
-
}
|
|
18996
|
-
}
|
|
18997
|
-
if (!targetDate) {
|
|
18998
|
-
const crossMonthMatch = dateContent.match(/([A-Z][a-z]+)\s+to\s+(?:early\s+)?([A-Z][a-z]+)\s+(\d{4})/i);
|
|
18999
|
-
if (crossMonthMatch) {
|
|
19000
|
-
const parsed = /* @__PURE__ */ new Date(`${crossMonthMatch[2]} 1, ${crossMonthMatch[3]}`);
|
|
19001
|
-
if (!isNaN(parsed.getTime())) targetDate = parsed;
|
|
19002
|
-
}
|
|
19003
|
-
}
|
|
19004
|
-
return targetDate;
|
|
19361
|
+
function findDateSpan(text) {
|
|
19362
|
+
const whole = parseDateSpan(text);
|
|
19363
|
+
if (whole) return whole;
|
|
19364
|
+
for (const pattern of [FREE_TEXT_DATE, QUALIFIED_YEAR]) for (const match of text.matchAll(pattern)) {
|
|
19365
|
+
const span = parseDateSpan(match[0]);
|
|
19366
|
+
if (span) return span;
|
|
19367
|
+
}
|
|
19368
|
+
return null;
|
|
19369
|
+
}
|
|
19370
|
+
function relativeSpan(span, today) {
|
|
19371
|
+
const start = formatRelativeDays(today - calendarDay(span.start));
|
|
19372
|
+
const end = formatRelativeDays(today - calendarDay(span.end));
|
|
19373
|
+
if (start === end) return start;
|
|
19374
|
+
if (!end.startsWith("in ")) return `${start} to ${end}`;
|
|
19375
|
+
return start.startsWith("in ") ? `${start} to ${end.slice(3)}` : `${start} to ${end.slice(3)} from now`;
|
|
19005
19376
|
}
|
|
19006
19377
|
/**
|
|
19007
19378
|
* Detects if an observation line indicates future intent (will do, plans to, looking forward to, etc.)
|
|
@@ -19021,40 +19392,59 @@ function isFutureIntentObservation(line) {
|
|
|
19021
19392
|
}
|
|
19022
19393
|
/**
|
|
19023
19394
|
* Expand inline estimated dates with relative time.
|
|
19024
|
-
* Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023)"
|
|
19025
|
-
* and expands them to "(meaning May 30, 2023 -
|
|
19395
|
+
* Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023 at 3:00 PM)"
|
|
19396
|
+
* and expands them to "(meaning May 30, 2023 - 3 weeks ago)". Notes without a year are left alone.
|
|
19026
19397
|
*/
|
|
19027
|
-
function expandInlineEstimatedDates(observations, currentDate) {
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
}
|
|
19039
|
-
|
|
19398
|
+
function expandInlineEstimatedDates(observations, currentDate, timeZone) {
|
|
19399
|
+
const inlineDateRegex = /\((estimated|meaning)\s([^()]*)\)/gi;
|
|
19400
|
+
const today = calendarDayIn(currentDate, timeZone);
|
|
19401
|
+
return observations.replace(inlineDateRegex, (match, prefix, noteText, offset) => {
|
|
19402
|
+
const dateContent = noteText.trimStart();
|
|
19403
|
+
const span = findDateSpan(dateContent);
|
|
19404
|
+
if (!span) return match;
|
|
19405
|
+
const relative = relativeSpan(span, today);
|
|
19406
|
+
const lineStart = observations.lastIndexOf("\n", offset) + 1;
|
|
19407
|
+
const lineBeforeDate = observations.slice(lineStart, offset);
|
|
19408
|
+
if (calendarDay(span.end) < today && isFutureIntentObservation(lineBeforeDate)) return `(${prefix} ${dateContent} - ${relative}, likely already happened)`;
|
|
19409
|
+
return `(${prefix} ${dateContent} - ${relative})`;
|
|
19410
|
+
});
|
|
19411
|
+
}
|
|
19412
|
+
/**
|
|
19413
|
+
* Annotates dates written into observation text, e.g. "exam on January 10, 2024" becomes
|
|
19414
|
+
* "exam on January 10, 2024 (5 months ago)". Only dates that state their year are annotated;
|
|
19415
|
+
* "Date:" headers and "(meaning/estimated …)" notes are handled separately and skipped here.
|
|
19416
|
+
*/
|
|
19417
|
+
function annotateObservationTextDates(observations, currentDate, timeZone) {
|
|
19418
|
+
const regex = new RegExp(String.raw`^Date:.*$|\((?:[Ee]stimated|[Mm]eaning)\b[^()]*\)|${FREE_TEXT_DATE.source}`, "gm");
|
|
19419
|
+
const today = calendarDayIn(currentDate, timeZone);
|
|
19420
|
+
return observations.replace(regex, (match, offset) => {
|
|
19421
|
+
if (match.startsWith("Date:") || match.startsWith("(")) return match;
|
|
19422
|
+
const span = parseDateSpan(match);
|
|
19423
|
+
if (!span) return match;
|
|
19424
|
+
const relative = relativeSpan(span, today);
|
|
19425
|
+
return observations[offset + match.length] === ")" ? `${match} - ${relative}` : `${match} (${relative})`;
|
|
19040
19426
|
});
|
|
19041
19427
|
}
|
|
19042
19428
|
/**
|
|
19043
19429
|
* Add relative time annotations to observations.
|
|
19044
|
-
* Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)"
|
|
19045
|
-
*
|
|
19430
|
+
* Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)", range headers such as
|
|
19431
|
+
* "Date: Aug 1, 2024 - Feb 28, 2025" to "(7 months ago to 4 weeks ago)", and annotates inline dates.
|
|
19432
|
+
*
|
|
19433
|
+
* `timeZone` is the zone the Observer wrote its dates in (the record's `observedTimezone`). "Today" is
|
|
19434
|
+
* `currentDate`'s calendar date in that zone, so the result doesn't depend on the zone of the process rendering it.
|
|
19046
19435
|
*/
|
|
19047
|
-
function addRelativeTimeToObservations(observations, currentDate) {
|
|
19048
|
-
const
|
|
19049
|
-
const
|
|
19436
|
+
function addRelativeTimeToObservations(observations, currentDate, timeZone) {
|
|
19437
|
+
const today = calendarDayIn(currentDate, timeZone);
|
|
19438
|
+
const withInlineDates = annotateObservationTextDates(expandInlineEstimatedDates(observations, currentDate, timeZone), currentDate, timeZone);
|
|
19439
|
+
const dateHeaderRegex = /^(Date:[ \t]*)(.*)$/gm;
|
|
19050
19440
|
const dates = [];
|
|
19051
19441
|
let regexMatch;
|
|
19052
19442
|
while ((regexMatch = dateHeaderRegex.exec(withInlineDates)) !== null) {
|
|
19053
|
-
const dateStr = regexMatch[2];
|
|
19054
|
-
const
|
|
19055
|
-
if (
|
|
19443
|
+
const dateStr = regexMatch[2].trimEnd();
|
|
19444
|
+
const span = parseDateSpan(dateStr);
|
|
19445
|
+
if (span) dates.push({
|
|
19056
19446
|
index: regexMatch.index,
|
|
19057
|
-
|
|
19447
|
+
span,
|
|
19058
19448
|
match: regexMatch[0],
|
|
19059
19449
|
prefix: regexMatch[1],
|
|
19060
19450
|
dateStr
|
|
@@ -19068,11 +19458,10 @@ function addRelativeTimeToObservations(observations, currentDate) {
|
|
|
19068
19458
|
const prev = i > 0 ? dates[i - 1] : null;
|
|
19069
19459
|
result += withInlineDates.slice(lastIndex, curr.index);
|
|
19070
19460
|
if (prev) {
|
|
19071
|
-
const gap = formatGapBetweenDates(prev.
|
|
19461
|
+
const gap = formatGapBetweenDates(prev.span.end, curr.span.start);
|
|
19072
19462
|
if (gap) result += `\n${gap}\n\n`;
|
|
19073
19463
|
}
|
|
19074
|
-
|
|
19075
|
-
result += `${curr.prefix}${curr.dateStr} (${relative})`;
|
|
19464
|
+
result += `${curr.prefix}${curr.dateStr} (${relativeSpan(curr.span, today)})`;
|
|
19076
19465
|
lastIndex = curr.index + curr.match.length;
|
|
19077
19466
|
}
|
|
19078
19467
|
result += withInlineDates.slice(lastIndex);
|
|
@@ -20087,11 +20476,20 @@ const OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
20087
20476
|
"heif",
|
|
20088
20477
|
"avif"
|
|
20089
20478
|
]);
|
|
20090
|
-
function formatObserverDate(createdAt) {
|
|
20091
|
-
|
|
20479
|
+
function formatObserverDate(createdAt, timeZone) {
|
|
20480
|
+
if (!createdAt) return "";
|
|
20481
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
20482
|
+
timeZone,
|
|
20483
|
+
year: "numeric",
|
|
20484
|
+
month: "short",
|
|
20485
|
+
day: "numeric"
|
|
20486
|
+
}).formatToParts(createdAt);
|
|
20487
|
+
const part = (type) => parts.find((p) => p.type === type)?.value;
|
|
20488
|
+
return `${part("month")} ${part("day")} ${part("year")}`;
|
|
20092
20489
|
}
|
|
20093
|
-
function formatObserverTime(createdAt) {
|
|
20490
|
+
function formatObserverTime(createdAt, timeZone) {
|
|
20094
20491
|
return createdAt ? createdAt.toLocaleTimeString("en-US", {
|
|
20492
|
+
timeZone,
|
|
20095
20493
|
hour: "numeric",
|
|
20096
20494
|
minute: "2-digit",
|
|
20097
20495
|
hour12: true
|
|
@@ -20181,11 +20579,11 @@ function formatObserverAttachmentPlaceholder(part, counter) {
|
|
|
20181
20579
|
const label = resolveObserverAttachmentLabel(part);
|
|
20182
20580
|
return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
|
|
20183
20581
|
}
|
|
20184
|
-
function isRecord$
|
|
20582
|
+
function isRecord$2(value) {
|
|
20185
20583
|
return !!value && typeof value === "object";
|
|
20186
20584
|
}
|
|
20187
20585
|
function mapToolResultBlockToAttachment(block) {
|
|
20188
|
-
if (!isRecord$
|
|
20586
|
+
if (!isRecord$2(block) || typeof block.type !== "string") return;
|
|
20189
20587
|
const mediaType = typeof block.mediaType === "string" ? block.mediaType : void 0;
|
|
20190
20588
|
const filename = typeof block.filename === "string" ? block.filename : void 0;
|
|
20191
20589
|
switch (block.type) {
|
|
@@ -20246,7 +20644,7 @@ function mapToolResultBlockToAttachment(block) {
|
|
|
20246
20644
|
}
|
|
20247
20645
|
}
|
|
20248
20646
|
function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
20249
|
-
if (!isRecord$
|
|
20647
|
+
if (!isRecord$2(result) || result.type !== "content" || !Array.isArray(result.value)) return {
|
|
20250
20648
|
resultWithoutAttachments: result,
|
|
20251
20649
|
attachments: []
|
|
20252
20650
|
};
|
|
@@ -20260,7 +20658,7 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
|
20260
20658
|
if (shouldIncludeObserverAttachment(attachment, attachmentFilter)) attachments.push(toObserverInputAttachmentPart(attachment));
|
|
20261
20659
|
const placeholder = formatObserverAttachmentPlaceholder(attachment, counter);
|
|
20262
20660
|
return {
|
|
20263
|
-
type: isRecord$
|
|
20661
|
+
type: isRecord$2(block) ? block.type : void 0,
|
|
20264
20662
|
placeholder
|
|
20265
20663
|
};
|
|
20266
20664
|
});
|
|
@@ -20377,6 +20775,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
20377
20775
|
const maxLen = options?.maxPartLength;
|
|
20378
20776
|
const maxToolResultTokens = options?.maxToolResultTokens ?? 5e3;
|
|
20379
20777
|
const attachmentFilter = options?.attachmentFilter;
|
|
20778
|
+
const timeZone = resolveTimeZone(options?.timeZone);
|
|
20380
20779
|
const role = getObserverMessageLabel(msg);
|
|
20381
20780
|
const attachments = [];
|
|
20382
20781
|
const messageCreatedAt = normalizeObserverCreatedAt(msg.createdAt);
|
|
@@ -20386,8 +20785,8 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
20386
20785
|
if (!body) return;
|
|
20387
20786
|
const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
|
|
20388
20787
|
lines.push({
|
|
20389
|
-
date: formatObserverDate(normalizedCreatedAt),
|
|
20390
|
-
time: formatObserverTime(normalizedCreatedAt),
|
|
20788
|
+
date: formatObserverDate(normalizedCreatedAt, timeZone),
|
|
20789
|
+
time: formatObserverTime(normalizedCreatedAt, timeZone),
|
|
20391
20790
|
title,
|
|
20392
20791
|
body
|
|
20393
20792
|
});
|
|
@@ -20941,171 +21340,6 @@ function optimizeObservationsForContext(observations) {
|
|
|
20941
21340
|
return optimized.trim();
|
|
20942
21341
|
}
|
|
20943
21342
|
//#endregion
|
|
20944
|
-
//#region src/processors/observational-memory/retry.ts
|
|
20945
|
-
/**
|
|
20946
|
-
* Retry knobs for the internal OM transport-error retry wrapper.
|
|
20947
|
-
* Exported as a mutable object so tests can shrink the backoff schedule
|
|
20948
|
-
* without changing public API.
|
|
20949
|
-
*
|
|
20950
|
-
* With the defaults the per-retry pre-jitter backoff schedule is:
|
|
20951
|
-
* 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
|
|
20952
|
-
* giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
|
|
20953
|
-
* before the final attempt fails. Designed to ride out short provider /
|
|
20954
|
-
* network blips without holding the actor turn for much longer than that.
|
|
20955
|
-
*
|
|
20956
|
-
* @internal
|
|
20957
|
-
*/
|
|
20958
|
-
const RETRY_CONFIG = {
|
|
20959
|
-
/** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
|
|
20960
|
-
maxRetries: 8,
|
|
20961
|
-
/** Initial backoff delay in milliseconds. */
|
|
20962
|
-
initialDelayMs: 1e3,
|
|
20963
|
-
/** Multiplier applied to the delay after each failed attempt. */
|
|
20964
|
-
backoffFactor: 2,
|
|
20965
|
-
/** Cap on per-attempt delay (ms). */
|
|
20966
|
-
maxDelayMs: 12e4,
|
|
20967
|
-
/** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
|
|
20968
|
-
jitter: .2
|
|
20969
|
-
};
|
|
20970
|
-
const TRANSIENT_MESSAGE_SUBSTRINGS = [
|
|
20971
|
-
"terminated",
|
|
20972
|
-
"fetch failed",
|
|
20973
|
-
"econnreset",
|
|
20974
|
-
"econnrefused",
|
|
20975
|
-
"enotfound",
|
|
20976
|
-
"eai_again",
|
|
20977
|
-
"socket hang up",
|
|
20978
|
-
"network error",
|
|
20979
|
-
"request timed out",
|
|
20980
|
-
"request timeout",
|
|
20981
|
-
"connection reset",
|
|
20982
|
-
"connection closed"
|
|
20983
|
-
];
|
|
20984
|
-
function isRecord$2(value) {
|
|
20985
|
-
return typeof value === "object" && value !== null;
|
|
20986
|
-
}
|
|
20987
|
-
function isAbortError(error) {
|
|
20988
|
-
if (!isRecord$2(error)) return false;
|
|
20989
|
-
if (error.name === "AbortError") return true;
|
|
20990
|
-
if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
|
|
20991
|
-
return false;
|
|
20992
|
-
}
|
|
20993
|
-
function hasTransientMessage(value) {
|
|
20994
|
-
if (!isRecord$2(value)) return false;
|
|
20995
|
-
const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
|
|
20996
|
-
if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
|
|
20997
|
-
if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
|
|
20998
|
-
return false;
|
|
20999
|
-
}
|
|
21000
|
-
function isRetryableHttpStatus(status) {
|
|
21001
|
-
if (status === 408 || status === 425 || status === 429) return true;
|
|
21002
|
-
return status >= 500 && status <= 599;
|
|
21003
|
-
}
|
|
21004
|
-
function hasRetryableHttpStatus(value) {
|
|
21005
|
-
if (!isRecord$2(value)) return false;
|
|
21006
|
-
if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
|
|
21007
|
-
if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
|
|
21008
|
-
return false;
|
|
21009
|
-
}
|
|
21010
|
-
function hasIsRetryableFlag(value) {
|
|
21011
|
-
if (!isRecord$2(value)) return false;
|
|
21012
|
-
return value.isRetryable === true;
|
|
21013
|
-
}
|
|
21014
|
-
/**
|
|
21015
|
-
* Returns true when the given error looks like a transient transport-class
|
|
21016
|
-
* failure that's worth retrying — undici `terminated`, `fetch failed`,
|
|
21017
|
-
* `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
|
|
21018
|
-
* common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
|
|
21019
|
-
* wrapper errors don't hide the real failure.
|
|
21020
|
-
*
|
|
21021
|
-
* Never retries on user-initiated aborts.
|
|
21022
|
-
*
|
|
21023
|
-
* @internal
|
|
21024
|
-
*/
|
|
21025
|
-
function isTransientLLMError(error) {
|
|
21026
|
-
if (isAbortError(error)) return false;
|
|
21027
|
-
const visited = /* @__PURE__ */ new WeakSet();
|
|
21028
|
-
function visit(candidate) {
|
|
21029
|
-
if (isRecord$2(candidate)) {
|
|
21030
|
-
if (visited.has(candidate)) return false;
|
|
21031
|
-
visited.add(candidate);
|
|
21032
|
-
}
|
|
21033
|
-
if (hasTransientMessage(candidate)) return true;
|
|
21034
|
-
if (hasRetryableHttpStatus(candidate)) return true;
|
|
21035
|
-
if (hasIsRetryableFlag(candidate)) return true;
|
|
21036
|
-
if (isRecord$2(candidate)) {
|
|
21037
|
-
if (visit(candidate.cause)) return true;
|
|
21038
|
-
if (visit(candidate.error)) return true;
|
|
21039
|
-
}
|
|
21040
|
-
return false;
|
|
21041
|
-
}
|
|
21042
|
-
return visit(error);
|
|
21043
|
-
}
|
|
21044
|
-
/**
|
|
21045
|
-
* Compute the backoff delay (ms) for the Nth retry (0-indexed).
|
|
21046
|
-
*
|
|
21047
|
-
* Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
|
|
21048
|
-
* `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
|
|
21049
|
-
* tests that lock the schedule against drift.
|
|
21050
|
-
*
|
|
21051
|
-
* @internal
|
|
21052
|
-
*/
|
|
21053
|
-
function computeDelay(attempt) {
|
|
21054
|
-
const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
|
|
21055
|
-
const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
|
|
21056
|
-
if (RETRY_CONFIG.jitter <= 0) return capped;
|
|
21057
|
-
const jitterRange = capped * RETRY_CONFIG.jitter;
|
|
21058
|
-
const offset = (Math.random() * 2 - 1) * jitterRange;
|
|
21059
|
-
return Math.max(0, Math.round(capped + offset));
|
|
21060
|
-
}
|
|
21061
|
-
function sleep(ms, abortSignal) {
|
|
21062
|
-
if (ms <= 0) return Promise.resolve();
|
|
21063
|
-
return new Promise((resolve, reject) => {
|
|
21064
|
-
if (abortSignal?.aborted) {
|
|
21065
|
-
reject(/* @__PURE__ */ new Error("The operation was aborted."));
|
|
21066
|
-
return;
|
|
21067
|
-
}
|
|
21068
|
-
const timer = setTimeout(() => {
|
|
21069
|
-
abortSignal?.removeEventListener("abort", onAbort);
|
|
21070
|
-
resolve();
|
|
21071
|
-
}, ms);
|
|
21072
|
-
const onAbort = () => {
|
|
21073
|
-
clearTimeout(timer);
|
|
21074
|
-
abortSignal?.removeEventListener("abort", onAbort);
|
|
21075
|
-
reject(/* @__PURE__ */ new Error("The operation was aborted."));
|
|
21076
|
-
};
|
|
21077
|
-
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
21078
|
-
});
|
|
21079
|
-
}
|
|
21080
|
-
/**
|
|
21081
|
-
* Run `fn` with retries on transient transport-class errors.
|
|
21082
|
-
*
|
|
21083
|
-
* Non-transient errors (auth, validation, schema, etc.) are rethrown
|
|
21084
|
-
* immediately. User-initiated aborts are rethrown without delay.
|
|
21085
|
-
*
|
|
21086
|
-
* @internal
|
|
21087
|
-
*/
|
|
21088
|
-
async function withRetry(fn, opts) {
|
|
21089
|
-
const { label, abortSignal } = opts;
|
|
21090
|
-
let attempt = 0;
|
|
21091
|
-
while (true) {
|
|
21092
|
-
if (abortSignal?.aborted) throw new Error("The operation was aborted.");
|
|
21093
|
-
try {
|
|
21094
|
-
return await fn();
|
|
21095
|
-
} catch (error) {
|
|
21096
|
-
if (isAbortError(error) || abortSignal?.aborted) throw error;
|
|
21097
|
-
if (attempt >= RETRY_CONFIG.maxRetries || !isTransientLLMError(error)) {
|
|
21098
|
-
if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
|
|
21099
|
-
throw error;
|
|
21100
|
-
}
|
|
21101
|
-
const delay = computeDelay(attempt);
|
|
21102
|
-
attempt++;
|
|
21103
|
-
omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
|
|
21104
|
-
await sleep(delay, abortSignal);
|
|
21105
|
-
}
|
|
21106
|
-
}
|
|
21107
|
-
}
|
|
21108
|
-
//#endregion
|
|
21109
21343
|
//#region src/processors/observational-memory/temporary-memory.ts
|
|
21110
21344
|
function createTemporaryOmMemoryContext(prefix) {
|
|
21111
21345
|
const options = {
|
|
@@ -21247,11 +21481,27 @@ var ObserverRunner = class {
|
|
|
21247
21481
|
this.mastra = mastra;
|
|
21248
21482
|
}
|
|
21249
21483
|
createAgent(model, isMultiThread = false, memory, extractors = this.observationConfig.extractors ?? []) {
|
|
21484
|
+
let agentModel = model;
|
|
21485
|
+
if (Array.isArray(agentModel)) agentModel = agentModel.map((fallback) => ({
|
|
21486
|
+
...fallback,
|
|
21487
|
+
maxRetries: 0
|
|
21488
|
+
}));
|
|
21489
|
+
else if (typeof agentModel === "function") {
|
|
21490
|
+
const resolveDynamicModel = agentModel;
|
|
21491
|
+
agentModel = (async (args) => {
|
|
21492
|
+
const resolvedModel = await resolveDynamicModel(args);
|
|
21493
|
+
return Array.isArray(resolvedModel) ? resolvedModel.map((fallback) => ({
|
|
21494
|
+
...fallback,
|
|
21495
|
+
maxRetries: 0
|
|
21496
|
+
})) : resolvedModel;
|
|
21497
|
+
});
|
|
21498
|
+
}
|
|
21250
21499
|
return new _mastra_core_agent.Agent({
|
|
21251
21500
|
id: isMultiThread ? "multi-thread-observer" : "observational-memory-observer",
|
|
21252
21501
|
name: isMultiThread ? "multi-thread-observer" : "Observer",
|
|
21502
|
+
maxRetries: 0,
|
|
21253
21503
|
instructions: buildObserverSystemPrompt(isMultiThread, this.observationConfig.instruction, this.observationConfig.threadTitle, extractors),
|
|
21254
|
-
model,
|
|
21504
|
+
model: agentModel,
|
|
21255
21505
|
...memory ? { memory } : {},
|
|
21256
21506
|
...this.mastra ? { mastra: this.mastra } : {}
|
|
21257
21507
|
});
|
|
@@ -21343,7 +21593,10 @@ var ObserverRunner = class {
|
|
|
21343
21593
|
...options,
|
|
21344
21594
|
includeThreadTitle: this.observationConfig.threadTitle,
|
|
21345
21595
|
extractors: activeExtractors
|
|
21346
|
-
}, {
|
|
21596
|
+
}, {
|
|
21597
|
+
attachmentFilter,
|
|
21598
|
+
timeZone: options?.timeZone
|
|
21599
|
+
})];
|
|
21347
21600
|
const doGenerate = async () => {
|
|
21348
21601
|
return withRetry(() => withOmTracingSpan({
|
|
21349
21602
|
phase: "observer",
|
|
@@ -21381,12 +21634,14 @@ var ObserverRunner = class {
|
|
|
21381
21634
|
hasRequestContext: Boolean(internalRequestContext),
|
|
21382
21635
|
aborted: abortSignal?.aborted ?? false
|
|
21383
21636
|
});
|
|
21384
|
-
throw error;
|
|
21637
|
+
if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
|
|
21638
|
+
throw new OmModelExecutionError("observer-model", error);
|
|
21385
21639
|
}
|
|
21386
21640
|
}, abortSignal)
|
|
21387
21641
|
}), {
|
|
21388
21642
|
label: "observer",
|
|
21389
|
-
abortSignal
|
|
21643
|
+
abortSignal,
|
|
21644
|
+
maxRetries: this.observationConfig.maxRetries
|
|
21390
21645
|
});
|
|
21391
21646
|
};
|
|
21392
21647
|
let result = await doGenerate();
|
|
@@ -21454,7 +21709,7 @@ var ObserverRunner = class {
|
|
|
21454
21709
|
/**
|
|
21455
21710
|
* Call the Observer agent for multiple threads in a single batched request.
|
|
21456
21711
|
*/
|
|
21457
|
-
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext) {
|
|
21712
|
+
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone) {
|
|
21458
21713
|
const contextFor = (threadId) => ({
|
|
21459
21714
|
threadId,
|
|
21460
21715
|
...hookContext
|
|
@@ -21465,7 +21720,7 @@ var ObserverRunner = class {
|
|
|
21465
21720
|
if (filtered.length > 0) messagesByThread.set(threadId, filtered);
|
|
21466
21721
|
}
|
|
21467
21722
|
const threadOrder = allThreadOrder.filter((threadId) => messagesByThread.has(threadId));
|
|
21468
|
-
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model);
|
|
21723
|
+
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone);
|
|
21469
21724
|
for (const threadId of allThreadOrder) {
|
|
21470
21725
|
const threadResult = output.results.get(threadId);
|
|
21471
21726
|
if (!threadResult) {
|
|
@@ -21476,7 +21731,7 @@ var ObserverRunner = class {
|
|
|
21476
21731
|
}
|
|
21477
21732
|
return output;
|
|
21478
21733
|
}
|
|
21479
|
-
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model) {
|
|
21734
|
+
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone) {
|
|
21480
21735
|
if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
|
|
21481
21736
|
const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
|
|
21482
21737
|
const resolvedModel = (() => {
|
|
@@ -21518,7 +21773,8 @@ var ObserverRunner = class {
|
|
|
21518
21773
|
priorSuggestedResponse: priorMetadataByThread?.get(threadId)?.suggestedResponse,
|
|
21519
21774
|
priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
|
|
21520
21775
|
priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
|
|
21521
|
-
model: resolvedModel.model
|
|
21776
|
+
model: resolvedModel.model,
|
|
21777
|
+
timeZone
|
|
21522
21778
|
});
|
|
21523
21779
|
results.set(threadId, {
|
|
21524
21780
|
observations: threadResult.observations,
|
|
@@ -21535,6 +21791,7 @@ var ObserverRunner = class {
|
|
|
21535
21791
|
totalUsage.totalTokens += threadResult.usage.totalTokens ?? 0;
|
|
21536
21792
|
}
|
|
21537
21793
|
}
|
|
21794
|
+
for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
|
|
21538
21795
|
return {
|
|
21539
21796
|
results,
|
|
21540
21797
|
usage: totalUsage
|
|
@@ -21543,8 +21800,10 @@ var ObserverRunner = class {
|
|
|
21543
21800
|
const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
|
|
21544
21801
|
const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
|
|
21545
21802
|
const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
|
|
21546
|
-
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, {
|
|
21547
|
-
|
|
21803
|
+
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, {
|
|
21804
|
+
attachmentFilter: multiThreadAttachmentFilter,
|
|
21805
|
+
timeZone
|
|
21806
|
+
})];
|
|
21548
21807
|
const doGenerate = async () => {
|
|
21549
21808
|
return withRetry(() => withOmTracingSpan({
|
|
21550
21809
|
phase: "observer-multi-thread",
|
|
@@ -21582,12 +21841,14 @@ var ObserverRunner = class {
|
|
|
21582
21841
|
hasRequestContext: Boolean(internalRequestContext),
|
|
21583
21842
|
aborted: abortSignal?.aborted ?? false
|
|
21584
21843
|
});
|
|
21585
|
-
throw error;
|
|
21844
|
+
if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
|
|
21845
|
+
throw new OmModelExecutionError("observer-model", error);
|
|
21586
21846
|
}
|
|
21587
21847
|
}, abortSignal)
|
|
21588
21848
|
}), {
|
|
21589
21849
|
label: "observer-multi-thread",
|
|
21590
|
-
abortSignal
|
|
21850
|
+
abortSignal,
|
|
21851
|
+
maxRetries: this.observationConfig.maxRetries
|
|
21591
21852
|
});
|
|
21592
21853
|
};
|
|
21593
21854
|
let result = await doGenerate();
|
|
@@ -21643,6 +21904,7 @@ var ObserverRunner = class {
|
|
|
21643
21904
|
});
|
|
21644
21905
|
}
|
|
21645
21906
|
for (const threadId of threadOrder) if (!results.has(threadId)) results.set(threadId, { observations: "" });
|
|
21907
|
+
for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
|
|
21646
21908
|
const usage = result.totalUsage ?? result.usage;
|
|
21647
21909
|
return {
|
|
21648
21910
|
results,
|
|
@@ -23136,6 +23398,8 @@ async function summarizeConversation(opts) {
|
|
|
23136
23398
|
const runner = new ObserverRunner({
|
|
23137
23399
|
observationConfig: {
|
|
23138
23400
|
model: opts.model,
|
|
23401
|
+
maxRetries: 8,
|
|
23402
|
+
failurePolicy: "abort",
|
|
23139
23403
|
messageTokens: OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens,
|
|
23140
23404
|
shareTokenBudget: false,
|
|
23141
23405
|
modelSettings: { ...OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings },
|
|
@@ -24966,6 +25230,7 @@ function createObservationFailedMarker(params) {
|
|
|
24966
25230
|
durationMs,
|
|
24967
25231
|
tokensAttempted: params.tokensAttempted,
|
|
24968
25232
|
error: formatOmError(params.error),
|
|
25233
|
+
...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
|
|
24969
25234
|
recordId: params.recordId,
|
|
24970
25235
|
threadId: params.threadId
|
|
24971
25236
|
}
|
|
@@ -25027,6 +25292,7 @@ function createBufferingFailedMarker(params) {
|
|
|
25027
25292
|
durationMs,
|
|
25028
25293
|
tokensAttempted: params.tokensAttempted,
|
|
25029
25294
|
error: formatOmError(params.error),
|
|
25295
|
+
...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
|
|
25030
25296
|
recordId: params.recordId,
|
|
25031
25297
|
threadId: params.threadId
|
|
25032
25298
|
}
|
|
@@ -25083,7 +25349,10 @@ function createThreadUpdateMarker(params) {
|
|
|
25083
25349
|
function findLastCompletedObservationBoundary(message) {
|
|
25084
25350
|
const parts = message.content?.parts;
|
|
25085
25351
|
if (!parts || !Array.isArray(parts)) return -1;
|
|
25086
|
-
for (let i = parts.length - 1; i >= 0; i--)
|
|
25352
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
25353
|
+
const part = parts[i];
|
|
25354
|
+
if (part?.type === "data-om-observation-end" && part.data?.operationType !== "reflection") return i;
|
|
25355
|
+
}
|
|
25087
25356
|
return -1;
|
|
25088
25357
|
}
|
|
25089
25358
|
/**
|
|
@@ -25335,6 +25604,7 @@ function scanObservationGroupTags(observations) {
|
|
|
25335
25604
|
const incompleteOpenings = [];
|
|
25336
25605
|
let cursor = 0;
|
|
25337
25606
|
let closeFloor = 0;
|
|
25607
|
+
let nestedFloor = -1;
|
|
25338
25608
|
while (cursor < observations.length) {
|
|
25339
25609
|
const start = observations.indexOf(OBSERVATION_GROUP_OPEN, cursor);
|
|
25340
25610
|
if (start === -1) break;
|
|
@@ -25355,8 +25625,12 @@ function scanObservationGroupTags(observations) {
|
|
|
25355
25625
|
cursor = openEnd + 1;
|
|
25356
25626
|
continue;
|
|
25357
25627
|
}
|
|
25358
|
-
|
|
25359
|
-
|
|
25628
|
+
if (nestedFloor <= openEnd) {
|
|
25629
|
+
const lineOpen = observations.indexOf(`\n${OBSERVATION_GROUP_OPEN}`, openEnd);
|
|
25630
|
+
nestedFloor = lineOpen === -1 ? observations.length : lineOpen + 1;
|
|
25631
|
+
}
|
|
25632
|
+
const nestedStart = nestedFloor;
|
|
25633
|
+
if (nestedStart < closeStart) {
|
|
25360
25634
|
incompleteOpenings.push({
|
|
25361
25635
|
start,
|
|
25362
25636
|
end: openEnd + 1
|
|
@@ -25743,6 +26017,7 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
25743
26017
|
observationTokens: processed.observationTokens,
|
|
25744
26018
|
threadId,
|
|
25745
26019
|
writer,
|
|
26020
|
+
messageList: this.opts.messageList,
|
|
25746
26021
|
abortSignal,
|
|
25747
26022
|
mainAgent: this.opts.agent,
|
|
25748
26023
|
sendSignal: this.opts.sendSignal,
|
|
@@ -25767,6 +26042,7 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
25767
26042
|
operationType: "observation",
|
|
25768
26043
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25769
26044
|
error: formatOmError(error),
|
|
26045
|
+
...getOmFailureMetadata(error, this.observationConfig.failurePolicy),
|
|
25770
26046
|
recordId: record.id,
|
|
25771
26047
|
threadId
|
|
25772
26048
|
}
|
|
@@ -25780,6 +26056,10 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
25780
26056
|
};
|
|
25781
26057
|
}
|
|
25782
26058
|
omError("[OM] Observation failed", error);
|
|
26059
|
+
if (this.observationConfig.failurePolicy === "continue" && isOmModelExecutionError(error) && error.failureKind === "observer-model") return {
|
|
26060
|
+
observed: false,
|
|
26061
|
+
error
|
|
26062
|
+
};
|
|
25783
26063
|
throw error;
|
|
25784
26064
|
}
|
|
25785
26065
|
}
|
|
@@ -26049,7 +26329,8 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
26049
26329
|
threadId: this.opts.threadId,
|
|
26050
26330
|
resourceId: this.opts.resourceId,
|
|
26051
26331
|
trigger: this.opts.trigger,
|
|
26052
|
-
mainAgent: this.opts.agent
|
|
26332
|
+
mainAgent: this.opts.agent,
|
|
26333
|
+
timeZone: this.opts.record.observedTimezone
|
|
26053
26334
|
});
|
|
26054
26335
|
const hookedValues = await applyExtractorHooks({
|
|
26055
26336
|
source: "observer",
|
|
@@ -26058,7 +26339,10 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
26058
26339
|
failures: result.extractionFailures,
|
|
26059
26340
|
previousValues: this.priorExtractedValues,
|
|
26060
26341
|
rawObservations: result.observations,
|
|
26061
|
-
recentMessages: formatMessagesForObserver(this.opts.messages, {
|
|
26342
|
+
recentMessages: formatMessagesForObserver(this.opts.messages, {
|
|
26343
|
+
maxPartLength: 500,
|
|
26344
|
+
timeZone: this.opts.record.observedTimezone
|
|
26345
|
+
}),
|
|
26062
26346
|
threadId: this.opts.threadId,
|
|
26063
26347
|
resourceId: this.opts.resourceId,
|
|
26064
26348
|
mainAgent: this.opts.agent,
|
|
@@ -26189,6 +26473,7 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
26189
26473
|
startedAt: this.startedAt,
|
|
26190
26474
|
tokensAttempted: this.tokensToObserve,
|
|
26191
26475
|
error,
|
|
26476
|
+
failurePolicy: this.observationConfig.failurePolicy,
|
|
26192
26477
|
recordId: this.opts.record.id,
|
|
26193
26478
|
threadId: this.opts.threadId
|
|
26194
26479
|
});
|
|
@@ -26240,7 +26525,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
26240
26525
|
threadId: this.opts.threadId,
|
|
26241
26526
|
resourceId: this.opts.resourceId,
|
|
26242
26527
|
trigger: this.opts.trigger,
|
|
26243
|
-
mainAgent: this.opts.agent
|
|
26528
|
+
mainAgent: this.opts.agent,
|
|
26529
|
+
timeZone: this.opts.record.observedTimezone
|
|
26244
26530
|
});
|
|
26245
26531
|
const hookedValues = await applyExtractorHooks({
|
|
26246
26532
|
source: "observer",
|
|
@@ -26249,7 +26535,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
26249
26535
|
failures: result.extractionFailures,
|
|
26250
26536
|
previousValues: this.priorExtractedValues,
|
|
26251
26537
|
rawObservations: result.observations,
|
|
26252
|
-
recentMessages: formatMessagesForObserver(messages, {
|
|
26538
|
+
recentMessages: formatMessagesForObserver(messages, {
|
|
26539
|
+
maxPartLength: 500,
|
|
26540
|
+
timeZone: this.opts.record.observedTimezone
|
|
26541
|
+
}),
|
|
26253
26542
|
threadId: this.opts.threadId,
|
|
26254
26543
|
resourceId: this.opts.resourceId,
|
|
26255
26544
|
mainAgent: this.opts.agent,
|
|
@@ -26395,6 +26684,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
26395
26684
|
startedAt: this.startedAt,
|
|
26396
26685
|
tokensAttempted,
|
|
26397
26686
|
error,
|
|
26687
|
+
failurePolicy: this.observationConfig.failurePolicy,
|
|
26398
26688
|
recordId: record.id,
|
|
26399
26689
|
threadId
|
|
26400
26690
|
});
|
|
@@ -26571,7 +26861,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
26571
26861
|
return this.deps.observer.callMultiThread(_existingObservations, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
|
|
26572
26862
|
resourceId: this.opts.resourceId,
|
|
26573
26863
|
trigger: this.opts.trigger
|
|
26574
|
-
});
|
|
26864
|
+
}, this.opts.record.observedTimezone);
|
|
26575
26865
|
}));
|
|
26576
26866
|
for (const batchResult of batchResults) {
|
|
26577
26867
|
for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
|
|
@@ -26604,7 +26894,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
26604
26894
|
failures: result.extractionFailures,
|
|
26605
26895
|
previousValues,
|
|
26606
26896
|
rawObservations: result.observations,
|
|
26607
|
-
recentMessages: formatMessagesForObserver(threadMessages, {
|
|
26897
|
+
recentMessages: formatMessagesForObserver(threadMessages, {
|
|
26898
|
+
maxPartLength: 500,
|
|
26899
|
+
timeZone: this.opts.record.observedTimezone
|
|
26900
|
+
}),
|
|
26608
26901
|
threadId,
|
|
26609
26902
|
resourceId: this.resourceId,
|
|
26610
26903
|
mainAgent: this.opts.agent,
|
|
@@ -26763,6 +27056,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
26763
27056
|
startedAt: this.startedAt,
|
|
26764
27057
|
tokensAttempted,
|
|
26765
27058
|
error,
|
|
27059
|
+
failurePolicy: this.observationConfig.failurePolicy,
|
|
26766
27060
|
recordId: this.opts.record.id,
|
|
26767
27061
|
threadId
|
|
26768
27062
|
});
|
|
@@ -27177,19 +27471,28 @@ var ObservationStep = class {
|
|
|
27177
27471
|
};
|
|
27178
27472
|
}
|
|
27179
27473
|
}
|
|
27180
|
-
|
|
27181
|
-
|
|
27182
|
-
|
|
27183
|
-
|
|
27184
|
-
|
|
27185
|
-
|
|
27186
|
-
|
|
27187
|
-
|
|
27188
|
-
|
|
27189
|
-
|
|
27190
|
-
|
|
27191
|
-
|
|
27192
|
-
|
|
27474
|
+
let obsResult;
|
|
27475
|
+
try {
|
|
27476
|
+
obsResult = await om.observe({
|
|
27477
|
+
threadId,
|
|
27478
|
+
resourceId,
|
|
27479
|
+
messages: observableMessages,
|
|
27480
|
+
messageList,
|
|
27481
|
+
trigger: "turn-sync",
|
|
27482
|
+
agent: this.turn.agent,
|
|
27483
|
+
sendSignal: this.turn.sendSignal,
|
|
27484
|
+
sendStateSignal: this.turn.sendStateSignal,
|
|
27485
|
+
requestContext: this.turn.requestContext,
|
|
27486
|
+
writer: this.turn.writer,
|
|
27487
|
+
observabilityContext: this.turn.observabilityContext
|
|
27488
|
+
});
|
|
27489
|
+
} catch (error) {
|
|
27490
|
+
if (om.config.observation.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "observer-model") throw error;
|
|
27491
|
+
return {
|
|
27492
|
+
succeeded: false,
|
|
27493
|
+
record: freshStatus.record
|
|
27494
|
+
};
|
|
27495
|
+
}
|
|
27193
27496
|
if (obsResult.observed) {
|
|
27194
27497
|
const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
|
|
27195
27498
|
const liveMessages = getObservableMessages(messageList);
|
|
@@ -27847,11 +28150,27 @@ var ReflectorRunner = class {
|
|
|
27847
28150
|
this.mastra = mastra;
|
|
27848
28151
|
}
|
|
27849
28152
|
createAgent(model, memory, extractors = this.reflectionConfig.extractors) {
|
|
28153
|
+
let agentModel = model;
|
|
28154
|
+
if (Array.isArray(agentModel)) agentModel = agentModel.map((fallback) => ({
|
|
28155
|
+
...fallback,
|
|
28156
|
+
maxRetries: 0
|
|
28157
|
+
}));
|
|
28158
|
+
else if (typeof agentModel === "function") {
|
|
28159
|
+
const resolveDynamicModel = agentModel;
|
|
28160
|
+
agentModel = (async (args) => {
|
|
28161
|
+
const resolvedModel = await resolveDynamicModel(args);
|
|
28162
|
+
return Array.isArray(resolvedModel) ? resolvedModel.map((fallback) => ({
|
|
28163
|
+
...fallback,
|
|
28164
|
+
maxRetries: 0
|
|
28165
|
+
})) : resolvedModel;
|
|
28166
|
+
});
|
|
28167
|
+
}
|
|
27850
28168
|
return new _mastra_core_agent.Agent({
|
|
27851
28169
|
id: "observational-memory-reflector",
|
|
27852
28170
|
name: "Reflector",
|
|
27853
28171
|
instructions: buildReflectorSystemPrompt(this.reflectionConfig.instruction, extractors),
|
|
27854
|
-
model,
|
|
28172
|
+
model: agentModel,
|
|
28173
|
+
maxRetries: 0,
|
|
27855
28174
|
...memory ? { memory } : {},
|
|
27856
28175
|
...this.mastra ? { mastra: this.mastra } : {}
|
|
27857
28176
|
});
|
|
@@ -27933,36 +28252,42 @@ var ReflectorRunner = class {
|
|
|
27933
28252
|
},
|
|
27934
28253
|
callback: (childObservabilityContext) => withAbortCheck(async () => {
|
|
27935
28254
|
chunkCount = 0;
|
|
27936
|
-
|
|
27937
|
-
|
|
27938
|
-
|
|
27939
|
-
|
|
27940
|
-
|
|
27941
|
-
|
|
27942
|
-
|
|
27943
|
-
|
|
27944
|
-
|
|
27945
|
-
|
|
27946
|
-
|
|
27947
|
-
|
|
27948
|
-
|
|
28255
|
+
try {
|
|
28256
|
+
return await (await agent.stream(prompt, {
|
|
28257
|
+
modelSettings: { ...this.reflectionConfig.modelSettings },
|
|
28258
|
+
providerOptions: this.reflectionConfig.providerOptions,
|
|
28259
|
+
...temporaryMemory ? { memory: temporaryMemory.options } : {},
|
|
28260
|
+
...abortSignal ? { abortSignal } : {},
|
|
28261
|
+
...internalRequestContext ? { requestContext: internalRequestContext } : {},
|
|
28262
|
+
...childObservabilityContext,
|
|
28263
|
+
...attemptNumber === 1 ? {
|
|
28264
|
+
onChunk(chunk) {
|
|
28265
|
+
chunkCount++;
|
|
28266
|
+
if (chunkCount === 1 || chunkCount % 50 === 0) {
|
|
28267
|
+
const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
|
|
28268
|
+
omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
|
|
28269
|
+
}
|
|
28270
|
+
},
|
|
28271
|
+
onFinish(event) {
|
|
28272
|
+
omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
|
|
28273
|
+
},
|
|
28274
|
+
onAbort(event) {
|
|
28275
|
+
omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
|
|
28276
|
+
},
|
|
28277
|
+
onError({ error }) {
|
|
28278
|
+
omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
|
|
27949
28279
|
}
|
|
27950
|
-
}
|
|
27951
|
-
|
|
27952
|
-
|
|
27953
|
-
|
|
27954
|
-
|
|
27955
|
-
|
|
27956
|
-
},
|
|
27957
|
-
onError({ error }) {
|
|
27958
|
-
omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
|
|
27959
|
-
}
|
|
27960
|
-
} : {}
|
|
27961
|
-
})).getFullOutput();
|
|
28280
|
+
} : {}
|
|
28281
|
+
})).getFullOutput();
|
|
28282
|
+
} catch (error) {
|
|
28283
|
+
if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
|
|
28284
|
+
throw new OmModelExecutionError("reflector-model", error);
|
|
28285
|
+
}
|
|
27962
28286
|
}, abortSignal)
|
|
27963
28287
|
}), {
|
|
27964
28288
|
label: "reflector",
|
|
27965
|
-
abortSignal
|
|
28289
|
+
abortSignal,
|
|
28290
|
+
maxRetries: this.reflectionConfig.maxRetries
|
|
27966
28291
|
});
|
|
27967
28292
|
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}`);
|
|
27968
28293
|
const usage = result.totalUsage ?? result.usage;
|
|
@@ -28086,6 +28411,7 @@ var ReflectorRunner = class {
|
|
|
28086
28411
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28087
28412
|
tokensAttempted: observationTokens,
|
|
28088
28413
|
error,
|
|
28414
|
+
failurePolicy: this.reflectionConfig.failurePolicy,
|
|
28089
28415
|
recordId: record.id,
|
|
28090
28416
|
threadId: record.threadId ?? ""
|
|
28091
28417
|
});
|
|
@@ -28498,24 +28824,43 @@ var ReflectorRunner = class {
|
|
|
28498
28824
|
usage: reflectResult.usage
|
|
28499
28825
|
});
|
|
28500
28826
|
} catch (error) {
|
|
28501
|
-
|
|
28502
|
-
|
|
28503
|
-
|
|
28504
|
-
|
|
28505
|
-
|
|
28506
|
-
|
|
28507
|
-
|
|
28508
|
-
|
|
28509
|
-
|
|
28510
|
-
|
|
28511
|
-
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
|
|
28827
|
+
reflectionError = error instanceof Error ? error : new Error(String(error));
|
|
28828
|
+
const failedMarker = createObservationFailedMarker({
|
|
28829
|
+
cycleId: streamContext?.cycleId ?? cycleId,
|
|
28830
|
+
operationType: "reflection",
|
|
28831
|
+
startedAt: streamContext?.startedAt ?? startedAt,
|
|
28832
|
+
tokensAttempted: observationTokens,
|
|
28833
|
+
error,
|
|
28834
|
+
failurePolicy: this.reflectionConfig.failurePolicy,
|
|
28835
|
+
recordId: record.id,
|
|
28836
|
+
threadId
|
|
28837
|
+
});
|
|
28838
|
+
if (writer) await writer.custom({
|
|
28839
|
+
...failedMarker,
|
|
28840
|
+
transient: true
|
|
28841
|
+
}).catch(() => {});
|
|
28842
|
+
let persistedToList = false;
|
|
28843
|
+
try {
|
|
28844
|
+
persistedToList = await this.persistMarkerToMessage(failedMarker, messageList, threadId, record.resourceId ?? void 0);
|
|
28845
|
+
} catch {
|
|
28846
|
+
persistedToList = false;
|
|
28847
|
+
}
|
|
28848
|
+
if (!persistedToList) try {
|
|
28515
28849
|
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
|
|
28850
|
+
} catch (markerError) {
|
|
28851
|
+
omError("[OM] Failed to persist reflection-failed marker", markerError);
|
|
28516
28852
|
}
|
|
28517
|
-
|
|
28518
|
-
|
|
28853
|
+
this.emitDebugEvent({
|
|
28854
|
+
type: "reflection_failed",
|
|
28855
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
28856
|
+
threadId,
|
|
28857
|
+
resourceId: record.resourceId ?? "",
|
|
28858
|
+
inputTokens: observationTokens,
|
|
28859
|
+
failurePolicy: this.reflectionConfig.failurePolicy,
|
|
28860
|
+
failureKind: failedMarker.data.failureKind,
|
|
28861
|
+
error: reflectionError.message
|
|
28862
|
+
});
|
|
28863
|
+
if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
|
|
28519
28864
|
this.syncReflectionSuppression.set(lockKey, observationTokens);
|
|
28520
28865
|
omError("[OM] Reflection failed", error);
|
|
28521
28866
|
} finally {
|
|
@@ -29153,6 +29498,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29153
29498
|
const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
|
|
29154
29499
|
this.observationConfig = {
|
|
29155
29500
|
model: observationModel,
|
|
29501
|
+
maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
|
|
29502
|
+
failurePolicy: config.observation?.failurePolicy ?? "abort",
|
|
29156
29503
|
messageTokens: isSharedBudget ? {
|
|
29157
29504
|
min: messageTokens,
|
|
29158
29505
|
max: totalBudget
|
|
@@ -29182,6 +29529,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29182
29529
|
};
|
|
29183
29530
|
this.reflectionConfig = {
|
|
29184
29531
|
model: reflectionModel,
|
|
29532
|
+
maxRetries: config.reflection?.maxRetries ?? RETRY_CONFIG.maxRetries,
|
|
29533
|
+
failurePolicy: config.reflection?.failurePolicy ?? "abort",
|
|
29185
29534
|
observationTokens,
|
|
29186
29535
|
shareTokenBudget: isSharedBudget,
|
|
29187
29536
|
modelSettings: {
|
|
@@ -29255,9 +29604,15 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29255
29604
|
retrieval: this.retrieval,
|
|
29256
29605
|
observation: {
|
|
29257
29606
|
messageTokens: this.observationConfig.messageTokens,
|
|
29258
|
-
previousObserverTokens: this.observationConfig.previousObserverTokens
|
|
29607
|
+
previousObserverTokens: this.observationConfig.previousObserverTokens,
|
|
29608
|
+
maxRetries: this.observationConfig.maxRetries,
|
|
29609
|
+
failurePolicy: this.observationConfig.failurePolicy
|
|
29259
29610
|
},
|
|
29260
|
-
reflection: {
|
|
29611
|
+
reflection: {
|
|
29612
|
+
observationTokens: this.reflectionConfig.observationTokens,
|
|
29613
|
+
maxRetries: this.reflectionConfig.maxRetries,
|
|
29614
|
+
failurePolicy: this.reflectionConfig.failurePolicy
|
|
29615
|
+
}
|
|
29261
29616
|
};
|
|
29262
29617
|
}
|
|
29263
29618
|
/**
|
|
@@ -29361,11 +29716,15 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29361
29716
|
messageTokens: this.observationConfig.messageTokens,
|
|
29362
29717
|
model: observationResolved.model,
|
|
29363
29718
|
previousObserverTokens: this.observationConfig.previousObserverTokens,
|
|
29719
|
+
maxRetries: this.observationConfig.maxRetries,
|
|
29720
|
+
failurePolicy: this.observationConfig.failurePolicy,
|
|
29364
29721
|
routing: observationResolved.routing
|
|
29365
29722
|
},
|
|
29366
29723
|
reflection: {
|
|
29367
29724
|
observationTokens: this.reflectionConfig.observationTokens,
|
|
29368
29725
|
model: reflectionResolved.model,
|
|
29726
|
+
maxRetries: this.reflectionConfig.maxRetries,
|
|
29727
|
+
failurePolicy: this.reflectionConfig.failurePolicy,
|
|
29369
29728
|
routing: reflectionResolved.routing
|
|
29370
29729
|
}
|
|
29371
29730
|
};
|
|
@@ -29382,6 +29741,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29382
29741
|
* Ensures bufferTokens is less than the threshold and bufferActivation is valid.
|
|
29383
29742
|
*/
|
|
29384
29743
|
validateBufferConfig() {
|
|
29744
|
+
for (const [path, value] of [["observation.maxRetries", this.observationConfig.maxRetries], ["reflection.maxRetries", this.reflectionConfig.maxRetries]]) if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${path} must be a finite non-negative integer, got ${value}`);
|
|
29385
29745
|
if ((this.observationConfig.bufferTokens !== void 0 || this.observationConfig.bufferActivation !== void 0 || this.reflectionConfig.bufferActivation !== void 0) && this.scope === "resource") throw new Error("Async buffering is not yet supported with scope: 'resource'. Use scope: 'thread', or set observation: { bufferTokens: false } to disable async buffering.");
|
|
29386
29746
|
const observationThreshold = getMaxThreshold(this.observationConfig.messageTokens);
|
|
29387
29747
|
if (this.observationConfig.bufferTokens !== void 0) {
|
|
@@ -29509,18 +29869,19 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29509
29869
|
};
|
|
29510
29870
|
}
|
|
29511
29871
|
/**
|
|
29512
|
-
* Persist a data-om-* marker part on the
|
|
29513
|
-
*
|
|
29514
|
-
*
|
|
29872
|
+
* Persist a data-om-* marker part on the message that owns the operation in
|
|
29873
|
+
* messageList and save it to the DB. Observation markers belong to the latest
|
|
29874
|
+
* assistant message; reflection markers belong to the current user input.
|
|
29515
29875
|
* @internal Used by ReflectorRunner. Do not call directly.
|
|
29516
29876
|
*/
|
|
29517
29877
|
async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
|
|
29518
|
-
if (!messageList) return;
|
|
29519
|
-
const allMsgs =
|
|
29878
|
+
if (!messageList) return false;
|
|
29879
|
+
const allMsgs = messageList.get.all.db();
|
|
29880
|
+
const markerData = marker.data;
|
|
29881
|
+
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
29520
29882
|
for (let i = allMsgs.length - 1; i >= 0; i--) {
|
|
29521
29883
|
const msg = allMsgs[i];
|
|
29522
|
-
if (msg?.role ===
|
|
29523
|
-
const markerData = marker.data;
|
|
29884
|
+
if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
|
|
29524
29885
|
if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
|
|
29525
29886
|
try {
|
|
29526
29887
|
await this.messageHistory.persistMessages({
|
|
@@ -29530,16 +29891,19 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29530
29891
|
});
|
|
29531
29892
|
} catch (e) {
|
|
29532
29893
|
omDebug(`[OM:persistMarker] failed to save marker to DB: ${e}`);
|
|
29894
|
+
return false;
|
|
29533
29895
|
}
|
|
29534
|
-
return;
|
|
29896
|
+
return true;
|
|
29535
29897
|
}
|
|
29536
29898
|
}
|
|
29899
|
+
return false;
|
|
29537
29900
|
}
|
|
29538
29901
|
/**
|
|
29539
|
-
* Persist a marker to the
|
|
29540
|
-
*
|
|
29541
|
-
*
|
|
29542
|
-
*
|
|
29902
|
+
* Persist a marker to the message that owns the operation in storage.
|
|
29903
|
+
* Observation markers belong to the latest assistant message. Reflection runs
|
|
29904
|
+
* before the current assistant reply exists, so its failure marker belongs to
|
|
29905
|
+
* the latest user input that triggered it.
|
|
29906
|
+
* @internal Used by observation strategies and the reflector. Do not call directly.
|
|
29543
29907
|
*/
|
|
29544
29908
|
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
29545
29909
|
try {
|
|
@@ -29551,8 +29915,9 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29551
29915
|
direction: "DESC"
|
|
29552
29916
|
}
|
|
29553
29917
|
}))?.messages ?? [];
|
|
29554
|
-
|
|
29555
|
-
|
|
29918
|
+
const markerData = marker.data;
|
|
29919
|
+
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
29920
|
+
for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
|
|
29556
29921
|
if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
|
|
29557
29922
|
await this.messageHistory.persistMessages({
|
|
29558
29923
|
messages: [msg],
|
|
@@ -29794,9 +30159,9 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29794
30159
|
* In resource scope mode, filters continuity messages to only show
|
|
29795
30160
|
* the message for the current thread.
|
|
29796
30161
|
*/
|
|
29797
|
-
formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false) {
|
|
30162
|
+
formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false, timeZone) {
|
|
29798
30163
|
let optimized = retrieval ? renderObservationGroupsForReflection(observations) ?? optimizeObservationsForContext(observations) : optimizeObservationsForContext(observations);
|
|
29799
|
-
if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate);
|
|
30164
|
+
if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate, timeZone);
|
|
29800
30165
|
const messages = [`${getObservationContextPrompt(this.scope)}\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}${retrieval ? `\n\n${getRetrievalInstructions(this.retrievalScope, this.retrievalInstructions, this.retrievalSearch)}` : ""}`];
|
|
29801
30166
|
if (unobservedContextBlocks) messages.push(`The following content is from OTHER conversations different from the current conversation, they're here for reference, but they're not necessarily your focus:\nSTART_OTHER_CONVERSATIONS_BLOCK\n${unobservedContextBlocks}\nEND_OTHER_CONVERSATIONS_BLOCK`);
|
|
29802
30167
|
const observationChunks = this.splitObservationContextChunks(optimized);
|
|
@@ -29896,12 +30261,15 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
29896
30261
|
* These are injected into the Actor's context so it has awareness of activity
|
|
29897
30262
|
* in other threads for the same resource.
|
|
29898
30263
|
*/
|
|
29899
|
-
async formatUnobservedContextBlocks(messagesByThread, currentThreadId) {
|
|
30264
|
+
async formatUnobservedContextBlocks(messagesByThread, currentThreadId, timeZone) {
|
|
29900
30265
|
const blocks = [];
|
|
29901
30266
|
for (const [threadId, messages] of messagesByThread) {
|
|
29902
30267
|
if (threadId === currentThreadId) continue;
|
|
29903
30268
|
if (messages.length === 0) continue;
|
|
29904
|
-
const formattedMessages = formatMessagesForObserver(messages, {
|
|
30269
|
+
const formattedMessages = formatMessagesForObserver(messages, {
|
|
30270
|
+
maxPartLength: 500,
|
|
30271
|
+
timeZone
|
|
30272
|
+
});
|
|
29905
30273
|
if (formattedMessages) {
|
|
29906
30274
|
const obscuredId = await this.representThreadIDInContext(threadId);
|
|
29907
30275
|
blocks.push(`<other-conversation id="${obscuredId}">
|
|
@@ -30078,7 +30446,7 @@ ${formattedMessages}
|
|
|
30078
30446
|
transient: true
|
|
30079
30447
|
}).catch(() => {});
|
|
30080
30448
|
omDebug(`[OM:bufferInput] cycleId=${cycleId}, msgCount=${messagesToBuffer.length}, msgTokens=${tokensToBuffer}, ids=${messagesToBuffer.map((m) => `${m.id?.slice(0, 8)}@${m.createdAt ? new Date(m.createdAt).toISOString() : "none"}`).join(",")}`);
|
|
30081
|
-
await this.runBufferedObservationCycle({
|
|
30449
|
+
if ((await this.runBufferedObservationCycle({
|
|
30082
30450
|
threadId,
|
|
30083
30451
|
resourceId: freshRecord.resourceId ?? void 0,
|
|
30084
30452
|
trigger: "async-buffer"
|
|
@@ -30093,10 +30461,11 @@ ${formattedMessages}
|
|
|
30093
30461
|
requestContext,
|
|
30094
30462
|
observabilityContext,
|
|
30095
30463
|
trigger: "async-buffer"
|
|
30096
|
-
}).run())
|
|
30097
|
-
|
|
30098
|
-
|
|
30099
|
-
|
|
30464
|
+
}).run()))?.observed) {
|
|
30465
|
+
const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
30466
|
+
const cursor = new Date(maxTs.getTime() + 1);
|
|
30467
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
|
|
30468
|
+
}
|
|
30100
30469
|
}
|
|
30101
30470
|
/**
|
|
30102
30471
|
* Trigger async buffered observation if the token count has crossed a new interval.
|
|
@@ -30324,7 +30693,7 @@ ${formattedMessages}
|
|
|
30324
30693
|
const currentTask = activeExtractors.some((extractor) => extractor.slug === "current-task") ? omMetadata?.currentTask : void 0;
|
|
30325
30694
|
const suggestedResponse = activeExtractors.some((extractor) => extractor.slug === "suggested-response") ? omMetadata?.suggestedResponse : void 0;
|
|
30326
30695
|
const currentDate = opts.currentDate ?? /* @__PURE__ */ new Date();
|
|
30327
|
-
return this.formatObservationsForContext(record.activeObservations, currentTask, suggestedResponse, omMetadata?.extracted, unobservedContextBlocks, currentDate, this.retrieval);
|
|
30696
|
+
return this.formatObservationsForContext(record.activeObservations, currentTask, suggestedResponse, omMetadata?.extracted, unobservedContextBlocks, currentDate, this.retrieval, record.observedTimezone);
|
|
30328
30697
|
}
|
|
30329
30698
|
/**
|
|
30330
30699
|
* Get unobserved messages from other threads for resource-scoped observation.
|
|
@@ -30336,7 +30705,8 @@ ${formattedMessages}
|
|
|
30336
30705
|
async getOtherThreadsContext(resourceId, currentThreadId) {
|
|
30337
30706
|
const { threads: allThreads } = await this.storage.listThreads({ filter: { resourceId } });
|
|
30338
30707
|
const messagesByThread = /* @__PURE__ */ new Map();
|
|
30339
|
-
const
|
|
30708
|
+
const record = await this.getRecord(currentThreadId, resourceId);
|
|
30709
|
+
const recordLastObservedAt = record?.lastObservedAt;
|
|
30340
30710
|
for (const thread of allThreads) {
|
|
30341
30711
|
if (thread.id === currentThreadId) continue;
|
|
30342
30712
|
const threadLastObservedAt = (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata)?.lastObservedAt ?? recordLastObservedAt;
|
|
@@ -30353,7 +30723,7 @@ ${formattedMessages}
|
|
|
30353
30723
|
if (filtered.length > 0) messagesByThread.set(thread.id, filtered);
|
|
30354
30724
|
}
|
|
30355
30725
|
if (messagesByThread.size === 0) return void 0;
|
|
30356
|
-
return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId) || void 0;
|
|
30726
|
+
return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId, record?.observedTimezone) || void 0;
|
|
30357
30727
|
}
|
|
30358
30728
|
/**
|
|
30359
30729
|
* Emit debug event and stream progress for UI feedback.
|
|
@@ -30696,7 +31066,7 @@ ${formattedMessages}
|
|
|
30696
31066
|
...startMarker,
|
|
30697
31067
|
transient: true
|
|
30698
31068
|
}).catch(() => {});
|
|
30699
|
-
await this.runBufferedObservationCycle({
|
|
31069
|
+
if (!(await this.runBufferedObservationCycle({
|
|
30700
31070
|
threadId,
|
|
30701
31071
|
resourceId: record.resourceId ?? resourceId,
|
|
30702
31072
|
trigger: "async-buffer"
|
|
@@ -30715,7 +31085,10 @@ ${formattedMessages}
|
|
|
30715
31085
|
currentModel: opts.currentModel,
|
|
30716
31086
|
observabilityContext,
|
|
30717
31087
|
trigger: "async-buffer"
|
|
30718
|
-
}).run())
|
|
31088
|
+
}).run()))?.observed) return {
|
|
31089
|
+
buffered: false,
|
|
31090
|
+
record
|
|
31091
|
+
};
|
|
30719
31092
|
if (isOmReproCaptureEnabled()) writeObserverExchangeReproCapture({
|
|
30720
31093
|
threadId,
|
|
30721
31094
|
resourceId: record.resourceId ?? void 0,
|
|
@@ -31075,6 +31448,7 @@ ${formattedMessages}
|
|
|
31075
31448
|
observed = result.observed;
|
|
31076
31449
|
observationUsage = result.usage;
|
|
31077
31450
|
observationProviderMetadata = result.providerMetadata;
|
|
31451
|
+
observationError = result.error;
|
|
31078
31452
|
});
|
|
31079
31453
|
} catch (error) {
|
|
31080
31454
|
lifecycleError = error;
|
|
@@ -33010,6 +33384,7 @@ ${workingMemory}`;
|
|
|
33010
33384
|
model: omConfig.model,
|
|
33011
33385
|
mastra: this._mastraInstance,
|
|
33012
33386
|
onIndexObservations,
|
|
33387
|
+
onDebugEvent: omConfig.onDebugEvent,
|
|
33013
33388
|
hooks: omConfig.hooks,
|
|
33014
33389
|
observation: omConfig.observation ? {
|
|
33015
33390
|
model: omConfig.observation.model,
|
|
@@ -33026,10 +33401,14 @@ ${workingMemory}`;
|
|
|
33026
33401
|
threadTitle: omConfig.observation.threadTitle,
|
|
33027
33402
|
observeAttachments: omConfig.observation.observeAttachments,
|
|
33028
33403
|
continuationHints: omConfig.observation.continuationHints,
|
|
33404
|
+
maxRetries: omConfig.observation.maxRetries,
|
|
33405
|
+
failurePolicy: omConfig.observation.failurePolicy,
|
|
33029
33406
|
extract: omConfig.observation.extract
|
|
33030
33407
|
} : void 0,
|
|
33031
33408
|
reflection: omConfig.reflection ? {
|
|
33032
33409
|
model: omConfig.reflection.model,
|
|
33410
|
+
maxRetries: omConfig.reflection.maxRetries,
|
|
33411
|
+
failurePolicy: omConfig.reflection.failurePolicy,
|
|
33033
33412
|
observationTokens: omConfig.reflection.observationTokens,
|
|
33034
33413
|
modelSettings: omConfig.reflection.modelSettings,
|
|
33035
33414
|
providerOptions: omConfig.reflection.providerOptions,
|
|
@@ -34394,4 +34773,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
|
|
|
34394
34773
|
}
|
|
34395
34774
|
});
|
|
34396
34775
|
|
|
34397
|
-
//# sourceMappingURL=src-
|
|
34776
|
+
//# sourceMappingURL=src-CeFdO1YZ.cjs.map
|