@mastra/memory 1.6.1 → 1.6.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/{chunk-GBBQIJQF.js → chunk-3CM4XQJO.js} +1940 -463
- package/dist/chunk-3CM4XQJO.js.map +1 -0
- package/dist/{chunk-D6II7EP4.cjs → chunk-5W5463NI.cjs} +1939 -461
- package/dist/chunk-5W5463NI.cjs.map +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +25 -25
- package/dist/docs/references/docs-agents-agent-memory.md +2 -2
- package/dist/docs/references/docs-agents-network-approval.md +4 -1
- package/dist/docs/references/docs-agents-networks.md +1 -1
- package/dist/docs/references/docs-memory-memory-processors.md +1 -1
- package/dist/docs/references/docs-memory-message-history.md +2 -2
- package/dist/docs/references/docs-memory-observational-memory.md +6 -2
- package/dist/docs/references/docs-memory-semantic-recall.md +2 -2
- package/dist/docs/references/docs-memory-storage.md +1 -1
- package/dist/docs/references/docs-memory-working-memory.md +6 -6
- package/dist/docs/references/reference-memory-cloneThread.md +1 -1
- package/dist/docs/references/reference-memory-observational-memory.md +7 -5
- package/dist/docs/references/reference-processors-token-limiter-processor.md +2 -2
- package/dist/docs/references/reference-storage-dynamodb.md +2 -2
- package/dist/docs/references/reference-storage-mongodb.md +1 -1
- package/dist/docs/references/reference-storage-postgresql.md +2 -2
- package/dist/docs/references/reference-storage-upstash.md +1 -1
- package/dist/docs/references/reference-vectors-pg.md +2 -0
- package/dist/index.cjs +48 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -21
- package/dist/index.js.map +1 -1
- package/dist/{observational-memory-AHVELJX4.cjs → observational-memory-C5LO7RBR.cjs} +17 -17
- package/dist/{observational-memory-AHVELJX4.cjs.map → observational-memory-C5LO7RBR.cjs.map} +1 -1
- package/dist/{observational-memory-QFQUF5EY.js → observational-memory-OYK4MEUD.js} +3 -3
- package/dist/{observational-memory-QFQUF5EY.js.map → observational-memory-OYK4MEUD.js.map} +1 -1
- package/dist/processors/index.cjs +15 -15
- package/dist/processors/index.js +1 -1
- package/dist/processors/observational-memory/observational-memory.d.ts +33 -2
- package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-agent.d.ts +7 -4
- package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/repro-capture.d.ts +23 -0
- package/dist/processors/observational-memory/repro-capture.d.ts.map +1 -0
- package/dist/processors/observational-memory/token-counter.d.ts +28 -3
- package/dist/processors/observational-memory/token-counter.d.ts.map +1 -1
- package/dist/tools/working-memory.d.ts +6 -10
- package/dist/tools/working-memory.d.ts.map +1 -1
- package/package.json +13 -8
- package/dist/chunk-D6II7EP4.cjs.map +0 -1
- package/dist/chunk-GBBQIJQF.js.map +0 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { appendFileSync } from 'fs';
|
|
1
|
+
import { appendFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { Agent } from '@mastra/core/agent';
|
|
4
|
+
import { coreFeatures } from '@mastra/core/features';
|
|
4
5
|
import { resolveModelConfig } from '@mastra/core/llm';
|
|
5
6
|
import { setThreadOMMetadata, getThreadOMMetadata, parseMemoryRequestContext } from '@mastra/core/memory';
|
|
6
7
|
import { MessageHistory } from '@mastra/core/processors';
|
|
7
8
|
import xxhash from 'xxhash-wasm';
|
|
8
|
-
import { createHash } from 'crypto';
|
|
9
|
+
import { createHash, randomUUID } from 'crypto';
|
|
10
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
11
|
+
import imageSize from 'image-size';
|
|
9
12
|
import { Tiktoken } from 'js-tiktoken/lite';
|
|
10
13
|
import o200k_base from 'js-tiktoken/ranks/o200k_base';
|
|
11
14
|
|
|
@@ -662,45 +665,177 @@ User messages are extremely important. If the user asks a question or gives a ne
|
|
|
662
665
|
${instruction}` : ""}`;
|
|
663
666
|
}
|
|
664
667
|
var OBSERVER_SYSTEM_PROMPT = buildObserverSystemPrompt();
|
|
665
|
-
|
|
668
|
+
var OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
669
|
+
"png",
|
|
670
|
+
"jpg",
|
|
671
|
+
"jpeg",
|
|
672
|
+
"webp",
|
|
673
|
+
"gif",
|
|
674
|
+
"bmp",
|
|
675
|
+
"tiff",
|
|
676
|
+
"tif",
|
|
677
|
+
"heic",
|
|
678
|
+
"heif",
|
|
679
|
+
"avif"
|
|
680
|
+
]);
|
|
681
|
+
function formatObserverTimestamp(createdAt) {
|
|
682
|
+
return createdAt ? new Date(createdAt).toLocaleString("en-US", {
|
|
683
|
+
year: "numeric",
|
|
684
|
+
month: "short",
|
|
685
|
+
day: "numeric",
|
|
686
|
+
hour: "numeric",
|
|
687
|
+
minute: "2-digit",
|
|
688
|
+
hour12: true
|
|
689
|
+
}) : "";
|
|
690
|
+
}
|
|
691
|
+
function getObserverPathExtension(value) {
|
|
692
|
+
const normalized = value.split("#", 1)[0]?.split("?", 1)[0] ?? value;
|
|
693
|
+
const match = normalized.match(/\.([a-z0-9]+)$/i);
|
|
694
|
+
return match?.[1]?.toLowerCase();
|
|
695
|
+
}
|
|
696
|
+
function hasObserverImageFilenameExtension(filename) {
|
|
697
|
+
return typeof filename === "string" && OBSERVER_IMAGE_FILE_EXTENSIONS.has(getObserverPathExtension(filename) ?? "");
|
|
698
|
+
}
|
|
699
|
+
function isImageLikeObserverFilePart(part) {
|
|
700
|
+
if (part.type !== "file") {
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
if (typeof part.mimeType === "string" && part.mimeType.toLowerCase().startsWith("image/")) {
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
if (typeof part.data === "string" && part.data.startsWith("data:image/")) {
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
if (part.data instanceof URL && hasObserverImageFilenameExtension(part.data.pathname)) {
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
if (typeof part.data === "string") {
|
|
713
|
+
try {
|
|
714
|
+
const url = new URL(part.data);
|
|
715
|
+
if ((url.protocol === "http:" || url.protocol === "https:") && hasObserverImageFilenameExtension(url.pathname)) {
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
} catch {
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return hasObserverImageFilenameExtension(part.filename);
|
|
722
|
+
}
|
|
723
|
+
function toObserverInputAttachmentPart(part) {
|
|
724
|
+
if (part.type === "image") {
|
|
725
|
+
return {
|
|
726
|
+
type: "image",
|
|
727
|
+
image: part.image,
|
|
728
|
+
mimeType: part.mimeType,
|
|
729
|
+
providerOptions: part.providerOptions,
|
|
730
|
+
providerMetadata: part.providerMetadata,
|
|
731
|
+
experimental_providerMetadata: part.experimental_providerMetadata
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
if (isImageLikeObserverFilePart(part)) {
|
|
735
|
+
return {
|
|
736
|
+
type: "image",
|
|
737
|
+
image: part.data,
|
|
738
|
+
mimeType: part.mimeType,
|
|
739
|
+
providerOptions: part.providerOptions,
|
|
740
|
+
providerMetadata: part.providerMetadata,
|
|
741
|
+
experimental_providerMetadata: part.experimental_providerMetadata
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
type: "file",
|
|
746
|
+
data: part.data,
|
|
747
|
+
mimeType: part.mimeType,
|
|
748
|
+
filename: part.filename,
|
|
749
|
+
providerOptions: part.providerOptions,
|
|
750
|
+
providerMetadata: part.providerMetadata,
|
|
751
|
+
experimental_providerMetadata: part.experimental_providerMetadata
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function resolveObserverAttachmentLabel(part) {
|
|
755
|
+
if (part.filename?.trim()) {
|
|
756
|
+
return part.filename.trim();
|
|
757
|
+
}
|
|
758
|
+
const asset = part.type === "image" ? part.image : part.data;
|
|
759
|
+
if (typeof asset !== "string" || asset.startsWith("data:")) {
|
|
760
|
+
return part.mimeType;
|
|
761
|
+
}
|
|
762
|
+
try {
|
|
763
|
+
const url = new URL(asset);
|
|
764
|
+
const basename = url.pathname.split("/").filter(Boolean).pop();
|
|
765
|
+
return basename ? decodeURIComponent(basename) : part.mimeType;
|
|
766
|
+
} catch {
|
|
767
|
+
return part.mimeType;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function formatObserverAttachmentPlaceholder(part, counter) {
|
|
771
|
+
const attachmentType = part.type === "image" || isImageLikeObserverFilePart(part) ? "Image" : "File";
|
|
772
|
+
const attachmentId = attachmentType === "Image" ? counter.nextImageId++ : counter.nextFileId++;
|
|
773
|
+
const label = resolveObserverAttachmentLabel(part);
|
|
774
|
+
return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
|
|
775
|
+
}
|
|
776
|
+
function formatObserverMessage(msg, counter, options) {
|
|
666
777
|
const maxLen = options?.maxPartLength;
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
content = msg.content.parts.map((part) => {
|
|
683
|
-
if (part.type === "text") return maybeTruncate(part.text, maxLen);
|
|
684
|
-
if (part.type === "tool-invocation") {
|
|
685
|
-
const inv = part.toolInvocation;
|
|
686
|
-
if (inv.state === "result") {
|
|
687
|
-
const resultStr = JSON.stringify(inv.result, null, 2);
|
|
688
|
-
return `[Tool Result: ${inv.toolName}]
|
|
778
|
+
const timestamp = formatObserverTimestamp(msg.createdAt);
|
|
779
|
+
const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
|
|
780
|
+
const timestampStr = timestamp ? ` (${timestamp})` : "";
|
|
781
|
+
const attachments = [];
|
|
782
|
+
let content = "";
|
|
783
|
+
if (typeof msg.content === "string") {
|
|
784
|
+
content = maybeTruncate(msg.content, maxLen);
|
|
785
|
+
} else if (msg.content?.parts && Array.isArray(msg.content.parts) && msg.content.parts.length > 0) {
|
|
786
|
+
content = msg.content.parts.map((part) => {
|
|
787
|
+
if (part.type === "text") return maybeTruncate(part.text, maxLen);
|
|
788
|
+
if (part.type === "tool-invocation") {
|
|
789
|
+
const inv = part.toolInvocation;
|
|
790
|
+
if (inv.state === "result") {
|
|
791
|
+
const resultStr = JSON.stringify(inv.result, null, 2);
|
|
792
|
+
return `[Tool Result: ${inv.toolName}]
|
|
689
793
|
${maybeTruncate(resultStr, maxLen)}`;
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
794
|
+
}
|
|
795
|
+
const argsStr = JSON.stringify(inv.args, null, 2);
|
|
796
|
+
return `[Tool Call: ${inv.toolName}]
|
|
693
797
|
${maybeTruncate(argsStr, maxLen)}`;
|
|
798
|
+
}
|
|
799
|
+
const partType = part.type;
|
|
800
|
+
if (partType === "image" || partType === "file") {
|
|
801
|
+
const attachment = part;
|
|
802
|
+
const inputAttachment = toObserverInputAttachmentPart(attachment);
|
|
803
|
+
if (inputAttachment) {
|
|
804
|
+
attachments.push(inputAttachment);
|
|
694
805
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
806
|
+
return formatObserverAttachmentPlaceholder(attachment, counter);
|
|
807
|
+
}
|
|
808
|
+
if (partType?.startsWith("data-")) return "";
|
|
809
|
+
return "";
|
|
810
|
+
}).filter(Boolean).join("\n");
|
|
811
|
+
} else if (msg.content?.content) {
|
|
812
|
+
content = maybeTruncate(msg.content.content, maxLen);
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
text: `**${role}${timestampStr}:**
|
|
816
|
+
${content}`,
|
|
817
|
+
attachments
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function formatMessagesForObserver(messages, options) {
|
|
821
|
+
const counter = { nextImageId: 1, nextFileId: 1 };
|
|
822
|
+
return messages.map((msg) => formatObserverMessage(msg, counter, options).text).join("\n\n---\n\n");
|
|
823
|
+
}
|
|
824
|
+
function buildObserverHistoryMessage(messages) {
|
|
825
|
+
const counter = { nextImageId: 1, nextFileId: 1 };
|
|
826
|
+
const content = [{ type: "text", text: "## New Message History to Observe\n\n" }];
|
|
827
|
+
messages.forEach((message, index) => {
|
|
828
|
+
const formatted = formatObserverMessage(message, counter);
|
|
829
|
+
content.push({ type: "text", text: formatted.text });
|
|
830
|
+
content.push(...formatted.attachments);
|
|
831
|
+
if (index < messages.length - 1) {
|
|
832
|
+
content.push({ type: "text", text: "\n\n---\n\n" });
|
|
700
833
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
834
|
+
});
|
|
835
|
+
return {
|
|
836
|
+
role: "user",
|
|
837
|
+
content
|
|
838
|
+
};
|
|
704
839
|
}
|
|
705
840
|
function maybeTruncate(str, maxLen) {
|
|
706
841
|
if (!maxLen || str.length <= maxLen) return str;
|
|
@@ -709,20 +844,42 @@ function maybeTruncate(str, maxLen) {
|
|
|
709
844
|
return `${truncated}
|
|
710
845
|
... [truncated ${remaining} characters]`;
|
|
711
846
|
}
|
|
712
|
-
function
|
|
713
|
-
const
|
|
714
|
-
|
|
847
|
+
function buildMultiThreadObserverHistoryMessage(messagesByThread, threadOrder) {
|
|
848
|
+
const counter = { nextImageId: 1, nextFileId: 1 };
|
|
849
|
+
const content = [
|
|
850
|
+
{
|
|
851
|
+
type: "text",
|
|
852
|
+
text: `## New Message History to Observe
|
|
853
|
+
|
|
854
|
+
The following messages are from ${threadOrder.length} different conversation threads. Each thread is wrapped in a <thread id="..."> tag.
|
|
855
|
+
|
|
856
|
+
`
|
|
857
|
+
}
|
|
858
|
+
];
|
|
859
|
+
threadOrder.forEach((threadId, threadIndex) => {
|
|
715
860
|
const messages = messagesByThread.get(threadId);
|
|
716
|
-
if (!messages || messages.length === 0)
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
861
|
+
if (!messages || messages.length === 0) return;
|
|
862
|
+
content.push({ type: "text", text: `<thread id="${threadId}">
|
|
863
|
+
` });
|
|
864
|
+
messages.forEach((message, messageIndex) => {
|
|
865
|
+
const formatted = formatObserverMessage(message, counter);
|
|
866
|
+
content.push({ type: "text", text: formatted.text });
|
|
867
|
+
content.push(...formatted.attachments);
|
|
868
|
+
if (messageIndex < messages.length - 1) {
|
|
869
|
+
content.push({ type: "text", text: "\n\n---\n\n" });
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
content.push({ type: "text", text: "\n</thread>" });
|
|
873
|
+
if (threadIndex < threadOrder.length - 1) {
|
|
874
|
+
content.push({ type: "text", text: "\n\n" });
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
return {
|
|
878
|
+
role: "user",
|
|
879
|
+
content
|
|
880
|
+
};
|
|
723
881
|
}
|
|
724
|
-
function
|
|
725
|
-
const formattedMessages = formatMultiThreadMessagesForObserver(messagesByThread, threadOrder);
|
|
882
|
+
function buildMultiThreadObserverTaskPrompt(existingObservations) {
|
|
726
883
|
let prompt = "";
|
|
727
884
|
if (existingObservations) {
|
|
728
885
|
prompt += `## Previous Observations
|
|
@@ -734,15 +891,6 @@ ${existingObservations}
|
|
|
734
891
|
`;
|
|
735
892
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
736
893
|
}
|
|
737
|
-
prompt += `## New Message History to Observe
|
|
738
|
-
|
|
739
|
-
The following messages are from ${threadOrder.length} different conversation threads. Each thread is wrapped in a <thread id="..."> tag.
|
|
740
|
-
|
|
741
|
-
${formattedMessages}
|
|
742
|
-
|
|
743
|
-
---
|
|
744
|
-
|
|
745
|
-
`;
|
|
746
894
|
prompt += `## Your Task
|
|
747
895
|
|
|
748
896
|
`;
|
|
@@ -819,8 +967,7 @@ function parseMultiThreadObserverOutput(output) {
|
|
|
819
967
|
rawOutput: output
|
|
820
968
|
};
|
|
821
969
|
}
|
|
822
|
-
function
|
|
823
|
-
const formattedMessages = formatMessagesForObserver(messagesToObserve);
|
|
970
|
+
function buildObserverTaskPrompt(existingObservations, options) {
|
|
824
971
|
let prompt = "";
|
|
825
972
|
if (existingObservations) {
|
|
826
973
|
prompt += `## Previous Observations
|
|
@@ -832,13 +979,6 @@ ${existingObservations}
|
|
|
832
979
|
`;
|
|
833
980
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
834
981
|
}
|
|
835
|
-
prompt += `## New Message History to Observe
|
|
836
|
-
|
|
837
|
-
${formattedMessages}
|
|
838
|
-
|
|
839
|
-
---
|
|
840
|
-
|
|
841
|
-
`;
|
|
842
982
|
prompt += `## Your Task
|
|
843
983
|
|
|
844
984
|
`;
|
|
@@ -850,6 +990,16 @@ IMPORTANT: Do NOT include <current-task> or <suggested-response> sections in you
|
|
|
850
990
|
}
|
|
851
991
|
return prompt;
|
|
852
992
|
}
|
|
993
|
+
function buildObserverPrompt(existingObservations, messagesToObserve, options) {
|
|
994
|
+
const formattedMessages = formatMessagesForObserver(messagesToObserve);
|
|
995
|
+
return `## New Message History to Observe
|
|
996
|
+
|
|
997
|
+
${formattedMessages}
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
${buildObserverTaskPrompt(existingObservations, options)}`;
|
|
1002
|
+
}
|
|
853
1003
|
function parseObserverOutput(output) {
|
|
854
1004
|
if (detectDegenerateRepetition(output)) {
|
|
855
1005
|
return {
|
|
@@ -1221,6 +1371,140 @@ function extractReflectorListItems(content) {
|
|
|
1221
1371
|
function validateCompression(reflectedTokens, targetThreshold) {
|
|
1222
1372
|
return reflectedTokens < targetThreshold;
|
|
1223
1373
|
}
|
|
1374
|
+
var OM_REPRO_CAPTURE_DIR = process.env.OM_REPRO_CAPTURE_DIR ?? ".mastra-om-repro";
|
|
1375
|
+
function sanitizeCapturePathSegment(value) {
|
|
1376
|
+
const sanitized = value.replace(/[\\/]+/g, "_").replace(/\.{2,}/g, "_").trim();
|
|
1377
|
+
return sanitized.length > 0 ? sanitized : "unknown-thread";
|
|
1378
|
+
}
|
|
1379
|
+
function isOmReproCaptureEnabled() {
|
|
1380
|
+
return process.env.OM_REPRO_CAPTURE === "1";
|
|
1381
|
+
}
|
|
1382
|
+
function safeCaptureJson(value) {
|
|
1383
|
+
return JSON.parse(
|
|
1384
|
+
JSON.stringify(value, (_key, current) => {
|
|
1385
|
+
if (typeof current === "bigint") return current.toString();
|
|
1386
|
+
if (typeof current === "function") return "[function]";
|
|
1387
|
+
if (current instanceof Error) return { name: current.name, message: current.message, stack: current.stack };
|
|
1388
|
+
if (current instanceof Set) return { __type: "Set", values: Array.from(current.values()) };
|
|
1389
|
+
if (current instanceof Map) return { __type: "Map", entries: Array.from(current.entries()) };
|
|
1390
|
+
return current;
|
|
1391
|
+
})
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
function buildReproMessageFingerprint(message) {
|
|
1395
|
+
const createdAt = message.createdAt instanceof Date ? message.createdAt.toISOString() : message.createdAt ? new Date(message.createdAt).toISOString() : "";
|
|
1396
|
+
return JSON.stringify({
|
|
1397
|
+
role: message.role,
|
|
1398
|
+
createdAt,
|
|
1399
|
+
content: message.content
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
function inferReproIdRemap(preMessages, postMessages) {
|
|
1403
|
+
const preByFingerprint = /* @__PURE__ */ new Map();
|
|
1404
|
+
const postByFingerprint = /* @__PURE__ */ new Map();
|
|
1405
|
+
for (const message of preMessages) {
|
|
1406
|
+
if (!message.id) continue;
|
|
1407
|
+
const fingerprint = buildReproMessageFingerprint(message);
|
|
1408
|
+
const list = preByFingerprint.get(fingerprint) ?? [];
|
|
1409
|
+
list.push(message.id);
|
|
1410
|
+
preByFingerprint.set(fingerprint, list);
|
|
1411
|
+
}
|
|
1412
|
+
for (const message of postMessages) {
|
|
1413
|
+
if (!message.id) continue;
|
|
1414
|
+
const fingerprint = buildReproMessageFingerprint(message);
|
|
1415
|
+
const list = postByFingerprint.get(fingerprint) ?? [];
|
|
1416
|
+
list.push(message.id);
|
|
1417
|
+
postByFingerprint.set(fingerprint, list);
|
|
1418
|
+
}
|
|
1419
|
+
const remap = [];
|
|
1420
|
+
for (const [fingerprint, preIds] of preByFingerprint.entries()) {
|
|
1421
|
+
const postIds = postByFingerprint.get(fingerprint);
|
|
1422
|
+
if (!postIds || preIds.length !== 1 || postIds.length !== 1) continue;
|
|
1423
|
+
const fromId = preIds[0];
|
|
1424
|
+
const toId = postIds[0];
|
|
1425
|
+
if (!fromId || !toId || fromId === toId) {
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
remap.push({ fromId, toId, fingerprint });
|
|
1429
|
+
}
|
|
1430
|
+
return remap;
|
|
1431
|
+
}
|
|
1432
|
+
function writeProcessInputStepReproCapture(params) {
|
|
1433
|
+
if (!isOmReproCaptureEnabled()) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
try {
|
|
1437
|
+
const sanitizedThreadId = sanitizeCapturePathSegment(params.threadId);
|
|
1438
|
+
const runId = `${Date.now()}-step-${params.stepNumber}-${randomUUID()}`;
|
|
1439
|
+
const captureDir = join(process.cwd(), OM_REPRO_CAPTURE_DIR, sanitizedThreadId, runId);
|
|
1440
|
+
mkdirSync(captureDir, { recursive: true });
|
|
1441
|
+
const contextMessages = params.messageList.get.all.db();
|
|
1442
|
+
const memoryContext = parseMemoryRequestContext(params.args.requestContext);
|
|
1443
|
+
const preMessageIds = new Set(params.preMessages.map((message) => message.id));
|
|
1444
|
+
const postMessageIds = new Set(contextMessages.map((message) => message.id));
|
|
1445
|
+
const removedMessageIds = params.preMessages.map((message) => message.id).filter((id) => Boolean(id) && !postMessageIds.has(id));
|
|
1446
|
+
const addedMessageIds = contextMessages.map((message) => message.id).filter((id) => Boolean(id) && !preMessageIds.has(id));
|
|
1447
|
+
const idRemap = inferReproIdRemap(params.preMessages, contextMessages);
|
|
1448
|
+
const rawState = params.args.state ?? {};
|
|
1449
|
+
const inputPayload = safeCaptureJson({
|
|
1450
|
+
stepNumber: params.stepNumber,
|
|
1451
|
+
threadId: params.threadId,
|
|
1452
|
+
resourceId: params.resourceId,
|
|
1453
|
+
readOnly: memoryContext?.memoryConfig?.readOnly,
|
|
1454
|
+
messageCount: contextMessages.length,
|
|
1455
|
+
messageIds: contextMessages.map((message) => message.id),
|
|
1456
|
+
stateKeys: Object.keys(rawState),
|
|
1457
|
+
state: rawState,
|
|
1458
|
+
args: {
|
|
1459
|
+
messages: params.args.messages,
|
|
1460
|
+
steps: params.args.steps,
|
|
1461
|
+
systemMessages: params.args.systemMessages,
|
|
1462
|
+
retryCount: params.args.retryCount,
|
|
1463
|
+
tools: params.args.tools,
|
|
1464
|
+
toolChoice: params.args.toolChoice,
|
|
1465
|
+
activeTools: params.args.activeTools,
|
|
1466
|
+
providerOptions: params.args.providerOptions,
|
|
1467
|
+
modelSettings: params.args.modelSettings,
|
|
1468
|
+
structuredOutput: params.args.structuredOutput
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
const preStatePayload = safeCaptureJson({
|
|
1472
|
+
record: params.preRecord,
|
|
1473
|
+
bufferedChunks: params.preBufferedChunks,
|
|
1474
|
+
contextTokenCount: params.preContextTokenCount,
|
|
1475
|
+
messages: params.preMessages,
|
|
1476
|
+
messageList: params.preSerializedMessageList
|
|
1477
|
+
});
|
|
1478
|
+
const outputPayload = safeCaptureJson({
|
|
1479
|
+
details: params.details,
|
|
1480
|
+
messageDiff: {
|
|
1481
|
+
removedMessageIds,
|
|
1482
|
+
addedMessageIds,
|
|
1483
|
+
idRemap
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
const postStatePayload = safeCaptureJson({
|
|
1487
|
+
record: params.postRecord,
|
|
1488
|
+
bufferedChunks: params.postBufferedChunks,
|
|
1489
|
+
contextTokenCount: params.postContextTokenCount,
|
|
1490
|
+
messageCount: contextMessages.length,
|
|
1491
|
+
messageIds: contextMessages.map((message) => message.id),
|
|
1492
|
+
messages: contextMessages,
|
|
1493
|
+
messageList: params.messageList.serialize()
|
|
1494
|
+
});
|
|
1495
|
+
writeFileSync(join(captureDir, "input.json"), `${JSON.stringify(inputPayload, null, 2)}
|
|
1496
|
+
`);
|
|
1497
|
+
writeFileSync(join(captureDir, "pre-state.json"), `${JSON.stringify(preStatePayload, null, 2)}
|
|
1498
|
+
`);
|
|
1499
|
+
writeFileSync(join(captureDir, "output.json"), `${JSON.stringify(outputPayload, null, 2)}
|
|
1500
|
+
`);
|
|
1501
|
+
writeFileSync(join(captureDir, "post-state.json"), `${JSON.stringify(postStatePayload, null, 2)}
|
|
1502
|
+
`);
|
|
1503
|
+
params.debug?.(`[OM:repro-capture] wrote processInputStep capture to ${captureDir}`);
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
params.debug?.(`[OM:repro-capture] failed to write processInputStep capture: ${String(error)}`);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1224
1508
|
|
|
1225
1509
|
// src/processors/observational-memory/thresholds.ts
|
|
1226
1510
|
function getMaxThreshold(threshold) {
|
|
@@ -1306,14 +1590,90 @@ function calculateProjectedMessageRemoval(chunks, bufferActivation, messageToken
|
|
|
1306
1590
|
}
|
|
1307
1591
|
return bestBoundaryMessageTokens;
|
|
1308
1592
|
}
|
|
1309
|
-
var
|
|
1593
|
+
var GLOBAL_TIKTOKEN_KEY = "__mastraTiktoken";
|
|
1310
1594
|
function getDefaultEncoder() {
|
|
1311
|
-
|
|
1312
|
-
|
|
1595
|
+
const cached = globalThis[GLOBAL_TIKTOKEN_KEY];
|
|
1596
|
+
if (cached) return cached;
|
|
1597
|
+
const encoder = new Tiktoken(o200k_base);
|
|
1598
|
+
globalThis[GLOBAL_TIKTOKEN_KEY] = encoder;
|
|
1599
|
+
return encoder;
|
|
1600
|
+
}
|
|
1601
|
+
var IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1602
|
+
"png",
|
|
1603
|
+
"jpg",
|
|
1604
|
+
"jpeg",
|
|
1605
|
+
"webp",
|
|
1606
|
+
"gif",
|
|
1607
|
+
"bmp",
|
|
1608
|
+
"tiff",
|
|
1609
|
+
"tif",
|
|
1610
|
+
"heic",
|
|
1611
|
+
"heif",
|
|
1612
|
+
"avif"
|
|
1613
|
+
]);
|
|
1614
|
+
var TOKEN_ESTIMATE_CACHE_VERSION = 5;
|
|
1615
|
+
var DEFAULT_IMAGE_ESTIMATOR = {
|
|
1616
|
+
baseTokens: 85,
|
|
1617
|
+
tileTokens: 170,
|
|
1618
|
+
fallbackTiles: 4
|
|
1619
|
+
};
|
|
1620
|
+
var GOOGLE_LEGACY_IMAGE_TOKENS_PER_TILE = 258;
|
|
1621
|
+
var GOOGLE_GEMINI_3_IMAGE_TOKENS_BY_RESOLUTION = {
|
|
1622
|
+
low: 280,
|
|
1623
|
+
medium: 560,
|
|
1624
|
+
high: 1120,
|
|
1625
|
+
ultra_high: 2240,
|
|
1626
|
+
unspecified: 1120
|
|
1627
|
+
};
|
|
1628
|
+
var ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
|
|
1629
|
+
var ANTHROPIC_IMAGE_MAX_LONG_EDGE = 1568;
|
|
1630
|
+
var GOOGLE_MEDIA_RESOLUTION_VALUES = /* @__PURE__ */ new Set([
|
|
1631
|
+
"low",
|
|
1632
|
+
"medium",
|
|
1633
|
+
"high",
|
|
1634
|
+
"ultra_high",
|
|
1635
|
+
"unspecified"
|
|
1636
|
+
]);
|
|
1637
|
+
var ATTACHMENT_COUNT_TIMEOUT_MS = 2e4;
|
|
1638
|
+
var REMOTE_IMAGE_PROBE_TIMEOUT_MS = 2500;
|
|
1639
|
+
var PROVIDER_API_KEY_ENV_VARS = {
|
|
1640
|
+
openai: ["OPENAI_API_KEY"],
|
|
1641
|
+
google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY"],
|
|
1642
|
+
anthropic: ["ANTHROPIC_API_KEY"]
|
|
1643
|
+
};
|
|
1644
|
+
function getPartMastraMetadata(part) {
|
|
1645
|
+
return part.providerMetadata?.mastra;
|
|
1646
|
+
}
|
|
1647
|
+
function ensurePartMastraMetadata(part) {
|
|
1648
|
+
const typedPart = part;
|
|
1649
|
+
typedPart.providerMetadata ??= {};
|
|
1650
|
+
typedPart.providerMetadata.mastra ??= {};
|
|
1651
|
+
return typedPart.providerMetadata.mastra;
|
|
1652
|
+
}
|
|
1653
|
+
function getContentMastraMetadata(content) {
|
|
1654
|
+
if (!content || typeof content !== "object") {
|
|
1655
|
+
return void 0;
|
|
1313
1656
|
}
|
|
1314
|
-
return
|
|
1657
|
+
return content.metadata?.mastra;
|
|
1658
|
+
}
|
|
1659
|
+
function ensureContentMastraMetadata(content) {
|
|
1660
|
+
if (!content || typeof content !== "object") {
|
|
1661
|
+
return void 0;
|
|
1662
|
+
}
|
|
1663
|
+
const typedContent = content;
|
|
1664
|
+
typedContent.metadata ??= {};
|
|
1665
|
+
typedContent.metadata.mastra ??= {};
|
|
1666
|
+
return typedContent.metadata.mastra;
|
|
1667
|
+
}
|
|
1668
|
+
function getMessageMastraMetadata(message) {
|
|
1669
|
+
return message.metadata?.mastra;
|
|
1670
|
+
}
|
|
1671
|
+
function ensureMessageMastraMetadata(message) {
|
|
1672
|
+
const typedMessage = message;
|
|
1673
|
+
typedMessage.metadata ??= {};
|
|
1674
|
+
typedMessage.metadata.mastra ??= {};
|
|
1675
|
+
return typedMessage.metadata.mastra;
|
|
1315
1676
|
}
|
|
1316
|
-
var TOKEN_ESTIMATE_CACHE_VERSION = 1;
|
|
1317
1677
|
function buildEstimateKey(kind, text) {
|
|
1318
1678
|
const payloadHash = createHash("sha1").update(text).digest("hex");
|
|
1319
1679
|
return `${kind}:${payloadHash}`;
|
|
@@ -1336,51 +1696,60 @@ function getCacheEntry(cache, key) {
|
|
|
1336
1696
|
if (isTokenEstimateEntry(cache)) {
|
|
1337
1697
|
return cache.key === key ? cache : void 0;
|
|
1338
1698
|
}
|
|
1339
|
-
|
|
1699
|
+
const keyedEntry = cache[key];
|
|
1700
|
+
return isTokenEstimateEntry(keyedEntry) ? keyedEntry : void 0;
|
|
1701
|
+
}
|
|
1702
|
+
function mergeCacheEntry(cache, key, entry) {
|
|
1703
|
+
if (isTokenEstimateEntry(cache)) {
|
|
1704
|
+
if (cache.key === key) {
|
|
1705
|
+
return entry;
|
|
1706
|
+
}
|
|
1707
|
+
return {
|
|
1708
|
+
[cache.key]: cache,
|
|
1709
|
+
[key]: entry
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
if (cache && typeof cache === "object") {
|
|
1713
|
+
return {
|
|
1714
|
+
...cache,
|
|
1715
|
+
[key]: entry
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
return entry;
|
|
1340
1719
|
}
|
|
1341
1720
|
function getPartCacheEntry(part, key) {
|
|
1342
|
-
|
|
1343
|
-
return getCacheEntry(cache, key);
|
|
1721
|
+
return getCacheEntry(getPartMastraMetadata(part)?.tokenEstimate, key);
|
|
1344
1722
|
}
|
|
1345
|
-
function setPartCacheEntry(part,
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
mutablePart.providerMetadata.mastra ??= {};
|
|
1349
|
-
mutablePart.providerMetadata.mastra.tokenEstimate = entry;
|
|
1723
|
+
function setPartCacheEntry(part, key, entry) {
|
|
1724
|
+
const mastraMetadata = ensurePartMastraMetadata(part);
|
|
1725
|
+
mastraMetadata.tokenEstimate = mergeCacheEntry(mastraMetadata.tokenEstimate, key, entry);
|
|
1350
1726
|
}
|
|
1351
1727
|
function getMessageCacheEntry(message, key) {
|
|
1352
|
-
const
|
|
1353
|
-
if (
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
}
|
|
1361
|
-
function setMessageCacheEntry(message, _key, entry) {
|
|
1362
|
-
const content = message.content;
|
|
1363
|
-
if (content && typeof content === "object") {
|
|
1364
|
-
content.metadata ??= {};
|
|
1365
|
-
content.metadata.mastra ??= {};
|
|
1366
|
-
content.metadata.mastra.tokenEstimate = entry;
|
|
1728
|
+
const contentLevelEntry = getCacheEntry(getContentMastraMetadata(message.content)?.tokenEstimate, key);
|
|
1729
|
+
if (contentLevelEntry) return contentLevelEntry;
|
|
1730
|
+
return getCacheEntry(getMessageMastraMetadata(message)?.tokenEstimate, key);
|
|
1731
|
+
}
|
|
1732
|
+
function setMessageCacheEntry(message, key, entry) {
|
|
1733
|
+
const contentMastraMetadata = ensureContentMastraMetadata(message.content);
|
|
1734
|
+
if (contentMastraMetadata) {
|
|
1735
|
+
contentMastraMetadata.tokenEstimate = mergeCacheEntry(contentMastraMetadata.tokenEstimate, key, entry);
|
|
1367
1736
|
return;
|
|
1368
1737
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
message.metadata.mastra.tokenEstimate = entry;
|
|
1738
|
+
const messageMastraMetadata = ensureMessageMastraMetadata(message);
|
|
1739
|
+
messageMastraMetadata.tokenEstimate = mergeCacheEntry(messageMastraMetadata.tokenEstimate, key, entry);
|
|
1372
1740
|
}
|
|
1373
1741
|
function serializePartForTokenCounting(part) {
|
|
1374
|
-
const
|
|
1742
|
+
const typedPart = part;
|
|
1743
|
+
const hasTokenEstimate = Boolean(typedPart.providerMetadata?.mastra?.tokenEstimate);
|
|
1375
1744
|
if (!hasTokenEstimate) {
|
|
1376
1745
|
return JSON.stringify(part);
|
|
1377
1746
|
}
|
|
1378
1747
|
const clonedPart = {
|
|
1379
|
-
...
|
|
1748
|
+
...typedPart,
|
|
1380
1749
|
providerMetadata: {
|
|
1381
|
-
...
|
|
1750
|
+
...typedPart.providerMetadata ?? {},
|
|
1382
1751
|
mastra: {
|
|
1383
|
-
...
|
|
1752
|
+
...typedPart.providerMetadata?.mastra ?? {}
|
|
1384
1753
|
}
|
|
1385
1754
|
}
|
|
1386
1755
|
};
|
|
@@ -1393,23 +1762,648 @@ function serializePartForTokenCounting(part) {
|
|
|
1393
1762
|
}
|
|
1394
1763
|
return JSON.stringify(clonedPart);
|
|
1395
1764
|
}
|
|
1765
|
+
function getFilenameFromAttachmentData(data) {
|
|
1766
|
+
const pathname = data instanceof URL ? data.pathname : typeof data === "string" && isHttpUrlString(data) ? (() => {
|
|
1767
|
+
try {
|
|
1768
|
+
return new URL(data).pathname;
|
|
1769
|
+
} catch {
|
|
1770
|
+
return void 0;
|
|
1771
|
+
}
|
|
1772
|
+
})() : void 0;
|
|
1773
|
+
const filename = pathname?.split("/").filter(Boolean).pop();
|
|
1774
|
+
return filename ? decodeURIComponent(filename) : void 0;
|
|
1775
|
+
}
|
|
1776
|
+
function serializeNonImageFilePartForTokenCounting(part) {
|
|
1777
|
+
const filename = getObjectValue(part, "filename");
|
|
1778
|
+
const inferredFilename = getFilenameFromAttachmentData(getObjectValue(part, "data"));
|
|
1779
|
+
return JSON.stringify({
|
|
1780
|
+
type: "file",
|
|
1781
|
+
mimeType: getObjectValue(part, "mimeType") ?? null,
|
|
1782
|
+
filename: typeof filename === "string" && filename.trim().length > 0 ? filename.trim() : inferredFilename ?? null
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1396
1785
|
function isValidCacheEntry(entry, expectedKey, expectedSource) {
|
|
1397
1786
|
return Boolean(
|
|
1398
1787
|
entry && entry.v === TOKEN_ESTIMATE_CACHE_VERSION && entry.source === expectedSource && entry.key === expectedKey && Number.isFinite(entry.tokens)
|
|
1399
1788
|
);
|
|
1400
1789
|
}
|
|
1790
|
+
function parseModelContext(model) {
|
|
1791
|
+
if (!model) return void 0;
|
|
1792
|
+
if (typeof model === "object") {
|
|
1793
|
+
return model.provider || model.modelId ? { provider: model.provider, modelId: model.modelId } : void 0;
|
|
1794
|
+
}
|
|
1795
|
+
const slashIndex = model.indexOf("/");
|
|
1796
|
+
if (slashIndex === -1) {
|
|
1797
|
+
return { modelId: model };
|
|
1798
|
+
}
|
|
1799
|
+
return {
|
|
1800
|
+
provider: model.slice(0, slashIndex),
|
|
1801
|
+
modelId: model.slice(slashIndex + 1)
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function normalizeImageDetail(detail) {
|
|
1805
|
+
if (detail === "low" || detail === "high") return detail;
|
|
1806
|
+
return "auto";
|
|
1807
|
+
}
|
|
1808
|
+
function getObjectValue(value, key) {
|
|
1809
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1810
|
+
return value[key];
|
|
1811
|
+
}
|
|
1812
|
+
function resolveImageDetail(part) {
|
|
1813
|
+
const openAIProviderOptions = getObjectValue(getObjectValue(part, "providerOptions"), "openai");
|
|
1814
|
+
const openAIProviderMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "openai");
|
|
1815
|
+
const mastraMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "mastra");
|
|
1816
|
+
return normalizeImageDetail(
|
|
1817
|
+
getObjectValue(part, "detail") ?? getObjectValue(part, "imageDetail") ?? getObjectValue(openAIProviderOptions, "detail") ?? getObjectValue(openAIProviderOptions, "imageDetail") ?? getObjectValue(openAIProviderMetadata, "detail") ?? getObjectValue(openAIProviderMetadata, "imageDetail") ?? getObjectValue(mastraMetadata, "imageDetail")
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
function normalizeGoogleMediaResolution(value) {
|
|
1821
|
+
return typeof value === "string" && GOOGLE_MEDIA_RESOLUTION_VALUES.has(value) ? value : void 0;
|
|
1822
|
+
}
|
|
1823
|
+
function resolveGoogleMediaResolution(part) {
|
|
1824
|
+
const providerOptions = getObjectValue(getObjectValue(part, "providerOptions"), "google");
|
|
1825
|
+
const providerMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "google");
|
|
1826
|
+
const mastraMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "mastra");
|
|
1827
|
+
return normalizeGoogleMediaResolution(getObjectValue(part, "mediaResolution")) ?? normalizeGoogleMediaResolution(getObjectValue(providerOptions, "mediaResolution")) ?? normalizeGoogleMediaResolution(getObjectValue(providerMetadata, "mediaResolution")) ?? normalizeGoogleMediaResolution(getObjectValue(mastraMetadata, "mediaResolution")) ?? "unspecified";
|
|
1828
|
+
}
|
|
1829
|
+
function getFiniteNumber(value) {
|
|
1830
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1831
|
+
}
|
|
1832
|
+
function isHttpUrlString(value) {
|
|
1833
|
+
return typeof value === "string" && /^https?:\/\//i.test(value);
|
|
1834
|
+
}
|
|
1835
|
+
function isLikelyFilesystemPath(value) {
|
|
1836
|
+
return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("~/") || /^[A-Za-z]:[\\/]/.test(value) || value.includes("\\");
|
|
1837
|
+
}
|
|
1838
|
+
function isLikelyBase64Content(value) {
|
|
1839
|
+
if (value.length < 16 || value.length % 4 !== 0 || /\s/.test(value) || isLikelyFilesystemPath(value)) {
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
return /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
1843
|
+
}
|
|
1844
|
+
function decodeImageBuffer(value) {
|
|
1845
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
1846
|
+
return value;
|
|
1847
|
+
}
|
|
1848
|
+
if (value instanceof Uint8Array) {
|
|
1849
|
+
return Buffer.from(value);
|
|
1850
|
+
}
|
|
1851
|
+
if (value instanceof ArrayBuffer) {
|
|
1852
|
+
return Buffer.from(value);
|
|
1853
|
+
}
|
|
1854
|
+
if (ArrayBuffer.isView(value)) {
|
|
1855
|
+
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
1856
|
+
}
|
|
1857
|
+
if (typeof value !== "string" || isHttpUrlString(value)) {
|
|
1858
|
+
return void 0;
|
|
1859
|
+
}
|
|
1860
|
+
if (value.startsWith("data:")) {
|
|
1861
|
+
const commaIndex = value.indexOf(",");
|
|
1862
|
+
if (commaIndex === -1) return void 0;
|
|
1863
|
+
const header = value.slice(0, commaIndex);
|
|
1864
|
+
const payload = value.slice(commaIndex + 1);
|
|
1865
|
+
if (/;base64/i.test(header)) {
|
|
1866
|
+
return Buffer.from(payload, "base64");
|
|
1867
|
+
}
|
|
1868
|
+
return Buffer.from(decodeURIComponent(payload), "utf8");
|
|
1869
|
+
}
|
|
1870
|
+
if (!isLikelyBase64Content(value)) {
|
|
1871
|
+
return void 0;
|
|
1872
|
+
}
|
|
1873
|
+
return Buffer.from(value, "base64");
|
|
1874
|
+
}
|
|
1875
|
+
function persistImageDimensions(part, dimensions) {
|
|
1876
|
+
const mastraMetadata = ensurePartMastraMetadata(part);
|
|
1877
|
+
mastraMetadata.imageDimensions = dimensions;
|
|
1878
|
+
}
|
|
1879
|
+
function resolveHttpAssetUrl(value) {
|
|
1880
|
+
if (value instanceof URL) {
|
|
1881
|
+
return value.toString();
|
|
1882
|
+
}
|
|
1883
|
+
if (typeof value === "string" && isHttpUrlString(value)) {
|
|
1884
|
+
return value;
|
|
1885
|
+
}
|
|
1886
|
+
return void 0;
|
|
1887
|
+
}
|
|
1888
|
+
async function resolveImageDimensionsAsync(part) {
|
|
1889
|
+
const existing = resolveImageDimensions(part);
|
|
1890
|
+
if (existing.width && existing.height) {
|
|
1891
|
+
return existing;
|
|
1892
|
+
}
|
|
1893
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
1894
|
+
const url = resolveHttpAssetUrl(asset);
|
|
1895
|
+
if (!url) {
|
|
1896
|
+
return existing;
|
|
1897
|
+
}
|
|
1898
|
+
try {
|
|
1899
|
+
const mod = await import('probe-image-size');
|
|
1900
|
+
const probeImageSize = mod.default;
|
|
1901
|
+
const probed = await probeImageSize(url, {
|
|
1902
|
+
open_timeout: REMOTE_IMAGE_PROBE_TIMEOUT_MS,
|
|
1903
|
+
response_timeout: REMOTE_IMAGE_PROBE_TIMEOUT_MS,
|
|
1904
|
+
read_timeout: REMOTE_IMAGE_PROBE_TIMEOUT_MS,
|
|
1905
|
+
follow_max: 2
|
|
1906
|
+
});
|
|
1907
|
+
const width = existing.width ?? getFiniteNumber(probed.width);
|
|
1908
|
+
const height = existing.height ?? getFiniteNumber(probed.height);
|
|
1909
|
+
if (!width || !height) {
|
|
1910
|
+
return existing;
|
|
1911
|
+
}
|
|
1912
|
+
const resolved = { width, height };
|
|
1913
|
+
persistImageDimensions(part, resolved);
|
|
1914
|
+
return resolved;
|
|
1915
|
+
} catch {
|
|
1916
|
+
return existing;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
function resolveImageDimensions(part) {
|
|
1920
|
+
const mastraMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "mastra");
|
|
1921
|
+
const dimensions = getObjectValue(mastraMetadata, "imageDimensions");
|
|
1922
|
+
const width = getFiniteNumber(getObjectValue(part, "width")) ?? getFiniteNumber(getObjectValue(part, "imageWidth")) ?? getFiniteNumber(getObjectValue(dimensions, "width"));
|
|
1923
|
+
const height = getFiniteNumber(getObjectValue(part, "height")) ?? getFiniteNumber(getObjectValue(part, "imageHeight")) ?? getFiniteNumber(getObjectValue(dimensions, "height"));
|
|
1924
|
+
if (width && height) {
|
|
1925
|
+
return { width, height };
|
|
1926
|
+
}
|
|
1927
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
1928
|
+
const buffer = decodeImageBuffer(asset);
|
|
1929
|
+
if (!buffer) {
|
|
1930
|
+
return { width, height };
|
|
1931
|
+
}
|
|
1932
|
+
try {
|
|
1933
|
+
const measured = imageSize(buffer);
|
|
1934
|
+
const measuredWidth = getFiniteNumber(measured.width);
|
|
1935
|
+
const measuredHeight = getFiniteNumber(measured.height);
|
|
1936
|
+
if (!measuredWidth || !measuredHeight) {
|
|
1937
|
+
return { width, height };
|
|
1938
|
+
}
|
|
1939
|
+
const resolved = {
|
|
1940
|
+
width: width ?? measuredWidth,
|
|
1941
|
+
height: height ?? measuredHeight
|
|
1942
|
+
};
|
|
1943
|
+
persistImageDimensions(part, resolved);
|
|
1944
|
+
return resolved;
|
|
1945
|
+
} catch {
|
|
1946
|
+
return { width, height };
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
function getBase64Size(base64) {
|
|
1950
|
+
const sanitized = base64.replace(/\s+/g, "");
|
|
1951
|
+
const padding = sanitized.endsWith("==") ? 2 : sanitized.endsWith("=") ? 1 : 0;
|
|
1952
|
+
return Math.max(0, Math.floor(sanitized.length * 3 / 4) - padding);
|
|
1953
|
+
}
|
|
1954
|
+
function resolveImageSourceStats(image) {
|
|
1955
|
+
if (image instanceof URL) {
|
|
1956
|
+
return { source: "url" };
|
|
1957
|
+
}
|
|
1958
|
+
if (typeof image === "string") {
|
|
1959
|
+
if (isHttpUrlString(image)) {
|
|
1960
|
+
return { source: "url" };
|
|
1961
|
+
}
|
|
1962
|
+
if (image.startsWith("data:")) {
|
|
1963
|
+
const commaIndex = image.indexOf(",");
|
|
1964
|
+
const encoded = commaIndex === -1 ? "" : image.slice(commaIndex + 1);
|
|
1965
|
+
return {
|
|
1966
|
+
source: "data-uri",
|
|
1967
|
+
sizeBytes: getBase64Size(encoded)
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
return {
|
|
1971
|
+
source: "binary",
|
|
1972
|
+
sizeBytes: getBase64Size(image)
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(image)) {
|
|
1976
|
+
return { source: "binary", sizeBytes: image.length };
|
|
1977
|
+
}
|
|
1978
|
+
if (image instanceof Uint8Array) {
|
|
1979
|
+
return { source: "binary", sizeBytes: image.byteLength };
|
|
1980
|
+
}
|
|
1981
|
+
if (image instanceof ArrayBuffer) {
|
|
1982
|
+
return { source: "binary", sizeBytes: image.byteLength };
|
|
1983
|
+
}
|
|
1984
|
+
if (ArrayBuffer.isView(image)) {
|
|
1985
|
+
return { source: "binary", sizeBytes: image.byteLength };
|
|
1986
|
+
}
|
|
1987
|
+
return { source: "binary" };
|
|
1988
|
+
}
|
|
1989
|
+
function getPathnameExtension(value) {
|
|
1990
|
+
const normalized = value.split("#", 1)[0]?.split("?", 1)[0] ?? value;
|
|
1991
|
+
const match = normalized.match(/\.([a-z0-9]+)$/i);
|
|
1992
|
+
return match?.[1]?.toLowerCase();
|
|
1993
|
+
}
|
|
1994
|
+
function hasImageFilenameExtension(filename) {
|
|
1995
|
+
return typeof filename === "string" && IMAGE_FILE_EXTENSIONS.has(getPathnameExtension(filename) ?? "");
|
|
1996
|
+
}
|
|
1997
|
+
function isImageLikeFilePart(part) {
|
|
1998
|
+
if (getObjectValue(part, "type") !== "file") {
|
|
1999
|
+
return false;
|
|
2000
|
+
}
|
|
2001
|
+
const mimeType = getObjectValue(part, "mimeType");
|
|
2002
|
+
if (typeof mimeType === "string" && mimeType.toLowerCase().startsWith("image/")) {
|
|
2003
|
+
return true;
|
|
2004
|
+
}
|
|
2005
|
+
const data = getObjectValue(part, "data");
|
|
2006
|
+
if (typeof data === "string" && data.startsWith("data:image/")) {
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
if (data instanceof URL && hasImageFilenameExtension(data.pathname)) {
|
|
2010
|
+
return true;
|
|
2011
|
+
}
|
|
2012
|
+
if (isHttpUrlString(data)) {
|
|
2013
|
+
try {
|
|
2014
|
+
const url = new URL(data);
|
|
2015
|
+
if (hasImageFilenameExtension(url.pathname)) {
|
|
2016
|
+
return true;
|
|
2017
|
+
}
|
|
2018
|
+
} catch {
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return hasImageFilenameExtension(getObjectValue(part, "filename"));
|
|
2022
|
+
}
|
|
2023
|
+
function resolveProviderId(modelContext) {
|
|
2024
|
+
return modelContext?.provider?.toLowerCase();
|
|
2025
|
+
}
|
|
2026
|
+
function resolveModelId(modelContext) {
|
|
2027
|
+
return modelContext?.modelId?.toLowerCase() ?? "";
|
|
2028
|
+
}
|
|
2029
|
+
function resolveOpenAIImageEstimatorConfig(modelContext) {
|
|
2030
|
+
const modelId = resolveModelId(modelContext);
|
|
2031
|
+
if (modelId.startsWith("gpt-5") || modelId === "gpt-5-chat-latest") {
|
|
2032
|
+
return { baseTokens: 70, tileTokens: 140, fallbackTiles: 4 };
|
|
2033
|
+
}
|
|
2034
|
+
if (modelId.startsWith("gpt-4o-mini")) {
|
|
2035
|
+
return { baseTokens: 2833, tileTokens: 5667, fallbackTiles: 1 };
|
|
2036
|
+
}
|
|
2037
|
+
if (modelId.startsWith("o1") || modelId.startsWith("o3")) {
|
|
2038
|
+
return { baseTokens: 75, tileTokens: 150, fallbackTiles: 4 };
|
|
2039
|
+
}
|
|
2040
|
+
if (modelId.includes("computer-use")) {
|
|
2041
|
+
return { baseTokens: 65, tileTokens: 129, fallbackTiles: 4 };
|
|
2042
|
+
}
|
|
2043
|
+
return DEFAULT_IMAGE_ESTIMATOR;
|
|
2044
|
+
}
|
|
2045
|
+
function isGoogleGemini3Model(modelContext) {
|
|
2046
|
+
return resolveProviderId(modelContext) === "google" && resolveModelId(modelContext).startsWith("gemini-3");
|
|
2047
|
+
}
|
|
2048
|
+
function scaleDimensionsForOpenAIHighDetail(width, height) {
|
|
2049
|
+
let scaledWidth = width;
|
|
2050
|
+
let scaledHeight = height;
|
|
2051
|
+
const largestSide = Math.max(scaledWidth, scaledHeight);
|
|
2052
|
+
if (largestSide > 2048) {
|
|
2053
|
+
const ratio = 2048 / largestSide;
|
|
2054
|
+
scaledWidth *= ratio;
|
|
2055
|
+
scaledHeight *= ratio;
|
|
2056
|
+
}
|
|
2057
|
+
const shortestSide = Math.min(scaledWidth, scaledHeight);
|
|
2058
|
+
if (shortestSide > 768) {
|
|
2059
|
+
const ratio = 768 / shortestSide;
|
|
2060
|
+
scaledWidth *= ratio;
|
|
2061
|
+
scaledHeight *= ratio;
|
|
2062
|
+
}
|
|
2063
|
+
return {
|
|
2064
|
+
width: Math.max(1, Math.round(scaledWidth)),
|
|
2065
|
+
height: Math.max(1, Math.round(scaledHeight))
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
function scaleDimensionsForAnthropic(width, height) {
|
|
2069
|
+
const largestSide = Math.max(width, height);
|
|
2070
|
+
if (largestSide <= ANTHROPIC_IMAGE_MAX_LONG_EDGE) {
|
|
2071
|
+
return { width, height };
|
|
2072
|
+
}
|
|
2073
|
+
const ratio = ANTHROPIC_IMAGE_MAX_LONG_EDGE / largestSide;
|
|
2074
|
+
return {
|
|
2075
|
+
width: Math.max(1, Math.round(width * ratio)),
|
|
2076
|
+
height: Math.max(1, Math.round(height * ratio))
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
function estimateOpenAIHighDetailTiles(dimensions, sourceStats, estimator) {
|
|
2080
|
+
if (dimensions.width && dimensions.height) {
|
|
2081
|
+
const scaled = scaleDimensionsForOpenAIHighDetail(dimensions.width, dimensions.height);
|
|
2082
|
+
return Math.max(1, Math.ceil(scaled.width / 512) * Math.ceil(scaled.height / 512));
|
|
2083
|
+
}
|
|
2084
|
+
if (sourceStats.sizeBytes !== void 0) {
|
|
2085
|
+
if (sourceStats.sizeBytes <= 512 * 1024) return 1;
|
|
2086
|
+
if (sourceStats.sizeBytes <= 2 * 1024 * 1024) return 4;
|
|
2087
|
+
if (sourceStats.sizeBytes <= 4 * 1024 * 1024) return 6;
|
|
2088
|
+
return 8;
|
|
2089
|
+
}
|
|
2090
|
+
return estimator.fallbackTiles;
|
|
2091
|
+
}
|
|
2092
|
+
function resolveEffectiveOpenAIImageDetail(detail, dimensions, sourceStats) {
|
|
2093
|
+
if (detail === "low" || detail === "high") return detail;
|
|
2094
|
+
if (dimensions.width && dimensions.height) {
|
|
2095
|
+
return Math.max(dimensions.width, dimensions.height) > 768 ? "high" : "low";
|
|
2096
|
+
}
|
|
2097
|
+
if (sourceStats.sizeBytes !== void 0) {
|
|
2098
|
+
return sourceStats.sizeBytes > 1024 * 1024 ? "high" : "low";
|
|
2099
|
+
}
|
|
2100
|
+
return "low";
|
|
2101
|
+
}
|
|
2102
|
+
function estimateLegacyGoogleImageTiles(dimensions) {
|
|
2103
|
+
if (!dimensions.width || !dimensions.height) return 1;
|
|
2104
|
+
return Math.max(1, Math.ceil(dimensions.width / 768) * Math.ceil(dimensions.height / 768));
|
|
2105
|
+
}
|
|
2106
|
+
function estimateAnthropicImageTokens(dimensions, sourceStats) {
|
|
2107
|
+
if (dimensions.width && dimensions.height) {
|
|
2108
|
+
const scaled = scaleDimensionsForAnthropic(dimensions.width, dimensions.height);
|
|
2109
|
+
return Math.max(1, Math.ceil(scaled.width * scaled.height * ANTHROPIC_IMAGE_TOKENS_PER_PIXEL));
|
|
2110
|
+
}
|
|
2111
|
+
if (sourceStats.sizeBytes !== void 0) {
|
|
2112
|
+
if (sourceStats.sizeBytes <= 512 * 1024) return 341;
|
|
2113
|
+
if (sourceStats.sizeBytes <= 2 * 1024 * 1024) return 1366;
|
|
2114
|
+
if (sourceStats.sizeBytes <= 4 * 1024 * 1024) return 2048;
|
|
2115
|
+
return 2731;
|
|
2116
|
+
}
|
|
2117
|
+
return 1600;
|
|
2118
|
+
}
|
|
2119
|
+
function estimateGoogleImageTokens(modelContext, part, dimensions) {
|
|
2120
|
+
if (isGoogleGemini3Model(modelContext)) {
|
|
2121
|
+
const mediaResolution = resolveGoogleMediaResolution(part);
|
|
2122
|
+
return {
|
|
2123
|
+
tokens: GOOGLE_GEMINI_3_IMAGE_TOKENS_BY_RESOLUTION[mediaResolution],
|
|
2124
|
+
mediaResolution
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
return {
|
|
2128
|
+
tokens: estimateLegacyGoogleImageTiles(dimensions) * GOOGLE_LEGACY_IMAGE_TOKENS_PER_TILE,
|
|
2129
|
+
mediaResolution: "unspecified"
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
function getProviderApiKey(provider) {
|
|
2133
|
+
for (const envVar of PROVIDER_API_KEY_ENV_VARS[provider] ?? []) {
|
|
2134
|
+
const value = process.env[envVar];
|
|
2135
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
2136
|
+
return value.trim();
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return void 0;
|
|
2140
|
+
}
|
|
2141
|
+
function getAttachmentFilename(part) {
|
|
2142
|
+
const explicitFilename = getObjectValue(part, "filename");
|
|
2143
|
+
if (typeof explicitFilename === "string" && explicitFilename.trim().length > 0) {
|
|
2144
|
+
return explicitFilename.trim();
|
|
2145
|
+
}
|
|
2146
|
+
return getFilenameFromAttachmentData(getObjectValue(part, "data") ?? getObjectValue(part, "image"));
|
|
2147
|
+
}
|
|
2148
|
+
function getAttachmentMimeType(part, fallback) {
|
|
2149
|
+
const mimeType = getObjectValue(part, "mimeType");
|
|
2150
|
+
if (typeof mimeType === "string" && mimeType.trim().length > 0) {
|
|
2151
|
+
return mimeType.trim();
|
|
2152
|
+
}
|
|
2153
|
+
const asset = getObjectValue(part, "data") ?? getObjectValue(part, "image");
|
|
2154
|
+
if (typeof asset === "string" && asset.startsWith("data:")) {
|
|
2155
|
+
const semicolonIndex = asset.indexOf(";");
|
|
2156
|
+
const commaIndex = asset.indexOf(",");
|
|
2157
|
+
const endIndex = semicolonIndex === -1 ? commaIndex : Math.min(semicolonIndex, commaIndex);
|
|
2158
|
+
if (endIndex > 5) {
|
|
2159
|
+
return asset.slice(5, endIndex);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
return fallback;
|
|
2163
|
+
}
|
|
2164
|
+
function getAttachmentUrl(asset) {
|
|
2165
|
+
if (asset instanceof URL) {
|
|
2166
|
+
return asset.toString();
|
|
2167
|
+
}
|
|
2168
|
+
if (typeof asset === "string" && /^(https?:\/\/|data:)/i.test(asset)) {
|
|
2169
|
+
return asset;
|
|
2170
|
+
}
|
|
2171
|
+
return void 0;
|
|
2172
|
+
}
|
|
2173
|
+
function getAttachmentFingerprint(asset) {
|
|
2174
|
+
const url = getAttachmentUrl(asset);
|
|
2175
|
+
if (url) {
|
|
2176
|
+
return { url };
|
|
2177
|
+
}
|
|
2178
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2179
|
+
if (base64) {
|
|
2180
|
+
return { contentHash: createHash("sha1").update(base64).digest("hex") };
|
|
2181
|
+
}
|
|
2182
|
+
return {};
|
|
2183
|
+
}
|
|
2184
|
+
function encodeAttachmentBase64(asset) {
|
|
2185
|
+
if (typeof asset === "string") {
|
|
2186
|
+
if (asset.startsWith("data:")) {
|
|
2187
|
+
const commaIndex = asset.indexOf(",");
|
|
2188
|
+
return commaIndex === -1 ? void 0 : asset.slice(commaIndex + 1);
|
|
2189
|
+
}
|
|
2190
|
+
if (/^https?:\/\//i.test(asset)) {
|
|
2191
|
+
return void 0;
|
|
2192
|
+
}
|
|
2193
|
+
return asset;
|
|
2194
|
+
}
|
|
2195
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(asset)) {
|
|
2196
|
+
return asset.toString("base64");
|
|
2197
|
+
}
|
|
2198
|
+
if (asset instanceof Uint8Array) {
|
|
2199
|
+
return Buffer.from(asset).toString("base64");
|
|
2200
|
+
}
|
|
2201
|
+
if (asset instanceof ArrayBuffer) {
|
|
2202
|
+
return Buffer.from(asset).toString("base64");
|
|
2203
|
+
}
|
|
2204
|
+
if (ArrayBuffer.isView(asset)) {
|
|
2205
|
+
return Buffer.from(asset.buffer, asset.byteOffset, asset.byteLength).toString("base64");
|
|
2206
|
+
}
|
|
2207
|
+
return void 0;
|
|
2208
|
+
}
|
|
2209
|
+
function createTimeoutSignal(timeoutMs) {
|
|
2210
|
+
const controller = new AbortController();
|
|
2211
|
+
const timeout = setTimeout(
|
|
2212
|
+
() => controller.abort(new Error(`Attachment token counting timed out after ${timeoutMs}ms`)),
|
|
2213
|
+
timeoutMs
|
|
2214
|
+
);
|
|
2215
|
+
const cleanup = () => clearTimeout(timeout);
|
|
2216
|
+
controller.signal.addEventListener("abort", cleanup, { once: true });
|
|
2217
|
+
return { signal: controller.signal, cleanup };
|
|
2218
|
+
}
|
|
2219
|
+
function getNumericResponseField(value, paths) {
|
|
2220
|
+
for (const path of paths) {
|
|
2221
|
+
let current = value;
|
|
2222
|
+
for (const segment of path) {
|
|
2223
|
+
current = getObjectValue(current, segment);
|
|
2224
|
+
if (current === void 0) break;
|
|
2225
|
+
}
|
|
2226
|
+
if (typeof current === "number" && Number.isFinite(current)) {
|
|
2227
|
+
return current;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
return void 0;
|
|
2231
|
+
}
|
|
2232
|
+
function toOpenAIInputPart(part) {
|
|
2233
|
+
if (getObjectValue(part, "type") === "image" || isImageLikeFilePart(part)) {
|
|
2234
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
2235
|
+
const imageUrl = getAttachmentUrl(asset);
|
|
2236
|
+
if (imageUrl) {
|
|
2237
|
+
return { type: "input_image", image_url: imageUrl, detail: resolveImageDetail(part) };
|
|
2238
|
+
}
|
|
2239
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2240
|
+
if (!base64) return void 0;
|
|
2241
|
+
return {
|
|
2242
|
+
type: "input_image",
|
|
2243
|
+
image_url: `data:${getAttachmentMimeType(part, "image/png")};base64,${base64}`,
|
|
2244
|
+
detail: resolveImageDetail(part)
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
if (getObjectValue(part, "type") === "file") {
|
|
2248
|
+
const asset = getObjectValue(part, "data");
|
|
2249
|
+
const fileUrl = getAttachmentUrl(asset);
|
|
2250
|
+
return fileUrl ? {
|
|
2251
|
+
type: "input_file",
|
|
2252
|
+
file_url: fileUrl,
|
|
2253
|
+
filename: getAttachmentFilename(part) ?? "attachment"
|
|
2254
|
+
} : (() => {
|
|
2255
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2256
|
+
if (!base64) return void 0;
|
|
2257
|
+
return {
|
|
2258
|
+
type: "input_file",
|
|
2259
|
+
file_data: `data:${getAttachmentMimeType(part, "application/octet-stream")};base64,${base64}`,
|
|
2260
|
+
filename: getAttachmentFilename(part) ?? "attachment"
|
|
2261
|
+
};
|
|
2262
|
+
})();
|
|
2263
|
+
}
|
|
2264
|
+
return void 0;
|
|
2265
|
+
}
|
|
2266
|
+
function toAnthropicContentPart(part) {
|
|
2267
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
2268
|
+
const url = getAttachmentUrl(asset);
|
|
2269
|
+
if (getObjectValue(part, "type") === "image" || isImageLikeFilePart(part)) {
|
|
2270
|
+
return url && /^https?:\/\//i.test(url) ? { type: "image", source: { type: "url", url } } : (() => {
|
|
2271
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2272
|
+
if (!base64) return void 0;
|
|
2273
|
+
return {
|
|
2274
|
+
type: "image",
|
|
2275
|
+
source: { type: "base64", media_type: getAttachmentMimeType(part, "image/png"), data: base64 }
|
|
2276
|
+
};
|
|
2277
|
+
})();
|
|
2278
|
+
}
|
|
2279
|
+
if (getObjectValue(part, "type") === "file") {
|
|
2280
|
+
return url && /^https?:\/\//i.test(url) ? { type: "document", source: { type: "url", url } } : (() => {
|
|
2281
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2282
|
+
if (!base64) return void 0;
|
|
2283
|
+
return {
|
|
2284
|
+
type: "document",
|
|
2285
|
+
source: { type: "base64", media_type: getAttachmentMimeType(part, "application/pdf"), data: base64 }
|
|
2286
|
+
};
|
|
2287
|
+
})();
|
|
2288
|
+
}
|
|
2289
|
+
return void 0;
|
|
2290
|
+
}
|
|
2291
|
+
function toGooglePart(part) {
|
|
2292
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
2293
|
+
const url = getAttachmentUrl(asset);
|
|
2294
|
+
const mimeType = getAttachmentMimeType(
|
|
2295
|
+
part,
|
|
2296
|
+
getObjectValue(part, "type") === "file" && !isImageLikeFilePart(part) ? "application/pdf" : "image/png"
|
|
2297
|
+
);
|
|
2298
|
+
if (url && !url.startsWith("data:")) {
|
|
2299
|
+
return { fileData: { mimeType, fileUri: url } };
|
|
2300
|
+
}
|
|
2301
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2302
|
+
if (!base64) return void 0;
|
|
2303
|
+
return { inlineData: { mimeType, data: base64 } };
|
|
2304
|
+
}
|
|
2305
|
+
async function fetchOpenAIAttachmentTokenEstimate(modelId, part) {
|
|
2306
|
+
const apiKey = getProviderApiKey("openai");
|
|
2307
|
+
const inputPart = toOpenAIInputPart(part);
|
|
2308
|
+
if (!apiKey || !inputPart) return void 0;
|
|
2309
|
+
const { signal, cleanup } = createTimeoutSignal(ATTACHMENT_COUNT_TIMEOUT_MS);
|
|
2310
|
+
try {
|
|
2311
|
+
const response = await fetch("https://api.openai.com/v1/responses/input_tokens", {
|
|
2312
|
+
method: "POST",
|
|
2313
|
+
headers: {
|
|
2314
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2315
|
+
"Content-Type": "application/json"
|
|
2316
|
+
},
|
|
2317
|
+
body: JSON.stringify({
|
|
2318
|
+
model: modelId,
|
|
2319
|
+
input: [{ type: "message", role: "user", content: [inputPart] }]
|
|
2320
|
+
}),
|
|
2321
|
+
signal
|
|
2322
|
+
});
|
|
2323
|
+
if (!response.ok) return void 0;
|
|
2324
|
+
const body = await response.json();
|
|
2325
|
+
return getNumericResponseField(body, [
|
|
2326
|
+
["input_tokens"],
|
|
2327
|
+
["total_tokens"],
|
|
2328
|
+
["usage", "input_tokens"],
|
|
2329
|
+
["usage", "total_tokens"]
|
|
2330
|
+
]);
|
|
2331
|
+
} finally {
|
|
2332
|
+
cleanup();
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
async function fetchAnthropicAttachmentTokenEstimate(modelId, part) {
|
|
2336
|
+
const apiKey = getProviderApiKey("anthropic");
|
|
2337
|
+
const contentPart = toAnthropicContentPart(part);
|
|
2338
|
+
if (!apiKey || !contentPart) return void 0;
|
|
2339
|
+
const { signal, cleanup } = createTimeoutSignal(ATTACHMENT_COUNT_TIMEOUT_MS);
|
|
2340
|
+
try {
|
|
2341
|
+
const response = await fetch("https://api.anthropic.com/v1/messages/count_tokens", {
|
|
2342
|
+
method: "POST",
|
|
2343
|
+
headers: {
|
|
2344
|
+
"x-api-key": apiKey,
|
|
2345
|
+
"anthropic-version": "2023-06-01",
|
|
2346
|
+
"Content-Type": "application/json"
|
|
2347
|
+
},
|
|
2348
|
+
body: JSON.stringify({
|
|
2349
|
+
model: modelId,
|
|
2350
|
+
messages: [{ role: "user", content: [contentPart] }]
|
|
2351
|
+
}),
|
|
2352
|
+
signal
|
|
2353
|
+
});
|
|
2354
|
+
if (!response.ok) return void 0;
|
|
2355
|
+
const body = await response.json();
|
|
2356
|
+
return getNumericResponseField(body, [["input_tokens"]]);
|
|
2357
|
+
} finally {
|
|
2358
|
+
cleanup();
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
async function fetchGoogleAttachmentTokenEstimate(modelId, part) {
|
|
2362
|
+
const apiKey = getProviderApiKey("google");
|
|
2363
|
+
const googlePart = toGooglePart(part);
|
|
2364
|
+
if (!apiKey || !googlePart) return void 0;
|
|
2365
|
+
const { signal, cleanup } = createTimeoutSignal(ATTACHMENT_COUNT_TIMEOUT_MS);
|
|
2366
|
+
try {
|
|
2367
|
+
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${modelId}:countTokens`, {
|
|
2368
|
+
method: "POST",
|
|
2369
|
+
headers: {
|
|
2370
|
+
"x-goog-api-key": apiKey,
|
|
2371
|
+
"Content-Type": "application/json"
|
|
2372
|
+
},
|
|
2373
|
+
body: JSON.stringify({
|
|
2374
|
+
contents: [{ role: "user", parts: [googlePart] }]
|
|
2375
|
+
}),
|
|
2376
|
+
signal
|
|
2377
|
+
});
|
|
2378
|
+
if (!response.ok) return void 0;
|
|
2379
|
+
const body = await response.json();
|
|
2380
|
+
return getNumericResponseField(body, [["totalTokens"], ["total_tokens"]]);
|
|
2381
|
+
} finally {
|
|
2382
|
+
cleanup();
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
1401
2385
|
var TokenCounter = class _TokenCounter {
|
|
1402
2386
|
encoder;
|
|
1403
2387
|
cacheSource;
|
|
2388
|
+
defaultModelContext;
|
|
2389
|
+
modelContextStorage = new AsyncLocalStorage();
|
|
2390
|
+
inFlightAttachmentCounts = /* @__PURE__ */ new Map();
|
|
1404
2391
|
// Per-message overhead: accounts for role tokens, message framing, and separators.
|
|
1405
2392
|
// Empirically derived from OpenAI's token counting guide (3 tokens per message base +
|
|
1406
2393
|
// fractional overhead from name/role encoding). 3.8 is a practical average across models.
|
|
1407
2394
|
static TOKENS_PER_MESSAGE = 3.8;
|
|
1408
2395
|
// Conversation-level overhead: system prompt framing, reply priming tokens, etc.
|
|
1409
2396
|
static TOKENS_PER_CONVERSATION = 24;
|
|
1410
|
-
constructor(encoding) {
|
|
2397
|
+
constructor(encoding, options) {
|
|
1411
2398
|
this.encoder = encoding ? new Tiktoken(encoding) : getDefaultEncoder();
|
|
1412
2399
|
this.cacheSource = `v${TOKEN_ESTIMATE_CACHE_VERSION}:${resolveEncodingId(encoding)}`;
|
|
2400
|
+
this.defaultModelContext = parseModelContext(options?.model);
|
|
2401
|
+
}
|
|
2402
|
+
runWithModelContext(model, fn) {
|
|
2403
|
+
return this.modelContextStorage.run(parseModelContext(model), fn);
|
|
2404
|
+
}
|
|
2405
|
+
getModelContext() {
|
|
2406
|
+
return this.modelContextStorage.getStore() ?? this.defaultModelContext;
|
|
1413
2407
|
}
|
|
1414
2408
|
/**
|
|
1415
2409
|
* Count tokens in a plain string
|
|
@@ -1433,6 +2427,20 @@ var TokenCounter = class _TokenCounter {
|
|
|
1433
2427
|
});
|
|
1434
2428
|
return tokens;
|
|
1435
2429
|
}
|
|
2430
|
+
readOrPersistFixedPartEstimate(part, kind, payload, tokens) {
|
|
2431
|
+
const key = buildEstimateKey(kind, payload);
|
|
2432
|
+
const cached = getPartCacheEntry(part, key);
|
|
2433
|
+
if (isValidCacheEntry(cached, key, this.cacheSource)) {
|
|
2434
|
+
return cached.tokens;
|
|
2435
|
+
}
|
|
2436
|
+
setPartCacheEntry(part, key, {
|
|
2437
|
+
v: TOKEN_ESTIMATE_CACHE_VERSION,
|
|
2438
|
+
source: this.cacheSource,
|
|
2439
|
+
key,
|
|
2440
|
+
tokens
|
|
2441
|
+
});
|
|
2442
|
+
return tokens;
|
|
2443
|
+
}
|
|
1436
2444
|
readOrPersistMessageEstimate(message, kind, payload) {
|
|
1437
2445
|
const key = buildEstimateKey(kind, payload);
|
|
1438
2446
|
const cached = getMessageCacheEntry(message, key);
|
|
@@ -1456,15 +2464,315 @@ var TokenCounter = class _TokenCounter {
|
|
|
1456
2464
|
usingStoredModelOutput: true
|
|
1457
2465
|
};
|
|
1458
2466
|
}
|
|
1459
|
-
return {
|
|
1460
|
-
value: invocationResult,
|
|
1461
|
-
usingStoredModelOutput: false
|
|
1462
|
-
};
|
|
2467
|
+
return {
|
|
2468
|
+
value: invocationResult,
|
|
2469
|
+
usingStoredModelOutput: false
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
estimateImageAssetTokens(part, asset, kind) {
|
|
2473
|
+
const modelContext = this.getModelContext();
|
|
2474
|
+
const provider = resolveProviderId(modelContext);
|
|
2475
|
+
const modelId = modelContext?.modelId ?? null;
|
|
2476
|
+
const detail = resolveImageDetail(part);
|
|
2477
|
+
const dimensions = resolveImageDimensions(part);
|
|
2478
|
+
const sourceStats = resolveImageSourceStats(asset);
|
|
2479
|
+
if (provider === "google") {
|
|
2480
|
+
const googleEstimate = estimateGoogleImageTokens(modelContext, part, dimensions);
|
|
2481
|
+
return {
|
|
2482
|
+
tokens: googleEstimate.tokens,
|
|
2483
|
+
cachePayload: JSON.stringify({
|
|
2484
|
+
kind,
|
|
2485
|
+
provider,
|
|
2486
|
+
modelId,
|
|
2487
|
+
estimator: isGoogleGemini3Model(modelContext) ? "google-gemini-3" : "google-legacy",
|
|
2488
|
+
mediaResolution: googleEstimate.mediaResolution,
|
|
2489
|
+
width: dimensions.width ?? null,
|
|
2490
|
+
height: dimensions.height ?? null,
|
|
2491
|
+
source: sourceStats.source,
|
|
2492
|
+
sizeBytes: sourceStats.sizeBytes ?? null,
|
|
2493
|
+
mimeType: getObjectValue(part, "mimeType") ?? null,
|
|
2494
|
+
filename: getObjectValue(part, "filename") ?? null
|
|
2495
|
+
})
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
if (provider === "anthropic") {
|
|
2499
|
+
return {
|
|
2500
|
+
tokens: estimateAnthropicImageTokens(dimensions, sourceStats),
|
|
2501
|
+
cachePayload: JSON.stringify({
|
|
2502
|
+
kind,
|
|
2503
|
+
provider,
|
|
2504
|
+
modelId,
|
|
2505
|
+
estimator: "anthropic",
|
|
2506
|
+
width: dimensions.width ?? null,
|
|
2507
|
+
height: dimensions.height ?? null,
|
|
2508
|
+
source: sourceStats.source,
|
|
2509
|
+
sizeBytes: sourceStats.sizeBytes ?? null,
|
|
2510
|
+
mimeType: getObjectValue(part, "mimeType") ?? null,
|
|
2511
|
+
filename: getObjectValue(part, "filename") ?? null
|
|
2512
|
+
})
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
const estimator = resolveOpenAIImageEstimatorConfig(modelContext);
|
|
2516
|
+
const effectiveDetail = resolveEffectiveOpenAIImageDetail(detail, dimensions, sourceStats);
|
|
2517
|
+
const tiles = effectiveDetail === "high" ? estimateOpenAIHighDetailTiles(dimensions, sourceStats, estimator) : 0;
|
|
2518
|
+
const tokens = estimator.baseTokens + tiles * estimator.tileTokens;
|
|
2519
|
+
return {
|
|
2520
|
+
tokens,
|
|
2521
|
+
cachePayload: JSON.stringify({
|
|
2522
|
+
kind,
|
|
2523
|
+
provider,
|
|
2524
|
+
modelId,
|
|
2525
|
+
estimator: provider === "openai" ? "openai" : "fallback",
|
|
2526
|
+
detail,
|
|
2527
|
+
effectiveDetail,
|
|
2528
|
+
width: dimensions.width ?? null,
|
|
2529
|
+
height: dimensions.height ?? null,
|
|
2530
|
+
source: sourceStats.source,
|
|
2531
|
+
sizeBytes: sourceStats.sizeBytes ?? null,
|
|
2532
|
+
mimeType: getObjectValue(part, "mimeType") ?? null,
|
|
2533
|
+
filename: getObjectValue(part, "filename") ?? null
|
|
2534
|
+
})
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
estimateImageTokens(part) {
|
|
2538
|
+
return this.estimateImageAssetTokens(part, part.image, "image");
|
|
2539
|
+
}
|
|
2540
|
+
estimateImageLikeFileTokens(part) {
|
|
2541
|
+
return this.estimateImageAssetTokens(part, part.data, "file");
|
|
2542
|
+
}
|
|
2543
|
+
countAttachmentPartSync(part) {
|
|
2544
|
+
if (part.type === "image") {
|
|
2545
|
+
const estimate = this.estimateImageTokens(part);
|
|
2546
|
+
return this.readOrPersistFixedPartEstimate(part, "image", estimate.cachePayload, estimate.tokens);
|
|
2547
|
+
}
|
|
2548
|
+
if (part.type === "file" && isImageLikeFilePart(part)) {
|
|
2549
|
+
const estimate = this.estimateImageLikeFileTokens(part);
|
|
2550
|
+
return this.readOrPersistFixedPartEstimate(part, "image-like-file", estimate.cachePayload, estimate.tokens);
|
|
2551
|
+
}
|
|
2552
|
+
if (part.type === "file") {
|
|
2553
|
+
return this.readOrPersistPartEstimate(part, "file-descriptor", serializeNonImageFilePartForTokenCounting(part));
|
|
2554
|
+
}
|
|
2555
|
+
return void 0;
|
|
2556
|
+
}
|
|
2557
|
+
buildRemoteAttachmentCachePayload(part) {
|
|
2558
|
+
const isImageAttachment = part.type === "image" || part.type === "file" && isImageLikeFilePart(part);
|
|
2559
|
+
const isNonImageFileAttachment = part.type === "file" && !isImageAttachment;
|
|
2560
|
+
if (!isImageAttachment && !isNonImageFileAttachment) {
|
|
2561
|
+
return void 0;
|
|
2562
|
+
}
|
|
2563
|
+
const modelContext = this.getModelContext();
|
|
2564
|
+
const provider = resolveProviderId(modelContext);
|
|
2565
|
+
const modelId = modelContext?.modelId ?? null;
|
|
2566
|
+
if (!provider || !modelId || !["openai", "google", "anthropic"].includes(provider)) {
|
|
2567
|
+
return void 0;
|
|
2568
|
+
}
|
|
2569
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
2570
|
+
const sourceStats = resolveImageSourceStats(asset);
|
|
2571
|
+
const fingerprint = getAttachmentFingerprint(asset);
|
|
2572
|
+
return JSON.stringify({
|
|
2573
|
+
strategy: "provider-endpoint",
|
|
2574
|
+
provider,
|
|
2575
|
+
modelId,
|
|
2576
|
+
type: getObjectValue(part, "type") ?? null,
|
|
2577
|
+
detail: isImageAttachment ? resolveImageDetail(part) : null,
|
|
2578
|
+
mediaResolution: provider === "google" && isImageAttachment ? resolveGoogleMediaResolution(part) : null,
|
|
2579
|
+
mimeType: getAttachmentMimeType(part, isNonImageFileAttachment ? "application/pdf" : "image/png"),
|
|
2580
|
+
filename: getAttachmentFilename(part) ?? null,
|
|
2581
|
+
source: sourceStats.source,
|
|
2582
|
+
sizeBytes: sourceStats.sizeBytes ?? null,
|
|
2583
|
+
assetUrl: fingerprint.url ?? null,
|
|
2584
|
+
assetHash: fingerprint.contentHash ?? null
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
async fetchProviderAttachmentTokenEstimate(part) {
|
|
2588
|
+
const modelContext = this.getModelContext();
|
|
2589
|
+
const provider = resolveProviderId(modelContext);
|
|
2590
|
+
const modelId = modelContext?.modelId;
|
|
2591
|
+
if (!provider || !modelId) return void 0;
|
|
2592
|
+
try {
|
|
2593
|
+
if (provider === "openai") {
|
|
2594
|
+
return await fetchOpenAIAttachmentTokenEstimate(modelId, part);
|
|
2595
|
+
}
|
|
2596
|
+
if (provider === "google") {
|
|
2597
|
+
return await fetchGoogleAttachmentTokenEstimate(modelId, part);
|
|
2598
|
+
}
|
|
2599
|
+
if (provider === "anthropic") {
|
|
2600
|
+
return await fetchAnthropicAttachmentTokenEstimate(modelId, part);
|
|
2601
|
+
}
|
|
2602
|
+
} catch {
|
|
2603
|
+
return void 0;
|
|
2604
|
+
}
|
|
2605
|
+
return void 0;
|
|
2606
|
+
}
|
|
2607
|
+
async countAttachmentPartAsync(part) {
|
|
2608
|
+
const isImageAttachment = part.type === "image" || part.type === "file" && isImageLikeFilePart(part);
|
|
2609
|
+
const remotePayload = this.buildRemoteAttachmentCachePayload(part);
|
|
2610
|
+
if (remotePayload) {
|
|
2611
|
+
const remoteKey = buildEstimateKey("attachment-provider", remotePayload);
|
|
2612
|
+
const cachedRemote = getPartCacheEntry(part, remoteKey);
|
|
2613
|
+
if (isValidCacheEntry(cachedRemote, remoteKey, this.cacheSource)) {
|
|
2614
|
+
return cachedRemote.tokens;
|
|
2615
|
+
}
|
|
2616
|
+
const existingRequest = this.inFlightAttachmentCounts.get(remoteKey);
|
|
2617
|
+
if (existingRequest) {
|
|
2618
|
+
const remoteTokens = await existingRequest;
|
|
2619
|
+
if (typeof remoteTokens === "number" && Number.isFinite(remoteTokens) && remoteTokens > 0) {
|
|
2620
|
+
setPartCacheEntry(part, remoteKey, {
|
|
2621
|
+
v: TOKEN_ESTIMATE_CACHE_VERSION,
|
|
2622
|
+
source: this.cacheSource,
|
|
2623
|
+
key: remoteKey,
|
|
2624
|
+
tokens: remoteTokens
|
|
2625
|
+
});
|
|
2626
|
+
return remoteTokens;
|
|
2627
|
+
}
|
|
2628
|
+
} else {
|
|
2629
|
+
const remoteRequest = this.fetchProviderAttachmentTokenEstimate(part);
|
|
2630
|
+
this.inFlightAttachmentCounts.set(remoteKey, remoteRequest);
|
|
2631
|
+
let remoteTokens;
|
|
2632
|
+
try {
|
|
2633
|
+
remoteTokens = await remoteRequest;
|
|
2634
|
+
} finally {
|
|
2635
|
+
this.inFlightAttachmentCounts.delete(remoteKey);
|
|
2636
|
+
}
|
|
2637
|
+
if (typeof remoteTokens === "number" && Number.isFinite(remoteTokens) && remoteTokens > 0) {
|
|
2638
|
+
setPartCacheEntry(part, remoteKey, {
|
|
2639
|
+
v: TOKEN_ESTIMATE_CACHE_VERSION,
|
|
2640
|
+
source: this.cacheSource,
|
|
2641
|
+
key: remoteKey,
|
|
2642
|
+
tokens: remoteTokens
|
|
2643
|
+
});
|
|
2644
|
+
return remoteTokens;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
if (isImageAttachment) {
|
|
2648
|
+
await resolveImageDimensionsAsync(part);
|
|
2649
|
+
}
|
|
2650
|
+
const fallbackPayload = JSON.stringify({
|
|
2651
|
+
...JSON.parse(remotePayload),
|
|
2652
|
+
strategy: "local-fallback",
|
|
2653
|
+
...isImageAttachment ? resolveImageDimensions(part) : {}
|
|
2654
|
+
});
|
|
2655
|
+
const fallbackKey = buildEstimateKey("attachment-provider", fallbackPayload);
|
|
2656
|
+
const cachedFallback = getPartCacheEntry(part, fallbackKey);
|
|
2657
|
+
if (isValidCacheEntry(cachedFallback, fallbackKey, this.cacheSource)) {
|
|
2658
|
+
return cachedFallback.tokens;
|
|
2659
|
+
}
|
|
2660
|
+
const localTokens2 = this.countAttachmentPartSync(part);
|
|
2661
|
+
if (localTokens2 === void 0) {
|
|
2662
|
+
return void 0;
|
|
2663
|
+
}
|
|
2664
|
+
setPartCacheEntry(part, fallbackKey, {
|
|
2665
|
+
v: TOKEN_ESTIMATE_CACHE_VERSION,
|
|
2666
|
+
source: this.cacheSource,
|
|
2667
|
+
key: fallbackKey,
|
|
2668
|
+
tokens: localTokens2
|
|
2669
|
+
});
|
|
2670
|
+
return localTokens2;
|
|
2671
|
+
}
|
|
2672
|
+
if (isImageAttachment) {
|
|
2673
|
+
await resolveImageDimensionsAsync(part);
|
|
2674
|
+
}
|
|
2675
|
+
const localTokens = this.countAttachmentPartSync(part);
|
|
2676
|
+
return localTokens;
|
|
2677
|
+
}
|
|
2678
|
+
countNonAttachmentPart(part) {
|
|
2679
|
+
let overheadDelta = 0;
|
|
2680
|
+
let toolResultDelta = 0;
|
|
2681
|
+
if (part.type === "text") {
|
|
2682
|
+
return { tokens: this.readOrPersistPartEstimate(part, "text", part.text), overheadDelta, toolResultDelta };
|
|
2683
|
+
}
|
|
2684
|
+
if (part.type === "tool-invocation") {
|
|
2685
|
+
const invocation = part.toolInvocation;
|
|
2686
|
+
let tokens = 0;
|
|
2687
|
+
if (invocation.state === "call" || invocation.state === "partial-call") {
|
|
2688
|
+
if (invocation.toolName) {
|
|
2689
|
+
tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-name`, invocation.toolName);
|
|
2690
|
+
}
|
|
2691
|
+
if (invocation.args) {
|
|
2692
|
+
if (typeof invocation.args === "string") {
|
|
2693
|
+
tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-args`, invocation.args);
|
|
2694
|
+
} else {
|
|
2695
|
+
const argsJson = JSON.stringify(invocation.args);
|
|
2696
|
+
tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-args-json`, argsJson);
|
|
2697
|
+
overheadDelta -= 12;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
return { tokens, overheadDelta, toolResultDelta };
|
|
2701
|
+
}
|
|
2702
|
+
if (invocation.state === "result") {
|
|
2703
|
+
toolResultDelta++;
|
|
2704
|
+
const { value: resultForCounting, usingStoredModelOutput } = this.resolveToolResultForTokenCounting(
|
|
2705
|
+
part,
|
|
2706
|
+
invocation.result
|
|
2707
|
+
);
|
|
2708
|
+
if (resultForCounting !== void 0) {
|
|
2709
|
+
if (typeof resultForCounting === "string") {
|
|
2710
|
+
tokens += this.readOrPersistPartEstimate(
|
|
2711
|
+
part,
|
|
2712
|
+
usingStoredModelOutput ? "tool-result-model-output" : "tool-result",
|
|
2713
|
+
resultForCounting
|
|
2714
|
+
);
|
|
2715
|
+
} else {
|
|
2716
|
+
const resultJson = JSON.stringify(resultForCounting);
|
|
2717
|
+
tokens += this.readOrPersistPartEstimate(
|
|
2718
|
+
part,
|
|
2719
|
+
usingStoredModelOutput ? "tool-result-model-output-json" : "tool-result-json",
|
|
2720
|
+
resultJson
|
|
2721
|
+
);
|
|
2722
|
+
overheadDelta -= 12;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
return { tokens, overheadDelta, toolResultDelta };
|
|
2726
|
+
}
|
|
2727
|
+
throw new Error(
|
|
2728
|
+
`Unhandled tool-invocation state '${part.toolInvocation?.state}' in token counting for part type '${part.type}'`
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2731
|
+
if (typeof part.type === "string" && part.type.startsWith("data-")) {
|
|
2732
|
+
return { tokens: 0, overheadDelta, toolResultDelta };
|
|
2733
|
+
}
|
|
2734
|
+
if (part.type === "reasoning") {
|
|
2735
|
+
return { tokens: 0, overheadDelta, toolResultDelta };
|
|
2736
|
+
}
|
|
2737
|
+
const serialized = serializePartForTokenCounting(part);
|
|
2738
|
+
return {
|
|
2739
|
+
tokens: this.readOrPersistPartEstimate(part, `part-${part.type}`, serialized),
|
|
2740
|
+
overheadDelta,
|
|
2741
|
+
toolResultDelta
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Count tokens in a single message
|
|
2746
|
+
*/
|
|
2747
|
+
countMessage(message) {
|
|
2748
|
+
let payloadTokens = this.countString(message.role);
|
|
2749
|
+
let overhead = _TokenCounter.TOKENS_PER_MESSAGE;
|
|
2750
|
+
let toolResultCount = 0;
|
|
2751
|
+
if (typeof message.content === "string") {
|
|
2752
|
+
payloadTokens += this.readOrPersistMessageEstimate(message, "message-content", message.content);
|
|
2753
|
+
} else if (message.content && typeof message.content === "object") {
|
|
2754
|
+
if (message.content.content && !Array.isArray(message.content.parts)) {
|
|
2755
|
+
payloadTokens += this.readOrPersistMessageEstimate(message, "content-content", message.content.content);
|
|
2756
|
+
} else if (Array.isArray(message.content.parts)) {
|
|
2757
|
+
for (const part of message.content.parts) {
|
|
2758
|
+
const attachmentTokens = this.countAttachmentPartSync(part);
|
|
2759
|
+
if (attachmentTokens !== void 0) {
|
|
2760
|
+
payloadTokens += attachmentTokens;
|
|
2761
|
+
continue;
|
|
2762
|
+
}
|
|
2763
|
+
const result = this.countNonAttachmentPart(part);
|
|
2764
|
+
payloadTokens += result.tokens;
|
|
2765
|
+
overhead += result.overheadDelta;
|
|
2766
|
+
toolResultCount += result.toolResultDelta;
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
if (toolResultCount > 0) {
|
|
2771
|
+
overhead += toolResultCount * _TokenCounter.TOKENS_PER_MESSAGE;
|
|
2772
|
+
}
|
|
2773
|
+
return Math.round(payloadTokens + overhead);
|
|
1463
2774
|
}
|
|
1464
|
-
|
|
1465
|
-
* Count tokens in a single message
|
|
1466
|
-
*/
|
|
1467
|
-
countMessage(message) {
|
|
2775
|
+
async countMessageAsync(message) {
|
|
1468
2776
|
let payloadTokens = this.countString(message.role);
|
|
1469
2777
|
let overhead = _TokenCounter.TOKENS_PER_MESSAGE;
|
|
1470
2778
|
let toolResultCount = 0;
|
|
@@ -1475,63 +2783,15 @@ var TokenCounter = class _TokenCounter {
|
|
|
1475
2783
|
payloadTokens += this.readOrPersistMessageEstimate(message, "content-content", message.content.content);
|
|
1476
2784
|
} else if (Array.isArray(message.content.parts)) {
|
|
1477
2785
|
for (const part of message.content.parts) {
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
if (invocation.state === "call" || invocation.state === "partial-call") {
|
|
1483
|
-
if (invocation.toolName) {
|
|
1484
|
-
payloadTokens += this.readOrPersistPartEstimate(
|
|
1485
|
-
part,
|
|
1486
|
-
`tool-${invocation.state}-name`,
|
|
1487
|
-
invocation.toolName
|
|
1488
|
-
);
|
|
1489
|
-
}
|
|
1490
|
-
if (invocation.args) {
|
|
1491
|
-
if (typeof invocation.args === "string") {
|
|
1492
|
-
payloadTokens += this.readOrPersistPartEstimate(
|
|
1493
|
-
part,
|
|
1494
|
-
`tool-${invocation.state}-args`,
|
|
1495
|
-
invocation.args
|
|
1496
|
-
);
|
|
1497
|
-
} else {
|
|
1498
|
-
const argsJson = JSON.stringify(invocation.args);
|
|
1499
|
-
payloadTokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-args-json`, argsJson);
|
|
1500
|
-
overhead -= 12;
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
} else if (invocation.state === "result") {
|
|
1504
|
-
toolResultCount++;
|
|
1505
|
-
const { value: resultForCounting, usingStoredModelOutput } = this.resolveToolResultForTokenCounting(
|
|
1506
|
-
part,
|
|
1507
|
-
invocation.result
|
|
1508
|
-
);
|
|
1509
|
-
if (resultForCounting !== void 0) {
|
|
1510
|
-
if (typeof resultForCounting === "string") {
|
|
1511
|
-
payloadTokens += this.readOrPersistPartEstimate(
|
|
1512
|
-
part,
|
|
1513
|
-
usingStoredModelOutput ? "tool-result-model-output" : "tool-result",
|
|
1514
|
-
resultForCounting
|
|
1515
|
-
);
|
|
1516
|
-
} else {
|
|
1517
|
-
const resultJson = JSON.stringify(resultForCounting);
|
|
1518
|
-
payloadTokens += this.readOrPersistPartEstimate(
|
|
1519
|
-
part,
|
|
1520
|
-
usingStoredModelOutput ? "tool-result-model-output-json" : "tool-result-json",
|
|
1521
|
-
resultJson
|
|
1522
|
-
);
|
|
1523
|
-
overhead -= 12;
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
} else {
|
|
1527
|
-
throw new Error(
|
|
1528
|
-
`Unhandled tool-invocation state '${part.toolInvocation?.state}' in token counting for part type '${part.type}'`
|
|
1529
|
-
);
|
|
1530
|
-
}
|
|
1531
|
-
} else if (typeof part.type === "string" && part.type.startsWith("data-")) ; else if (part.type === "reasoning") ; else {
|
|
1532
|
-
const serialized = serializePartForTokenCounting(part);
|
|
1533
|
-
payloadTokens += this.readOrPersistPartEstimate(part, `part-${part.type}`, serialized);
|
|
2786
|
+
const attachmentTokens = await this.countAttachmentPartAsync(part);
|
|
2787
|
+
if (attachmentTokens !== void 0) {
|
|
2788
|
+
payloadTokens += attachmentTokens;
|
|
2789
|
+
continue;
|
|
1534
2790
|
}
|
|
2791
|
+
const result = this.countNonAttachmentPart(part);
|
|
2792
|
+
payloadTokens += result.tokens;
|
|
2793
|
+
overhead += result.overheadDelta;
|
|
2794
|
+
toolResultCount += result.toolResultDelta;
|
|
1535
2795
|
}
|
|
1536
2796
|
}
|
|
1537
2797
|
}
|
|
@@ -1551,6 +2811,11 @@ var TokenCounter = class _TokenCounter {
|
|
|
1551
2811
|
}
|
|
1552
2812
|
return total;
|
|
1553
2813
|
}
|
|
2814
|
+
async countMessagesAsync(messages) {
|
|
2815
|
+
if (!messages || messages.length === 0) return 0;
|
|
2816
|
+
const messageTotals = await Promise.all(messages.map((message) => this.countMessageAsync(message)));
|
|
2817
|
+
return _TokenCounter.TOKENS_PER_CONVERSATION + messageTotals.reduce((sum, count) => sum + count, 0);
|
|
2818
|
+
}
|
|
1554
2819
|
/**
|
|
1555
2820
|
* Count tokens in observations string
|
|
1556
2821
|
*/
|
|
@@ -1808,6 +3073,41 @@ var ObservationalMemory = class _ObservationalMemory {
|
|
|
1808
3073
|
}
|
|
1809
3074
|
return [];
|
|
1810
3075
|
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Refresh per-chunk messageTokens from the current in-memory message list.
|
|
3078
|
+
*
|
|
3079
|
+
* Buffered chunks store a messageTokens snapshot from when they were created,
|
|
3080
|
+
* but messages can be edited/sealed between buffering and activation, changing
|
|
3081
|
+
* their token weight. Using stale weights causes projected-removal math to
|
|
3082
|
+
* over- or under-estimate, leading to skipped activations or over-activation.
|
|
3083
|
+
*
|
|
3084
|
+
* Token recount only runs when the full chunk is present in the message list.
|
|
3085
|
+
* Partial recount is skipped because it would undercount and could cause
|
|
3086
|
+
* over-activation of buffered chunks.
|
|
3087
|
+
*/
|
|
3088
|
+
refreshBufferedChunkMessageTokens(chunks, messageList) {
|
|
3089
|
+
const allMessages = messageList.get.all.db();
|
|
3090
|
+
const messageMap = new Map(allMessages.filter((m) => m?.id).map((m) => [m.id, m]));
|
|
3091
|
+
return chunks.map((chunk) => {
|
|
3092
|
+
const chunkMessages = chunk.messageIds.map((id) => messageMap.get(id)).filter((m) => !!m);
|
|
3093
|
+
if (chunkMessages.length !== chunk.messageIds.length) {
|
|
3094
|
+
return chunk;
|
|
3095
|
+
}
|
|
3096
|
+
const refreshedTokens = this.tokenCounter.countMessages(chunkMessages);
|
|
3097
|
+
const refreshedMessageTokens = chunk.messageIds.reduce((acc, id) => {
|
|
3098
|
+
const msg = messageMap.get(id);
|
|
3099
|
+
if (msg) {
|
|
3100
|
+
acc[id] = this.tokenCounter.countMessages([msg]);
|
|
3101
|
+
}
|
|
3102
|
+
return acc;
|
|
3103
|
+
}, {});
|
|
3104
|
+
return {
|
|
3105
|
+
...chunk,
|
|
3106
|
+
messageTokens: refreshedTokens,
|
|
3107
|
+
messageTokenCounts: refreshedMessageTokens
|
|
3108
|
+
};
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
1811
3111
|
/**
|
|
1812
3112
|
* Check if we've crossed a new bufferTokens interval boundary.
|
|
1813
3113
|
* Returns true if async buffering should be triggered.
|
|
@@ -1904,6 +3204,11 @@ var ObservationalMemory = class _ObservationalMemory {
|
|
|
1904
3204
|
return `thread:${threadId ?? "unknown"}`;
|
|
1905
3205
|
}
|
|
1906
3206
|
constructor(config) {
|
|
3207
|
+
if (!coreFeatures.has("request-response-id-rotation")) {
|
|
3208
|
+
throw new Error(
|
|
3209
|
+
"Observational memory requires @mastra/core support for request-response-id-rotation. Please bump @mastra/core to a newer version."
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
1907
3212
|
if (config.model && config.observation?.model) {
|
|
1908
3213
|
throw new Error(
|
|
1909
3214
|
"Cannot set both `model` and `observation.model`. Use `model` to set both agents, or set each individually."
|
|
@@ -2001,7 +3306,9 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2001
3306
|
),
|
|
2002
3307
|
instruction: config.reflection?.instruction
|
|
2003
3308
|
};
|
|
2004
|
-
this.tokenCounter = new TokenCounter(
|
|
3309
|
+
this.tokenCounter = new TokenCounter(void 0, {
|
|
3310
|
+
model: typeof observationModel === "string" ? observationModel : void 0
|
|
3311
|
+
});
|
|
2005
3312
|
this.onDebugEvent = config.onDebugEvent;
|
|
2006
3313
|
this.messageHistory = new MessageHistory({ storage: this.storage });
|
|
2007
3314
|
this.validateBufferConfig();
|
|
@@ -2031,25 +3338,59 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2031
3338
|
async waitForBuffering(threadId, resourceId, timeoutMs = 3e4) {
|
|
2032
3339
|
return _ObservationalMemory.awaitBuffering(threadId, resourceId, this.scope, timeoutMs);
|
|
2033
3340
|
}
|
|
3341
|
+
getModelToResolve(model) {
|
|
3342
|
+
if (Array.isArray(model)) {
|
|
3343
|
+
return model[0]?.model ?? "unknown";
|
|
3344
|
+
}
|
|
3345
|
+
if (typeof model === "function") {
|
|
3346
|
+
return async (ctx) => {
|
|
3347
|
+
const result = await model(ctx);
|
|
3348
|
+
if (Array.isArray(result)) {
|
|
3349
|
+
return result[0]?.model ?? "unknown";
|
|
3350
|
+
}
|
|
3351
|
+
return result;
|
|
3352
|
+
};
|
|
3353
|
+
}
|
|
3354
|
+
return model;
|
|
3355
|
+
}
|
|
3356
|
+
formatModelName(model) {
|
|
3357
|
+
if (!model.modelId) {
|
|
3358
|
+
return "(unknown)";
|
|
3359
|
+
}
|
|
3360
|
+
return model.provider ? `${model.provider}/${model.modelId}` : model.modelId;
|
|
3361
|
+
}
|
|
3362
|
+
async resolveModelContext(modelConfig, requestContext) {
|
|
3363
|
+
const modelToResolve = this.getModelToResolve(modelConfig);
|
|
3364
|
+
if (!modelToResolve) {
|
|
3365
|
+
return void 0;
|
|
3366
|
+
}
|
|
3367
|
+
const resolved = await resolveModelConfig(modelToResolve, requestContext);
|
|
3368
|
+
return {
|
|
3369
|
+
provider: resolved.provider,
|
|
3370
|
+
modelId: resolved.modelId
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
getRuntimeModelContext(model) {
|
|
3374
|
+
if (!model?.modelId) {
|
|
3375
|
+
return void 0;
|
|
3376
|
+
}
|
|
3377
|
+
return {
|
|
3378
|
+
provider: model.provider,
|
|
3379
|
+
modelId: model.modelId
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
runWithTokenCounterModelContext(modelContext, fn) {
|
|
3383
|
+
return this.tokenCounter.runWithModelContext(modelContext, fn);
|
|
3384
|
+
}
|
|
2034
3385
|
/**
|
|
2035
3386
|
* Get the full config including resolved model names.
|
|
2036
3387
|
* This is async because it needs to resolve the model configs.
|
|
2037
3388
|
*/
|
|
2038
3389
|
async getResolvedConfig(requestContext) {
|
|
2039
|
-
const getModelToResolve = (model) => {
|
|
2040
|
-
if (Array.isArray(model)) {
|
|
2041
|
-
return model[0]?.model ?? "unknown";
|
|
2042
|
-
}
|
|
2043
|
-
return model;
|
|
2044
|
-
};
|
|
2045
|
-
const formatModelName = (model) => {
|
|
2046
|
-
return model.provider ? `${model.provider}/${model.modelId}` : model.modelId;
|
|
2047
|
-
};
|
|
2048
3390
|
const safeResolveModel = async (modelConfig) => {
|
|
2049
|
-
const modelToResolve = getModelToResolve(modelConfig);
|
|
2050
3391
|
try {
|
|
2051
|
-
const resolved = await
|
|
2052
|
-
return formatModelName(resolved);
|
|
3392
|
+
const resolved = await this.resolveModelContext(modelConfig, requestContext);
|
|
3393
|
+
return resolved?.modelId ? this.formatModelName(resolved) : "(unknown)";
|
|
2053
3394
|
} catch (error) {
|
|
2054
3395
|
omError("[OM] Failed to resolve model config", error);
|
|
2055
3396
|
return "(unknown)";
|
|
@@ -2530,10 +3871,16 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2530
3871
|
*/
|
|
2531
3872
|
async callObserver(existingObservations, messagesToObserve, abortSignal, options) {
|
|
2532
3873
|
const agent = this.getObserverAgent();
|
|
2533
|
-
const
|
|
3874
|
+
const observerMessages = [
|
|
3875
|
+
{
|
|
3876
|
+
role: "user",
|
|
3877
|
+
content: buildObserverTaskPrompt(existingObservations, options)
|
|
3878
|
+
},
|
|
3879
|
+
buildObserverHistoryMessage(messagesToObserve)
|
|
3880
|
+
];
|
|
2534
3881
|
const doGenerate = async () => {
|
|
2535
3882
|
return this.withAbortCheck(async () => {
|
|
2536
|
-
const streamResult = await agent.stream(
|
|
3883
|
+
const streamResult = await agent.stream(observerMessages, {
|
|
2537
3884
|
modelSettings: {
|
|
2538
3885
|
...this.observationConfig.modelSettings
|
|
2539
3886
|
},
|
|
@@ -2580,7 +3927,13 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2580
3927
|
model: this.observationConfig.model,
|
|
2581
3928
|
instructions: buildObserverSystemPrompt(true, this.observationConfig.instruction)
|
|
2582
3929
|
});
|
|
2583
|
-
const
|
|
3930
|
+
const observerMessages = [
|
|
3931
|
+
{
|
|
3932
|
+
role: "user",
|
|
3933
|
+
content: buildMultiThreadObserverTaskPrompt(existingObservations)
|
|
3934
|
+
},
|
|
3935
|
+
buildMultiThreadObserverHistoryMessage(messagesByThread, threadOrder)
|
|
3936
|
+
];
|
|
2584
3937
|
const allMessages = [];
|
|
2585
3938
|
for (const msgs of messagesByThread.values()) {
|
|
2586
3939
|
allMessages.push(...msgs);
|
|
@@ -2590,7 +3943,7 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2590
3943
|
}
|
|
2591
3944
|
const doGenerate = async () => {
|
|
2592
3945
|
return this.withAbortCheck(async () => {
|
|
2593
|
-
const streamResult = await agent.stream(
|
|
3946
|
+
const streamResult = await agent.stream(observerMessages, {
|
|
2594
3947
|
modelSettings: {
|
|
2595
3948
|
...this.observationConfig.modelSettings
|
|
2596
3949
|
},
|
|
@@ -2868,8 +4221,8 @@ ${suggestedResponse}
|
|
|
2868
4221
|
/**
|
|
2869
4222
|
* Calculate all threshold-related values for observation decision making.
|
|
2870
4223
|
*/
|
|
2871
|
-
calculateObservationThresholds(_allMessages, unobservedMessages, _pendingTokens, otherThreadTokens, currentObservationTokens, _record) {
|
|
2872
|
-
const contextWindowTokens = this.tokenCounter.
|
|
4224
|
+
async calculateObservationThresholds(_allMessages, unobservedMessages, _pendingTokens, otherThreadTokens, currentObservationTokens, _record) {
|
|
4225
|
+
const contextWindowTokens = await this.tokenCounter.countMessagesAsync(unobservedMessages);
|
|
2873
4226
|
const totalPendingTokens = Math.max(0, contextWindowTokens + otherThreadTokens);
|
|
2874
4227
|
const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
|
|
2875
4228
|
const baseReflectionThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
@@ -2979,7 +4332,7 @@ ${suggestedResponse}
|
|
|
2979
4332
|
let freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
2980
4333
|
const freshAllMessages = messageList.get.all.db();
|
|
2981
4334
|
let freshUnobservedMessages = this.getUnobservedMessages(freshAllMessages, freshRecord);
|
|
2982
|
-
const freshContextTokens = this.tokenCounter.
|
|
4335
|
+
const freshContextTokens = await this.tokenCounter.countMessagesAsync(freshUnobservedMessages);
|
|
2983
4336
|
let freshOtherThreadTokens = 0;
|
|
2984
4337
|
if (this.scope === "resource" && resourceId) {
|
|
2985
4338
|
const freshOtherContext = await this.loadOtherThreadsContext(resourceId, threadId);
|
|
@@ -3121,17 +4474,25 @@ ${suggestedResponse}
|
|
|
3121
4474
|
if (observedMessageIds && observedMessageIds.length > 0) {
|
|
3122
4475
|
const observedSet = new Set(observedMessageIds);
|
|
3123
4476
|
const idsToRemove = /* @__PURE__ */ new Set();
|
|
4477
|
+
const removalOrder = [];
|
|
3124
4478
|
let skipped = 0;
|
|
3125
4479
|
let backoffTriggered = false;
|
|
4480
|
+
const retentionCounter = typeof minRemaining === "number" ? new TokenCounter() : null;
|
|
3126
4481
|
for (const msg of allMsgs) {
|
|
3127
4482
|
if (!msg?.id || msg.id === "om-continuation" || !observedSet.has(msg.id)) {
|
|
3128
4483
|
continue;
|
|
3129
4484
|
}
|
|
3130
|
-
|
|
4485
|
+
const unobservedParts = this.getUnobservedParts(msg);
|
|
4486
|
+
const totalParts = msg.content?.parts?.length ?? 0;
|
|
4487
|
+
if (unobservedParts.length > 0 && unobservedParts.length < totalParts) {
|
|
4488
|
+
msg.content.parts = unobservedParts;
|
|
4489
|
+
continue;
|
|
4490
|
+
}
|
|
4491
|
+
if (retentionCounter && typeof minRemaining === "number") {
|
|
3131
4492
|
const nextRemainingMessages = allMsgs.filter(
|
|
3132
4493
|
(m) => m?.id && m.id !== "om-continuation" && !idsToRemove.has(m.id) && m.id !== msg.id
|
|
3133
4494
|
);
|
|
3134
|
-
const remainingIfRemoved =
|
|
4495
|
+
const remainingIfRemoved = retentionCounter.countMessages(nextRemainingMessages);
|
|
3135
4496
|
if (remainingIfRemoved < minRemaining) {
|
|
3136
4497
|
skipped += 1;
|
|
3137
4498
|
backoffTriggered = true;
|
|
@@ -3139,6 +4500,19 @@ ${suggestedResponse}
|
|
|
3139
4500
|
}
|
|
3140
4501
|
}
|
|
3141
4502
|
idsToRemove.add(msg.id);
|
|
4503
|
+
removalOrder.push(msg.id);
|
|
4504
|
+
}
|
|
4505
|
+
if (retentionCounter && typeof minRemaining === "number" && idsToRemove.size > 0) {
|
|
4506
|
+
let remainingMessages = allMsgs.filter((m) => m?.id && m.id !== "om-continuation" && !idsToRemove.has(m.id));
|
|
4507
|
+
let remainingTokens = retentionCounter.countMessages(remainingMessages);
|
|
4508
|
+
while (remainingTokens < minRemaining && removalOrder.length > 0) {
|
|
4509
|
+
const restoreId = removalOrder.pop();
|
|
4510
|
+
idsToRemove.delete(restoreId);
|
|
4511
|
+
skipped += 1;
|
|
4512
|
+
backoffTriggered = true;
|
|
4513
|
+
remainingMessages = allMsgs.filter((m) => m?.id && m.id !== "om-continuation" && !idsToRemove.has(m.id));
|
|
4514
|
+
remainingTokens = retentionCounter.countMessages(remainingMessages);
|
|
4515
|
+
}
|
|
3142
4516
|
}
|
|
3143
4517
|
omDebug(
|
|
3144
4518
|
`[OM:cleanupActivation] observedSet=${[...observedSet].map((id) => id.slice(0, 8)).join(",")}, matched=${idsToRemove.size}, skipped=${skipped}, backoffTriggered=${backoffTriggered}, idsToRemove=${[...idsToRemove].map((id) => id.slice(0, 8)).join(",")}`
|
|
@@ -3176,8 +4550,8 @@ ${suggestedResponse}
|
|
|
3176
4550
|
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
3177
4551
|
}
|
|
3178
4552
|
} else {
|
|
3179
|
-
const newInput = messageList.
|
|
3180
|
-
const newOutput = messageList.
|
|
4553
|
+
const newInput = messageList.get.input.db();
|
|
4554
|
+
const newOutput = messageList.get.response.db();
|
|
3181
4555
|
const messagesToSave = [...newInput, ...newOutput];
|
|
3182
4556
|
if (messagesToSave.length > 0) {
|
|
3183
4557
|
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
@@ -3245,11 +4619,17 @@ ${suggestedResponse}
|
|
|
3245
4619
|
messageList.add(continuationMessage, "memory");
|
|
3246
4620
|
}
|
|
3247
4621
|
/**
|
|
3248
|
-
* Filter out already-observed messages from
|
|
3249
|
-
*
|
|
4622
|
+
* Filter out already-observed messages from the in-memory context.
|
|
4623
|
+
*
|
|
4624
|
+
* Marker-boundary pruning is safest at step 0 (historical resume/rebuild), where
|
|
4625
|
+
* list ordering mirrors persisted history.
|
|
4626
|
+
* For step > 0, the list may include mid-loop mutations (sealing/splitting/trim),
|
|
4627
|
+
* so we prefer record-based fallback pruning over position-based marker pruning.
|
|
3250
4628
|
*/
|
|
3251
|
-
filterAlreadyObservedMessages(messageList, record) {
|
|
4629
|
+
async filterAlreadyObservedMessages(messageList, record, options) {
|
|
3252
4630
|
const allMessages = messageList.get.all.db();
|
|
4631
|
+
const useMarkerBoundaryPruning = options?.useMarkerBoundaryPruning ?? true;
|
|
4632
|
+
const fallbackCursor = record?.threadId ? getThreadOMMetadata((await this.storage.getThreadById({ threadId: record.threadId }))?.metadata)?.lastObservedMessageCursor : void 0;
|
|
3253
4633
|
let markerMessageIndex = -1;
|
|
3254
4634
|
let markerMessage = null;
|
|
3255
4635
|
for (let i = allMessages.length - 1; i >= 0; i--) {
|
|
@@ -3261,7 +4641,7 @@ ${suggestedResponse}
|
|
|
3261
4641
|
break;
|
|
3262
4642
|
}
|
|
3263
4643
|
}
|
|
3264
|
-
if (markerMessage && markerMessageIndex !== -1) {
|
|
4644
|
+
if (useMarkerBoundaryPruning && markerMessage && markerMessageIndex !== -1) {
|
|
3265
4645
|
const messagesToRemove = [];
|
|
3266
4646
|
for (let i = 0; i < markerMessageIndex; i++) {
|
|
3267
4647
|
const msg = allMessages[i];
|
|
@@ -3282,6 +4662,9 @@ ${suggestedResponse}
|
|
|
3282
4662
|
}
|
|
3283
4663
|
} else if (record) {
|
|
3284
4664
|
const observedIds = new Set(Array.isArray(record.observedMessageIds) ? record.observedMessageIds : []);
|
|
4665
|
+
const derivedCursor = fallbackCursor ?? this.getLastObservedMessageCursor(
|
|
4666
|
+
allMessages.filter((msg) => !!msg?.id && observedIds.has(msg.id) && !!msg.createdAt)
|
|
4667
|
+
);
|
|
3285
4668
|
const lastObservedAt = record.lastObservedAt;
|
|
3286
4669
|
const messagesToRemove = [];
|
|
3287
4670
|
for (const msg of allMessages) {
|
|
@@ -3290,6 +4673,10 @@ ${suggestedResponse}
|
|
|
3290
4673
|
messagesToRemove.push(msg.id);
|
|
3291
4674
|
continue;
|
|
3292
4675
|
}
|
|
4676
|
+
if (derivedCursor && this.isMessageAtOrBeforeCursor(msg, derivedCursor)) {
|
|
4677
|
+
messagesToRemove.push(msg.id);
|
|
4678
|
+
continue;
|
|
4679
|
+
}
|
|
3293
4680
|
if (lastObservedAt && msg.createdAt) {
|
|
3294
4681
|
const msgDate = new Date(msg.createdAt);
|
|
3295
4682
|
if (msgDate <= lastObservedAt) {
|
|
@@ -3314,7 +4701,7 @@ ${suggestedResponse}
|
|
|
3314
4701
|
* 5. Filter out already-observed messages
|
|
3315
4702
|
*/
|
|
3316
4703
|
async processInputStep(args) {
|
|
3317
|
-
const { messageList, requestContext, stepNumber, state: _state, writer, abortSignal, abort } = args;
|
|
4704
|
+
const { messageList, requestContext, stepNumber, state: _state, writer, abortSignal, abort, model } = args;
|
|
3318
4705
|
const state = _state ?? {};
|
|
3319
4706
|
omDebug(
|
|
3320
4707
|
`[OM:processInputStep:ENTER] step=${stepNumber}, hasMastraMemory=${!!requestContext?.get("MastraMemory")}, hasMemoryInfo=${!!messageList?.serialize()?.memoryInfo?.threadId}`
|
|
@@ -3327,262 +4714,322 @@ ${suggestedResponse}
|
|
|
3327
4714
|
const { threadId, resourceId } = context;
|
|
3328
4715
|
const memoryContext = parseMemoryRequestContext(requestContext);
|
|
3329
4716
|
const readOnly = memoryContext?.memoryConfig?.readOnly;
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
4717
|
+
const actorModelContext = this.getRuntimeModelContext(model);
|
|
4718
|
+
state.__omActorModelContext = actorModelContext;
|
|
4719
|
+
return this.runWithTokenCounterModelContext(actorModelContext, async () => {
|
|
4720
|
+
let record = await this.getOrCreateRecord(threadId, resourceId);
|
|
4721
|
+
const reproCaptureEnabled = isOmReproCaptureEnabled();
|
|
4722
|
+
const preRecordSnapshot = reproCaptureEnabled ? safeCaptureJson(record) : null;
|
|
4723
|
+
const preMessagesSnapshot = reproCaptureEnabled ? safeCaptureJson(messageList.get.all.db()) : null;
|
|
4724
|
+
const preSerializedMessageList = reproCaptureEnabled ? safeCaptureJson(messageList.serialize()) : null;
|
|
4725
|
+
const reproCaptureDetails = {
|
|
4726
|
+
step0Activation: null,
|
|
4727
|
+
thresholdCleanup: null,
|
|
4728
|
+
thresholdReached: false
|
|
4729
|
+
};
|
|
3342
4730
|
omDebug(
|
|
3343
|
-
`[OM:
|
|
4731
|
+
`[OM:step] processInputStep step=${stepNumber}: recordId=${record.id}, genCount=${record.generationCount}, obsTokens=${record.observationTokenCount}, bufferedReflection=${record.bufferedReflection ? "present (" + record.bufferedReflection.length + " chars)" : "empty"}, activeObsLen=${record.activeObservations?.length}`
|
|
3344
4732
|
);
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
if (dbBoundary > currentContextTokens) {
|
|
3350
|
-
omDebug(
|
|
3351
|
-
`[OM:step0-boundary-reset] dbBoundary=${dbBoundary} > currentContext=${currentContextTokens}, resetting to current`
|
|
3352
|
-
);
|
|
3353
|
-
_ObservationalMemory.lastBufferedBoundary.set(bufKey, currentContextTokens);
|
|
3354
|
-
this.storage.setBufferingObservationFlag(record.id, false, currentContextTokens).catch(() => {
|
|
3355
|
-
});
|
|
3356
|
-
}
|
|
4733
|
+
await this.loadHistoricalMessagesIfNeeded(messageList, state, threadId, resourceId, record.lastObservedAt);
|
|
4734
|
+
let unobservedContextBlocks;
|
|
4735
|
+
if (this.scope === "resource" && resourceId) {
|
|
4736
|
+
unobservedContextBlocks = await this.loadOtherThreadsContext(resourceId, threadId);
|
|
3357
4737
|
}
|
|
3358
|
-
if (
|
|
3359
|
-
const
|
|
3360
|
-
const
|
|
3361
|
-
const otherThreadTokensForCheck = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3362
|
-
const currentObsTokensForCheck = record.observationTokenCount ?? 0;
|
|
3363
|
-
const { totalPendingTokens: step0PendingTokens, threshold: step0Threshold } = this.calculateObservationThresholds(
|
|
3364
|
-
allMsgsForCheck,
|
|
3365
|
-
unobservedMsgsForCheck,
|
|
3366
|
-
0,
|
|
3367
|
-
// pendingTokens not needed — allMessages covers context
|
|
3368
|
-
otherThreadTokensForCheck,
|
|
3369
|
-
currentObsTokensForCheck,
|
|
3370
|
-
record
|
|
3371
|
-
);
|
|
4738
|
+
if (stepNumber === 0 && !readOnly && this.isAsyncObservationEnabled()) {
|
|
4739
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
4740
|
+
const bufferedChunks = this.getBufferedChunks(record);
|
|
3372
4741
|
omDebug(
|
|
3373
|
-
`[OM:step0-activation]
|
|
4742
|
+
`[OM:step0-activation] asyncObsEnabled=true, bufferedChunks=${bufferedChunks.length}, isBufferingObs=${record.isBufferingObservation}`
|
|
3374
4743
|
);
|
|
3375
|
-
|
|
3376
|
-
const
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
4744
|
+
{
|
|
4745
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
4746
|
+
const dbBoundary = record.lastBufferedAtTokens ?? 0;
|
|
4747
|
+
const currentContextTokens = this.tokenCounter.countMessages(messageList.get.all.db());
|
|
4748
|
+
if (dbBoundary > currentContextTokens) {
|
|
4749
|
+
omDebug(
|
|
4750
|
+
`[OM:step0-boundary-reset] dbBoundary=${dbBoundary} > currentContext=${currentContextTokens}, resetting to current`
|
|
4751
|
+
);
|
|
4752
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, currentContextTokens);
|
|
4753
|
+
this.storage.setBufferingObservationFlag(record.id, false, currentContextTokens).catch(() => {
|
|
4754
|
+
});
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
if (bufferedChunks.length > 0) {
|
|
4758
|
+
const allMsgsForCheck = messageList.get.all.db();
|
|
4759
|
+
const unobservedMsgsForCheck = this.getUnobservedMessages(allMsgsForCheck, record);
|
|
4760
|
+
const otherThreadTokensForCheck = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
4761
|
+
const currentObsTokensForCheck = record.observationTokenCount ?? 0;
|
|
4762
|
+
const { totalPendingTokens: step0PendingTokens, threshold: step0Threshold } = await this.calculateObservationThresholds(
|
|
4763
|
+
allMsgsForCheck,
|
|
4764
|
+
unobservedMsgsForCheck,
|
|
4765
|
+
0,
|
|
4766
|
+
// pendingTokens not needed — allMessages covers context
|
|
4767
|
+
otherThreadTokensForCheck,
|
|
4768
|
+
currentObsTokensForCheck,
|
|
4769
|
+
record
|
|
3382
4770
|
);
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
4771
|
+
omDebug(
|
|
4772
|
+
`[OM:step0-activation] pendingTokens=${step0PendingTokens}, threshold=${step0Threshold}, blockAfter=${this.observationConfig.blockAfter}, shouldActivate=${step0PendingTokens >= step0Threshold}, allMsgs=${allMsgsForCheck.length}`
|
|
4773
|
+
);
|
|
4774
|
+
if (step0PendingTokens >= step0Threshold) {
|
|
4775
|
+
const activationResult = await this.tryActivateBufferedObservations(
|
|
4776
|
+
record,
|
|
4777
|
+
lockKey,
|
|
4778
|
+
step0PendingTokens,
|
|
4779
|
+
writer,
|
|
4780
|
+
messageList
|
|
4781
|
+
);
|
|
4782
|
+
reproCaptureDetails.step0Activation = {
|
|
4783
|
+
attempted: true,
|
|
4784
|
+
success: activationResult.success,
|
|
4785
|
+
messageTokensActivated: activationResult.messageTokensActivated ?? 0,
|
|
4786
|
+
activatedMessageIds: activationResult.activatedMessageIds ?? [],
|
|
4787
|
+
hadUpdatedRecord: !!activationResult.updatedRecord
|
|
4788
|
+
};
|
|
4789
|
+
if (activationResult.success && activationResult.updatedRecord) {
|
|
4790
|
+
record = activationResult.updatedRecord;
|
|
4791
|
+
const activatedIds = activationResult.activatedMessageIds ?? [];
|
|
4792
|
+
if (activatedIds.length > 0) {
|
|
4793
|
+
const activatedSet = new Set(activatedIds);
|
|
4794
|
+
const allMsgs = messageList.get.all.db();
|
|
4795
|
+
const idsToRemove = allMsgs.filter((msg) => msg?.id && msg.id !== "om-continuation" && activatedSet.has(msg.id)).map((msg) => msg.id);
|
|
4796
|
+
if (idsToRemove.length > 0) {
|
|
4797
|
+
messageList.removeByIds(idsToRemove);
|
|
4798
|
+
}
|
|
3392
4799
|
}
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {
|
|
3398
|
-
});
|
|
3399
|
-
const thread = await this.storage.getThreadById({ threadId });
|
|
3400
|
-
if (thread) {
|
|
3401
|
-
const newMetadata = setThreadOMMetadata(thread.metadata, {
|
|
3402
|
-
suggestedResponse: activationResult.suggestedContinuation,
|
|
3403
|
-
currentTask: activationResult.currentTask
|
|
4800
|
+
this.cleanupStaticMaps(threadId, resourceId, activatedIds);
|
|
4801
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
4802
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, 0);
|
|
4803
|
+
this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {
|
|
3404
4804
|
});
|
|
3405
|
-
await this.storage.
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
4805
|
+
const thread = await this.storage.getThreadById({ threadId });
|
|
4806
|
+
if (thread) {
|
|
4807
|
+
const activatedSet = new Set(activationResult.activatedMessageIds ?? []);
|
|
4808
|
+
const activatedMessages = messageList.get.all.db().filter((msg) => msg?.id && activatedSet.has(msg.id));
|
|
4809
|
+
const newMetadata = setThreadOMMetadata(thread.metadata, {
|
|
4810
|
+
suggestedResponse: activationResult.suggestedContinuation,
|
|
4811
|
+
currentTask: activationResult.currentTask,
|
|
4812
|
+
lastObservedMessageCursor: this.getLastObservedMessageCursor(activatedMessages)
|
|
4813
|
+
});
|
|
4814
|
+
await this.storage.updateThread({
|
|
4815
|
+
id: threadId,
|
|
4816
|
+
title: thread.title ?? "",
|
|
4817
|
+
metadata: newMetadata
|
|
4818
|
+
});
|
|
4819
|
+
}
|
|
4820
|
+
await this.maybeReflect({
|
|
4821
|
+
record,
|
|
4822
|
+
observationTokens: record.observationTokenCount ?? 0,
|
|
4823
|
+
threadId,
|
|
4824
|
+
writer,
|
|
4825
|
+
messageList,
|
|
4826
|
+
requestContext
|
|
3409
4827
|
});
|
|
4828
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3410
4829
|
}
|
|
3411
|
-
await this.maybeReflect({
|
|
3412
|
-
record,
|
|
3413
|
-
observationTokens: record.observationTokenCount ?? 0,
|
|
3414
|
-
threadId,
|
|
3415
|
-
writer,
|
|
3416
|
-
messageList,
|
|
3417
|
-
requestContext
|
|
3418
|
-
});
|
|
3419
|
-
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3420
4830
|
}
|
|
3421
4831
|
}
|
|
3422
4832
|
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
await this.maybeReflect({
|
|
3429
|
-
record,
|
|
3430
|
-
observationTokens: obsTokens,
|
|
3431
|
-
threadId,
|
|
3432
|
-
writer,
|
|
3433
|
-
messageList,
|
|
3434
|
-
requestContext
|
|
3435
|
-
});
|
|
3436
|
-
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3437
|
-
} else if (this.isAsyncReflectionEnabled()) {
|
|
3438
|
-
const lockKey = this.getLockKey(threadId, resourceId);
|
|
3439
|
-
if (this.shouldTriggerAsyncReflection(obsTokens, lockKey, record)) {
|
|
3440
|
-
omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} above activation point, triggering async reflection`);
|
|
3441
|
-
await this.maybeAsyncReflect(record, obsTokens, writer, messageList, requestContext);
|
|
3442
|
-
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3443
|
-
}
|
|
3444
|
-
}
|
|
3445
|
-
}
|
|
3446
|
-
if (!readOnly) {
|
|
3447
|
-
const allMessages = messageList.get.all.db();
|
|
3448
|
-
const unobservedMessages = this.getUnobservedMessages(allMessages, record);
|
|
3449
|
-
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3450
|
-
const currentObservationTokens = record.observationTokenCount ?? 0;
|
|
3451
|
-
const thresholds = this.calculateObservationThresholds(
|
|
3452
|
-
allMessages,
|
|
3453
|
-
unobservedMessages,
|
|
3454
|
-
0,
|
|
3455
|
-
// pendingTokens not needed — allMessages covers context
|
|
3456
|
-
otherThreadTokens,
|
|
3457
|
-
currentObservationTokens,
|
|
3458
|
-
record
|
|
3459
|
-
);
|
|
3460
|
-
const { totalPendingTokens, threshold } = thresholds;
|
|
3461
|
-
const bufferedChunkTokens = this.getBufferedChunks(record).reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
3462
|
-
const unbufferedPendingTokens = Math.max(0, totalPendingTokens - bufferedChunkTokens);
|
|
3463
|
-
const stateSealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
|
|
3464
|
-
const staticSealedIds = _ObservationalMemory.sealedMessageIds.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
3465
|
-
const sealedIds = /* @__PURE__ */ new Set([...stateSealedIds, ...staticSealedIds]);
|
|
3466
|
-
state.sealedIds = sealedIds;
|
|
3467
|
-
const lockKey = this.getLockKey(threadId, resourceId);
|
|
3468
|
-
if (this.isAsyncObservationEnabled() && totalPendingTokens < threshold) {
|
|
3469
|
-
const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record, threshold);
|
|
3470
|
-
omDebug(
|
|
3471
|
-
`[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}`
|
|
3472
|
-
);
|
|
3473
|
-
if (shouldTrigger) {
|
|
3474
|
-
this.startAsyncBufferedObservation(
|
|
4833
|
+
if (stepNumber === 0 && !readOnly) {
|
|
4834
|
+
const obsTokens = record.observationTokenCount ?? 0;
|
|
4835
|
+
if (this.shouldReflect(obsTokens)) {
|
|
4836
|
+
omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} over reflectThreshold, triggering reflection`);
|
|
4837
|
+
await this.maybeReflect({
|
|
3475
4838
|
record,
|
|
4839
|
+
observationTokens: obsTokens,
|
|
3476
4840
|
threadId,
|
|
3477
|
-
unobservedMessages,
|
|
3478
|
-
lockKey,
|
|
3479
4841
|
writer,
|
|
3480
|
-
|
|
4842
|
+
messageList,
|
|
3481
4843
|
requestContext
|
|
3482
|
-
);
|
|
4844
|
+
});
|
|
4845
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
4846
|
+
} else if (this.isAsyncReflectionEnabled()) {
|
|
4847
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
4848
|
+
if (this.shouldTriggerAsyncReflection(obsTokens, lockKey, record)) {
|
|
4849
|
+
omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} above activation point, triggering async reflection`);
|
|
4850
|
+
await this.maybeAsyncReflect(record, obsTokens, writer, messageList, requestContext);
|
|
4851
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
4852
|
+
}
|
|
3483
4853
|
}
|
|
3484
|
-
}
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
4854
|
+
}
|
|
4855
|
+
let didThresholdCleanup = false;
|
|
4856
|
+
if (!readOnly) {
|
|
4857
|
+
let allMessages = messageList.get.all.db();
|
|
4858
|
+
let unobservedMessages = this.getUnobservedMessages(allMessages, record);
|
|
4859
|
+
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
4860
|
+
let currentObservationTokens = record.observationTokenCount ?? 0;
|
|
4861
|
+
let thresholds = await this.calculateObservationThresholds(
|
|
4862
|
+
allMessages,
|
|
4863
|
+
unobservedMessages,
|
|
4864
|
+
0,
|
|
4865
|
+
// pendingTokens not needed — allMessages covers context
|
|
4866
|
+
otherThreadTokens,
|
|
4867
|
+
currentObservationTokens,
|
|
4868
|
+
record
|
|
3488
4869
|
);
|
|
3489
|
-
|
|
3490
|
-
|
|
4870
|
+
let { totalPendingTokens, threshold } = thresholds;
|
|
4871
|
+
let bufferedChunkTokens = this.getBufferedChunks(record).reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
4872
|
+
let unbufferedPendingTokens = Math.max(0, totalPendingTokens - bufferedChunkTokens);
|
|
4873
|
+
const stateSealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
|
|
4874
|
+
const staticSealedIds = _ObservationalMemory.sealedMessageIds.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
4875
|
+
const sealedIds = /* @__PURE__ */ new Set([...stateSealedIds, ...staticSealedIds]);
|
|
4876
|
+
state.sealedIds = sealedIds;
|
|
4877
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
4878
|
+
if (this.isAsyncObservationEnabled() && totalPendingTokens < threshold) {
|
|
4879
|
+
const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record, threshold);
|
|
4880
|
+
omDebug(
|
|
4881
|
+
`[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}`
|
|
4882
|
+
);
|
|
4883
|
+
if (shouldTrigger) {
|
|
4884
|
+
void this.startAsyncBufferedObservation(
|
|
4885
|
+
record,
|
|
4886
|
+
threadId,
|
|
4887
|
+
unobservedMessages,
|
|
4888
|
+
lockKey,
|
|
4889
|
+
writer,
|
|
4890
|
+
unbufferedPendingTokens,
|
|
4891
|
+
requestContext
|
|
4892
|
+
);
|
|
4893
|
+
}
|
|
4894
|
+
} else if (this.isAsyncObservationEnabled()) {
|
|
4895
|
+
const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record, threshold);
|
|
4896
|
+
omDebug(
|
|
4897
|
+
`[OM:async-obs] atOrAboveThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, step=${stepNumber}, shouldTrigger=${shouldTrigger}`
|
|
4898
|
+
);
|
|
4899
|
+
if (shouldTrigger) {
|
|
4900
|
+
void this.startAsyncBufferedObservation(
|
|
4901
|
+
record,
|
|
4902
|
+
threadId,
|
|
4903
|
+
unobservedMessages,
|
|
4904
|
+
lockKey,
|
|
4905
|
+
writer,
|
|
4906
|
+
unbufferedPendingTokens,
|
|
4907
|
+
requestContext
|
|
4908
|
+
);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
if (stepNumber > 0) {
|
|
4912
|
+
await this.handlePerStepSave(messageList, sealedIds, threadId, resourceId, state);
|
|
4913
|
+
}
|
|
4914
|
+
if (stepNumber > 0 && totalPendingTokens >= threshold) {
|
|
4915
|
+
reproCaptureDetails.thresholdReached = true;
|
|
4916
|
+
const { observationSucceeded, updatedRecord, activatedMessageIds } = await this.handleThresholdReached(
|
|
4917
|
+
messageList,
|
|
3491
4918
|
record,
|
|
3492
4919
|
threadId,
|
|
3493
|
-
|
|
4920
|
+
resourceId,
|
|
4921
|
+
threshold,
|
|
3494
4922
|
lockKey,
|
|
3495
4923
|
writer,
|
|
3496
|
-
|
|
4924
|
+
abortSignal,
|
|
4925
|
+
abort,
|
|
3497
4926
|
requestContext
|
|
3498
4927
|
);
|
|
4928
|
+
if (observationSucceeded) {
|
|
4929
|
+
const observedIds = activatedMessageIds?.length ? activatedMessageIds : Array.isArray(updatedRecord.observedMessageIds) ? updatedRecord.observedMessageIds : void 0;
|
|
4930
|
+
const minRemaining = typeof this.observationConfig.bufferActivation === "number" ? resolveRetentionFloor(this.observationConfig.bufferActivation, threshold) : void 0;
|
|
4931
|
+
reproCaptureDetails.thresholdCleanup = {
|
|
4932
|
+
observationSucceeded,
|
|
4933
|
+
observedIdsCount: observedIds?.length ?? 0,
|
|
4934
|
+
observedIds,
|
|
4935
|
+
minRemaining,
|
|
4936
|
+
updatedRecordObservedIds: updatedRecord.observedMessageIds
|
|
4937
|
+
};
|
|
4938
|
+
omDebug(
|
|
4939
|
+
`[OM:cleanup] observedIds=${observedIds?.length ?? "undefined"}, ids=${observedIds?.join(",") ?? "none"}, updatedRecord.observedMessageIds=${JSON.stringify(updatedRecord.observedMessageIds)}, minRemaining=${minRemaining ?? "n/a"}`
|
|
4940
|
+
);
|
|
4941
|
+
await this.cleanupAfterObservation(
|
|
4942
|
+
messageList,
|
|
4943
|
+
sealedIds,
|
|
4944
|
+
threadId,
|
|
4945
|
+
resourceId,
|
|
4946
|
+
state,
|
|
4947
|
+
observedIds,
|
|
4948
|
+
minRemaining
|
|
4949
|
+
);
|
|
4950
|
+
didThresholdCleanup = true;
|
|
4951
|
+
if (activatedMessageIds?.length) {
|
|
4952
|
+
this.cleanupStaticMaps(threadId, resourceId, activatedMessageIds);
|
|
4953
|
+
}
|
|
4954
|
+
if (this.isAsyncObservationEnabled()) {
|
|
4955
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
4956
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, 0);
|
|
4957
|
+
this.storage.setBufferingObservationFlag(updatedRecord.id, false, 0).catch(() => {
|
|
4958
|
+
});
|
|
4959
|
+
omDebug(`[OM:threshold] post-activation boundary reset to 0`);
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
record = updatedRecord;
|
|
3499
4963
|
}
|
|
3500
4964
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
4965
|
+
await this.injectObservationsIntoContext(
|
|
4966
|
+
messageList,
|
|
4967
|
+
record,
|
|
4968
|
+
threadId,
|
|
4969
|
+
resourceId,
|
|
4970
|
+
unobservedContextBlocks,
|
|
4971
|
+
requestContext
|
|
4972
|
+
);
|
|
4973
|
+
if (!didThresholdCleanup) {
|
|
4974
|
+
await this.filterAlreadyObservedMessages(messageList, record, { useMarkerBoundaryPruning: stepNumber === 0 });
|
|
3503
4975
|
}
|
|
3504
|
-
|
|
3505
|
-
const
|
|
3506
|
-
|
|
3507
|
-
|
|
4976
|
+
{
|
|
4977
|
+
const freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
4978
|
+
const contextMessages = messageList.get.all.db();
|
|
4979
|
+
const freshUnobservedTokens = await this.tokenCounter.countMessagesAsync(contextMessages);
|
|
4980
|
+
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
4981
|
+
const currentObservationTokens = freshRecord.observationTokenCount ?? 0;
|
|
4982
|
+
const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
|
|
4983
|
+
const baseReflectionThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
4984
|
+
const isSharedBudget = typeof this.observationConfig.messageTokens !== "number";
|
|
4985
|
+
const totalBudget = isSharedBudget ? this.observationConfig.messageTokens.max : 0;
|
|
4986
|
+
const effectiveObservationTokensThreshold = isSharedBudget ? Math.max(totalBudget - threshold, 1e3) : baseReflectionThreshold;
|
|
4987
|
+
const totalPendingTokens = freshUnobservedTokens + otherThreadTokens;
|
|
4988
|
+
await this.emitStepProgress(
|
|
4989
|
+
writer,
|
|
3508
4990
|
threadId,
|
|
3509
4991
|
resourceId,
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
4992
|
+
stepNumber,
|
|
4993
|
+
freshRecord,
|
|
4994
|
+
{
|
|
4995
|
+
totalPendingTokens,
|
|
4996
|
+
threshold,
|
|
4997
|
+
effectiveObservationTokensThreshold
|
|
4998
|
+
},
|
|
4999
|
+
currentObservationTokens
|
|
3516
5000
|
);
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
`[OM:cleanup] observedIds=${observedIds?.length ?? "undefined"}, ids=${observedIds?.join(",") ?? "none"}, updatedRecord.observedMessageIds=${JSON.stringify(updatedRecord.observedMessageIds)}, minRemaining=${minRemaining ?? "n/a"}`
|
|
3522
|
-
);
|
|
3523
|
-
await this.cleanupAfterObservation(
|
|
3524
|
-
messageList,
|
|
3525
|
-
sealedIds,
|
|
5001
|
+
this.storage.setPendingMessageTokens(freshRecord.id, totalPendingTokens).catch(() => {
|
|
5002
|
+
});
|
|
5003
|
+
if (reproCaptureEnabled && preRecordSnapshot && preMessagesSnapshot && preSerializedMessageList) {
|
|
5004
|
+
writeProcessInputStepReproCapture({
|
|
3526
5005
|
threadId,
|
|
3527
5006
|
resourceId,
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
this.
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
5007
|
+
stepNumber,
|
|
5008
|
+
args,
|
|
5009
|
+
preRecord: preRecordSnapshot,
|
|
5010
|
+
postRecord: freshRecord,
|
|
5011
|
+
preMessages: preMessagesSnapshot,
|
|
5012
|
+
preBufferedChunks: this.getBufferedChunks(preRecordSnapshot),
|
|
5013
|
+
preContextTokenCount: this.tokenCounter.countMessages(preMessagesSnapshot),
|
|
5014
|
+
preSerializedMessageList,
|
|
5015
|
+
postBufferedChunks: this.getBufferedChunks(freshRecord),
|
|
5016
|
+
postContextTokenCount: this.tokenCounter.countMessages(contextMessages),
|
|
5017
|
+
messageList,
|
|
5018
|
+
details: {
|
|
5019
|
+
...reproCaptureDetails,
|
|
5020
|
+
totalPendingTokens,
|
|
5021
|
+
threshold,
|
|
5022
|
+
effectiveObservationTokensThreshold,
|
|
5023
|
+
currentObservationTokens,
|
|
5024
|
+
otherThreadTokens,
|
|
5025
|
+
contextMessageCount: contextMessages.length
|
|
5026
|
+
},
|
|
5027
|
+
debug: omDebug
|
|
5028
|
+
});
|
|
3542
5029
|
}
|
|
3543
|
-
record = updatedRecord;
|
|
3544
5030
|
}
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
messageList,
|
|
3548
|
-
record,
|
|
3549
|
-
threadId,
|
|
3550
|
-
resourceId,
|
|
3551
|
-
unobservedContextBlocks,
|
|
3552
|
-
requestContext
|
|
3553
|
-
);
|
|
3554
|
-
if (stepNumber === 0) {
|
|
3555
|
-
this.filterAlreadyObservedMessages(messageList, record);
|
|
3556
|
-
}
|
|
3557
|
-
{
|
|
3558
|
-
const freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
3559
|
-
const contextMessages = messageList.get.all.db();
|
|
3560
|
-
const freshUnobservedTokens = this.tokenCounter.countMessages(contextMessages);
|
|
3561
|
-
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3562
|
-
const currentObservationTokens = freshRecord.observationTokenCount ?? 0;
|
|
3563
|
-
const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
|
|
3564
|
-
const baseReflectionThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
3565
|
-
const isSharedBudget = typeof this.observationConfig.messageTokens !== "number";
|
|
3566
|
-
const totalBudget = isSharedBudget ? this.observationConfig.messageTokens.max : 0;
|
|
3567
|
-
const effectiveObservationTokensThreshold = isSharedBudget ? Math.max(totalBudget - threshold, 1e3) : baseReflectionThreshold;
|
|
3568
|
-
const totalPendingTokens = freshUnobservedTokens + otherThreadTokens;
|
|
3569
|
-
await this.emitStepProgress(
|
|
3570
|
-
writer,
|
|
3571
|
-
threadId,
|
|
3572
|
-
resourceId,
|
|
3573
|
-
stepNumber,
|
|
3574
|
-
freshRecord,
|
|
3575
|
-
{
|
|
3576
|
-
totalPendingTokens,
|
|
3577
|
-
threshold,
|
|
3578
|
-
effectiveObservationTokensThreshold
|
|
3579
|
-
},
|
|
3580
|
-
currentObservationTokens
|
|
3581
|
-
);
|
|
3582
|
-
this.storage.setPendingMessageTokens(freshRecord.id, totalPendingTokens).catch(() => {
|
|
3583
|
-
});
|
|
3584
|
-
}
|
|
3585
|
-
return messageList;
|
|
5031
|
+
return messageList;
|
|
5032
|
+
});
|
|
3586
5033
|
}
|
|
3587
5034
|
/**
|
|
3588
5035
|
* Save any unsaved messages at the end of the agent turn.
|
|
@@ -3599,30 +5046,35 @@ ${suggestedResponse}
|
|
|
3599
5046
|
return messageList;
|
|
3600
5047
|
}
|
|
3601
5048
|
const { threadId, resourceId } = context;
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
5049
|
+
return this.runWithTokenCounterModelContext(
|
|
5050
|
+
state.__omActorModelContext,
|
|
5051
|
+
async () => {
|
|
5052
|
+
const memoryContext = parseMemoryRequestContext(requestContext);
|
|
5053
|
+
const readOnly = memoryContext?.memoryConfig?.readOnly;
|
|
5054
|
+
if (readOnly) {
|
|
5055
|
+
return messageList;
|
|
5056
|
+
}
|
|
5057
|
+
const newInput = messageList.get.input.db();
|
|
5058
|
+
const newOutput = messageList.get.response.db();
|
|
5059
|
+
const messagesToSave = [...newInput, ...newOutput];
|
|
5060
|
+
omDebug(
|
|
5061
|
+
`[OM:processOutputResult] threadId=${threadId}, inputMsgs=${newInput.length}, responseMsgs=${newOutput.length}, totalToSave=${messagesToSave.length}, allMsgsInList=${messageList.get.all.db().length}`
|
|
5062
|
+
);
|
|
5063
|
+
if (messagesToSave.length === 0) {
|
|
5064
|
+
omDebug(`[OM:processOutputResult] nothing to save \u2014 all messages were already saved during per-step saves`);
|
|
5065
|
+
return messageList;
|
|
5066
|
+
}
|
|
5067
|
+
const sealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
|
|
5068
|
+
omDebug(
|
|
5069
|
+
`[OM:processOutputResult] saving ${messagesToSave.length} messages, sealedIds=${sealedIds.size}, ids=${messagesToSave.map((m) => m.id?.slice(0, 8)).join(",")}`
|
|
5070
|
+
);
|
|
5071
|
+
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
5072
|
+
omDebug(
|
|
5073
|
+
`[OM:processOutputResult] saved successfully, finalIds=${messagesToSave.map((m) => m.id?.slice(0, 8)).join(",")}`
|
|
5074
|
+
);
|
|
5075
|
+
return messageList;
|
|
5076
|
+
}
|
|
3624
5077
|
);
|
|
3625
|
-
return messageList;
|
|
3626
5078
|
}
|
|
3627
5079
|
/**
|
|
3628
5080
|
* Save messages to storage while preventing duplicate inserts for sealed messages.
|
|
@@ -3776,6 +5228,30 @@ ${formattedMessages}
|
|
|
3776
5228
|
}
|
|
3777
5229
|
return maxTime > 0 ? new Date(maxTime) : /* @__PURE__ */ new Date();
|
|
3778
5230
|
}
|
|
5231
|
+
/**
|
|
5232
|
+
* Compute a cursor pointing at the latest message by createdAt.
|
|
5233
|
+
* Used to derive a stable observation boundary for replay pruning.
|
|
5234
|
+
*/
|
|
5235
|
+
getLastObservedMessageCursor(messages) {
|
|
5236
|
+
let latest;
|
|
5237
|
+
for (const msg of messages) {
|
|
5238
|
+
if (!msg?.id || !msg.createdAt) continue;
|
|
5239
|
+
if (!latest || new Date(msg.createdAt).getTime() > new Date(latest.createdAt).getTime()) {
|
|
5240
|
+
latest = msg;
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
return latest ? { createdAt: new Date(latest.createdAt).toISOString(), id: latest.id } : void 0;
|
|
5244
|
+
}
|
|
5245
|
+
/**
|
|
5246
|
+
* Check if a message is at or before a cursor (by createdAt then id).
|
|
5247
|
+
*/
|
|
5248
|
+
isMessageAtOrBeforeCursor(msg, cursor) {
|
|
5249
|
+
if (!msg.createdAt) return false;
|
|
5250
|
+
const msgIso = new Date(msg.createdAt).toISOString();
|
|
5251
|
+
if (msgIso < cursor.createdAt) return true;
|
|
5252
|
+
if (msgIso === cursor.createdAt && msg.id === cursor.id) return true;
|
|
5253
|
+
return false;
|
|
5254
|
+
}
|
|
3779
5255
|
/**
|
|
3780
5256
|
* Wrap observations in a thread attribution tag.
|
|
3781
5257
|
* Used in resource scope to track which thread observations came from.
|
|
@@ -3874,7 +5350,7 @@ ${newThreadSection}`;
|
|
|
3874
5350
|
await this.storage.setObservingFlag(record.id, true);
|
|
3875
5351
|
registerOp(record.id, "observing");
|
|
3876
5352
|
const cycleId = crypto.randomUUID();
|
|
3877
|
-
const tokensToObserve = this.tokenCounter.
|
|
5353
|
+
const tokensToObserve = await this.tokenCounter.countMessagesAsync(unobservedMessages);
|
|
3878
5354
|
const lastMessage = unobservedMessages[unobservedMessages.length - 1];
|
|
3879
5355
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3880
5356
|
if (lastMessage?.id) {
|
|
@@ -3936,7 +5412,8 @@ ${result.observations}` : result.observations;
|
|
|
3936
5412
|
if (thread) {
|
|
3937
5413
|
const newMetadata = setThreadOMMetadata(thread.metadata, {
|
|
3938
5414
|
suggestedResponse: result.suggestedContinuation,
|
|
3939
|
-
currentTask: result.currentTask
|
|
5415
|
+
currentTask: result.currentTask,
|
|
5416
|
+
lastObservedMessageCursor: this.getLastObservedMessageCursor(messagesToObserve)
|
|
3940
5417
|
});
|
|
3941
5418
|
await this.storage.updateThread({
|
|
3942
5419
|
id: threadId,
|
|
@@ -3951,7 +5428,7 @@ ${result.observations}` : result.observations;
|
|
|
3951
5428
|
lastObservedAt,
|
|
3952
5429
|
observedMessageIds: allObservedIds
|
|
3953
5430
|
});
|
|
3954
|
-
const actualTokensObserved = this.tokenCounter.
|
|
5431
|
+
const actualTokensObserved = await this.tokenCounter.countMessagesAsync(messagesToObserve);
|
|
3955
5432
|
if (lastMessage?.id) {
|
|
3956
5433
|
const endMarker = createObservationEndMarker({
|
|
3957
5434
|
cycleId,
|
|
@@ -4032,9 +5509,9 @@ ${result.observations}` : result.observations;
|
|
|
4032
5509
|
* @param lockKey - Lock key for this scope
|
|
4033
5510
|
* @param writer - Optional stream writer for emitting buffering markers
|
|
4034
5511
|
*/
|
|
4035
|
-
startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext) {
|
|
5512
|
+
async startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext) {
|
|
4036
5513
|
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
4037
|
-
const currentTokens = contextWindowTokens ?? this.tokenCounter.
|
|
5514
|
+
const currentTokens = contextWindowTokens ?? await this.tokenCounter.countMessagesAsync(unobservedMessages) + (record.pendingMessageTokens ?? 0);
|
|
4038
5515
|
_ObservationalMemory.lastBufferedBoundary.set(bufferKey, currentTokens);
|
|
4039
5516
|
registerOp(record.id, "bufferingObservation");
|
|
4040
5517
|
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
@@ -4094,7 +5571,7 @@ ${result.observations}` : result.observations;
|
|
|
4094
5571
|
);
|
|
4095
5572
|
const bufferTokens = this.observationConfig.bufferTokens ?? 5e3;
|
|
4096
5573
|
const minNewTokens = bufferTokens / 2;
|
|
4097
|
-
const newTokens = this.tokenCounter.
|
|
5574
|
+
const newTokens = await this.tokenCounter.countMessagesAsync(candidateMessages);
|
|
4098
5575
|
if (newTokens < minNewTokens) {
|
|
4099
5576
|
return;
|
|
4100
5577
|
}
|
|
@@ -4115,7 +5592,7 @@ ${result.observations}` : result.observations;
|
|
|
4115
5592
|
}
|
|
4116
5593
|
const cycleId = `buffer-obs-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
4117
5594
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4118
|
-
const tokensToBuffer = this.tokenCounter.
|
|
5595
|
+
const tokensToBuffer = await this.tokenCounter.countMessagesAsync(messagesToBuffer);
|
|
4119
5596
|
if (writer) {
|
|
4120
5597
|
const startMarker = createBufferingStartMarker({
|
|
4121
5598
|
cycleId,
|
|
@@ -4193,7 +5670,7 @@ ${result.observations}` : result.observations;
|
|
|
4193
5670
|
}
|
|
4194
5671
|
const newTokenCount = this.tokenCounter.countObservations(newObservations);
|
|
4195
5672
|
const newMessageIds = messagesToBuffer.map((m) => m.id);
|
|
4196
|
-
const messageTokens = this.tokenCounter.
|
|
5673
|
+
const messageTokens = await this.tokenCounter.countMessagesAsync(messagesToBuffer);
|
|
4197
5674
|
const maxMessageTimestamp = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
4198
5675
|
const lastObservedAt = new Date(maxMessageTimestamp.getTime() + 1);
|
|
4199
5676
|
await this.storage.updateBufferedObservations({
|
|
@@ -4211,7 +5688,7 @@ ${result.observations}` : result.observations;
|
|
|
4211
5688
|
lastBufferedAtTime: lastObservedAt
|
|
4212
5689
|
});
|
|
4213
5690
|
if (writer) {
|
|
4214
|
-
const tokensBuffered = this.tokenCounter.
|
|
5691
|
+
const tokensBuffered = await this.tokenCounter.countMessagesAsync(messagesToBuffer);
|
|
4215
5692
|
const updatedRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
4216
5693
|
const updatedChunks = this.getBufferedChunks(updatedRecord);
|
|
4217
5694
|
const totalBufferedTokens = updatedChunks.reduce((sum, c) => sum + (c.tokenCount ?? 0), 0) || newTokenCount;
|
|
@@ -4280,14 +5757,14 @@ ${bufferedObservations}`;
|
|
|
4280
5757
|
if (!freshRecord) {
|
|
4281
5758
|
return { success: false };
|
|
4282
5759
|
}
|
|
4283
|
-
const
|
|
4284
|
-
if (!
|
|
5760
|
+
const rawFreshChunks = this.getBufferedChunks(freshRecord);
|
|
5761
|
+
if (!rawFreshChunks.length) {
|
|
4285
5762
|
return { success: false };
|
|
4286
5763
|
}
|
|
4287
5764
|
const messageTokensThreshold = getMaxThreshold(this.observationConfig.messageTokens);
|
|
4288
5765
|
let effectivePendingTokens = currentPendingTokens;
|
|
4289
5766
|
if (messageList) {
|
|
4290
|
-
effectivePendingTokens = this.tokenCounter.
|
|
5767
|
+
effectivePendingTokens = await this.tokenCounter.countMessagesAsync(messageList.get.all.db());
|
|
4291
5768
|
if (effectivePendingTokens < messageTokensThreshold) {
|
|
4292
5769
|
omDebug(
|
|
4293
5770
|
`[OM:tryActivate] skipping activation: freshPendingTokens=${effectivePendingTokens} < threshold=${messageTokensThreshold}`
|
|
@@ -4295,6 +5772,7 @@ ${bufferedObservations}`;
|
|
|
4295
5772
|
return { success: false };
|
|
4296
5773
|
}
|
|
4297
5774
|
}
|
|
5775
|
+
const freshChunks = messageList ? this.refreshBufferedChunkMessageTokens(rawFreshChunks, messageList) : rawFreshChunks;
|
|
4298
5776
|
const bufferActivation = this.observationConfig.bufferActivation ?? 0.7;
|
|
4299
5777
|
const activationRatio = resolveActivationRatio(bufferActivation, messageTokensThreshold);
|
|
4300
5778
|
const forceMaxActivation = !!(this.observationConfig.blockAfter && effectivePendingTokens >= this.observationConfig.blockAfter);
|
|
@@ -4322,7 +5800,8 @@ ${bufferedObservations}`;
|
|
|
4322
5800
|
activationRatio,
|
|
4323
5801
|
messageTokensThreshold,
|
|
4324
5802
|
currentPendingTokens: effectivePendingTokens,
|
|
4325
|
-
forceMaxActivation
|
|
5803
|
+
forceMaxActivation,
|
|
5804
|
+
bufferedChunks: freshChunks
|
|
4326
5805
|
});
|
|
4327
5806
|
omDebug(
|
|
4328
5807
|
`[OM:tryActivate] swapResult: chunksActivated=${activationResult.chunksActivated}, tokensActivated=${activationResult.messageTokensActivated}, obsTokensActivated=${activationResult.observationTokensActivated}, activatedCycleIds=${activationResult.activatedCycleIds.join(",")}`
|
|
@@ -4650,10 +6129,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
4650
6129
|
const threshold = getMaxThreshold(this.observationConfig.messageTokens);
|
|
4651
6130
|
const threadTokenCounts = /* @__PURE__ */ new Map();
|
|
4652
6131
|
for (const [threadId, msgs] of messagesByThread) {
|
|
4653
|
-
|
|
4654
|
-
for (const msg of msgs) {
|
|
4655
|
-
tokens += this.tokenCounter.countMessage(msg);
|
|
4656
|
-
}
|
|
6132
|
+
const tokens = await this.tokenCounter.countMessagesAsync(msgs);
|
|
4657
6133
|
threadTokenCounts.set(threadId, tokens);
|
|
4658
6134
|
}
|
|
4659
6135
|
const threadsBySize = Array.from(messagesByThread.keys()).sort((a, b) => {
|
|
@@ -4710,7 +6186,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
4710
6186
|
const allThreadIds = Array.from(threadsWithMessages.keys());
|
|
4711
6187
|
for (const [threadId, msgs] of threadsWithMessages) {
|
|
4712
6188
|
const lastMessage = msgs[msgs.length - 1];
|
|
4713
|
-
const tokensToObserve = this.tokenCounter.
|
|
6189
|
+
const tokensToObserve = await this.tokenCounter.countMessagesAsync(msgs);
|
|
4714
6190
|
threadTokensToObserve.set(threadId, tokensToObserve);
|
|
4715
6191
|
if (lastMessage?.id) {
|
|
4716
6192
|
const startMarker = createObservationStartMarker({
|
|
@@ -4802,7 +6278,8 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
4802
6278
|
const newMetadata = setThreadOMMetadata(thread.metadata, {
|
|
4803
6279
|
lastObservedAt: threadLastObservedAt.toISOString(),
|
|
4804
6280
|
suggestedResponse: result.suggestedContinuation,
|
|
4805
|
-
currentTask: result.currentTask
|
|
6281
|
+
currentTask: result.currentTask,
|
|
6282
|
+
lastObservedMessageCursor: this.getLastObservedMessageCursor(threadMessages)
|
|
4806
6283
|
});
|
|
4807
6284
|
await this.storage.updateThread({
|
|
4808
6285
|
id: threadId,
|
|
@@ -4845,7 +6322,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
4845
6322
|
const { threadId, threadMessages, result } = obsResult;
|
|
4846
6323
|
const lastMessage = threadMessages[threadMessages.length - 1];
|
|
4847
6324
|
if (lastMessage?.id) {
|
|
4848
|
-
const tokensObserved = threadTokensToObserve.get(threadId) ?? this.tokenCounter.
|
|
6325
|
+
const tokensObserved = threadTokensToObserve.get(threadId) ?? await this.tokenCounter.countMessagesAsync(threadMessages);
|
|
4849
6326
|
const endMarker = createObservationEndMarker({
|
|
4850
6327
|
cycleId,
|
|
4851
6328
|
operationType: "observation",
|
|
@@ -5096,7 +6573,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
5096
6573
|
const currentMessages = messages ?? [];
|
|
5097
6574
|
if (!this.meetsObservationThreshold({
|
|
5098
6575
|
record: freshRecord,
|
|
5099
|
-
unobservedTokens: this.tokenCounter.
|
|
6576
|
+
unobservedTokens: await this.tokenCounter.countMessagesAsync(currentMessages)
|
|
5100
6577
|
})) {
|
|
5101
6578
|
return;
|
|
5102
6579
|
}
|
|
@@ -5124,7 +6601,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
5124
6601
|
}
|
|
5125
6602
|
if (!this.meetsObservationThreshold({
|
|
5126
6603
|
record: freshRecord,
|
|
5127
|
-
unobservedTokens: this.tokenCounter.
|
|
6604
|
+
unobservedTokens: await this.tokenCounter.countMessagesAsync(unobservedMessages)
|
|
5128
6605
|
})) {
|
|
5129
6606
|
return;
|
|
5130
6607
|
}
|
|
@@ -5241,5 +6718,5 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
5241
6718
|
};
|
|
5242
6719
|
|
|
5243
6720
|
export { OBSERVATIONAL_MEMORY_DEFAULTS, OBSERVATION_CONTEXT_INSTRUCTIONS, OBSERVATION_CONTEXT_PROMPT, OBSERVATION_CONTINUATION_HINT, OBSERVER_SYSTEM_PROMPT, ObservationalMemory, TokenCounter, buildObserverPrompt, buildObserverSystemPrompt, extractCurrentTask, formatMessagesForObserver, hasCurrentTaskSection, optimizeObservationsForContext, parseObserverOutput };
|
|
5244
|
-
//# sourceMappingURL=chunk-
|
|
5245
|
-
//# sourceMappingURL=chunk-
|
|
6721
|
+
//# sourceMappingURL=chunk-3CM4XQJO.js.map
|
|
6722
|
+
//# sourceMappingURL=chunk-3CM4XQJO.js.map
|