@mastra/memory 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -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 +24 -24
- package/dist/docs/assets/SOURCE_MAP.json +25 -25
- package/dist/docs/references/docs-agents-agent-approval.md +3 -3
- package/dist/docs/references/docs-agents-agent-memory.md +3 -3
- package/dist/docs/references/docs-agents-network-approval.md +5 -2
- package/dist/docs/references/docs-agents-networks.md +2 -2
- package/dist/docs/references/docs-agents-supervisor-agents.md +3 -3
- package/dist/docs/references/docs-memory-memory-processors.md +9 -9
- package/dist/docs/references/docs-memory-message-history.md +4 -4
- package/dist/docs/references/docs-memory-observational-memory.md +11 -7
- package/dist/docs/references/docs-memory-semantic-recall.md +9 -9
- package/dist/docs/references/docs-memory-storage.md +1 -1
- package/dist/docs/references/docs-memory-working-memory.md +20 -20
- package/dist/docs/references/reference-core-getMemory.md +1 -1
- package/dist/docs/references/reference-core-listMemory.md +1 -1
- package/dist/docs/references/reference-memory-clone-utilities.md +7 -7
- package/dist/docs/references/reference-memory-cloneThread.md +5 -5
- package/dist/docs/references/reference-memory-createThread.md +1 -1
- package/dist/docs/references/reference-memory-getThreadById.md +1 -1
- package/dist/docs/references/reference-memory-listThreads.md +3 -3
- package/dist/docs/references/reference-memory-memory-class.md +1 -1
- package/dist/docs/references/reference-memory-observational-memory.md +8 -6
- package/dist/docs/references/reference-processors-token-limiter-processor.md +2 -2
- package/dist/docs/references/reference-storage-dynamodb.md +7 -7
- package/dist/docs/references/reference-storage-libsql.md +1 -1
- package/dist/docs/references/reference-storage-mongodb.md +6 -6
- package/dist/docs/references/reference-storage-postgresql.md +7 -7
- package/dist/docs/references/reference-storage-upstash.md +4 -4
- package/dist/docs/references/reference-vectors-libsql.md +15 -15
- package/dist/docs/references/reference-vectors-mongodb.md +18 -18
- package/dist/docs/references/reference-vectors-pg.md +23 -21
- package/dist/docs/references/reference-vectors-upstash.md +15 -15
- 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 +16 -11
- package/dist/chunk-D6II7EP4.cjs.map +0 -1
- package/dist/chunk-GBBQIJQF.js.map +0 -1
|
@@ -3,17 +3,21 @@
|
|
|
3
3
|
var fs = require('fs');
|
|
4
4
|
var path = require('path');
|
|
5
5
|
var agent = require('@mastra/core/agent');
|
|
6
|
+
var features = require('@mastra/core/features');
|
|
6
7
|
var llm = require('@mastra/core/llm');
|
|
7
8
|
var memory = require('@mastra/core/memory');
|
|
8
9
|
var processors = require('@mastra/core/processors');
|
|
9
10
|
var xxhash = require('xxhash-wasm');
|
|
10
11
|
var crypto$1 = require('crypto');
|
|
12
|
+
var async_hooks = require('async_hooks');
|
|
13
|
+
var imageSize = require('image-size');
|
|
11
14
|
var lite = require('js-tiktoken/lite');
|
|
12
15
|
var o200k_base = require('js-tiktoken/ranks/o200k_base');
|
|
13
16
|
|
|
14
17
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
18
|
|
|
16
19
|
var xxhash__default = /*#__PURE__*/_interopDefault(xxhash);
|
|
20
|
+
var imageSize__default = /*#__PURE__*/_interopDefault(imageSize);
|
|
17
21
|
var o200k_base__default = /*#__PURE__*/_interopDefault(o200k_base);
|
|
18
22
|
|
|
19
23
|
// src/processors/observational-memory/observational-memory.ts
|
|
@@ -669,45 +673,177 @@ User messages are extremely important. If the user asks a question or gives a ne
|
|
|
669
673
|
${instruction}` : ""}`;
|
|
670
674
|
}
|
|
671
675
|
var OBSERVER_SYSTEM_PROMPT = buildObserverSystemPrompt();
|
|
672
|
-
|
|
676
|
+
var OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
677
|
+
"png",
|
|
678
|
+
"jpg",
|
|
679
|
+
"jpeg",
|
|
680
|
+
"webp",
|
|
681
|
+
"gif",
|
|
682
|
+
"bmp",
|
|
683
|
+
"tiff",
|
|
684
|
+
"tif",
|
|
685
|
+
"heic",
|
|
686
|
+
"heif",
|
|
687
|
+
"avif"
|
|
688
|
+
]);
|
|
689
|
+
function formatObserverTimestamp(createdAt) {
|
|
690
|
+
return createdAt ? new Date(createdAt).toLocaleString("en-US", {
|
|
691
|
+
year: "numeric",
|
|
692
|
+
month: "short",
|
|
693
|
+
day: "numeric",
|
|
694
|
+
hour: "numeric",
|
|
695
|
+
minute: "2-digit",
|
|
696
|
+
hour12: true
|
|
697
|
+
}) : "";
|
|
698
|
+
}
|
|
699
|
+
function getObserverPathExtension(value) {
|
|
700
|
+
const normalized = value.split("#", 1)[0]?.split("?", 1)[0] ?? value;
|
|
701
|
+
const match = normalized.match(/\.([a-z0-9]+)$/i);
|
|
702
|
+
return match?.[1]?.toLowerCase();
|
|
703
|
+
}
|
|
704
|
+
function hasObserverImageFilenameExtension(filename) {
|
|
705
|
+
return typeof filename === "string" && OBSERVER_IMAGE_FILE_EXTENSIONS.has(getObserverPathExtension(filename) ?? "");
|
|
706
|
+
}
|
|
707
|
+
function isImageLikeObserverFilePart(part) {
|
|
708
|
+
if (part.type !== "file") {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
if (typeof part.mimeType === "string" && part.mimeType.toLowerCase().startsWith("image/")) {
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
if (typeof part.data === "string" && part.data.startsWith("data:image/")) {
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
if (part.data instanceof URL && hasObserverImageFilenameExtension(part.data.pathname)) {
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
if (typeof part.data === "string") {
|
|
721
|
+
try {
|
|
722
|
+
const url = new URL(part.data);
|
|
723
|
+
if ((url.protocol === "http:" || url.protocol === "https:") && hasObserverImageFilenameExtension(url.pathname)) {
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return hasObserverImageFilenameExtension(part.filename);
|
|
730
|
+
}
|
|
731
|
+
function toObserverInputAttachmentPart(part) {
|
|
732
|
+
if (part.type === "image") {
|
|
733
|
+
return {
|
|
734
|
+
type: "image",
|
|
735
|
+
image: part.image,
|
|
736
|
+
mimeType: part.mimeType,
|
|
737
|
+
providerOptions: part.providerOptions,
|
|
738
|
+
providerMetadata: part.providerMetadata,
|
|
739
|
+
experimental_providerMetadata: part.experimental_providerMetadata
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
if (isImageLikeObserverFilePart(part)) {
|
|
743
|
+
return {
|
|
744
|
+
type: "image",
|
|
745
|
+
image: part.data,
|
|
746
|
+
mimeType: part.mimeType,
|
|
747
|
+
providerOptions: part.providerOptions,
|
|
748
|
+
providerMetadata: part.providerMetadata,
|
|
749
|
+
experimental_providerMetadata: part.experimental_providerMetadata
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
return {
|
|
753
|
+
type: "file",
|
|
754
|
+
data: part.data,
|
|
755
|
+
mimeType: part.mimeType,
|
|
756
|
+
filename: part.filename,
|
|
757
|
+
providerOptions: part.providerOptions,
|
|
758
|
+
providerMetadata: part.providerMetadata,
|
|
759
|
+
experimental_providerMetadata: part.experimental_providerMetadata
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function resolveObserverAttachmentLabel(part) {
|
|
763
|
+
if (part.filename?.trim()) {
|
|
764
|
+
return part.filename.trim();
|
|
765
|
+
}
|
|
766
|
+
const asset = part.type === "image" ? part.image : part.data;
|
|
767
|
+
if (typeof asset !== "string" || asset.startsWith("data:")) {
|
|
768
|
+
return part.mimeType;
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
const url = new URL(asset);
|
|
772
|
+
const basename = url.pathname.split("/").filter(Boolean).pop();
|
|
773
|
+
return basename ? decodeURIComponent(basename) : part.mimeType;
|
|
774
|
+
} catch {
|
|
775
|
+
return part.mimeType;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function formatObserverAttachmentPlaceholder(part, counter) {
|
|
779
|
+
const attachmentType = part.type === "image" || isImageLikeObserverFilePart(part) ? "Image" : "File";
|
|
780
|
+
const attachmentId = attachmentType === "Image" ? counter.nextImageId++ : counter.nextFileId++;
|
|
781
|
+
const label = resolveObserverAttachmentLabel(part);
|
|
782
|
+
return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
|
|
783
|
+
}
|
|
784
|
+
function formatObserverMessage(msg, counter, options) {
|
|
673
785
|
const maxLen = options?.maxPartLength;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
content = msg.content.parts.map((part) => {
|
|
690
|
-
if (part.type === "text") return maybeTruncate(part.text, maxLen);
|
|
691
|
-
if (part.type === "tool-invocation") {
|
|
692
|
-
const inv = part.toolInvocation;
|
|
693
|
-
if (inv.state === "result") {
|
|
694
|
-
const resultStr = JSON.stringify(inv.result, null, 2);
|
|
695
|
-
return `[Tool Result: ${inv.toolName}]
|
|
786
|
+
const timestamp = formatObserverTimestamp(msg.createdAt);
|
|
787
|
+
const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
|
|
788
|
+
const timestampStr = timestamp ? ` (${timestamp})` : "";
|
|
789
|
+
const attachments = [];
|
|
790
|
+
let content = "";
|
|
791
|
+
if (typeof msg.content === "string") {
|
|
792
|
+
content = maybeTruncate(msg.content, maxLen);
|
|
793
|
+
} else if (msg.content?.parts && Array.isArray(msg.content.parts) && msg.content.parts.length > 0) {
|
|
794
|
+
content = msg.content.parts.map((part) => {
|
|
795
|
+
if (part.type === "text") return maybeTruncate(part.text, maxLen);
|
|
796
|
+
if (part.type === "tool-invocation") {
|
|
797
|
+
const inv = part.toolInvocation;
|
|
798
|
+
if (inv.state === "result") {
|
|
799
|
+
const resultStr = JSON.stringify(inv.result, null, 2);
|
|
800
|
+
return `[Tool Result: ${inv.toolName}]
|
|
696
801
|
${maybeTruncate(resultStr, maxLen)}`;
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
802
|
+
}
|
|
803
|
+
const argsStr = JSON.stringify(inv.args, null, 2);
|
|
804
|
+
return `[Tool Call: ${inv.toolName}]
|
|
700
805
|
${maybeTruncate(argsStr, maxLen)}`;
|
|
806
|
+
}
|
|
807
|
+
const partType = part.type;
|
|
808
|
+
if (partType === "image" || partType === "file") {
|
|
809
|
+
const attachment = part;
|
|
810
|
+
const inputAttachment = toObserverInputAttachmentPart(attachment);
|
|
811
|
+
if (inputAttachment) {
|
|
812
|
+
attachments.push(inputAttachment);
|
|
701
813
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
814
|
+
return formatObserverAttachmentPlaceholder(attachment, counter);
|
|
815
|
+
}
|
|
816
|
+
if (partType?.startsWith("data-")) return "";
|
|
817
|
+
return "";
|
|
818
|
+
}).filter(Boolean).join("\n");
|
|
819
|
+
} else if (msg.content?.content) {
|
|
820
|
+
content = maybeTruncate(msg.content.content, maxLen);
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
text: `**${role}${timestampStr}:**
|
|
824
|
+
${content}`,
|
|
825
|
+
attachments
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
function formatMessagesForObserver(messages, options) {
|
|
829
|
+
const counter = { nextImageId: 1, nextFileId: 1 };
|
|
830
|
+
return messages.map((msg) => formatObserverMessage(msg, counter, options).text).join("\n\n---\n\n");
|
|
831
|
+
}
|
|
832
|
+
function buildObserverHistoryMessage(messages) {
|
|
833
|
+
const counter = { nextImageId: 1, nextFileId: 1 };
|
|
834
|
+
const content = [{ type: "text", text: "## New Message History to Observe\n\n" }];
|
|
835
|
+
messages.forEach((message, index) => {
|
|
836
|
+
const formatted = formatObserverMessage(message, counter);
|
|
837
|
+
content.push({ type: "text", text: formatted.text });
|
|
838
|
+
content.push(...formatted.attachments);
|
|
839
|
+
if (index < messages.length - 1) {
|
|
840
|
+
content.push({ type: "text", text: "\n\n---\n\n" });
|
|
707
841
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
842
|
+
});
|
|
843
|
+
return {
|
|
844
|
+
role: "user",
|
|
845
|
+
content
|
|
846
|
+
};
|
|
711
847
|
}
|
|
712
848
|
function maybeTruncate(str, maxLen) {
|
|
713
849
|
if (!maxLen || str.length <= maxLen) return str;
|
|
@@ -716,20 +852,42 @@ function maybeTruncate(str, maxLen) {
|
|
|
716
852
|
return `${truncated}
|
|
717
853
|
... [truncated ${remaining} characters]`;
|
|
718
854
|
}
|
|
719
|
-
function
|
|
720
|
-
const
|
|
721
|
-
|
|
855
|
+
function buildMultiThreadObserverHistoryMessage(messagesByThread, threadOrder) {
|
|
856
|
+
const counter = { nextImageId: 1, nextFileId: 1 };
|
|
857
|
+
const content = [
|
|
858
|
+
{
|
|
859
|
+
type: "text",
|
|
860
|
+
text: `## New Message History to Observe
|
|
861
|
+
|
|
862
|
+
The following messages are from ${threadOrder.length} different conversation threads. Each thread is wrapped in a <thread id="..."> tag.
|
|
863
|
+
|
|
864
|
+
`
|
|
865
|
+
}
|
|
866
|
+
];
|
|
867
|
+
threadOrder.forEach((threadId, threadIndex) => {
|
|
722
868
|
const messages = messagesByThread.get(threadId);
|
|
723
|
-
if (!messages || messages.length === 0)
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
869
|
+
if (!messages || messages.length === 0) return;
|
|
870
|
+
content.push({ type: "text", text: `<thread id="${threadId}">
|
|
871
|
+
` });
|
|
872
|
+
messages.forEach((message, messageIndex) => {
|
|
873
|
+
const formatted = formatObserverMessage(message, counter);
|
|
874
|
+
content.push({ type: "text", text: formatted.text });
|
|
875
|
+
content.push(...formatted.attachments);
|
|
876
|
+
if (messageIndex < messages.length - 1) {
|
|
877
|
+
content.push({ type: "text", text: "\n\n---\n\n" });
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
content.push({ type: "text", text: "\n</thread>" });
|
|
881
|
+
if (threadIndex < threadOrder.length - 1) {
|
|
882
|
+
content.push({ type: "text", text: "\n\n" });
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
return {
|
|
886
|
+
role: "user",
|
|
887
|
+
content
|
|
888
|
+
};
|
|
730
889
|
}
|
|
731
|
-
function
|
|
732
|
-
const formattedMessages = formatMultiThreadMessagesForObserver(messagesByThread, threadOrder);
|
|
890
|
+
function buildMultiThreadObserverTaskPrompt(existingObservations) {
|
|
733
891
|
let prompt = "";
|
|
734
892
|
if (existingObservations) {
|
|
735
893
|
prompt += `## Previous Observations
|
|
@@ -741,15 +899,6 @@ ${existingObservations}
|
|
|
741
899
|
`;
|
|
742
900
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
743
901
|
}
|
|
744
|
-
prompt += `## New Message History to Observe
|
|
745
|
-
|
|
746
|
-
The following messages are from ${threadOrder.length} different conversation threads. Each thread is wrapped in a <thread id="..."> tag.
|
|
747
|
-
|
|
748
|
-
${formattedMessages}
|
|
749
|
-
|
|
750
|
-
---
|
|
751
|
-
|
|
752
|
-
`;
|
|
753
902
|
prompt += `## Your Task
|
|
754
903
|
|
|
755
904
|
`;
|
|
@@ -826,8 +975,7 @@ function parseMultiThreadObserverOutput(output) {
|
|
|
826
975
|
rawOutput: output
|
|
827
976
|
};
|
|
828
977
|
}
|
|
829
|
-
function
|
|
830
|
-
const formattedMessages = formatMessagesForObserver(messagesToObserve);
|
|
978
|
+
function buildObserverTaskPrompt(existingObservations, options) {
|
|
831
979
|
let prompt = "";
|
|
832
980
|
if (existingObservations) {
|
|
833
981
|
prompt += `## Previous Observations
|
|
@@ -839,13 +987,6 @@ ${existingObservations}
|
|
|
839
987
|
`;
|
|
840
988
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
841
989
|
}
|
|
842
|
-
prompt += `## New Message History to Observe
|
|
843
|
-
|
|
844
|
-
${formattedMessages}
|
|
845
|
-
|
|
846
|
-
---
|
|
847
|
-
|
|
848
|
-
`;
|
|
849
990
|
prompt += `## Your Task
|
|
850
991
|
|
|
851
992
|
`;
|
|
@@ -857,6 +998,16 @@ IMPORTANT: Do NOT include <current-task> or <suggested-response> sections in you
|
|
|
857
998
|
}
|
|
858
999
|
return prompt;
|
|
859
1000
|
}
|
|
1001
|
+
function buildObserverPrompt(existingObservations, messagesToObserve, options) {
|
|
1002
|
+
const formattedMessages = formatMessagesForObserver(messagesToObserve);
|
|
1003
|
+
return `## New Message History to Observe
|
|
1004
|
+
|
|
1005
|
+
${formattedMessages}
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
${buildObserverTaskPrompt(existingObservations, options)}`;
|
|
1010
|
+
}
|
|
860
1011
|
function parseObserverOutput(output) {
|
|
861
1012
|
if (detectDegenerateRepetition(output)) {
|
|
862
1013
|
return {
|
|
@@ -1228,6 +1379,140 @@ function extractReflectorListItems(content) {
|
|
|
1228
1379
|
function validateCompression(reflectedTokens, targetThreshold) {
|
|
1229
1380
|
return reflectedTokens < targetThreshold;
|
|
1230
1381
|
}
|
|
1382
|
+
var OM_REPRO_CAPTURE_DIR = process.env.OM_REPRO_CAPTURE_DIR ?? ".mastra-om-repro";
|
|
1383
|
+
function sanitizeCapturePathSegment(value) {
|
|
1384
|
+
const sanitized = value.replace(/[\\/]+/g, "_").replace(/\.{2,}/g, "_").trim();
|
|
1385
|
+
return sanitized.length > 0 ? sanitized : "unknown-thread";
|
|
1386
|
+
}
|
|
1387
|
+
function isOmReproCaptureEnabled() {
|
|
1388
|
+
return process.env.OM_REPRO_CAPTURE === "1";
|
|
1389
|
+
}
|
|
1390
|
+
function safeCaptureJson(value) {
|
|
1391
|
+
return JSON.parse(
|
|
1392
|
+
JSON.stringify(value, (_key, current) => {
|
|
1393
|
+
if (typeof current === "bigint") return current.toString();
|
|
1394
|
+
if (typeof current === "function") return "[function]";
|
|
1395
|
+
if (current instanceof Error) return { name: current.name, message: current.message, stack: current.stack };
|
|
1396
|
+
if (current instanceof Set) return { __type: "Set", values: Array.from(current.values()) };
|
|
1397
|
+
if (current instanceof Map) return { __type: "Map", entries: Array.from(current.entries()) };
|
|
1398
|
+
return current;
|
|
1399
|
+
})
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
function buildReproMessageFingerprint(message) {
|
|
1403
|
+
const createdAt = message.createdAt instanceof Date ? message.createdAt.toISOString() : message.createdAt ? new Date(message.createdAt).toISOString() : "";
|
|
1404
|
+
return JSON.stringify({
|
|
1405
|
+
role: message.role,
|
|
1406
|
+
createdAt,
|
|
1407
|
+
content: message.content
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
function inferReproIdRemap(preMessages, postMessages) {
|
|
1411
|
+
const preByFingerprint = /* @__PURE__ */ new Map();
|
|
1412
|
+
const postByFingerprint = /* @__PURE__ */ new Map();
|
|
1413
|
+
for (const message of preMessages) {
|
|
1414
|
+
if (!message.id) continue;
|
|
1415
|
+
const fingerprint = buildReproMessageFingerprint(message);
|
|
1416
|
+
const list = preByFingerprint.get(fingerprint) ?? [];
|
|
1417
|
+
list.push(message.id);
|
|
1418
|
+
preByFingerprint.set(fingerprint, list);
|
|
1419
|
+
}
|
|
1420
|
+
for (const message of postMessages) {
|
|
1421
|
+
if (!message.id) continue;
|
|
1422
|
+
const fingerprint = buildReproMessageFingerprint(message);
|
|
1423
|
+
const list = postByFingerprint.get(fingerprint) ?? [];
|
|
1424
|
+
list.push(message.id);
|
|
1425
|
+
postByFingerprint.set(fingerprint, list);
|
|
1426
|
+
}
|
|
1427
|
+
const remap = [];
|
|
1428
|
+
for (const [fingerprint, preIds] of preByFingerprint.entries()) {
|
|
1429
|
+
const postIds = postByFingerprint.get(fingerprint);
|
|
1430
|
+
if (!postIds || preIds.length !== 1 || postIds.length !== 1) continue;
|
|
1431
|
+
const fromId = preIds[0];
|
|
1432
|
+
const toId = postIds[0];
|
|
1433
|
+
if (!fromId || !toId || fromId === toId) {
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
remap.push({ fromId, toId, fingerprint });
|
|
1437
|
+
}
|
|
1438
|
+
return remap;
|
|
1439
|
+
}
|
|
1440
|
+
function writeProcessInputStepReproCapture(params) {
|
|
1441
|
+
if (!isOmReproCaptureEnabled()) {
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
try {
|
|
1445
|
+
const sanitizedThreadId = sanitizeCapturePathSegment(params.threadId);
|
|
1446
|
+
const runId = `${Date.now()}-step-${params.stepNumber}-${crypto$1.randomUUID()}`;
|
|
1447
|
+
const captureDir = path.join(process.cwd(), OM_REPRO_CAPTURE_DIR, sanitizedThreadId, runId);
|
|
1448
|
+
fs.mkdirSync(captureDir, { recursive: true });
|
|
1449
|
+
const contextMessages = params.messageList.get.all.db();
|
|
1450
|
+
const memoryContext = memory.parseMemoryRequestContext(params.args.requestContext);
|
|
1451
|
+
const preMessageIds = new Set(params.preMessages.map((message) => message.id));
|
|
1452
|
+
const postMessageIds = new Set(contextMessages.map((message) => message.id));
|
|
1453
|
+
const removedMessageIds = params.preMessages.map((message) => message.id).filter((id) => Boolean(id) && !postMessageIds.has(id));
|
|
1454
|
+
const addedMessageIds = contextMessages.map((message) => message.id).filter((id) => Boolean(id) && !preMessageIds.has(id));
|
|
1455
|
+
const idRemap = inferReproIdRemap(params.preMessages, contextMessages);
|
|
1456
|
+
const rawState = params.args.state ?? {};
|
|
1457
|
+
const inputPayload = safeCaptureJson({
|
|
1458
|
+
stepNumber: params.stepNumber,
|
|
1459
|
+
threadId: params.threadId,
|
|
1460
|
+
resourceId: params.resourceId,
|
|
1461
|
+
readOnly: memoryContext?.memoryConfig?.readOnly,
|
|
1462
|
+
messageCount: contextMessages.length,
|
|
1463
|
+
messageIds: contextMessages.map((message) => message.id),
|
|
1464
|
+
stateKeys: Object.keys(rawState),
|
|
1465
|
+
state: rawState,
|
|
1466
|
+
args: {
|
|
1467
|
+
messages: params.args.messages,
|
|
1468
|
+
steps: params.args.steps,
|
|
1469
|
+
systemMessages: params.args.systemMessages,
|
|
1470
|
+
retryCount: params.args.retryCount,
|
|
1471
|
+
tools: params.args.tools,
|
|
1472
|
+
toolChoice: params.args.toolChoice,
|
|
1473
|
+
activeTools: params.args.activeTools,
|
|
1474
|
+
providerOptions: params.args.providerOptions,
|
|
1475
|
+
modelSettings: params.args.modelSettings,
|
|
1476
|
+
structuredOutput: params.args.structuredOutput
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
const preStatePayload = safeCaptureJson({
|
|
1480
|
+
record: params.preRecord,
|
|
1481
|
+
bufferedChunks: params.preBufferedChunks,
|
|
1482
|
+
contextTokenCount: params.preContextTokenCount,
|
|
1483
|
+
messages: params.preMessages,
|
|
1484
|
+
messageList: params.preSerializedMessageList
|
|
1485
|
+
});
|
|
1486
|
+
const outputPayload = safeCaptureJson({
|
|
1487
|
+
details: params.details,
|
|
1488
|
+
messageDiff: {
|
|
1489
|
+
removedMessageIds,
|
|
1490
|
+
addedMessageIds,
|
|
1491
|
+
idRemap
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
const postStatePayload = safeCaptureJson({
|
|
1495
|
+
record: params.postRecord,
|
|
1496
|
+
bufferedChunks: params.postBufferedChunks,
|
|
1497
|
+
contextTokenCount: params.postContextTokenCount,
|
|
1498
|
+
messageCount: contextMessages.length,
|
|
1499
|
+
messageIds: contextMessages.map((message) => message.id),
|
|
1500
|
+
messages: contextMessages,
|
|
1501
|
+
messageList: params.messageList.serialize()
|
|
1502
|
+
});
|
|
1503
|
+
fs.writeFileSync(path.join(captureDir, "input.json"), `${JSON.stringify(inputPayload, null, 2)}
|
|
1504
|
+
`);
|
|
1505
|
+
fs.writeFileSync(path.join(captureDir, "pre-state.json"), `${JSON.stringify(preStatePayload, null, 2)}
|
|
1506
|
+
`);
|
|
1507
|
+
fs.writeFileSync(path.join(captureDir, "output.json"), `${JSON.stringify(outputPayload, null, 2)}
|
|
1508
|
+
`);
|
|
1509
|
+
fs.writeFileSync(path.join(captureDir, "post-state.json"), `${JSON.stringify(postStatePayload, null, 2)}
|
|
1510
|
+
`);
|
|
1511
|
+
params.debug?.(`[OM:repro-capture] wrote processInputStep capture to ${captureDir}`);
|
|
1512
|
+
} catch (error) {
|
|
1513
|
+
params.debug?.(`[OM:repro-capture] failed to write processInputStep capture: ${String(error)}`);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1231
1516
|
|
|
1232
1517
|
// src/processors/observational-memory/thresholds.ts
|
|
1233
1518
|
function getMaxThreshold(threshold) {
|
|
@@ -1313,14 +1598,90 @@ function calculateProjectedMessageRemoval(chunks, bufferActivation, messageToken
|
|
|
1313
1598
|
}
|
|
1314
1599
|
return bestBoundaryMessageTokens;
|
|
1315
1600
|
}
|
|
1316
|
-
var
|
|
1601
|
+
var GLOBAL_TIKTOKEN_KEY = "__mastraTiktoken";
|
|
1317
1602
|
function getDefaultEncoder() {
|
|
1318
|
-
|
|
1319
|
-
|
|
1603
|
+
const cached = globalThis[GLOBAL_TIKTOKEN_KEY];
|
|
1604
|
+
if (cached) return cached;
|
|
1605
|
+
const encoder = new lite.Tiktoken(o200k_base__default.default);
|
|
1606
|
+
globalThis[GLOBAL_TIKTOKEN_KEY] = encoder;
|
|
1607
|
+
return encoder;
|
|
1608
|
+
}
|
|
1609
|
+
var IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1610
|
+
"png",
|
|
1611
|
+
"jpg",
|
|
1612
|
+
"jpeg",
|
|
1613
|
+
"webp",
|
|
1614
|
+
"gif",
|
|
1615
|
+
"bmp",
|
|
1616
|
+
"tiff",
|
|
1617
|
+
"tif",
|
|
1618
|
+
"heic",
|
|
1619
|
+
"heif",
|
|
1620
|
+
"avif"
|
|
1621
|
+
]);
|
|
1622
|
+
var TOKEN_ESTIMATE_CACHE_VERSION = 5;
|
|
1623
|
+
var DEFAULT_IMAGE_ESTIMATOR = {
|
|
1624
|
+
baseTokens: 85,
|
|
1625
|
+
tileTokens: 170,
|
|
1626
|
+
fallbackTiles: 4
|
|
1627
|
+
};
|
|
1628
|
+
var GOOGLE_LEGACY_IMAGE_TOKENS_PER_TILE = 258;
|
|
1629
|
+
var GOOGLE_GEMINI_3_IMAGE_TOKENS_BY_RESOLUTION = {
|
|
1630
|
+
low: 280,
|
|
1631
|
+
medium: 560,
|
|
1632
|
+
high: 1120,
|
|
1633
|
+
ultra_high: 2240,
|
|
1634
|
+
unspecified: 1120
|
|
1635
|
+
};
|
|
1636
|
+
var ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
|
|
1637
|
+
var ANTHROPIC_IMAGE_MAX_LONG_EDGE = 1568;
|
|
1638
|
+
var GOOGLE_MEDIA_RESOLUTION_VALUES = /* @__PURE__ */ new Set([
|
|
1639
|
+
"low",
|
|
1640
|
+
"medium",
|
|
1641
|
+
"high",
|
|
1642
|
+
"ultra_high",
|
|
1643
|
+
"unspecified"
|
|
1644
|
+
]);
|
|
1645
|
+
var ATTACHMENT_COUNT_TIMEOUT_MS = 2e4;
|
|
1646
|
+
var REMOTE_IMAGE_PROBE_TIMEOUT_MS = 2500;
|
|
1647
|
+
var PROVIDER_API_KEY_ENV_VARS = {
|
|
1648
|
+
openai: ["OPENAI_API_KEY"],
|
|
1649
|
+
google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY"],
|
|
1650
|
+
anthropic: ["ANTHROPIC_API_KEY"]
|
|
1651
|
+
};
|
|
1652
|
+
function getPartMastraMetadata(part) {
|
|
1653
|
+
return part.providerMetadata?.mastra;
|
|
1654
|
+
}
|
|
1655
|
+
function ensurePartMastraMetadata(part) {
|
|
1656
|
+
const typedPart = part;
|
|
1657
|
+
typedPart.providerMetadata ??= {};
|
|
1658
|
+
typedPart.providerMetadata.mastra ??= {};
|
|
1659
|
+
return typedPart.providerMetadata.mastra;
|
|
1660
|
+
}
|
|
1661
|
+
function getContentMastraMetadata(content) {
|
|
1662
|
+
if (!content || typeof content !== "object") {
|
|
1663
|
+
return void 0;
|
|
1320
1664
|
}
|
|
1321
|
-
return
|
|
1665
|
+
return content.metadata?.mastra;
|
|
1666
|
+
}
|
|
1667
|
+
function ensureContentMastraMetadata(content) {
|
|
1668
|
+
if (!content || typeof content !== "object") {
|
|
1669
|
+
return void 0;
|
|
1670
|
+
}
|
|
1671
|
+
const typedContent = content;
|
|
1672
|
+
typedContent.metadata ??= {};
|
|
1673
|
+
typedContent.metadata.mastra ??= {};
|
|
1674
|
+
return typedContent.metadata.mastra;
|
|
1675
|
+
}
|
|
1676
|
+
function getMessageMastraMetadata(message) {
|
|
1677
|
+
return message.metadata?.mastra;
|
|
1678
|
+
}
|
|
1679
|
+
function ensureMessageMastraMetadata(message) {
|
|
1680
|
+
const typedMessage = message;
|
|
1681
|
+
typedMessage.metadata ??= {};
|
|
1682
|
+
typedMessage.metadata.mastra ??= {};
|
|
1683
|
+
return typedMessage.metadata.mastra;
|
|
1322
1684
|
}
|
|
1323
|
-
var TOKEN_ESTIMATE_CACHE_VERSION = 1;
|
|
1324
1685
|
function buildEstimateKey(kind, text) {
|
|
1325
1686
|
const payloadHash = crypto$1.createHash("sha1").update(text).digest("hex");
|
|
1326
1687
|
return `${kind}:${payloadHash}`;
|
|
@@ -1343,51 +1704,60 @@ function getCacheEntry(cache, key) {
|
|
|
1343
1704
|
if (isTokenEstimateEntry(cache)) {
|
|
1344
1705
|
return cache.key === key ? cache : void 0;
|
|
1345
1706
|
}
|
|
1346
|
-
|
|
1707
|
+
const keyedEntry = cache[key];
|
|
1708
|
+
return isTokenEstimateEntry(keyedEntry) ? keyedEntry : void 0;
|
|
1709
|
+
}
|
|
1710
|
+
function mergeCacheEntry(cache, key, entry) {
|
|
1711
|
+
if (isTokenEstimateEntry(cache)) {
|
|
1712
|
+
if (cache.key === key) {
|
|
1713
|
+
return entry;
|
|
1714
|
+
}
|
|
1715
|
+
return {
|
|
1716
|
+
[cache.key]: cache,
|
|
1717
|
+
[key]: entry
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
if (cache && typeof cache === "object") {
|
|
1721
|
+
return {
|
|
1722
|
+
...cache,
|
|
1723
|
+
[key]: entry
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
return entry;
|
|
1347
1727
|
}
|
|
1348
1728
|
function getPartCacheEntry(part, key) {
|
|
1349
|
-
|
|
1350
|
-
return getCacheEntry(cache, key);
|
|
1729
|
+
return getCacheEntry(getPartMastraMetadata(part)?.tokenEstimate, key);
|
|
1351
1730
|
}
|
|
1352
|
-
function setPartCacheEntry(part,
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1355
|
-
mutablePart.providerMetadata.mastra ??= {};
|
|
1356
|
-
mutablePart.providerMetadata.mastra.tokenEstimate = entry;
|
|
1731
|
+
function setPartCacheEntry(part, key, entry) {
|
|
1732
|
+
const mastraMetadata = ensurePartMastraMetadata(part);
|
|
1733
|
+
mastraMetadata.tokenEstimate = mergeCacheEntry(mastraMetadata.tokenEstimate, key, entry);
|
|
1357
1734
|
}
|
|
1358
1735
|
function getMessageCacheEntry(message, key) {
|
|
1359
|
-
const
|
|
1360
|
-
if (
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
}
|
|
1368
|
-
function setMessageCacheEntry(message, _key, entry) {
|
|
1369
|
-
const content = message.content;
|
|
1370
|
-
if (content && typeof content === "object") {
|
|
1371
|
-
content.metadata ??= {};
|
|
1372
|
-
content.metadata.mastra ??= {};
|
|
1373
|
-
content.metadata.mastra.tokenEstimate = entry;
|
|
1736
|
+
const contentLevelEntry = getCacheEntry(getContentMastraMetadata(message.content)?.tokenEstimate, key);
|
|
1737
|
+
if (contentLevelEntry) return contentLevelEntry;
|
|
1738
|
+
return getCacheEntry(getMessageMastraMetadata(message)?.tokenEstimate, key);
|
|
1739
|
+
}
|
|
1740
|
+
function setMessageCacheEntry(message, key, entry) {
|
|
1741
|
+
const contentMastraMetadata = ensureContentMastraMetadata(message.content);
|
|
1742
|
+
if (contentMastraMetadata) {
|
|
1743
|
+
contentMastraMetadata.tokenEstimate = mergeCacheEntry(contentMastraMetadata.tokenEstimate, key, entry);
|
|
1374
1744
|
return;
|
|
1375
1745
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
message.metadata.mastra.tokenEstimate = entry;
|
|
1746
|
+
const messageMastraMetadata = ensureMessageMastraMetadata(message);
|
|
1747
|
+
messageMastraMetadata.tokenEstimate = mergeCacheEntry(messageMastraMetadata.tokenEstimate, key, entry);
|
|
1379
1748
|
}
|
|
1380
1749
|
function serializePartForTokenCounting(part) {
|
|
1381
|
-
const
|
|
1750
|
+
const typedPart = part;
|
|
1751
|
+
const hasTokenEstimate = Boolean(typedPart.providerMetadata?.mastra?.tokenEstimate);
|
|
1382
1752
|
if (!hasTokenEstimate) {
|
|
1383
1753
|
return JSON.stringify(part);
|
|
1384
1754
|
}
|
|
1385
1755
|
const clonedPart = {
|
|
1386
|
-
...
|
|
1756
|
+
...typedPart,
|
|
1387
1757
|
providerMetadata: {
|
|
1388
|
-
...
|
|
1758
|
+
...typedPart.providerMetadata ?? {},
|
|
1389
1759
|
mastra: {
|
|
1390
|
-
...
|
|
1760
|
+
...typedPart.providerMetadata?.mastra ?? {}
|
|
1391
1761
|
}
|
|
1392
1762
|
}
|
|
1393
1763
|
};
|
|
@@ -1400,23 +1770,648 @@ function serializePartForTokenCounting(part) {
|
|
|
1400
1770
|
}
|
|
1401
1771
|
return JSON.stringify(clonedPart);
|
|
1402
1772
|
}
|
|
1773
|
+
function getFilenameFromAttachmentData(data) {
|
|
1774
|
+
const pathname = data instanceof URL ? data.pathname : typeof data === "string" && isHttpUrlString(data) ? (() => {
|
|
1775
|
+
try {
|
|
1776
|
+
return new URL(data).pathname;
|
|
1777
|
+
} catch {
|
|
1778
|
+
return void 0;
|
|
1779
|
+
}
|
|
1780
|
+
})() : void 0;
|
|
1781
|
+
const filename = pathname?.split("/").filter(Boolean).pop();
|
|
1782
|
+
return filename ? decodeURIComponent(filename) : void 0;
|
|
1783
|
+
}
|
|
1784
|
+
function serializeNonImageFilePartForTokenCounting(part) {
|
|
1785
|
+
const filename = getObjectValue(part, "filename");
|
|
1786
|
+
const inferredFilename = getFilenameFromAttachmentData(getObjectValue(part, "data"));
|
|
1787
|
+
return JSON.stringify({
|
|
1788
|
+
type: "file",
|
|
1789
|
+
mimeType: getObjectValue(part, "mimeType") ?? null,
|
|
1790
|
+
filename: typeof filename === "string" && filename.trim().length > 0 ? filename.trim() : inferredFilename ?? null
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1403
1793
|
function isValidCacheEntry(entry, expectedKey, expectedSource) {
|
|
1404
1794
|
return Boolean(
|
|
1405
1795
|
entry && entry.v === TOKEN_ESTIMATE_CACHE_VERSION && entry.source === expectedSource && entry.key === expectedKey && Number.isFinite(entry.tokens)
|
|
1406
1796
|
);
|
|
1407
1797
|
}
|
|
1798
|
+
function parseModelContext(model) {
|
|
1799
|
+
if (!model) return void 0;
|
|
1800
|
+
if (typeof model === "object") {
|
|
1801
|
+
return model.provider || model.modelId ? { provider: model.provider, modelId: model.modelId } : void 0;
|
|
1802
|
+
}
|
|
1803
|
+
const slashIndex = model.indexOf("/");
|
|
1804
|
+
if (slashIndex === -1) {
|
|
1805
|
+
return { modelId: model };
|
|
1806
|
+
}
|
|
1807
|
+
return {
|
|
1808
|
+
provider: model.slice(0, slashIndex),
|
|
1809
|
+
modelId: model.slice(slashIndex + 1)
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
function normalizeImageDetail(detail) {
|
|
1813
|
+
if (detail === "low" || detail === "high") return detail;
|
|
1814
|
+
return "auto";
|
|
1815
|
+
}
|
|
1816
|
+
function getObjectValue(value, key) {
|
|
1817
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1818
|
+
return value[key];
|
|
1819
|
+
}
|
|
1820
|
+
function resolveImageDetail(part) {
|
|
1821
|
+
const openAIProviderOptions = getObjectValue(getObjectValue(part, "providerOptions"), "openai");
|
|
1822
|
+
const openAIProviderMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "openai");
|
|
1823
|
+
const mastraMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "mastra");
|
|
1824
|
+
return normalizeImageDetail(
|
|
1825
|
+
getObjectValue(part, "detail") ?? getObjectValue(part, "imageDetail") ?? getObjectValue(openAIProviderOptions, "detail") ?? getObjectValue(openAIProviderOptions, "imageDetail") ?? getObjectValue(openAIProviderMetadata, "detail") ?? getObjectValue(openAIProviderMetadata, "imageDetail") ?? getObjectValue(mastraMetadata, "imageDetail")
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
function normalizeGoogleMediaResolution(value) {
|
|
1829
|
+
return typeof value === "string" && GOOGLE_MEDIA_RESOLUTION_VALUES.has(value) ? value : void 0;
|
|
1830
|
+
}
|
|
1831
|
+
function resolveGoogleMediaResolution(part) {
|
|
1832
|
+
const providerOptions = getObjectValue(getObjectValue(part, "providerOptions"), "google");
|
|
1833
|
+
const providerMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "google");
|
|
1834
|
+
const mastraMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "mastra");
|
|
1835
|
+
return normalizeGoogleMediaResolution(getObjectValue(part, "mediaResolution")) ?? normalizeGoogleMediaResolution(getObjectValue(providerOptions, "mediaResolution")) ?? normalizeGoogleMediaResolution(getObjectValue(providerMetadata, "mediaResolution")) ?? normalizeGoogleMediaResolution(getObjectValue(mastraMetadata, "mediaResolution")) ?? "unspecified";
|
|
1836
|
+
}
|
|
1837
|
+
function getFiniteNumber(value) {
|
|
1838
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1839
|
+
}
|
|
1840
|
+
function isHttpUrlString(value) {
|
|
1841
|
+
return typeof value === "string" && /^https?:\/\//i.test(value);
|
|
1842
|
+
}
|
|
1843
|
+
function isLikelyFilesystemPath(value) {
|
|
1844
|
+
return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("~/") || /^[A-Za-z]:[\\/]/.test(value) || value.includes("\\");
|
|
1845
|
+
}
|
|
1846
|
+
function isLikelyBase64Content(value) {
|
|
1847
|
+
if (value.length < 16 || value.length % 4 !== 0 || /\s/.test(value) || isLikelyFilesystemPath(value)) {
|
|
1848
|
+
return false;
|
|
1849
|
+
}
|
|
1850
|
+
return /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
1851
|
+
}
|
|
1852
|
+
function decodeImageBuffer(value) {
|
|
1853
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
1854
|
+
return value;
|
|
1855
|
+
}
|
|
1856
|
+
if (value instanceof Uint8Array) {
|
|
1857
|
+
return Buffer.from(value);
|
|
1858
|
+
}
|
|
1859
|
+
if (value instanceof ArrayBuffer) {
|
|
1860
|
+
return Buffer.from(value);
|
|
1861
|
+
}
|
|
1862
|
+
if (ArrayBuffer.isView(value)) {
|
|
1863
|
+
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
1864
|
+
}
|
|
1865
|
+
if (typeof value !== "string" || isHttpUrlString(value)) {
|
|
1866
|
+
return void 0;
|
|
1867
|
+
}
|
|
1868
|
+
if (value.startsWith("data:")) {
|
|
1869
|
+
const commaIndex = value.indexOf(",");
|
|
1870
|
+
if (commaIndex === -1) return void 0;
|
|
1871
|
+
const header = value.slice(0, commaIndex);
|
|
1872
|
+
const payload = value.slice(commaIndex + 1);
|
|
1873
|
+
if (/;base64/i.test(header)) {
|
|
1874
|
+
return Buffer.from(payload, "base64");
|
|
1875
|
+
}
|
|
1876
|
+
return Buffer.from(decodeURIComponent(payload), "utf8");
|
|
1877
|
+
}
|
|
1878
|
+
if (!isLikelyBase64Content(value)) {
|
|
1879
|
+
return void 0;
|
|
1880
|
+
}
|
|
1881
|
+
return Buffer.from(value, "base64");
|
|
1882
|
+
}
|
|
1883
|
+
function persistImageDimensions(part, dimensions) {
|
|
1884
|
+
const mastraMetadata = ensurePartMastraMetadata(part);
|
|
1885
|
+
mastraMetadata.imageDimensions = dimensions;
|
|
1886
|
+
}
|
|
1887
|
+
function resolveHttpAssetUrl(value) {
|
|
1888
|
+
if (value instanceof URL) {
|
|
1889
|
+
return value.toString();
|
|
1890
|
+
}
|
|
1891
|
+
if (typeof value === "string" && isHttpUrlString(value)) {
|
|
1892
|
+
return value;
|
|
1893
|
+
}
|
|
1894
|
+
return void 0;
|
|
1895
|
+
}
|
|
1896
|
+
async function resolveImageDimensionsAsync(part) {
|
|
1897
|
+
const existing = resolveImageDimensions(part);
|
|
1898
|
+
if (existing.width && existing.height) {
|
|
1899
|
+
return existing;
|
|
1900
|
+
}
|
|
1901
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
1902
|
+
const url = resolveHttpAssetUrl(asset);
|
|
1903
|
+
if (!url) {
|
|
1904
|
+
return existing;
|
|
1905
|
+
}
|
|
1906
|
+
try {
|
|
1907
|
+
const mod = await import('probe-image-size');
|
|
1908
|
+
const probeImageSize = mod.default;
|
|
1909
|
+
const probed = await probeImageSize(url, {
|
|
1910
|
+
open_timeout: REMOTE_IMAGE_PROBE_TIMEOUT_MS,
|
|
1911
|
+
response_timeout: REMOTE_IMAGE_PROBE_TIMEOUT_MS,
|
|
1912
|
+
read_timeout: REMOTE_IMAGE_PROBE_TIMEOUT_MS,
|
|
1913
|
+
follow_max: 2
|
|
1914
|
+
});
|
|
1915
|
+
const width = existing.width ?? getFiniteNumber(probed.width);
|
|
1916
|
+
const height = existing.height ?? getFiniteNumber(probed.height);
|
|
1917
|
+
if (!width || !height) {
|
|
1918
|
+
return existing;
|
|
1919
|
+
}
|
|
1920
|
+
const resolved = { width, height };
|
|
1921
|
+
persistImageDimensions(part, resolved);
|
|
1922
|
+
return resolved;
|
|
1923
|
+
} catch {
|
|
1924
|
+
return existing;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function resolveImageDimensions(part) {
|
|
1928
|
+
const mastraMetadata = getObjectValue(getObjectValue(part, "providerMetadata"), "mastra");
|
|
1929
|
+
const dimensions = getObjectValue(mastraMetadata, "imageDimensions");
|
|
1930
|
+
const width = getFiniteNumber(getObjectValue(part, "width")) ?? getFiniteNumber(getObjectValue(part, "imageWidth")) ?? getFiniteNumber(getObjectValue(dimensions, "width"));
|
|
1931
|
+
const height = getFiniteNumber(getObjectValue(part, "height")) ?? getFiniteNumber(getObjectValue(part, "imageHeight")) ?? getFiniteNumber(getObjectValue(dimensions, "height"));
|
|
1932
|
+
if (width && height) {
|
|
1933
|
+
return { width, height };
|
|
1934
|
+
}
|
|
1935
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
1936
|
+
const buffer = decodeImageBuffer(asset);
|
|
1937
|
+
if (!buffer) {
|
|
1938
|
+
return { width, height };
|
|
1939
|
+
}
|
|
1940
|
+
try {
|
|
1941
|
+
const measured = imageSize__default.default(buffer);
|
|
1942
|
+
const measuredWidth = getFiniteNumber(measured.width);
|
|
1943
|
+
const measuredHeight = getFiniteNumber(measured.height);
|
|
1944
|
+
if (!measuredWidth || !measuredHeight) {
|
|
1945
|
+
return { width, height };
|
|
1946
|
+
}
|
|
1947
|
+
const resolved = {
|
|
1948
|
+
width: width ?? measuredWidth,
|
|
1949
|
+
height: height ?? measuredHeight
|
|
1950
|
+
};
|
|
1951
|
+
persistImageDimensions(part, resolved);
|
|
1952
|
+
return resolved;
|
|
1953
|
+
} catch {
|
|
1954
|
+
return { width, height };
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
function getBase64Size(base64) {
|
|
1958
|
+
const sanitized = base64.replace(/\s+/g, "");
|
|
1959
|
+
const padding = sanitized.endsWith("==") ? 2 : sanitized.endsWith("=") ? 1 : 0;
|
|
1960
|
+
return Math.max(0, Math.floor(sanitized.length * 3 / 4) - padding);
|
|
1961
|
+
}
|
|
1962
|
+
function resolveImageSourceStats(image) {
|
|
1963
|
+
if (image instanceof URL) {
|
|
1964
|
+
return { source: "url" };
|
|
1965
|
+
}
|
|
1966
|
+
if (typeof image === "string") {
|
|
1967
|
+
if (isHttpUrlString(image)) {
|
|
1968
|
+
return { source: "url" };
|
|
1969
|
+
}
|
|
1970
|
+
if (image.startsWith("data:")) {
|
|
1971
|
+
const commaIndex = image.indexOf(",");
|
|
1972
|
+
const encoded = commaIndex === -1 ? "" : image.slice(commaIndex + 1);
|
|
1973
|
+
return {
|
|
1974
|
+
source: "data-uri",
|
|
1975
|
+
sizeBytes: getBase64Size(encoded)
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
return {
|
|
1979
|
+
source: "binary",
|
|
1980
|
+
sizeBytes: getBase64Size(image)
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(image)) {
|
|
1984
|
+
return { source: "binary", sizeBytes: image.length };
|
|
1985
|
+
}
|
|
1986
|
+
if (image instanceof Uint8Array) {
|
|
1987
|
+
return { source: "binary", sizeBytes: image.byteLength };
|
|
1988
|
+
}
|
|
1989
|
+
if (image instanceof ArrayBuffer) {
|
|
1990
|
+
return { source: "binary", sizeBytes: image.byteLength };
|
|
1991
|
+
}
|
|
1992
|
+
if (ArrayBuffer.isView(image)) {
|
|
1993
|
+
return { source: "binary", sizeBytes: image.byteLength };
|
|
1994
|
+
}
|
|
1995
|
+
return { source: "binary" };
|
|
1996
|
+
}
|
|
1997
|
+
function getPathnameExtension(value) {
|
|
1998
|
+
const normalized = value.split("#", 1)[0]?.split("?", 1)[0] ?? value;
|
|
1999
|
+
const match = normalized.match(/\.([a-z0-9]+)$/i);
|
|
2000
|
+
return match?.[1]?.toLowerCase();
|
|
2001
|
+
}
|
|
2002
|
+
function hasImageFilenameExtension(filename) {
|
|
2003
|
+
return typeof filename === "string" && IMAGE_FILE_EXTENSIONS.has(getPathnameExtension(filename) ?? "");
|
|
2004
|
+
}
|
|
2005
|
+
function isImageLikeFilePart(part) {
|
|
2006
|
+
if (getObjectValue(part, "type") !== "file") {
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
const mimeType = getObjectValue(part, "mimeType");
|
|
2010
|
+
if (typeof mimeType === "string" && mimeType.toLowerCase().startsWith("image/")) {
|
|
2011
|
+
return true;
|
|
2012
|
+
}
|
|
2013
|
+
const data = getObjectValue(part, "data");
|
|
2014
|
+
if (typeof data === "string" && data.startsWith("data:image/")) {
|
|
2015
|
+
return true;
|
|
2016
|
+
}
|
|
2017
|
+
if (data instanceof URL && hasImageFilenameExtension(data.pathname)) {
|
|
2018
|
+
return true;
|
|
2019
|
+
}
|
|
2020
|
+
if (isHttpUrlString(data)) {
|
|
2021
|
+
try {
|
|
2022
|
+
const url = new URL(data);
|
|
2023
|
+
if (hasImageFilenameExtension(url.pathname)) {
|
|
2024
|
+
return true;
|
|
2025
|
+
}
|
|
2026
|
+
} catch {
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
return hasImageFilenameExtension(getObjectValue(part, "filename"));
|
|
2030
|
+
}
|
|
2031
|
+
function resolveProviderId(modelContext) {
|
|
2032
|
+
return modelContext?.provider?.toLowerCase();
|
|
2033
|
+
}
|
|
2034
|
+
function resolveModelId(modelContext) {
|
|
2035
|
+
return modelContext?.modelId?.toLowerCase() ?? "";
|
|
2036
|
+
}
|
|
2037
|
+
function resolveOpenAIImageEstimatorConfig(modelContext) {
|
|
2038
|
+
const modelId = resolveModelId(modelContext);
|
|
2039
|
+
if (modelId.startsWith("gpt-5") || modelId === "gpt-5-chat-latest") {
|
|
2040
|
+
return { baseTokens: 70, tileTokens: 140, fallbackTiles: 4 };
|
|
2041
|
+
}
|
|
2042
|
+
if (modelId.startsWith("gpt-4o-mini")) {
|
|
2043
|
+
return { baseTokens: 2833, tileTokens: 5667, fallbackTiles: 1 };
|
|
2044
|
+
}
|
|
2045
|
+
if (modelId.startsWith("o1") || modelId.startsWith("o3")) {
|
|
2046
|
+
return { baseTokens: 75, tileTokens: 150, fallbackTiles: 4 };
|
|
2047
|
+
}
|
|
2048
|
+
if (modelId.includes("computer-use")) {
|
|
2049
|
+
return { baseTokens: 65, tileTokens: 129, fallbackTiles: 4 };
|
|
2050
|
+
}
|
|
2051
|
+
return DEFAULT_IMAGE_ESTIMATOR;
|
|
2052
|
+
}
|
|
2053
|
+
function isGoogleGemini3Model(modelContext) {
|
|
2054
|
+
return resolveProviderId(modelContext) === "google" && resolveModelId(modelContext).startsWith("gemini-3");
|
|
2055
|
+
}
|
|
2056
|
+
function scaleDimensionsForOpenAIHighDetail(width, height) {
|
|
2057
|
+
let scaledWidth = width;
|
|
2058
|
+
let scaledHeight = height;
|
|
2059
|
+
const largestSide = Math.max(scaledWidth, scaledHeight);
|
|
2060
|
+
if (largestSide > 2048) {
|
|
2061
|
+
const ratio = 2048 / largestSide;
|
|
2062
|
+
scaledWidth *= ratio;
|
|
2063
|
+
scaledHeight *= ratio;
|
|
2064
|
+
}
|
|
2065
|
+
const shortestSide = Math.min(scaledWidth, scaledHeight);
|
|
2066
|
+
if (shortestSide > 768) {
|
|
2067
|
+
const ratio = 768 / shortestSide;
|
|
2068
|
+
scaledWidth *= ratio;
|
|
2069
|
+
scaledHeight *= ratio;
|
|
2070
|
+
}
|
|
2071
|
+
return {
|
|
2072
|
+
width: Math.max(1, Math.round(scaledWidth)),
|
|
2073
|
+
height: Math.max(1, Math.round(scaledHeight))
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
function scaleDimensionsForAnthropic(width, height) {
|
|
2077
|
+
const largestSide = Math.max(width, height);
|
|
2078
|
+
if (largestSide <= ANTHROPIC_IMAGE_MAX_LONG_EDGE) {
|
|
2079
|
+
return { width, height };
|
|
2080
|
+
}
|
|
2081
|
+
const ratio = ANTHROPIC_IMAGE_MAX_LONG_EDGE / largestSide;
|
|
2082
|
+
return {
|
|
2083
|
+
width: Math.max(1, Math.round(width * ratio)),
|
|
2084
|
+
height: Math.max(1, Math.round(height * ratio))
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
function estimateOpenAIHighDetailTiles(dimensions, sourceStats, estimator) {
|
|
2088
|
+
if (dimensions.width && dimensions.height) {
|
|
2089
|
+
const scaled = scaleDimensionsForOpenAIHighDetail(dimensions.width, dimensions.height);
|
|
2090
|
+
return Math.max(1, Math.ceil(scaled.width / 512) * Math.ceil(scaled.height / 512));
|
|
2091
|
+
}
|
|
2092
|
+
if (sourceStats.sizeBytes !== void 0) {
|
|
2093
|
+
if (sourceStats.sizeBytes <= 512 * 1024) return 1;
|
|
2094
|
+
if (sourceStats.sizeBytes <= 2 * 1024 * 1024) return 4;
|
|
2095
|
+
if (sourceStats.sizeBytes <= 4 * 1024 * 1024) return 6;
|
|
2096
|
+
return 8;
|
|
2097
|
+
}
|
|
2098
|
+
return estimator.fallbackTiles;
|
|
2099
|
+
}
|
|
2100
|
+
function resolveEffectiveOpenAIImageDetail(detail, dimensions, sourceStats) {
|
|
2101
|
+
if (detail === "low" || detail === "high") return detail;
|
|
2102
|
+
if (dimensions.width && dimensions.height) {
|
|
2103
|
+
return Math.max(dimensions.width, dimensions.height) > 768 ? "high" : "low";
|
|
2104
|
+
}
|
|
2105
|
+
if (sourceStats.sizeBytes !== void 0) {
|
|
2106
|
+
return sourceStats.sizeBytes > 1024 * 1024 ? "high" : "low";
|
|
2107
|
+
}
|
|
2108
|
+
return "low";
|
|
2109
|
+
}
|
|
2110
|
+
function estimateLegacyGoogleImageTiles(dimensions) {
|
|
2111
|
+
if (!dimensions.width || !dimensions.height) return 1;
|
|
2112
|
+
return Math.max(1, Math.ceil(dimensions.width / 768) * Math.ceil(dimensions.height / 768));
|
|
2113
|
+
}
|
|
2114
|
+
function estimateAnthropicImageTokens(dimensions, sourceStats) {
|
|
2115
|
+
if (dimensions.width && dimensions.height) {
|
|
2116
|
+
const scaled = scaleDimensionsForAnthropic(dimensions.width, dimensions.height);
|
|
2117
|
+
return Math.max(1, Math.ceil(scaled.width * scaled.height * ANTHROPIC_IMAGE_TOKENS_PER_PIXEL));
|
|
2118
|
+
}
|
|
2119
|
+
if (sourceStats.sizeBytes !== void 0) {
|
|
2120
|
+
if (sourceStats.sizeBytes <= 512 * 1024) return 341;
|
|
2121
|
+
if (sourceStats.sizeBytes <= 2 * 1024 * 1024) return 1366;
|
|
2122
|
+
if (sourceStats.sizeBytes <= 4 * 1024 * 1024) return 2048;
|
|
2123
|
+
return 2731;
|
|
2124
|
+
}
|
|
2125
|
+
return 1600;
|
|
2126
|
+
}
|
|
2127
|
+
function estimateGoogleImageTokens(modelContext, part, dimensions) {
|
|
2128
|
+
if (isGoogleGemini3Model(modelContext)) {
|
|
2129
|
+
const mediaResolution = resolveGoogleMediaResolution(part);
|
|
2130
|
+
return {
|
|
2131
|
+
tokens: GOOGLE_GEMINI_3_IMAGE_TOKENS_BY_RESOLUTION[mediaResolution],
|
|
2132
|
+
mediaResolution
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
return {
|
|
2136
|
+
tokens: estimateLegacyGoogleImageTiles(dimensions) * GOOGLE_LEGACY_IMAGE_TOKENS_PER_TILE,
|
|
2137
|
+
mediaResolution: "unspecified"
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
function getProviderApiKey(provider) {
|
|
2141
|
+
for (const envVar of PROVIDER_API_KEY_ENV_VARS[provider] ?? []) {
|
|
2142
|
+
const value = process.env[envVar];
|
|
2143
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
2144
|
+
return value.trim();
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
return void 0;
|
|
2148
|
+
}
|
|
2149
|
+
function getAttachmentFilename(part) {
|
|
2150
|
+
const explicitFilename = getObjectValue(part, "filename");
|
|
2151
|
+
if (typeof explicitFilename === "string" && explicitFilename.trim().length > 0) {
|
|
2152
|
+
return explicitFilename.trim();
|
|
2153
|
+
}
|
|
2154
|
+
return getFilenameFromAttachmentData(getObjectValue(part, "data") ?? getObjectValue(part, "image"));
|
|
2155
|
+
}
|
|
2156
|
+
function getAttachmentMimeType(part, fallback) {
|
|
2157
|
+
const mimeType = getObjectValue(part, "mimeType");
|
|
2158
|
+
if (typeof mimeType === "string" && mimeType.trim().length > 0) {
|
|
2159
|
+
return mimeType.trim();
|
|
2160
|
+
}
|
|
2161
|
+
const asset = getObjectValue(part, "data") ?? getObjectValue(part, "image");
|
|
2162
|
+
if (typeof asset === "string" && asset.startsWith("data:")) {
|
|
2163
|
+
const semicolonIndex = asset.indexOf(";");
|
|
2164
|
+
const commaIndex = asset.indexOf(",");
|
|
2165
|
+
const endIndex = semicolonIndex === -1 ? commaIndex : Math.min(semicolonIndex, commaIndex);
|
|
2166
|
+
if (endIndex > 5) {
|
|
2167
|
+
return asset.slice(5, endIndex);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
return fallback;
|
|
2171
|
+
}
|
|
2172
|
+
function getAttachmentUrl(asset) {
|
|
2173
|
+
if (asset instanceof URL) {
|
|
2174
|
+
return asset.toString();
|
|
2175
|
+
}
|
|
2176
|
+
if (typeof asset === "string" && /^(https?:\/\/|data:)/i.test(asset)) {
|
|
2177
|
+
return asset;
|
|
2178
|
+
}
|
|
2179
|
+
return void 0;
|
|
2180
|
+
}
|
|
2181
|
+
function getAttachmentFingerprint(asset) {
|
|
2182
|
+
const url = getAttachmentUrl(asset);
|
|
2183
|
+
if (url) {
|
|
2184
|
+
return { url };
|
|
2185
|
+
}
|
|
2186
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2187
|
+
if (base64) {
|
|
2188
|
+
return { contentHash: crypto$1.createHash("sha1").update(base64).digest("hex") };
|
|
2189
|
+
}
|
|
2190
|
+
return {};
|
|
2191
|
+
}
|
|
2192
|
+
function encodeAttachmentBase64(asset) {
|
|
2193
|
+
if (typeof asset === "string") {
|
|
2194
|
+
if (asset.startsWith("data:")) {
|
|
2195
|
+
const commaIndex = asset.indexOf(",");
|
|
2196
|
+
return commaIndex === -1 ? void 0 : asset.slice(commaIndex + 1);
|
|
2197
|
+
}
|
|
2198
|
+
if (/^https?:\/\//i.test(asset)) {
|
|
2199
|
+
return void 0;
|
|
2200
|
+
}
|
|
2201
|
+
return asset;
|
|
2202
|
+
}
|
|
2203
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(asset)) {
|
|
2204
|
+
return asset.toString("base64");
|
|
2205
|
+
}
|
|
2206
|
+
if (asset instanceof Uint8Array) {
|
|
2207
|
+
return Buffer.from(asset).toString("base64");
|
|
2208
|
+
}
|
|
2209
|
+
if (asset instanceof ArrayBuffer) {
|
|
2210
|
+
return Buffer.from(asset).toString("base64");
|
|
2211
|
+
}
|
|
2212
|
+
if (ArrayBuffer.isView(asset)) {
|
|
2213
|
+
return Buffer.from(asset.buffer, asset.byteOffset, asset.byteLength).toString("base64");
|
|
2214
|
+
}
|
|
2215
|
+
return void 0;
|
|
2216
|
+
}
|
|
2217
|
+
function createTimeoutSignal(timeoutMs) {
|
|
2218
|
+
const controller = new AbortController();
|
|
2219
|
+
const timeout = setTimeout(
|
|
2220
|
+
() => controller.abort(new Error(`Attachment token counting timed out after ${timeoutMs}ms`)),
|
|
2221
|
+
timeoutMs
|
|
2222
|
+
);
|
|
2223
|
+
const cleanup = () => clearTimeout(timeout);
|
|
2224
|
+
controller.signal.addEventListener("abort", cleanup, { once: true });
|
|
2225
|
+
return { signal: controller.signal, cleanup };
|
|
2226
|
+
}
|
|
2227
|
+
function getNumericResponseField(value, paths) {
|
|
2228
|
+
for (const path of paths) {
|
|
2229
|
+
let current = value;
|
|
2230
|
+
for (const segment of path) {
|
|
2231
|
+
current = getObjectValue(current, segment);
|
|
2232
|
+
if (current === void 0) break;
|
|
2233
|
+
}
|
|
2234
|
+
if (typeof current === "number" && Number.isFinite(current)) {
|
|
2235
|
+
return current;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
return void 0;
|
|
2239
|
+
}
|
|
2240
|
+
function toOpenAIInputPart(part) {
|
|
2241
|
+
if (getObjectValue(part, "type") === "image" || isImageLikeFilePart(part)) {
|
|
2242
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
2243
|
+
const imageUrl = getAttachmentUrl(asset);
|
|
2244
|
+
if (imageUrl) {
|
|
2245
|
+
return { type: "input_image", image_url: imageUrl, detail: resolveImageDetail(part) };
|
|
2246
|
+
}
|
|
2247
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2248
|
+
if (!base64) return void 0;
|
|
2249
|
+
return {
|
|
2250
|
+
type: "input_image",
|
|
2251
|
+
image_url: `data:${getAttachmentMimeType(part, "image/png")};base64,${base64}`,
|
|
2252
|
+
detail: resolveImageDetail(part)
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
if (getObjectValue(part, "type") === "file") {
|
|
2256
|
+
const asset = getObjectValue(part, "data");
|
|
2257
|
+
const fileUrl = getAttachmentUrl(asset);
|
|
2258
|
+
return fileUrl ? {
|
|
2259
|
+
type: "input_file",
|
|
2260
|
+
file_url: fileUrl,
|
|
2261
|
+
filename: getAttachmentFilename(part) ?? "attachment"
|
|
2262
|
+
} : (() => {
|
|
2263
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2264
|
+
if (!base64) return void 0;
|
|
2265
|
+
return {
|
|
2266
|
+
type: "input_file",
|
|
2267
|
+
file_data: `data:${getAttachmentMimeType(part, "application/octet-stream")};base64,${base64}`,
|
|
2268
|
+
filename: getAttachmentFilename(part) ?? "attachment"
|
|
2269
|
+
};
|
|
2270
|
+
})();
|
|
2271
|
+
}
|
|
2272
|
+
return void 0;
|
|
2273
|
+
}
|
|
2274
|
+
function toAnthropicContentPart(part) {
|
|
2275
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
2276
|
+
const url = getAttachmentUrl(asset);
|
|
2277
|
+
if (getObjectValue(part, "type") === "image" || isImageLikeFilePart(part)) {
|
|
2278
|
+
return url && /^https?:\/\//i.test(url) ? { type: "image", source: { type: "url", url } } : (() => {
|
|
2279
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2280
|
+
if (!base64) return void 0;
|
|
2281
|
+
return {
|
|
2282
|
+
type: "image",
|
|
2283
|
+
source: { type: "base64", media_type: getAttachmentMimeType(part, "image/png"), data: base64 }
|
|
2284
|
+
};
|
|
2285
|
+
})();
|
|
2286
|
+
}
|
|
2287
|
+
if (getObjectValue(part, "type") === "file") {
|
|
2288
|
+
return url && /^https?:\/\//i.test(url) ? { type: "document", source: { type: "url", url } } : (() => {
|
|
2289
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2290
|
+
if (!base64) return void 0;
|
|
2291
|
+
return {
|
|
2292
|
+
type: "document",
|
|
2293
|
+
source: { type: "base64", media_type: getAttachmentMimeType(part, "application/pdf"), data: base64 }
|
|
2294
|
+
};
|
|
2295
|
+
})();
|
|
2296
|
+
}
|
|
2297
|
+
return void 0;
|
|
2298
|
+
}
|
|
2299
|
+
function toGooglePart(part) {
|
|
2300
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
2301
|
+
const url = getAttachmentUrl(asset);
|
|
2302
|
+
const mimeType = getAttachmentMimeType(
|
|
2303
|
+
part,
|
|
2304
|
+
getObjectValue(part, "type") === "file" && !isImageLikeFilePart(part) ? "application/pdf" : "image/png"
|
|
2305
|
+
);
|
|
2306
|
+
if (url && !url.startsWith("data:")) {
|
|
2307
|
+
return { fileData: { mimeType, fileUri: url } };
|
|
2308
|
+
}
|
|
2309
|
+
const base64 = encodeAttachmentBase64(asset);
|
|
2310
|
+
if (!base64) return void 0;
|
|
2311
|
+
return { inlineData: { mimeType, data: base64 } };
|
|
2312
|
+
}
|
|
2313
|
+
async function fetchOpenAIAttachmentTokenEstimate(modelId, part) {
|
|
2314
|
+
const apiKey = getProviderApiKey("openai");
|
|
2315
|
+
const inputPart = toOpenAIInputPart(part);
|
|
2316
|
+
if (!apiKey || !inputPart) return void 0;
|
|
2317
|
+
const { signal, cleanup } = createTimeoutSignal(ATTACHMENT_COUNT_TIMEOUT_MS);
|
|
2318
|
+
try {
|
|
2319
|
+
const response = await fetch("https://api.openai.com/v1/responses/input_tokens", {
|
|
2320
|
+
method: "POST",
|
|
2321
|
+
headers: {
|
|
2322
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2323
|
+
"Content-Type": "application/json"
|
|
2324
|
+
},
|
|
2325
|
+
body: JSON.stringify({
|
|
2326
|
+
model: modelId,
|
|
2327
|
+
input: [{ type: "message", role: "user", content: [inputPart] }]
|
|
2328
|
+
}),
|
|
2329
|
+
signal
|
|
2330
|
+
});
|
|
2331
|
+
if (!response.ok) return void 0;
|
|
2332
|
+
const body = await response.json();
|
|
2333
|
+
return getNumericResponseField(body, [
|
|
2334
|
+
["input_tokens"],
|
|
2335
|
+
["total_tokens"],
|
|
2336
|
+
["usage", "input_tokens"],
|
|
2337
|
+
["usage", "total_tokens"]
|
|
2338
|
+
]);
|
|
2339
|
+
} finally {
|
|
2340
|
+
cleanup();
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
async function fetchAnthropicAttachmentTokenEstimate(modelId, part) {
|
|
2344
|
+
const apiKey = getProviderApiKey("anthropic");
|
|
2345
|
+
const contentPart = toAnthropicContentPart(part);
|
|
2346
|
+
if (!apiKey || !contentPart) return void 0;
|
|
2347
|
+
const { signal, cleanup } = createTimeoutSignal(ATTACHMENT_COUNT_TIMEOUT_MS);
|
|
2348
|
+
try {
|
|
2349
|
+
const response = await fetch("https://api.anthropic.com/v1/messages/count_tokens", {
|
|
2350
|
+
method: "POST",
|
|
2351
|
+
headers: {
|
|
2352
|
+
"x-api-key": apiKey,
|
|
2353
|
+
"anthropic-version": "2023-06-01",
|
|
2354
|
+
"Content-Type": "application/json"
|
|
2355
|
+
},
|
|
2356
|
+
body: JSON.stringify({
|
|
2357
|
+
model: modelId,
|
|
2358
|
+
messages: [{ role: "user", content: [contentPart] }]
|
|
2359
|
+
}),
|
|
2360
|
+
signal
|
|
2361
|
+
});
|
|
2362
|
+
if (!response.ok) return void 0;
|
|
2363
|
+
const body = await response.json();
|
|
2364
|
+
return getNumericResponseField(body, [["input_tokens"]]);
|
|
2365
|
+
} finally {
|
|
2366
|
+
cleanup();
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
async function fetchGoogleAttachmentTokenEstimate(modelId, part) {
|
|
2370
|
+
const apiKey = getProviderApiKey("google");
|
|
2371
|
+
const googlePart = toGooglePart(part);
|
|
2372
|
+
if (!apiKey || !googlePart) return void 0;
|
|
2373
|
+
const { signal, cleanup } = createTimeoutSignal(ATTACHMENT_COUNT_TIMEOUT_MS);
|
|
2374
|
+
try {
|
|
2375
|
+
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${modelId}:countTokens`, {
|
|
2376
|
+
method: "POST",
|
|
2377
|
+
headers: {
|
|
2378
|
+
"x-goog-api-key": apiKey,
|
|
2379
|
+
"Content-Type": "application/json"
|
|
2380
|
+
},
|
|
2381
|
+
body: JSON.stringify({
|
|
2382
|
+
contents: [{ role: "user", parts: [googlePart] }]
|
|
2383
|
+
}),
|
|
2384
|
+
signal
|
|
2385
|
+
});
|
|
2386
|
+
if (!response.ok) return void 0;
|
|
2387
|
+
const body = await response.json();
|
|
2388
|
+
return getNumericResponseField(body, [["totalTokens"], ["total_tokens"]]);
|
|
2389
|
+
} finally {
|
|
2390
|
+
cleanup();
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
1408
2393
|
var TokenCounter = class _TokenCounter {
|
|
1409
2394
|
encoder;
|
|
1410
2395
|
cacheSource;
|
|
2396
|
+
defaultModelContext;
|
|
2397
|
+
modelContextStorage = new async_hooks.AsyncLocalStorage();
|
|
2398
|
+
inFlightAttachmentCounts = /* @__PURE__ */ new Map();
|
|
1411
2399
|
// Per-message overhead: accounts for role tokens, message framing, and separators.
|
|
1412
2400
|
// Empirically derived from OpenAI's token counting guide (3 tokens per message base +
|
|
1413
2401
|
// fractional overhead from name/role encoding). 3.8 is a practical average across models.
|
|
1414
2402
|
static TOKENS_PER_MESSAGE = 3.8;
|
|
1415
2403
|
// Conversation-level overhead: system prompt framing, reply priming tokens, etc.
|
|
1416
2404
|
static TOKENS_PER_CONVERSATION = 24;
|
|
1417
|
-
constructor(encoding) {
|
|
2405
|
+
constructor(encoding, options) {
|
|
1418
2406
|
this.encoder = encoding ? new lite.Tiktoken(encoding) : getDefaultEncoder();
|
|
1419
2407
|
this.cacheSource = `v${TOKEN_ESTIMATE_CACHE_VERSION}:${resolveEncodingId(encoding)}`;
|
|
2408
|
+
this.defaultModelContext = parseModelContext(options?.model);
|
|
2409
|
+
}
|
|
2410
|
+
runWithModelContext(model, fn) {
|
|
2411
|
+
return this.modelContextStorage.run(parseModelContext(model), fn);
|
|
2412
|
+
}
|
|
2413
|
+
getModelContext() {
|
|
2414
|
+
return this.modelContextStorage.getStore() ?? this.defaultModelContext;
|
|
1420
2415
|
}
|
|
1421
2416
|
/**
|
|
1422
2417
|
* Count tokens in a plain string
|
|
@@ -1440,6 +2435,20 @@ var TokenCounter = class _TokenCounter {
|
|
|
1440
2435
|
});
|
|
1441
2436
|
return tokens;
|
|
1442
2437
|
}
|
|
2438
|
+
readOrPersistFixedPartEstimate(part, kind, payload, tokens) {
|
|
2439
|
+
const key = buildEstimateKey(kind, payload);
|
|
2440
|
+
const cached = getPartCacheEntry(part, key);
|
|
2441
|
+
if (isValidCacheEntry(cached, key, this.cacheSource)) {
|
|
2442
|
+
return cached.tokens;
|
|
2443
|
+
}
|
|
2444
|
+
setPartCacheEntry(part, key, {
|
|
2445
|
+
v: TOKEN_ESTIMATE_CACHE_VERSION,
|
|
2446
|
+
source: this.cacheSource,
|
|
2447
|
+
key,
|
|
2448
|
+
tokens
|
|
2449
|
+
});
|
|
2450
|
+
return tokens;
|
|
2451
|
+
}
|
|
1443
2452
|
readOrPersistMessageEstimate(message, kind, payload) {
|
|
1444
2453
|
const key = buildEstimateKey(kind, payload);
|
|
1445
2454
|
const cached = getMessageCacheEntry(message, key);
|
|
@@ -1463,15 +2472,315 @@ var TokenCounter = class _TokenCounter {
|
|
|
1463
2472
|
usingStoredModelOutput: true
|
|
1464
2473
|
};
|
|
1465
2474
|
}
|
|
1466
|
-
return {
|
|
1467
|
-
value: invocationResult,
|
|
1468
|
-
usingStoredModelOutput: false
|
|
1469
|
-
};
|
|
2475
|
+
return {
|
|
2476
|
+
value: invocationResult,
|
|
2477
|
+
usingStoredModelOutput: false
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
estimateImageAssetTokens(part, asset, kind) {
|
|
2481
|
+
const modelContext = this.getModelContext();
|
|
2482
|
+
const provider = resolveProviderId(modelContext);
|
|
2483
|
+
const modelId = modelContext?.modelId ?? null;
|
|
2484
|
+
const detail = resolveImageDetail(part);
|
|
2485
|
+
const dimensions = resolveImageDimensions(part);
|
|
2486
|
+
const sourceStats = resolveImageSourceStats(asset);
|
|
2487
|
+
if (provider === "google") {
|
|
2488
|
+
const googleEstimate = estimateGoogleImageTokens(modelContext, part, dimensions);
|
|
2489
|
+
return {
|
|
2490
|
+
tokens: googleEstimate.tokens,
|
|
2491
|
+
cachePayload: JSON.stringify({
|
|
2492
|
+
kind,
|
|
2493
|
+
provider,
|
|
2494
|
+
modelId,
|
|
2495
|
+
estimator: isGoogleGemini3Model(modelContext) ? "google-gemini-3" : "google-legacy",
|
|
2496
|
+
mediaResolution: googleEstimate.mediaResolution,
|
|
2497
|
+
width: dimensions.width ?? null,
|
|
2498
|
+
height: dimensions.height ?? null,
|
|
2499
|
+
source: sourceStats.source,
|
|
2500
|
+
sizeBytes: sourceStats.sizeBytes ?? null,
|
|
2501
|
+
mimeType: getObjectValue(part, "mimeType") ?? null,
|
|
2502
|
+
filename: getObjectValue(part, "filename") ?? null
|
|
2503
|
+
})
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
if (provider === "anthropic") {
|
|
2507
|
+
return {
|
|
2508
|
+
tokens: estimateAnthropicImageTokens(dimensions, sourceStats),
|
|
2509
|
+
cachePayload: JSON.stringify({
|
|
2510
|
+
kind,
|
|
2511
|
+
provider,
|
|
2512
|
+
modelId,
|
|
2513
|
+
estimator: "anthropic",
|
|
2514
|
+
width: dimensions.width ?? null,
|
|
2515
|
+
height: dimensions.height ?? null,
|
|
2516
|
+
source: sourceStats.source,
|
|
2517
|
+
sizeBytes: sourceStats.sizeBytes ?? null,
|
|
2518
|
+
mimeType: getObjectValue(part, "mimeType") ?? null,
|
|
2519
|
+
filename: getObjectValue(part, "filename") ?? null
|
|
2520
|
+
})
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
const estimator = resolveOpenAIImageEstimatorConfig(modelContext);
|
|
2524
|
+
const effectiveDetail = resolveEffectiveOpenAIImageDetail(detail, dimensions, sourceStats);
|
|
2525
|
+
const tiles = effectiveDetail === "high" ? estimateOpenAIHighDetailTiles(dimensions, sourceStats, estimator) : 0;
|
|
2526
|
+
const tokens = estimator.baseTokens + tiles * estimator.tileTokens;
|
|
2527
|
+
return {
|
|
2528
|
+
tokens,
|
|
2529
|
+
cachePayload: JSON.stringify({
|
|
2530
|
+
kind,
|
|
2531
|
+
provider,
|
|
2532
|
+
modelId,
|
|
2533
|
+
estimator: provider === "openai" ? "openai" : "fallback",
|
|
2534
|
+
detail,
|
|
2535
|
+
effectiveDetail,
|
|
2536
|
+
width: dimensions.width ?? null,
|
|
2537
|
+
height: dimensions.height ?? null,
|
|
2538
|
+
source: sourceStats.source,
|
|
2539
|
+
sizeBytes: sourceStats.sizeBytes ?? null,
|
|
2540
|
+
mimeType: getObjectValue(part, "mimeType") ?? null,
|
|
2541
|
+
filename: getObjectValue(part, "filename") ?? null
|
|
2542
|
+
})
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
estimateImageTokens(part) {
|
|
2546
|
+
return this.estimateImageAssetTokens(part, part.image, "image");
|
|
2547
|
+
}
|
|
2548
|
+
estimateImageLikeFileTokens(part) {
|
|
2549
|
+
return this.estimateImageAssetTokens(part, part.data, "file");
|
|
2550
|
+
}
|
|
2551
|
+
countAttachmentPartSync(part) {
|
|
2552
|
+
if (part.type === "image") {
|
|
2553
|
+
const estimate = this.estimateImageTokens(part);
|
|
2554
|
+
return this.readOrPersistFixedPartEstimate(part, "image", estimate.cachePayload, estimate.tokens);
|
|
2555
|
+
}
|
|
2556
|
+
if (part.type === "file" && isImageLikeFilePart(part)) {
|
|
2557
|
+
const estimate = this.estimateImageLikeFileTokens(part);
|
|
2558
|
+
return this.readOrPersistFixedPartEstimate(part, "image-like-file", estimate.cachePayload, estimate.tokens);
|
|
2559
|
+
}
|
|
2560
|
+
if (part.type === "file") {
|
|
2561
|
+
return this.readOrPersistPartEstimate(part, "file-descriptor", serializeNonImageFilePartForTokenCounting(part));
|
|
2562
|
+
}
|
|
2563
|
+
return void 0;
|
|
2564
|
+
}
|
|
2565
|
+
buildRemoteAttachmentCachePayload(part) {
|
|
2566
|
+
const isImageAttachment = part.type === "image" || part.type === "file" && isImageLikeFilePart(part);
|
|
2567
|
+
const isNonImageFileAttachment = part.type === "file" && !isImageAttachment;
|
|
2568
|
+
if (!isImageAttachment && !isNonImageFileAttachment) {
|
|
2569
|
+
return void 0;
|
|
2570
|
+
}
|
|
2571
|
+
const modelContext = this.getModelContext();
|
|
2572
|
+
const provider = resolveProviderId(modelContext);
|
|
2573
|
+
const modelId = modelContext?.modelId ?? null;
|
|
2574
|
+
if (!provider || !modelId || !["openai", "google", "anthropic"].includes(provider)) {
|
|
2575
|
+
return void 0;
|
|
2576
|
+
}
|
|
2577
|
+
const asset = getObjectValue(part, "image") ?? getObjectValue(part, "data");
|
|
2578
|
+
const sourceStats = resolveImageSourceStats(asset);
|
|
2579
|
+
const fingerprint = getAttachmentFingerprint(asset);
|
|
2580
|
+
return JSON.stringify({
|
|
2581
|
+
strategy: "provider-endpoint",
|
|
2582
|
+
provider,
|
|
2583
|
+
modelId,
|
|
2584
|
+
type: getObjectValue(part, "type") ?? null,
|
|
2585
|
+
detail: isImageAttachment ? resolveImageDetail(part) : null,
|
|
2586
|
+
mediaResolution: provider === "google" && isImageAttachment ? resolveGoogleMediaResolution(part) : null,
|
|
2587
|
+
mimeType: getAttachmentMimeType(part, isNonImageFileAttachment ? "application/pdf" : "image/png"),
|
|
2588
|
+
filename: getAttachmentFilename(part) ?? null,
|
|
2589
|
+
source: sourceStats.source,
|
|
2590
|
+
sizeBytes: sourceStats.sizeBytes ?? null,
|
|
2591
|
+
assetUrl: fingerprint.url ?? null,
|
|
2592
|
+
assetHash: fingerprint.contentHash ?? null
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
async fetchProviderAttachmentTokenEstimate(part) {
|
|
2596
|
+
const modelContext = this.getModelContext();
|
|
2597
|
+
const provider = resolveProviderId(modelContext);
|
|
2598
|
+
const modelId = modelContext?.modelId;
|
|
2599
|
+
if (!provider || !modelId) return void 0;
|
|
2600
|
+
try {
|
|
2601
|
+
if (provider === "openai") {
|
|
2602
|
+
return await fetchOpenAIAttachmentTokenEstimate(modelId, part);
|
|
2603
|
+
}
|
|
2604
|
+
if (provider === "google") {
|
|
2605
|
+
return await fetchGoogleAttachmentTokenEstimate(modelId, part);
|
|
2606
|
+
}
|
|
2607
|
+
if (provider === "anthropic") {
|
|
2608
|
+
return await fetchAnthropicAttachmentTokenEstimate(modelId, part);
|
|
2609
|
+
}
|
|
2610
|
+
} catch {
|
|
2611
|
+
return void 0;
|
|
2612
|
+
}
|
|
2613
|
+
return void 0;
|
|
2614
|
+
}
|
|
2615
|
+
async countAttachmentPartAsync(part) {
|
|
2616
|
+
const isImageAttachment = part.type === "image" || part.type === "file" && isImageLikeFilePart(part);
|
|
2617
|
+
const remotePayload = this.buildRemoteAttachmentCachePayload(part);
|
|
2618
|
+
if (remotePayload) {
|
|
2619
|
+
const remoteKey = buildEstimateKey("attachment-provider", remotePayload);
|
|
2620
|
+
const cachedRemote = getPartCacheEntry(part, remoteKey);
|
|
2621
|
+
if (isValidCacheEntry(cachedRemote, remoteKey, this.cacheSource)) {
|
|
2622
|
+
return cachedRemote.tokens;
|
|
2623
|
+
}
|
|
2624
|
+
const existingRequest = this.inFlightAttachmentCounts.get(remoteKey);
|
|
2625
|
+
if (existingRequest) {
|
|
2626
|
+
const remoteTokens = await existingRequest;
|
|
2627
|
+
if (typeof remoteTokens === "number" && Number.isFinite(remoteTokens) && remoteTokens > 0) {
|
|
2628
|
+
setPartCacheEntry(part, remoteKey, {
|
|
2629
|
+
v: TOKEN_ESTIMATE_CACHE_VERSION,
|
|
2630
|
+
source: this.cacheSource,
|
|
2631
|
+
key: remoteKey,
|
|
2632
|
+
tokens: remoteTokens
|
|
2633
|
+
});
|
|
2634
|
+
return remoteTokens;
|
|
2635
|
+
}
|
|
2636
|
+
} else {
|
|
2637
|
+
const remoteRequest = this.fetchProviderAttachmentTokenEstimate(part);
|
|
2638
|
+
this.inFlightAttachmentCounts.set(remoteKey, remoteRequest);
|
|
2639
|
+
let remoteTokens;
|
|
2640
|
+
try {
|
|
2641
|
+
remoteTokens = await remoteRequest;
|
|
2642
|
+
} finally {
|
|
2643
|
+
this.inFlightAttachmentCounts.delete(remoteKey);
|
|
2644
|
+
}
|
|
2645
|
+
if (typeof remoteTokens === "number" && Number.isFinite(remoteTokens) && remoteTokens > 0) {
|
|
2646
|
+
setPartCacheEntry(part, remoteKey, {
|
|
2647
|
+
v: TOKEN_ESTIMATE_CACHE_VERSION,
|
|
2648
|
+
source: this.cacheSource,
|
|
2649
|
+
key: remoteKey,
|
|
2650
|
+
tokens: remoteTokens
|
|
2651
|
+
});
|
|
2652
|
+
return remoteTokens;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
if (isImageAttachment) {
|
|
2656
|
+
await resolveImageDimensionsAsync(part);
|
|
2657
|
+
}
|
|
2658
|
+
const fallbackPayload = JSON.stringify({
|
|
2659
|
+
...JSON.parse(remotePayload),
|
|
2660
|
+
strategy: "local-fallback",
|
|
2661
|
+
...isImageAttachment ? resolveImageDimensions(part) : {}
|
|
2662
|
+
});
|
|
2663
|
+
const fallbackKey = buildEstimateKey("attachment-provider", fallbackPayload);
|
|
2664
|
+
const cachedFallback = getPartCacheEntry(part, fallbackKey);
|
|
2665
|
+
if (isValidCacheEntry(cachedFallback, fallbackKey, this.cacheSource)) {
|
|
2666
|
+
return cachedFallback.tokens;
|
|
2667
|
+
}
|
|
2668
|
+
const localTokens2 = this.countAttachmentPartSync(part);
|
|
2669
|
+
if (localTokens2 === void 0) {
|
|
2670
|
+
return void 0;
|
|
2671
|
+
}
|
|
2672
|
+
setPartCacheEntry(part, fallbackKey, {
|
|
2673
|
+
v: TOKEN_ESTIMATE_CACHE_VERSION,
|
|
2674
|
+
source: this.cacheSource,
|
|
2675
|
+
key: fallbackKey,
|
|
2676
|
+
tokens: localTokens2
|
|
2677
|
+
});
|
|
2678
|
+
return localTokens2;
|
|
2679
|
+
}
|
|
2680
|
+
if (isImageAttachment) {
|
|
2681
|
+
await resolveImageDimensionsAsync(part);
|
|
2682
|
+
}
|
|
2683
|
+
const localTokens = this.countAttachmentPartSync(part);
|
|
2684
|
+
return localTokens;
|
|
2685
|
+
}
|
|
2686
|
+
countNonAttachmentPart(part) {
|
|
2687
|
+
let overheadDelta = 0;
|
|
2688
|
+
let toolResultDelta = 0;
|
|
2689
|
+
if (part.type === "text") {
|
|
2690
|
+
return { tokens: this.readOrPersistPartEstimate(part, "text", part.text), overheadDelta, toolResultDelta };
|
|
2691
|
+
}
|
|
2692
|
+
if (part.type === "tool-invocation") {
|
|
2693
|
+
const invocation = part.toolInvocation;
|
|
2694
|
+
let tokens = 0;
|
|
2695
|
+
if (invocation.state === "call" || invocation.state === "partial-call") {
|
|
2696
|
+
if (invocation.toolName) {
|
|
2697
|
+
tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-name`, invocation.toolName);
|
|
2698
|
+
}
|
|
2699
|
+
if (invocation.args) {
|
|
2700
|
+
if (typeof invocation.args === "string") {
|
|
2701
|
+
tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-args`, invocation.args);
|
|
2702
|
+
} else {
|
|
2703
|
+
const argsJson = JSON.stringify(invocation.args);
|
|
2704
|
+
tokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-args-json`, argsJson);
|
|
2705
|
+
overheadDelta -= 12;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
return { tokens, overheadDelta, toolResultDelta };
|
|
2709
|
+
}
|
|
2710
|
+
if (invocation.state === "result") {
|
|
2711
|
+
toolResultDelta++;
|
|
2712
|
+
const { value: resultForCounting, usingStoredModelOutput } = this.resolveToolResultForTokenCounting(
|
|
2713
|
+
part,
|
|
2714
|
+
invocation.result
|
|
2715
|
+
);
|
|
2716
|
+
if (resultForCounting !== void 0) {
|
|
2717
|
+
if (typeof resultForCounting === "string") {
|
|
2718
|
+
tokens += this.readOrPersistPartEstimate(
|
|
2719
|
+
part,
|
|
2720
|
+
usingStoredModelOutput ? "tool-result-model-output" : "tool-result",
|
|
2721
|
+
resultForCounting
|
|
2722
|
+
);
|
|
2723
|
+
} else {
|
|
2724
|
+
const resultJson = JSON.stringify(resultForCounting);
|
|
2725
|
+
tokens += this.readOrPersistPartEstimate(
|
|
2726
|
+
part,
|
|
2727
|
+
usingStoredModelOutput ? "tool-result-model-output-json" : "tool-result-json",
|
|
2728
|
+
resultJson
|
|
2729
|
+
);
|
|
2730
|
+
overheadDelta -= 12;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
return { tokens, overheadDelta, toolResultDelta };
|
|
2734
|
+
}
|
|
2735
|
+
throw new Error(
|
|
2736
|
+
`Unhandled tool-invocation state '${part.toolInvocation?.state}' in token counting for part type '${part.type}'`
|
|
2737
|
+
);
|
|
2738
|
+
}
|
|
2739
|
+
if (typeof part.type === "string" && part.type.startsWith("data-")) {
|
|
2740
|
+
return { tokens: 0, overheadDelta, toolResultDelta };
|
|
2741
|
+
}
|
|
2742
|
+
if (part.type === "reasoning") {
|
|
2743
|
+
return { tokens: 0, overheadDelta, toolResultDelta };
|
|
2744
|
+
}
|
|
2745
|
+
const serialized = serializePartForTokenCounting(part);
|
|
2746
|
+
return {
|
|
2747
|
+
tokens: this.readOrPersistPartEstimate(part, `part-${part.type}`, serialized),
|
|
2748
|
+
overheadDelta,
|
|
2749
|
+
toolResultDelta
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
/**
|
|
2753
|
+
* Count tokens in a single message
|
|
2754
|
+
*/
|
|
2755
|
+
countMessage(message) {
|
|
2756
|
+
let payloadTokens = this.countString(message.role);
|
|
2757
|
+
let overhead = _TokenCounter.TOKENS_PER_MESSAGE;
|
|
2758
|
+
let toolResultCount = 0;
|
|
2759
|
+
if (typeof message.content === "string") {
|
|
2760
|
+
payloadTokens += this.readOrPersistMessageEstimate(message, "message-content", message.content);
|
|
2761
|
+
} else if (message.content && typeof message.content === "object") {
|
|
2762
|
+
if (message.content.content && !Array.isArray(message.content.parts)) {
|
|
2763
|
+
payloadTokens += this.readOrPersistMessageEstimate(message, "content-content", message.content.content);
|
|
2764
|
+
} else if (Array.isArray(message.content.parts)) {
|
|
2765
|
+
for (const part of message.content.parts) {
|
|
2766
|
+
const attachmentTokens = this.countAttachmentPartSync(part);
|
|
2767
|
+
if (attachmentTokens !== void 0) {
|
|
2768
|
+
payloadTokens += attachmentTokens;
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
const result = this.countNonAttachmentPart(part);
|
|
2772
|
+
payloadTokens += result.tokens;
|
|
2773
|
+
overhead += result.overheadDelta;
|
|
2774
|
+
toolResultCount += result.toolResultDelta;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
if (toolResultCount > 0) {
|
|
2779
|
+
overhead += toolResultCount * _TokenCounter.TOKENS_PER_MESSAGE;
|
|
2780
|
+
}
|
|
2781
|
+
return Math.round(payloadTokens + overhead);
|
|
1470
2782
|
}
|
|
1471
|
-
|
|
1472
|
-
* Count tokens in a single message
|
|
1473
|
-
*/
|
|
1474
|
-
countMessage(message) {
|
|
2783
|
+
async countMessageAsync(message) {
|
|
1475
2784
|
let payloadTokens = this.countString(message.role);
|
|
1476
2785
|
let overhead = _TokenCounter.TOKENS_PER_MESSAGE;
|
|
1477
2786
|
let toolResultCount = 0;
|
|
@@ -1482,63 +2791,15 @@ var TokenCounter = class _TokenCounter {
|
|
|
1482
2791
|
payloadTokens += this.readOrPersistMessageEstimate(message, "content-content", message.content.content);
|
|
1483
2792
|
} else if (Array.isArray(message.content.parts)) {
|
|
1484
2793
|
for (const part of message.content.parts) {
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
if (invocation.state === "call" || invocation.state === "partial-call") {
|
|
1490
|
-
if (invocation.toolName) {
|
|
1491
|
-
payloadTokens += this.readOrPersistPartEstimate(
|
|
1492
|
-
part,
|
|
1493
|
-
`tool-${invocation.state}-name`,
|
|
1494
|
-
invocation.toolName
|
|
1495
|
-
);
|
|
1496
|
-
}
|
|
1497
|
-
if (invocation.args) {
|
|
1498
|
-
if (typeof invocation.args === "string") {
|
|
1499
|
-
payloadTokens += this.readOrPersistPartEstimate(
|
|
1500
|
-
part,
|
|
1501
|
-
`tool-${invocation.state}-args`,
|
|
1502
|
-
invocation.args
|
|
1503
|
-
);
|
|
1504
|
-
} else {
|
|
1505
|
-
const argsJson = JSON.stringify(invocation.args);
|
|
1506
|
-
payloadTokens += this.readOrPersistPartEstimate(part, `tool-${invocation.state}-args-json`, argsJson);
|
|
1507
|
-
overhead -= 12;
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
} else if (invocation.state === "result") {
|
|
1511
|
-
toolResultCount++;
|
|
1512
|
-
const { value: resultForCounting, usingStoredModelOutput } = this.resolveToolResultForTokenCounting(
|
|
1513
|
-
part,
|
|
1514
|
-
invocation.result
|
|
1515
|
-
);
|
|
1516
|
-
if (resultForCounting !== void 0) {
|
|
1517
|
-
if (typeof resultForCounting === "string") {
|
|
1518
|
-
payloadTokens += this.readOrPersistPartEstimate(
|
|
1519
|
-
part,
|
|
1520
|
-
usingStoredModelOutput ? "tool-result-model-output" : "tool-result",
|
|
1521
|
-
resultForCounting
|
|
1522
|
-
);
|
|
1523
|
-
} else {
|
|
1524
|
-
const resultJson = JSON.stringify(resultForCounting);
|
|
1525
|
-
payloadTokens += this.readOrPersistPartEstimate(
|
|
1526
|
-
part,
|
|
1527
|
-
usingStoredModelOutput ? "tool-result-model-output-json" : "tool-result-json",
|
|
1528
|
-
resultJson
|
|
1529
|
-
);
|
|
1530
|
-
overhead -= 12;
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
} else {
|
|
1534
|
-
throw new Error(
|
|
1535
|
-
`Unhandled tool-invocation state '${part.toolInvocation?.state}' in token counting for part type '${part.type}'`
|
|
1536
|
-
);
|
|
1537
|
-
}
|
|
1538
|
-
} else if (typeof part.type === "string" && part.type.startsWith("data-")) ; else if (part.type === "reasoning") ; else {
|
|
1539
|
-
const serialized = serializePartForTokenCounting(part);
|
|
1540
|
-
payloadTokens += this.readOrPersistPartEstimate(part, `part-${part.type}`, serialized);
|
|
2794
|
+
const attachmentTokens = await this.countAttachmentPartAsync(part);
|
|
2795
|
+
if (attachmentTokens !== void 0) {
|
|
2796
|
+
payloadTokens += attachmentTokens;
|
|
2797
|
+
continue;
|
|
1541
2798
|
}
|
|
2799
|
+
const result = this.countNonAttachmentPart(part);
|
|
2800
|
+
payloadTokens += result.tokens;
|
|
2801
|
+
overhead += result.overheadDelta;
|
|
2802
|
+
toolResultCount += result.toolResultDelta;
|
|
1542
2803
|
}
|
|
1543
2804
|
}
|
|
1544
2805
|
}
|
|
@@ -1558,6 +2819,11 @@ var TokenCounter = class _TokenCounter {
|
|
|
1558
2819
|
}
|
|
1559
2820
|
return total;
|
|
1560
2821
|
}
|
|
2822
|
+
async countMessagesAsync(messages) {
|
|
2823
|
+
if (!messages || messages.length === 0) return 0;
|
|
2824
|
+
const messageTotals = await Promise.all(messages.map((message) => this.countMessageAsync(message)));
|
|
2825
|
+
return _TokenCounter.TOKENS_PER_CONVERSATION + messageTotals.reduce((sum, count) => sum + count, 0);
|
|
2826
|
+
}
|
|
1561
2827
|
/**
|
|
1562
2828
|
* Count tokens in observations string
|
|
1563
2829
|
*/
|
|
@@ -1815,6 +3081,41 @@ var ObservationalMemory = class _ObservationalMemory {
|
|
|
1815
3081
|
}
|
|
1816
3082
|
return [];
|
|
1817
3083
|
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Refresh per-chunk messageTokens from the current in-memory message list.
|
|
3086
|
+
*
|
|
3087
|
+
* Buffered chunks store a messageTokens snapshot from when they were created,
|
|
3088
|
+
* but messages can be edited/sealed between buffering and activation, changing
|
|
3089
|
+
* their token weight. Using stale weights causes projected-removal math to
|
|
3090
|
+
* over- or under-estimate, leading to skipped activations or over-activation.
|
|
3091
|
+
*
|
|
3092
|
+
* Token recount only runs when the full chunk is present in the message list.
|
|
3093
|
+
* Partial recount is skipped because it would undercount and could cause
|
|
3094
|
+
* over-activation of buffered chunks.
|
|
3095
|
+
*/
|
|
3096
|
+
refreshBufferedChunkMessageTokens(chunks, messageList) {
|
|
3097
|
+
const allMessages = messageList.get.all.db();
|
|
3098
|
+
const messageMap = new Map(allMessages.filter((m) => m?.id).map((m) => [m.id, m]));
|
|
3099
|
+
return chunks.map((chunk) => {
|
|
3100
|
+
const chunkMessages = chunk.messageIds.map((id) => messageMap.get(id)).filter((m) => !!m);
|
|
3101
|
+
if (chunkMessages.length !== chunk.messageIds.length) {
|
|
3102
|
+
return chunk;
|
|
3103
|
+
}
|
|
3104
|
+
const refreshedTokens = this.tokenCounter.countMessages(chunkMessages);
|
|
3105
|
+
const refreshedMessageTokens = chunk.messageIds.reduce((acc, id) => {
|
|
3106
|
+
const msg = messageMap.get(id);
|
|
3107
|
+
if (msg) {
|
|
3108
|
+
acc[id] = this.tokenCounter.countMessages([msg]);
|
|
3109
|
+
}
|
|
3110
|
+
return acc;
|
|
3111
|
+
}, {});
|
|
3112
|
+
return {
|
|
3113
|
+
...chunk,
|
|
3114
|
+
messageTokens: refreshedTokens,
|
|
3115
|
+
messageTokenCounts: refreshedMessageTokens
|
|
3116
|
+
};
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
1818
3119
|
/**
|
|
1819
3120
|
* Check if we've crossed a new bufferTokens interval boundary.
|
|
1820
3121
|
* Returns true if async buffering should be triggered.
|
|
@@ -1911,6 +3212,11 @@ var ObservationalMemory = class _ObservationalMemory {
|
|
|
1911
3212
|
return `thread:${threadId ?? "unknown"}`;
|
|
1912
3213
|
}
|
|
1913
3214
|
constructor(config) {
|
|
3215
|
+
if (!features.coreFeatures.has("request-response-id-rotation")) {
|
|
3216
|
+
throw new Error(
|
|
3217
|
+
"Observational memory requires @mastra/core support for request-response-id-rotation. Please bump @mastra/core to a newer version."
|
|
3218
|
+
);
|
|
3219
|
+
}
|
|
1914
3220
|
if (config.model && config.observation?.model) {
|
|
1915
3221
|
throw new Error(
|
|
1916
3222
|
"Cannot set both `model` and `observation.model`. Use `model` to set both agents, or set each individually."
|
|
@@ -2008,7 +3314,9 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2008
3314
|
),
|
|
2009
3315
|
instruction: config.reflection?.instruction
|
|
2010
3316
|
};
|
|
2011
|
-
this.tokenCounter = new TokenCounter(
|
|
3317
|
+
this.tokenCounter = new TokenCounter(void 0, {
|
|
3318
|
+
model: typeof observationModel === "string" ? observationModel : void 0
|
|
3319
|
+
});
|
|
2012
3320
|
this.onDebugEvent = config.onDebugEvent;
|
|
2013
3321
|
this.messageHistory = new processors.MessageHistory({ storage: this.storage });
|
|
2014
3322
|
this.validateBufferConfig();
|
|
@@ -2038,25 +3346,59 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2038
3346
|
async waitForBuffering(threadId, resourceId, timeoutMs = 3e4) {
|
|
2039
3347
|
return _ObservationalMemory.awaitBuffering(threadId, resourceId, this.scope, timeoutMs);
|
|
2040
3348
|
}
|
|
3349
|
+
getModelToResolve(model) {
|
|
3350
|
+
if (Array.isArray(model)) {
|
|
3351
|
+
return model[0]?.model ?? "unknown";
|
|
3352
|
+
}
|
|
3353
|
+
if (typeof model === "function") {
|
|
3354
|
+
return async (ctx) => {
|
|
3355
|
+
const result = await model(ctx);
|
|
3356
|
+
if (Array.isArray(result)) {
|
|
3357
|
+
return result[0]?.model ?? "unknown";
|
|
3358
|
+
}
|
|
3359
|
+
return result;
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
return model;
|
|
3363
|
+
}
|
|
3364
|
+
formatModelName(model) {
|
|
3365
|
+
if (!model.modelId) {
|
|
3366
|
+
return "(unknown)";
|
|
3367
|
+
}
|
|
3368
|
+
return model.provider ? `${model.provider}/${model.modelId}` : model.modelId;
|
|
3369
|
+
}
|
|
3370
|
+
async resolveModelContext(modelConfig, requestContext) {
|
|
3371
|
+
const modelToResolve = this.getModelToResolve(modelConfig);
|
|
3372
|
+
if (!modelToResolve) {
|
|
3373
|
+
return void 0;
|
|
3374
|
+
}
|
|
3375
|
+
const resolved = await llm.resolveModelConfig(modelToResolve, requestContext);
|
|
3376
|
+
return {
|
|
3377
|
+
provider: resolved.provider,
|
|
3378
|
+
modelId: resolved.modelId
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
getRuntimeModelContext(model) {
|
|
3382
|
+
if (!model?.modelId) {
|
|
3383
|
+
return void 0;
|
|
3384
|
+
}
|
|
3385
|
+
return {
|
|
3386
|
+
provider: model.provider,
|
|
3387
|
+
modelId: model.modelId
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
runWithTokenCounterModelContext(modelContext, fn) {
|
|
3391
|
+
return this.tokenCounter.runWithModelContext(modelContext, fn);
|
|
3392
|
+
}
|
|
2041
3393
|
/**
|
|
2042
3394
|
* Get the full config including resolved model names.
|
|
2043
3395
|
* This is async because it needs to resolve the model configs.
|
|
2044
3396
|
*/
|
|
2045
3397
|
async getResolvedConfig(requestContext) {
|
|
2046
|
-
const getModelToResolve = (model) => {
|
|
2047
|
-
if (Array.isArray(model)) {
|
|
2048
|
-
return model[0]?.model ?? "unknown";
|
|
2049
|
-
}
|
|
2050
|
-
return model;
|
|
2051
|
-
};
|
|
2052
|
-
const formatModelName = (model) => {
|
|
2053
|
-
return model.provider ? `${model.provider}/${model.modelId}` : model.modelId;
|
|
2054
|
-
};
|
|
2055
3398
|
const safeResolveModel = async (modelConfig) => {
|
|
2056
|
-
const modelToResolve = getModelToResolve(modelConfig);
|
|
2057
3399
|
try {
|
|
2058
|
-
const resolved = await
|
|
2059
|
-
return formatModelName(resolved);
|
|
3400
|
+
const resolved = await this.resolveModelContext(modelConfig, requestContext);
|
|
3401
|
+
return resolved?.modelId ? this.formatModelName(resolved) : "(unknown)";
|
|
2060
3402
|
} catch (error) {
|
|
2061
3403
|
omError("[OM] Failed to resolve model config", error);
|
|
2062
3404
|
return "(unknown)";
|
|
@@ -2537,10 +3879,16 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2537
3879
|
*/
|
|
2538
3880
|
async callObserver(existingObservations, messagesToObserve, abortSignal, options) {
|
|
2539
3881
|
const agent = this.getObserverAgent();
|
|
2540
|
-
const
|
|
3882
|
+
const observerMessages = [
|
|
3883
|
+
{
|
|
3884
|
+
role: "user",
|
|
3885
|
+
content: buildObserverTaskPrompt(existingObservations, options)
|
|
3886
|
+
},
|
|
3887
|
+
buildObserverHistoryMessage(messagesToObserve)
|
|
3888
|
+
];
|
|
2541
3889
|
const doGenerate = async () => {
|
|
2542
3890
|
return this.withAbortCheck(async () => {
|
|
2543
|
-
const streamResult = await agent.stream(
|
|
3891
|
+
const streamResult = await agent.stream(observerMessages, {
|
|
2544
3892
|
modelSettings: {
|
|
2545
3893
|
...this.observationConfig.modelSettings
|
|
2546
3894
|
},
|
|
@@ -2587,7 +3935,13 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2587
3935
|
model: this.observationConfig.model,
|
|
2588
3936
|
instructions: buildObserverSystemPrompt(true, this.observationConfig.instruction)
|
|
2589
3937
|
});
|
|
2590
|
-
const
|
|
3938
|
+
const observerMessages = [
|
|
3939
|
+
{
|
|
3940
|
+
role: "user",
|
|
3941
|
+
content: buildMultiThreadObserverTaskPrompt(existingObservations)
|
|
3942
|
+
},
|
|
3943
|
+
buildMultiThreadObserverHistoryMessage(messagesByThread, threadOrder)
|
|
3944
|
+
];
|
|
2591
3945
|
const allMessages = [];
|
|
2592
3946
|
for (const msgs of messagesByThread.values()) {
|
|
2593
3947
|
allMessages.push(...msgs);
|
|
@@ -2597,7 +3951,7 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
|
|
|
2597
3951
|
}
|
|
2598
3952
|
const doGenerate = async () => {
|
|
2599
3953
|
return this.withAbortCheck(async () => {
|
|
2600
|
-
const streamResult = await agent$1.stream(
|
|
3954
|
+
const streamResult = await agent$1.stream(observerMessages, {
|
|
2601
3955
|
modelSettings: {
|
|
2602
3956
|
...this.observationConfig.modelSettings
|
|
2603
3957
|
},
|
|
@@ -2875,8 +4229,8 @@ ${suggestedResponse}
|
|
|
2875
4229
|
/**
|
|
2876
4230
|
* Calculate all threshold-related values for observation decision making.
|
|
2877
4231
|
*/
|
|
2878
|
-
calculateObservationThresholds(_allMessages, unobservedMessages, _pendingTokens, otherThreadTokens, currentObservationTokens, _record) {
|
|
2879
|
-
const contextWindowTokens = this.tokenCounter.
|
|
4232
|
+
async calculateObservationThresholds(_allMessages, unobservedMessages, _pendingTokens, otherThreadTokens, currentObservationTokens, _record) {
|
|
4233
|
+
const contextWindowTokens = await this.tokenCounter.countMessagesAsync(unobservedMessages);
|
|
2880
4234
|
const totalPendingTokens = Math.max(0, contextWindowTokens + otherThreadTokens);
|
|
2881
4235
|
const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
|
|
2882
4236
|
const baseReflectionThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
@@ -2986,7 +4340,7 @@ ${suggestedResponse}
|
|
|
2986
4340
|
let freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
2987
4341
|
const freshAllMessages = messageList.get.all.db();
|
|
2988
4342
|
let freshUnobservedMessages = this.getUnobservedMessages(freshAllMessages, freshRecord);
|
|
2989
|
-
const freshContextTokens = this.tokenCounter.
|
|
4343
|
+
const freshContextTokens = await this.tokenCounter.countMessagesAsync(freshUnobservedMessages);
|
|
2990
4344
|
let freshOtherThreadTokens = 0;
|
|
2991
4345
|
if (this.scope === "resource" && resourceId) {
|
|
2992
4346
|
const freshOtherContext = await this.loadOtherThreadsContext(resourceId, threadId);
|
|
@@ -3128,17 +4482,25 @@ ${suggestedResponse}
|
|
|
3128
4482
|
if (observedMessageIds && observedMessageIds.length > 0) {
|
|
3129
4483
|
const observedSet = new Set(observedMessageIds);
|
|
3130
4484
|
const idsToRemove = /* @__PURE__ */ new Set();
|
|
4485
|
+
const removalOrder = [];
|
|
3131
4486
|
let skipped = 0;
|
|
3132
4487
|
let backoffTriggered = false;
|
|
4488
|
+
const retentionCounter = typeof minRemaining === "number" ? new TokenCounter() : null;
|
|
3133
4489
|
for (const msg of allMsgs) {
|
|
3134
4490
|
if (!msg?.id || msg.id === "om-continuation" || !observedSet.has(msg.id)) {
|
|
3135
4491
|
continue;
|
|
3136
4492
|
}
|
|
3137
|
-
|
|
4493
|
+
const unobservedParts = this.getUnobservedParts(msg);
|
|
4494
|
+
const totalParts = msg.content?.parts?.length ?? 0;
|
|
4495
|
+
if (unobservedParts.length > 0 && unobservedParts.length < totalParts) {
|
|
4496
|
+
msg.content.parts = unobservedParts;
|
|
4497
|
+
continue;
|
|
4498
|
+
}
|
|
4499
|
+
if (retentionCounter && typeof minRemaining === "number") {
|
|
3138
4500
|
const nextRemainingMessages = allMsgs.filter(
|
|
3139
4501
|
(m) => m?.id && m.id !== "om-continuation" && !idsToRemove.has(m.id) && m.id !== msg.id
|
|
3140
4502
|
);
|
|
3141
|
-
const remainingIfRemoved =
|
|
4503
|
+
const remainingIfRemoved = retentionCounter.countMessages(nextRemainingMessages);
|
|
3142
4504
|
if (remainingIfRemoved < minRemaining) {
|
|
3143
4505
|
skipped += 1;
|
|
3144
4506
|
backoffTriggered = true;
|
|
@@ -3146,6 +4508,19 @@ ${suggestedResponse}
|
|
|
3146
4508
|
}
|
|
3147
4509
|
}
|
|
3148
4510
|
idsToRemove.add(msg.id);
|
|
4511
|
+
removalOrder.push(msg.id);
|
|
4512
|
+
}
|
|
4513
|
+
if (retentionCounter && typeof minRemaining === "number" && idsToRemove.size > 0) {
|
|
4514
|
+
let remainingMessages = allMsgs.filter((m) => m?.id && m.id !== "om-continuation" && !idsToRemove.has(m.id));
|
|
4515
|
+
let remainingTokens = retentionCounter.countMessages(remainingMessages);
|
|
4516
|
+
while (remainingTokens < minRemaining && removalOrder.length > 0) {
|
|
4517
|
+
const restoreId = removalOrder.pop();
|
|
4518
|
+
idsToRemove.delete(restoreId);
|
|
4519
|
+
skipped += 1;
|
|
4520
|
+
backoffTriggered = true;
|
|
4521
|
+
remainingMessages = allMsgs.filter((m) => m?.id && m.id !== "om-continuation" && !idsToRemove.has(m.id));
|
|
4522
|
+
remainingTokens = retentionCounter.countMessages(remainingMessages);
|
|
4523
|
+
}
|
|
3149
4524
|
}
|
|
3150
4525
|
omDebug(
|
|
3151
4526
|
`[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(",")}`
|
|
@@ -3183,8 +4558,8 @@ ${suggestedResponse}
|
|
|
3183
4558
|
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
3184
4559
|
}
|
|
3185
4560
|
} else {
|
|
3186
|
-
const newInput = messageList.
|
|
3187
|
-
const newOutput = messageList.
|
|
4561
|
+
const newInput = messageList.get.input.db();
|
|
4562
|
+
const newOutput = messageList.get.response.db();
|
|
3188
4563
|
const messagesToSave = [...newInput, ...newOutput];
|
|
3189
4564
|
if (messagesToSave.length > 0) {
|
|
3190
4565
|
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
@@ -3252,11 +4627,17 @@ ${suggestedResponse}
|
|
|
3252
4627
|
messageList.add(continuationMessage, "memory");
|
|
3253
4628
|
}
|
|
3254
4629
|
/**
|
|
3255
|
-
* Filter out already-observed messages from
|
|
3256
|
-
*
|
|
4630
|
+
* Filter out already-observed messages from the in-memory context.
|
|
4631
|
+
*
|
|
4632
|
+
* Marker-boundary pruning is safest at step 0 (historical resume/rebuild), where
|
|
4633
|
+
* list ordering mirrors persisted history.
|
|
4634
|
+
* For step > 0, the list may include mid-loop mutations (sealing/splitting/trim),
|
|
4635
|
+
* so we prefer record-based fallback pruning over position-based marker pruning.
|
|
3257
4636
|
*/
|
|
3258
|
-
filterAlreadyObservedMessages(messageList, record) {
|
|
4637
|
+
async filterAlreadyObservedMessages(messageList, record, options) {
|
|
3259
4638
|
const allMessages = messageList.get.all.db();
|
|
4639
|
+
const useMarkerBoundaryPruning = options?.useMarkerBoundaryPruning ?? true;
|
|
4640
|
+
const fallbackCursor = record?.threadId ? memory.getThreadOMMetadata((await this.storage.getThreadById({ threadId: record.threadId }))?.metadata)?.lastObservedMessageCursor : void 0;
|
|
3260
4641
|
let markerMessageIndex = -1;
|
|
3261
4642
|
let markerMessage = null;
|
|
3262
4643
|
for (let i = allMessages.length - 1; i >= 0; i--) {
|
|
@@ -3268,7 +4649,7 @@ ${suggestedResponse}
|
|
|
3268
4649
|
break;
|
|
3269
4650
|
}
|
|
3270
4651
|
}
|
|
3271
|
-
if (markerMessage && markerMessageIndex !== -1) {
|
|
4652
|
+
if (useMarkerBoundaryPruning && markerMessage && markerMessageIndex !== -1) {
|
|
3272
4653
|
const messagesToRemove = [];
|
|
3273
4654
|
for (let i = 0; i < markerMessageIndex; i++) {
|
|
3274
4655
|
const msg = allMessages[i];
|
|
@@ -3289,6 +4670,9 @@ ${suggestedResponse}
|
|
|
3289
4670
|
}
|
|
3290
4671
|
} else if (record) {
|
|
3291
4672
|
const observedIds = new Set(Array.isArray(record.observedMessageIds) ? record.observedMessageIds : []);
|
|
4673
|
+
const derivedCursor = fallbackCursor ?? this.getLastObservedMessageCursor(
|
|
4674
|
+
allMessages.filter((msg) => !!msg?.id && observedIds.has(msg.id) && !!msg.createdAt)
|
|
4675
|
+
);
|
|
3292
4676
|
const lastObservedAt = record.lastObservedAt;
|
|
3293
4677
|
const messagesToRemove = [];
|
|
3294
4678
|
for (const msg of allMessages) {
|
|
@@ -3297,6 +4681,10 @@ ${suggestedResponse}
|
|
|
3297
4681
|
messagesToRemove.push(msg.id);
|
|
3298
4682
|
continue;
|
|
3299
4683
|
}
|
|
4684
|
+
if (derivedCursor && this.isMessageAtOrBeforeCursor(msg, derivedCursor)) {
|
|
4685
|
+
messagesToRemove.push(msg.id);
|
|
4686
|
+
continue;
|
|
4687
|
+
}
|
|
3300
4688
|
if (lastObservedAt && msg.createdAt) {
|
|
3301
4689
|
const msgDate = new Date(msg.createdAt);
|
|
3302
4690
|
if (msgDate <= lastObservedAt) {
|
|
@@ -3321,7 +4709,7 @@ ${suggestedResponse}
|
|
|
3321
4709
|
* 5. Filter out already-observed messages
|
|
3322
4710
|
*/
|
|
3323
4711
|
async processInputStep(args) {
|
|
3324
|
-
const { messageList, requestContext, stepNumber, state: _state, writer, abortSignal, abort } = args;
|
|
4712
|
+
const { messageList, requestContext, stepNumber, state: _state, writer, abortSignal, abort, model } = args;
|
|
3325
4713
|
const state = _state ?? {};
|
|
3326
4714
|
omDebug(
|
|
3327
4715
|
`[OM:processInputStep:ENTER] step=${stepNumber}, hasMastraMemory=${!!requestContext?.get("MastraMemory")}, hasMemoryInfo=${!!messageList?.serialize()?.memoryInfo?.threadId}`
|
|
@@ -3334,262 +4722,322 @@ ${suggestedResponse}
|
|
|
3334
4722
|
const { threadId, resourceId } = context;
|
|
3335
4723
|
const memoryContext = memory.parseMemoryRequestContext(requestContext);
|
|
3336
4724
|
const readOnly = memoryContext?.memoryConfig?.readOnly;
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
4725
|
+
const actorModelContext = this.getRuntimeModelContext(model);
|
|
4726
|
+
state.__omActorModelContext = actorModelContext;
|
|
4727
|
+
return this.runWithTokenCounterModelContext(actorModelContext, async () => {
|
|
4728
|
+
let record = await this.getOrCreateRecord(threadId, resourceId);
|
|
4729
|
+
const reproCaptureEnabled = isOmReproCaptureEnabled();
|
|
4730
|
+
const preRecordSnapshot = reproCaptureEnabled ? safeCaptureJson(record) : null;
|
|
4731
|
+
const preMessagesSnapshot = reproCaptureEnabled ? safeCaptureJson(messageList.get.all.db()) : null;
|
|
4732
|
+
const preSerializedMessageList = reproCaptureEnabled ? safeCaptureJson(messageList.serialize()) : null;
|
|
4733
|
+
const reproCaptureDetails = {
|
|
4734
|
+
step0Activation: null,
|
|
4735
|
+
thresholdCleanup: null,
|
|
4736
|
+
thresholdReached: false
|
|
4737
|
+
};
|
|
3349
4738
|
omDebug(
|
|
3350
|
-
`[OM:
|
|
4739
|
+
`[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}`
|
|
3351
4740
|
);
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
if (dbBoundary > currentContextTokens) {
|
|
3357
|
-
omDebug(
|
|
3358
|
-
`[OM:step0-boundary-reset] dbBoundary=${dbBoundary} > currentContext=${currentContextTokens}, resetting to current`
|
|
3359
|
-
);
|
|
3360
|
-
_ObservationalMemory.lastBufferedBoundary.set(bufKey, currentContextTokens);
|
|
3361
|
-
this.storage.setBufferingObservationFlag(record.id, false, currentContextTokens).catch(() => {
|
|
3362
|
-
});
|
|
3363
|
-
}
|
|
4741
|
+
await this.loadHistoricalMessagesIfNeeded(messageList, state, threadId, resourceId, record.lastObservedAt);
|
|
4742
|
+
let unobservedContextBlocks;
|
|
4743
|
+
if (this.scope === "resource" && resourceId) {
|
|
4744
|
+
unobservedContextBlocks = await this.loadOtherThreadsContext(resourceId, threadId);
|
|
3364
4745
|
}
|
|
3365
|
-
if (
|
|
3366
|
-
const
|
|
3367
|
-
const
|
|
3368
|
-
const otherThreadTokensForCheck = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3369
|
-
const currentObsTokensForCheck = record.observationTokenCount ?? 0;
|
|
3370
|
-
const { totalPendingTokens: step0PendingTokens, threshold: step0Threshold } = this.calculateObservationThresholds(
|
|
3371
|
-
allMsgsForCheck,
|
|
3372
|
-
unobservedMsgsForCheck,
|
|
3373
|
-
0,
|
|
3374
|
-
// pendingTokens not needed — allMessages covers context
|
|
3375
|
-
otherThreadTokensForCheck,
|
|
3376
|
-
currentObsTokensForCheck,
|
|
3377
|
-
record
|
|
3378
|
-
);
|
|
4746
|
+
if (stepNumber === 0 && !readOnly && this.isAsyncObservationEnabled()) {
|
|
4747
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
4748
|
+
const bufferedChunks = this.getBufferedChunks(record);
|
|
3379
4749
|
omDebug(
|
|
3380
|
-
`[OM:step0-activation]
|
|
4750
|
+
`[OM:step0-activation] asyncObsEnabled=true, bufferedChunks=${bufferedChunks.length}, isBufferingObs=${record.isBufferingObservation}`
|
|
3381
4751
|
);
|
|
3382
|
-
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
4752
|
+
{
|
|
4753
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
4754
|
+
const dbBoundary = record.lastBufferedAtTokens ?? 0;
|
|
4755
|
+
const currentContextTokens = this.tokenCounter.countMessages(messageList.get.all.db());
|
|
4756
|
+
if (dbBoundary > currentContextTokens) {
|
|
4757
|
+
omDebug(
|
|
4758
|
+
`[OM:step0-boundary-reset] dbBoundary=${dbBoundary} > currentContext=${currentContextTokens}, resetting to current`
|
|
4759
|
+
);
|
|
4760
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, currentContextTokens);
|
|
4761
|
+
this.storage.setBufferingObservationFlag(record.id, false, currentContextTokens).catch(() => {
|
|
4762
|
+
});
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
if (bufferedChunks.length > 0) {
|
|
4766
|
+
const allMsgsForCheck = messageList.get.all.db();
|
|
4767
|
+
const unobservedMsgsForCheck = this.getUnobservedMessages(allMsgsForCheck, record);
|
|
4768
|
+
const otherThreadTokensForCheck = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
4769
|
+
const currentObsTokensForCheck = record.observationTokenCount ?? 0;
|
|
4770
|
+
const { totalPendingTokens: step0PendingTokens, threshold: step0Threshold } = await this.calculateObservationThresholds(
|
|
4771
|
+
allMsgsForCheck,
|
|
4772
|
+
unobservedMsgsForCheck,
|
|
4773
|
+
0,
|
|
4774
|
+
// pendingTokens not needed — allMessages covers context
|
|
4775
|
+
otherThreadTokensForCheck,
|
|
4776
|
+
currentObsTokensForCheck,
|
|
4777
|
+
record
|
|
3389
4778
|
);
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
4779
|
+
omDebug(
|
|
4780
|
+
`[OM:step0-activation] pendingTokens=${step0PendingTokens}, threshold=${step0Threshold}, blockAfter=${this.observationConfig.blockAfter}, shouldActivate=${step0PendingTokens >= step0Threshold}, allMsgs=${allMsgsForCheck.length}`
|
|
4781
|
+
);
|
|
4782
|
+
if (step0PendingTokens >= step0Threshold) {
|
|
4783
|
+
const activationResult = await this.tryActivateBufferedObservations(
|
|
4784
|
+
record,
|
|
4785
|
+
lockKey,
|
|
4786
|
+
step0PendingTokens,
|
|
4787
|
+
writer,
|
|
4788
|
+
messageList
|
|
4789
|
+
);
|
|
4790
|
+
reproCaptureDetails.step0Activation = {
|
|
4791
|
+
attempted: true,
|
|
4792
|
+
success: activationResult.success,
|
|
4793
|
+
messageTokensActivated: activationResult.messageTokensActivated ?? 0,
|
|
4794
|
+
activatedMessageIds: activationResult.activatedMessageIds ?? [],
|
|
4795
|
+
hadUpdatedRecord: !!activationResult.updatedRecord
|
|
4796
|
+
};
|
|
4797
|
+
if (activationResult.success && activationResult.updatedRecord) {
|
|
4798
|
+
record = activationResult.updatedRecord;
|
|
4799
|
+
const activatedIds = activationResult.activatedMessageIds ?? [];
|
|
4800
|
+
if (activatedIds.length > 0) {
|
|
4801
|
+
const activatedSet = new Set(activatedIds);
|
|
4802
|
+
const allMsgs = messageList.get.all.db();
|
|
4803
|
+
const idsToRemove = allMsgs.filter((msg) => msg?.id && msg.id !== "om-continuation" && activatedSet.has(msg.id)).map((msg) => msg.id);
|
|
4804
|
+
if (idsToRemove.length > 0) {
|
|
4805
|
+
messageList.removeByIds(idsToRemove);
|
|
4806
|
+
}
|
|
3399
4807
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {
|
|
3405
|
-
});
|
|
3406
|
-
const thread = await this.storage.getThreadById({ threadId });
|
|
3407
|
-
if (thread) {
|
|
3408
|
-
const newMetadata = memory.setThreadOMMetadata(thread.metadata, {
|
|
3409
|
-
suggestedResponse: activationResult.suggestedContinuation,
|
|
3410
|
-
currentTask: activationResult.currentTask
|
|
4808
|
+
this.cleanupStaticMaps(threadId, resourceId, activatedIds);
|
|
4809
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
4810
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, 0);
|
|
4811
|
+
this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {
|
|
3411
4812
|
});
|
|
3412
|
-
await this.storage.
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
4813
|
+
const thread = await this.storage.getThreadById({ threadId });
|
|
4814
|
+
if (thread) {
|
|
4815
|
+
const activatedSet = new Set(activationResult.activatedMessageIds ?? []);
|
|
4816
|
+
const activatedMessages = messageList.get.all.db().filter((msg) => msg?.id && activatedSet.has(msg.id));
|
|
4817
|
+
const newMetadata = memory.setThreadOMMetadata(thread.metadata, {
|
|
4818
|
+
suggestedResponse: activationResult.suggestedContinuation,
|
|
4819
|
+
currentTask: activationResult.currentTask,
|
|
4820
|
+
lastObservedMessageCursor: this.getLastObservedMessageCursor(activatedMessages)
|
|
4821
|
+
});
|
|
4822
|
+
await this.storage.updateThread({
|
|
4823
|
+
id: threadId,
|
|
4824
|
+
title: thread.title ?? "",
|
|
4825
|
+
metadata: newMetadata
|
|
4826
|
+
});
|
|
4827
|
+
}
|
|
4828
|
+
await this.maybeReflect({
|
|
4829
|
+
record,
|
|
4830
|
+
observationTokens: record.observationTokenCount ?? 0,
|
|
4831
|
+
threadId,
|
|
4832
|
+
writer,
|
|
4833
|
+
messageList,
|
|
4834
|
+
requestContext
|
|
3416
4835
|
});
|
|
4836
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3417
4837
|
}
|
|
3418
|
-
await this.maybeReflect({
|
|
3419
|
-
record,
|
|
3420
|
-
observationTokens: record.observationTokenCount ?? 0,
|
|
3421
|
-
threadId,
|
|
3422
|
-
writer,
|
|
3423
|
-
messageList,
|
|
3424
|
-
requestContext
|
|
3425
|
-
});
|
|
3426
|
-
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3427
4838
|
}
|
|
3428
4839
|
}
|
|
3429
4840
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
await this.maybeReflect({
|
|
3436
|
-
record,
|
|
3437
|
-
observationTokens: obsTokens,
|
|
3438
|
-
threadId,
|
|
3439
|
-
writer,
|
|
3440
|
-
messageList,
|
|
3441
|
-
requestContext
|
|
3442
|
-
});
|
|
3443
|
-
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3444
|
-
} else if (this.isAsyncReflectionEnabled()) {
|
|
3445
|
-
const lockKey = this.getLockKey(threadId, resourceId);
|
|
3446
|
-
if (this.shouldTriggerAsyncReflection(obsTokens, lockKey, record)) {
|
|
3447
|
-
omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} above activation point, triggering async reflection`);
|
|
3448
|
-
await this.maybeAsyncReflect(record, obsTokens, writer, messageList, requestContext);
|
|
3449
|
-
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3450
|
-
}
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
|
-
if (!readOnly) {
|
|
3454
|
-
const allMessages = messageList.get.all.db();
|
|
3455
|
-
const unobservedMessages = this.getUnobservedMessages(allMessages, record);
|
|
3456
|
-
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3457
|
-
const currentObservationTokens = record.observationTokenCount ?? 0;
|
|
3458
|
-
const thresholds = this.calculateObservationThresholds(
|
|
3459
|
-
allMessages,
|
|
3460
|
-
unobservedMessages,
|
|
3461
|
-
0,
|
|
3462
|
-
// pendingTokens not needed — allMessages covers context
|
|
3463
|
-
otherThreadTokens,
|
|
3464
|
-
currentObservationTokens,
|
|
3465
|
-
record
|
|
3466
|
-
);
|
|
3467
|
-
const { totalPendingTokens, threshold } = thresholds;
|
|
3468
|
-
const bufferedChunkTokens = this.getBufferedChunks(record).reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
3469
|
-
const unbufferedPendingTokens = Math.max(0, totalPendingTokens - bufferedChunkTokens);
|
|
3470
|
-
const stateSealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
|
|
3471
|
-
const staticSealedIds = _ObservationalMemory.sealedMessageIds.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
3472
|
-
const sealedIds = /* @__PURE__ */ new Set([...stateSealedIds, ...staticSealedIds]);
|
|
3473
|
-
state.sealedIds = sealedIds;
|
|
3474
|
-
const lockKey = this.getLockKey(threadId, resourceId);
|
|
3475
|
-
if (this.isAsyncObservationEnabled() && totalPendingTokens < threshold) {
|
|
3476
|
-
const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record, threshold);
|
|
3477
|
-
omDebug(
|
|
3478
|
-
`[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}`
|
|
3479
|
-
);
|
|
3480
|
-
if (shouldTrigger) {
|
|
3481
|
-
this.startAsyncBufferedObservation(
|
|
4841
|
+
if (stepNumber === 0 && !readOnly) {
|
|
4842
|
+
const obsTokens = record.observationTokenCount ?? 0;
|
|
4843
|
+
if (this.shouldReflect(obsTokens)) {
|
|
4844
|
+
omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} over reflectThreshold, triggering reflection`);
|
|
4845
|
+
await this.maybeReflect({
|
|
3482
4846
|
record,
|
|
4847
|
+
observationTokens: obsTokens,
|
|
3483
4848
|
threadId,
|
|
3484
|
-
unobservedMessages,
|
|
3485
|
-
lockKey,
|
|
3486
4849
|
writer,
|
|
3487
|
-
|
|
4850
|
+
messageList,
|
|
3488
4851
|
requestContext
|
|
3489
|
-
);
|
|
4852
|
+
});
|
|
4853
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
4854
|
+
} else if (this.isAsyncReflectionEnabled()) {
|
|
4855
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
4856
|
+
if (this.shouldTriggerAsyncReflection(obsTokens, lockKey, record)) {
|
|
4857
|
+
omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} above activation point, triggering async reflection`);
|
|
4858
|
+
await this.maybeAsyncReflect(record, obsTokens, writer, messageList, requestContext);
|
|
4859
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
4860
|
+
}
|
|
3490
4861
|
}
|
|
3491
|
-
}
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
4862
|
+
}
|
|
4863
|
+
let didThresholdCleanup = false;
|
|
4864
|
+
if (!readOnly) {
|
|
4865
|
+
let allMessages = messageList.get.all.db();
|
|
4866
|
+
let unobservedMessages = this.getUnobservedMessages(allMessages, record);
|
|
4867
|
+
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
4868
|
+
let currentObservationTokens = record.observationTokenCount ?? 0;
|
|
4869
|
+
let thresholds = await this.calculateObservationThresholds(
|
|
4870
|
+
allMessages,
|
|
4871
|
+
unobservedMessages,
|
|
4872
|
+
0,
|
|
4873
|
+
// pendingTokens not needed — allMessages covers context
|
|
4874
|
+
otherThreadTokens,
|
|
4875
|
+
currentObservationTokens,
|
|
4876
|
+
record
|
|
3495
4877
|
);
|
|
3496
|
-
|
|
3497
|
-
|
|
4878
|
+
let { totalPendingTokens, threshold } = thresholds;
|
|
4879
|
+
let bufferedChunkTokens = this.getBufferedChunks(record).reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
4880
|
+
let unbufferedPendingTokens = Math.max(0, totalPendingTokens - bufferedChunkTokens);
|
|
4881
|
+
const stateSealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
|
|
4882
|
+
const staticSealedIds = _ObservationalMemory.sealedMessageIds.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
4883
|
+
const sealedIds = /* @__PURE__ */ new Set([...stateSealedIds, ...staticSealedIds]);
|
|
4884
|
+
state.sealedIds = sealedIds;
|
|
4885
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
4886
|
+
if (this.isAsyncObservationEnabled() && totalPendingTokens < threshold) {
|
|
4887
|
+
const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record, threshold);
|
|
4888
|
+
omDebug(
|
|
4889
|
+
`[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}`
|
|
4890
|
+
);
|
|
4891
|
+
if (shouldTrigger) {
|
|
4892
|
+
void this.startAsyncBufferedObservation(
|
|
4893
|
+
record,
|
|
4894
|
+
threadId,
|
|
4895
|
+
unobservedMessages,
|
|
4896
|
+
lockKey,
|
|
4897
|
+
writer,
|
|
4898
|
+
unbufferedPendingTokens,
|
|
4899
|
+
requestContext
|
|
4900
|
+
);
|
|
4901
|
+
}
|
|
4902
|
+
} else if (this.isAsyncObservationEnabled()) {
|
|
4903
|
+
const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record, threshold);
|
|
4904
|
+
omDebug(
|
|
4905
|
+
`[OM:async-obs] atOrAboveThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, step=${stepNumber}, shouldTrigger=${shouldTrigger}`
|
|
4906
|
+
);
|
|
4907
|
+
if (shouldTrigger) {
|
|
4908
|
+
void this.startAsyncBufferedObservation(
|
|
4909
|
+
record,
|
|
4910
|
+
threadId,
|
|
4911
|
+
unobservedMessages,
|
|
4912
|
+
lockKey,
|
|
4913
|
+
writer,
|
|
4914
|
+
unbufferedPendingTokens,
|
|
4915
|
+
requestContext
|
|
4916
|
+
);
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
if (stepNumber > 0) {
|
|
4920
|
+
await this.handlePerStepSave(messageList, sealedIds, threadId, resourceId, state);
|
|
4921
|
+
}
|
|
4922
|
+
if (stepNumber > 0 && totalPendingTokens >= threshold) {
|
|
4923
|
+
reproCaptureDetails.thresholdReached = true;
|
|
4924
|
+
const { observationSucceeded, updatedRecord, activatedMessageIds } = await this.handleThresholdReached(
|
|
4925
|
+
messageList,
|
|
3498
4926
|
record,
|
|
3499
4927
|
threadId,
|
|
3500
|
-
|
|
4928
|
+
resourceId,
|
|
4929
|
+
threshold,
|
|
3501
4930
|
lockKey,
|
|
3502
4931
|
writer,
|
|
3503
|
-
|
|
4932
|
+
abortSignal,
|
|
4933
|
+
abort,
|
|
3504
4934
|
requestContext
|
|
3505
4935
|
);
|
|
4936
|
+
if (observationSucceeded) {
|
|
4937
|
+
const observedIds = activatedMessageIds?.length ? activatedMessageIds : Array.isArray(updatedRecord.observedMessageIds) ? updatedRecord.observedMessageIds : void 0;
|
|
4938
|
+
const minRemaining = typeof this.observationConfig.bufferActivation === "number" ? resolveRetentionFloor(this.observationConfig.bufferActivation, threshold) : void 0;
|
|
4939
|
+
reproCaptureDetails.thresholdCleanup = {
|
|
4940
|
+
observationSucceeded,
|
|
4941
|
+
observedIdsCount: observedIds?.length ?? 0,
|
|
4942
|
+
observedIds,
|
|
4943
|
+
minRemaining,
|
|
4944
|
+
updatedRecordObservedIds: updatedRecord.observedMessageIds
|
|
4945
|
+
};
|
|
4946
|
+
omDebug(
|
|
4947
|
+
`[OM:cleanup] observedIds=${observedIds?.length ?? "undefined"}, ids=${observedIds?.join(",") ?? "none"}, updatedRecord.observedMessageIds=${JSON.stringify(updatedRecord.observedMessageIds)}, minRemaining=${minRemaining ?? "n/a"}`
|
|
4948
|
+
);
|
|
4949
|
+
await this.cleanupAfterObservation(
|
|
4950
|
+
messageList,
|
|
4951
|
+
sealedIds,
|
|
4952
|
+
threadId,
|
|
4953
|
+
resourceId,
|
|
4954
|
+
state,
|
|
4955
|
+
observedIds,
|
|
4956
|
+
minRemaining
|
|
4957
|
+
);
|
|
4958
|
+
didThresholdCleanup = true;
|
|
4959
|
+
if (activatedMessageIds?.length) {
|
|
4960
|
+
this.cleanupStaticMaps(threadId, resourceId, activatedMessageIds);
|
|
4961
|
+
}
|
|
4962
|
+
if (this.isAsyncObservationEnabled()) {
|
|
4963
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
4964
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, 0);
|
|
4965
|
+
this.storage.setBufferingObservationFlag(updatedRecord.id, false, 0).catch(() => {
|
|
4966
|
+
});
|
|
4967
|
+
omDebug(`[OM:threshold] post-activation boundary reset to 0`);
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
record = updatedRecord;
|
|
3506
4971
|
}
|
|
3507
4972
|
}
|
|
3508
|
-
|
|
3509
|
-
|
|
4973
|
+
await this.injectObservationsIntoContext(
|
|
4974
|
+
messageList,
|
|
4975
|
+
record,
|
|
4976
|
+
threadId,
|
|
4977
|
+
resourceId,
|
|
4978
|
+
unobservedContextBlocks,
|
|
4979
|
+
requestContext
|
|
4980
|
+
);
|
|
4981
|
+
if (!didThresholdCleanup) {
|
|
4982
|
+
await this.filterAlreadyObservedMessages(messageList, record, { useMarkerBoundaryPruning: stepNumber === 0 });
|
|
3510
4983
|
}
|
|
3511
|
-
|
|
3512
|
-
const
|
|
3513
|
-
|
|
3514
|
-
|
|
4984
|
+
{
|
|
4985
|
+
const freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
4986
|
+
const contextMessages = messageList.get.all.db();
|
|
4987
|
+
const freshUnobservedTokens = await this.tokenCounter.countMessagesAsync(contextMessages);
|
|
4988
|
+
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
4989
|
+
const currentObservationTokens = freshRecord.observationTokenCount ?? 0;
|
|
4990
|
+
const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
|
|
4991
|
+
const baseReflectionThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
4992
|
+
const isSharedBudget = typeof this.observationConfig.messageTokens !== "number";
|
|
4993
|
+
const totalBudget = isSharedBudget ? this.observationConfig.messageTokens.max : 0;
|
|
4994
|
+
const effectiveObservationTokensThreshold = isSharedBudget ? Math.max(totalBudget - threshold, 1e3) : baseReflectionThreshold;
|
|
4995
|
+
const totalPendingTokens = freshUnobservedTokens + otherThreadTokens;
|
|
4996
|
+
await this.emitStepProgress(
|
|
4997
|
+
writer,
|
|
3515
4998
|
threadId,
|
|
3516
4999
|
resourceId,
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
5000
|
+
stepNumber,
|
|
5001
|
+
freshRecord,
|
|
5002
|
+
{
|
|
5003
|
+
totalPendingTokens,
|
|
5004
|
+
threshold,
|
|
5005
|
+
effectiveObservationTokensThreshold
|
|
5006
|
+
},
|
|
5007
|
+
currentObservationTokens
|
|
3523
5008
|
);
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
`[OM:cleanup] observedIds=${observedIds?.length ?? "undefined"}, ids=${observedIds?.join(",") ?? "none"}, updatedRecord.observedMessageIds=${JSON.stringify(updatedRecord.observedMessageIds)}, minRemaining=${minRemaining ?? "n/a"}`
|
|
3529
|
-
);
|
|
3530
|
-
await this.cleanupAfterObservation(
|
|
3531
|
-
messageList,
|
|
3532
|
-
sealedIds,
|
|
5009
|
+
this.storage.setPendingMessageTokens(freshRecord.id, totalPendingTokens).catch(() => {
|
|
5010
|
+
});
|
|
5011
|
+
if (reproCaptureEnabled && preRecordSnapshot && preMessagesSnapshot && preSerializedMessageList) {
|
|
5012
|
+
writeProcessInputStepReproCapture({
|
|
3533
5013
|
threadId,
|
|
3534
5014
|
resourceId,
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
this.
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
5015
|
+
stepNumber,
|
|
5016
|
+
args,
|
|
5017
|
+
preRecord: preRecordSnapshot,
|
|
5018
|
+
postRecord: freshRecord,
|
|
5019
|
+
preMessages: preMessagesSnapshot,
|
|
5020
|
+
preBufferedChunks: this.getBufferedChunks(preRecordSnapshot),
|
|
5021
|
+
preContextTokenCount: this.tokenCounter.countMessages(preMessagesSnapshot),
|
|
5022
|
+
preSerializedMessageList,
|
|
5023
|
+
postBufferedChunks: this.getBufferedChunks(freshRecord),
|
|
5024
|
+
postContextTokenCount: this.tokenCounter.countMessages(contextMessages),
|
|
5025
|
+
messageList,
|
|
5026
|
+
details: {
|
|
5027
|
+
...reproCaptureDetails,
|
|
5028
|
+
totalPendingTokens,
|
|
5029
|
+
threshold,
|
|
5030
|
+
effectiveObservationTokensThreshold,
|
|
5031
|
+
currentObservationTokens,
|
|
5032
|
+
otherThreadTokens,
|
|
5033
|
+
contextMessageCount: contextMessages.length
|
|
5034
|
+
},
|
|
5035
|
+
debug: omDebug
|
|
5036
|
+
});
|
|
3549
5037
|
}
|
|
3550
|
-
record = updatedRecord;
|
|
3551
5038
|
}
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
messageList,
|
|
3555
|
-
record,
|
|
3556
|
-
threadId,
|
|
3557
|
-
resourceId,
|
|
3558
|
-
unobservedContextBlocks,
|
|
3559
|
-
requestContext
|
|
3560
|
-
);
|
|
3561
|
-
if (stepNumber === 0) {
|
|
3562
|
-
this.filterAlreadyObservedMessages(messageList, record);
|
|
3563
|
-
}
|
|
3564
|
-
{
|
|
3565
|
-
const freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
3566
|
-
const contextMessages = messageList.get.all.db();
|
|
3567
|
-
const freshUnobservedTokens = this.tokenCounter.countMessages(contextMessages);
|
|
3568
|
-
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3569
|
-
const currentObservationTokens = freshRecord.observationTokenCount ?? 0;
|
|
3570
|
-
const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
|
|
3571
|
-
const baseReflectionThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
3572
|
-
const isSharedBudget = typeof this.observationConfig.messageTokens !== "number";
|
|
3573
|
-
const totalBudget = isSharedBudget ? this.observationConfig.messageTokens.max : 0;
|
|
3574
|
-
const effectiveObservationTokensThreshold = isSharedBudget ? Math.max(totalBudget - threshold, 1e3) : baseReflectionThreshold;
|
|
3575
|
-
const totalPendingTokens = freshUnobservedTokens + otherThreadTokens;
|
|
3576
|
-
await this.emitStepProgress(
|
|
3577
|
-
writer,
|
|
3578
|
-
threadId,
|
|
3579
|
-
resourceId,
|
|
3580
|
-
stepNumber,
|
|
3581
|
-
freshRecord,
|
|
3582
|
-
{
|
|
3583
|
-
totalPendingTokens,
|
|
3584
|
-
threshold,
|
|
3585
|
-
effectiveObservationTokensThreshold
|
|
3586
|
-
},
|
|
3587
|
-
currentObservationTokens
|
|
3588
|
-
);
|
|
3589
|
-
this.storage.setPendingMessageTokens(freshRecord.id, totalPendingTokens).catch(() => {
|
|
3590
|
-
});
|
|
3591
|
-
}
|
|
3592
|
-
return messageList;
|
|
5039
|
+
return messageList;
|
|
5040
|
+
});
|
|
3593
5041
|
}
|
|
3594
5042
|
/**
|
|
3595
5043
|
* Save any unsaved messages at the end of the agent turn.
|
|
@@ -3606,30 +5054,35 @@ ${suggestedResponse}
|
|
|
3606
5054
|
return messageList;
|
|
3607
5055
|
}
|
|
3608
5056
|
const { threadId, resourceId } = context;
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
5057
|
+
return this.runWithTokenCounterModelContext(
|
|
5058
|
+
state.__omActorModelContext,
|
|
5059
|
+
async () => {
|
|
5060
|
+
const memoryContext = memory.parseMemoryRequestContext(requestContext);
|
|
5061
|
+
const readOnly = memoryContext?.memoryConfig?.readOnly;
|
|
5062
|
+
if (readOnly) {
|
|
5063
|
+
return messageList;
|
|
5064
|
+
}
|
|
5065
|
+
const newInput = messageList.get.input.db();
|
|
5066
|
+
const newOutput = messageList.get.response.db();
|
|
5067
|
+
const messagesToSave = [...newInput, ...newOutput];
|
|
5068
|
+
omDebug(
|
|
5069
|
+
`[OM:processOutputResult] threadId=${threadId}, inputMsgs=${newInput.length}, responseMsgs=${newOutput.length}, totalToSave=${messagesToSave.length}, allMsgsInList=${messageList.get.all.db().length}`
|
|
5070
|
+
);
|
|
5071
|
+
if (messagesToSave.length === 0) {
|
|
5072
|
+
omDebug(`[OM:processOutputResult] nothing to save \u2014 all messages were already saved during per-step saves`);
|
|
5073
|
+
return messageList;
|
|
5074
|
+
}
|
|
5075
|
+
const sealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
|
|
5076
|
+
omDebug(
|
|
5077
|
+
`[OM:processOutputResult] saving ${messagesToSave.length} messages, sealedIds=${sealedIds.size}, ids=${messagesToSave.map((m) => m.id?.slice(0, 8)).join(",")}`
|
|
5078
|
+
);
|
|
5079
|
+
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
5080
|
+
omDebug(
|
|
5081
|
+
`[OM:processOutputResult] saved successfully, finalIds=${messagesToSave.map((m) => m.id?.slice(0, 8)).join(",")}`
|
|
5082
|
+
);
|
|
5083
|
+
return messageList;
|
|
5084
|
+
}
|
|
3631
5085
|
);
|
|
3632
|
-
return messageList;
|
|
3633
5086
|
}
|
|
3634
5087
|
/**
|
|
3635
5088
|
* Save messages to storage while preventing duplicate inserts for sealed messages.
|
|
@@ -3783,6 +5236,30 @@ ${formattedMessages}
|
|
|
3783
5236
|
}
|
|
3784
5237
|
return maxTime > 0 ? new Date(maxTime) : /* @__PURE__ */ new Date();
|
|
3785
5238
|
}
|
|
5239
|
+
/**
|
|
5240
|
+
* Compute a cursor pointing at the latest message by createdAt.
|
|
5241
|
+
* Used to derive a stable observation boundary for replay pruning.
|
|
5242
|
+
*/
|
|
5243
|
+
getLastObservedMessageCursor(messages) {
|
|
5244
|
+
let latest;
|
|
5245
|
+
for (const msg of messages) {
|
|
5246
|
+
if (!msg?.id || !msg.createdAt) continue;
|
|
5247
|
+
if (!latest || new Date(msg.createdAt).getTime() > new Date(latest.createdAt).getTime()) {
|
|
5248
|
+
latest = msg;
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
return latest ? { createdAt: new Date(latest.createdAt).toISOString(), id: latest.id } : void 0;
|
|
5252
|
+
}
|
|
5253
|
+
/**
|
|
5254
|
+
* Check if a message is at or before a cursor (by createdAt then id).
|
|
5255
|
+
*/
|
|
5256
|
+
isMessageAtOrBeforeCursor(msg, cursor) {
|
|
5257
|
+
if (!msg.createdAt) return false;
|
|
5258
|
+
const msgIso = new Date(msg.createdAt).toISOString();
|
|
5259
|
+
if (msgIso < cursor.createdAt) return true;
|
|
5260
|
+
if (msgIso === cursor.createdAt && msg.id === cursor.id) return true;
|
|
5261
|
+
return false;
|
|
5262
|
+
}
|
|
3786
5263
|
/**
|
|
3787
5264
|
* Wrap observations in a thread attribution tag.
|
|
3788
5265
|
* Used in resource scope to track which thread observations came from.
|
|
@@ -3881,7 +5358,7 @@ ${newThreadSection}`;
|
|
|
3881
5358
|
await this.storage.setObservingFlag(record.id, true);
|
|
3882
5359
|
registerOp(record.id, "observing");
|
|
3883
5360
|
const cycleId = crypto.randomUUID();
|
|
3884
|
-
const tokensToObserve = this.tokenCounter.
|
|
5361
|
+
const tokensToObserve = await this.tokenCounter.countMessagesAsync(unobservedMessages);
|
|
3885
5362
|
const lastMessage = unobservedMessages[unobservedMessages.length - 1];
|
|
3886
5363
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3887
5364
|
if (lastMessage?.id) {
|
|
@@ -3943,7 +5420,8 @@ ${result.observations}` : result.observations;
|
|
|
3943
5420
|
if (thread) {
|
|
3944
5421
|
const newMetadata = memory.setThreadOMMetadata(thread.metadata, {
|
|
3945
5422
|
suggestedResponse: result.suggestedContinuation,
|
|
3946
|
-
currentTask: result.currentTask
|
|
5423
|
+
currentTask: result.currentTask,
|
|
5424
|
+
lastObservedMessageCursor: this.getLastObservedMessageCursor(messagesToObserve)
|
|
3947
5425
|
});
|
|
3948
5426
|
await this.storage.updateThread({
|
|
3949
5427
|
id: threadId,
|
|
@@ -3958,7 +5436,7 @@ ${result.observations}` : result.observations;
|
|
|
3958
5436
|
lastObservedAt,
|
|
3959
5437
|
observedMessageIds: allObservedIds
|
|
3960
5438
|
});
|
|
3961
|
-
const actualTokensObserved = this.tokenCounter.
|
|
5439
|
+
const actualTokensObserved = await this.tokenCounter.countMessagesAsync(messagesToObserve);
|
|
3962
5440
|
if (lastMessage?.id) {
|
|
3963
5441
|
const endMarker = createObservationEndMarker({
|
|
3964
5442
|
cycleId,
|
|
@@ -4039,9 +5517,9 @@ ${result.observations}` : result.observations;
|
|
|
4039
5517
|
* @param lockKey - Lock key for this scope
|
|
4040
5518
|
* @param writer - Optional stream writer for emitting buffering markers
|
|
4041
5519
|
*/
|
|
4042
|
-
startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext) {
|
|
5520
|
+
async startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext) {
|
|
4043
5521
|
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
4044
|
-
const currentTokens = contextWindowTokens ?? this.tokenCounter.
|
|
5522
|
+
const currentTokens = contextWindowTokens ?? await this.tokenCounter.countMessagesAsync(unobservedMessages) + (record.pendingMessageTokens ?? 0);
|
|
4045
5523
|
_ObservationalMemory.lastBufferedBoundary.set(bufferKey, currentTokens);
|
|
4046
5524
|
registerOp(record.id, "bufferingObservation");
|
|
4047
5525
|
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
@@ -4101,7 +5579,7 @@ ${result.observations}` : result.observations;
|
|
|
4101
5579
|
);
|
|
4102
5580
|
const bufferTokens = this.observationConfig.bufferTokens ?? 5e3;
|
|
4103
5581
|
const minNewTokens = bufferTokens / 2;
|
|
4104
|
-
const newTokens = this.tokenCounter.
|
|
5582
|
+
const newTokens = await this.tokenCounter.countMessagesAsync(candidateMessages);
|
|
4105
5583
|
if (newTokens < minNewTokens) {
|
|
4106
5584
|
return;
|
|
4107
5585
|
}
|
|
@@ -4122,7 +5600,7 @@ ${result.observations}` : result.observations;
|
|
|
4122
5600
|
}
|
|
4123
5601
|
const cycleId = `buffer-obs-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
4124
5602
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4125
|
-
const tokensToBuffer = this.tokenCounter.
|
|
5603
|
+
const tokensToBuffer = await this.tokenCounter.countMessagesAsync(messagesToBuffer);
|
|
4126
5604
|
if (writer) {
|
|
4127
5605
|
const startMarker = createBufferingStartMarker({
|
|
4128
5606
|
cycleId,
|
|
@@ -4200,7 +5678,7 @@ ${result.observations}` : result.observations;
|
|
|
4200
5678
|
}
|
|
4201
5679
|
const newTokenCount = this.tokenCounter.countObservations(newObservations);
|
|
4202
5680
|
const newMessageIds = messagesToBuffer.map((m) => m.id);
|
|
4203
|
-
const messageTokens = this.tokenCounter.
|
|
5681
|
+
const messageTokens = await this.tokenCounter.countMessagesAsync(messagesToBuffer);
|
|
4204
5682
|
const maxMessageTimestamp = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
4205
5683
|
const lastObservedAt = new Date(maxMessageTimestamp.getTime() + 1);
|
|
4206
5684
|
await this.storage.updateBufferedObservations({
|
|
@@ -4218,7 +5696,7 @@ ${result.observations}` : result.observations;
|
|
|
4218
5696
|
lastBufferedAtTime: lastObservedAt
|
|
4219
5697
|
});
|
|
4220
5698
|
if (writer) {
|
|
4221
|
-
const tokensBuffered = this.tokenCounter.
|
|
5699
|
+
const tokensBuffered = await this.tokenCounter.countMessagesAsync(messagesToBuffer);
|
|
4222
5700
|
const updatedRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
4223
5701
|
const updatedChunks = this.getBufferedChunks(updatedRecord);
|
|
4224
5702
|
const totalBufferedTokens = updatedChunks.reduce((sum, c) => sum + (c.tokenCount ?? 0), 0) || newTokenCount;
|
|
@@ -4287,14 +5765,14 @@ ${bufferedObservations}`;
|
|
|
4287
5765
|
if (!freshRecord) {
|
|
4288
5766
|
return { success: false };
|
|
4289
5767
|
}
|
|
4290
|
-
const
|
|
4291
|
-
if (!
|
|
5768
|
+
const rawFreshChunks = this.getBufferedChunks(freshRecord);
|
|
5769
|
+
if (!rawFreshChunks.length) {
|
|
4292
5770
|
return { success: false };
|
|
4293
5771
|
}
|
|
4294
5772
|
const messageTokensThreshold = getMaxThreshold(this.observationConfig.messageTokens);
|
|
4295
5773
|
let effectivePendingTokens = currentPendingTokens;
|
|
4296
5774
|
if (messageList) {
|
|
4297
|
-
effectivePendingTokens = this.tokenCounter.
|
|
5775
|
+
effectivePendingTokens = await this.tokenCounter.countMessagesAsync(messageList.get.all.db());
|
|
4298
5776
|
if (effectivePendingTokens < messageTokensThreshold) {
|
|
4299
5777
|
omDebug(
|
|
4300
5778
|
`[OM:tryActivate] skipping activation: freshPendingTokens=${effectivePendingTokens} < threshold=${messageTokensThreshold}`
|
|
@@ -4302,6 +5780,7 @@ ${bufferedObservations}`;
|
|
|
4302
5780
|
return { success: false };
|
|
4303
5781
|
}
|
|
4304
5782
|
}
|
|
5783
|
+
const freshChunks = messageList ? this.refreshBufferedChunkMessageTokens(rawFreshChunks, messageList) : rawFreshChunks;
|
|
4305
5784
|
const bufferActivation = this.observationConfig.bufferActivation ?? 0.7;
|
|
4306
5785
|
const activationRatio = resolveActivationRatio(bufferActivation, messageTokensThreshold);
|
|
4307
5786
|
const forceMaxActivation = !!(this.observationConfig.blockAfter && effectivePendingTokens >= this.observationConfig.blockAfter);
|
|
@@ -4329,7 +5808,8 @@ ${bufferedObservations}`;
|
|
|
4329
5808
|
activationRatio,
|
|
4330
5809
|
messageTokensThreshold,
|
|
4331
5810
|
currentPendingTokens: effectivePendingTokens,
|
|
4332
|
-
forceMaxActivation
|
|
5811
|
+
forceMaxActivation,
|
|
5812
|
+
bufferedChunks: freshChunks
|
|
4333
5813
|
});
|
|
4334
5814
|
omDebug(
|
|
4335
5815
|
`[OM:tryActivate] swapResult: chunksActivated=${activationResult.chunksActivated}, tokensActivated=${activationResult.messageTokensActivated}, obsTokensActivated=${activationResult.observationTokensActivated}, activatedCycleIds=${activationResult.activatedCycleIds.join(",")}`
|
|
@@ -4657,10 +6137,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
4657
6137
|
const threshold = getMaxThreshold(this.observationConfig.messageTokens);
|
|
4658
6138
|
const threadTokenCounts = /* @__PURE__ */ new Map();
|
|
4659
6139
|
for (const [threadId, msgs] of messagesByThread) {
|
|
4660
|
-
|
|
4661
|
-
for (const msg of msgs) {
|
|
4662
|
-
tokens += this.tokenCounter.countMessage(msg);
|
|
4663
|
-
}
|
|
6140
|
+
const tokens = await this.tokenCounter.countMessagesAsync(msgs);
|
|
4664
6141
|
threadTokenCounts.set(threadId, tokens);
|
|
4665
6142
|
}
|
|
4666
6143
|
const threadsBySize = Array.from(messagesByThread.keys()).sort((a, b) => {
|
|
@@ -4717,7 +6194,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
4717
6194
|
const allThreadIds = Array.from(threadsWithMessages.keys());
|
|
4718
6195
|
for (const [threadId, msgs] of threadsWithMessages) {
|
|
4719
6196
|
const lastMessage = msgs[msgs.length - 1];
|
|
4720
|
-
const tokensToObserve = this.tokenCounter.
|
|
6197
|
+
const tokensToObserve = await this.tokenCounter.countMessagesAsync(msgs);
|
|
4721
6198
|
threadTokensToObserve.set(threadId, tokensToObserve);
|
|
4722
6199
|
if (lastMessage?.id) {
|
|
4723
6200
|
const startMarker = createObservationStartMarker({
|
|
@@ -4809,7 +6286,8 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
4809
6286
|
const newMetadata = memory.setThreadOMMetadata(thread.metadata, {
|
|
4810
6287
|
lastObservedAt: threadLastObservedAt.toISOString(),
|
|
4811
6288
|
suggestedResponse: result.suggestedContinuation,
|
|
4812
|
-
currentTask: result.currentTask
|
|
6289
|
+
currentTask: result.currentTask,
|
|
6290
|
+
lastObservedMessageCursor: this.getLastObservedMessageCursor(threadMessages)
|
|
4813
6291
|
});
|
|
4814
6292
|
await this.storage.updateThread({
|
|
4815
6293
|
id: threadId,
|
|
@@ -4852,7 +6330,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
4852
6330
|
const { threadId, threadMessages, result } = obsResult;
|
|
4853
6331
|
const lastMessage = threadMessages[threadMessages.length - 1];
|
|
4854
6332
|
if (lastMessage?.id) {
|
|
4855
|
-
const tokensObserved = threadTokensToObserve.get(threadId) ?? this.tokenCounter.
|
|
6333
|
+
const tokensObserved = threadTokensToObserve.get(threadId) ?? await this.tokenCounter.countMessagesAsync(threadMessages);
|
|
4856
6334
|
const endMarker = createObservationEndMarker({
|
|
4857
6335
|
cycleId,
|
|
4858
6336
|
operationType: "observation",
|
|
@@ -5103,7 +6581,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
5103
6581
|
const currentMessages = messages ?? [];
|
|
5104
6582
|
if (!this.meetsObservationThreshold({
|
|
5105
6583
|
record: freshRecord,
|
|
5106
|
-
unobservedTokens: this.tokenCounter.
|
|
6584
|
+
unobservedTokens: await this.tokenCounter.countMessagesAsync(currentMessages)
|
|
5107
6585
|
})) {
|
|
5108
6586
|
return;
|
|
5109
6587
|
}
|
|
@@ -5131,7 +6609,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
|
5131
6609
|
}
|
|
5132
6610
|
if (!this.meetsObservationThreshold({
|
|
5133
6611
|
record: freshRecord,
|
|
5134
|
-
unobservedTokens: this.tokenCounter.
|
|
6612
|
+
unobservedTokens: await this.tokenCounter.countMessagesAsync(unobservedMessages)
|
|
5135
6613
|
})) {
|
|
5136
6614
|
return;
|
|
5137
6615
|
}
|
|
@@ -5261,5 +6739,5 @@ exports.formatMessagesForObserver = formatMessagesForObserver;
|
|
|
5261
6739
|
exports.hasCurrentTaskSection = hasCurrentTaskSection;
|
|
5262
6740
|
exports.optimizeObservationsForContext = optimizeObservationsForContext;
|
|
5263
6741
|
exports.parseObserverOutput = parseObserverOutput;
|
|
5264
|
-
//# sourceMappingURL=chunk-
|
|
5265
|
-
//# sourceMappingURL=chunk-
|
|
6742
|
+
//# sourceMappingURL=chunk-5W5463NI.cjs.map
|
|
6743
|
+
//# sourceMappingURL=chunk-5W5463NI.cjs.map
|