@aigne/core 1.71.0 → 1.72.0-beta.10
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 +158 -0
- package/lib/cjs/agents/agent.d.ts +42 -29
- package/lib/cjs/agents/agent.js +32 -11
- package/lib/cjs/agents/ai-agent.d.ts +63 -4
- package/lib/cjs/agents/ai-agent.js +148 -20
- package/lib/cjs/agents/chat-model.d.ts +162 -0
- package/lib/cjs/agents/chat-model.js +56 -5
- package/lib/cjs/agents/image-agent.d.ts +17 -1
- package/lib/cjs/agents/image-agent.js +16 -0
- package/lib/cjs/agents/image-model.d.ts +17 -2
- package/lib/cjs/agents/image-model.js +2 -0
- package/lib/cjs/agents/mcp-agent.d.ts +17 -0
- package/lib/cjs/agents/mcp-agent.js +18 -0
- package/lib/cjs/agents/team-agent.d.ts +55 -0
- package/lib/cjs/agents/team-agent.js +31 -0
- package/lib/cjs/agents/transform-agent.d.ts +12 -0
- package/lib/cjs/agents/transform-agent.js +13 -0
- package/lib/cjs/agents/video-model.d.ts +15 -0
- package/lib/cjs/agents/video-model.js +2 -0
- package/lib/cjs/aigne/usage.d.ts +5 -0
- package/lib/cjs/aigne/usage.js +6 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/loader/agent-yaml.d.ts +27 -64
- package/lib/cjs/loader/agent-yaml.js +22 -129
- package/lib/cjs/loader/agents.d.ts +4 -0
- package/lib/cjs/loader/agents.js +17 -0
- package/lib/cjs/loader/index.d.ts +16 -12
- package/lib/cjs/loader/index.js +46 -82
- package/lib/cjs/loader/schema.d.ts +21 -6
- package/lib/cjs/loader/schema.js +60 -1
- package/lib/cjs/memory/recorder.d.ts +4 -4
- package/lib/cjs/memory/retriever.d.ts +4 -4
- package/lib/cjs/prompt/agent-session.d.ts +135 -0
- package/lib/cjs/prompt/agent-session.js +889 -0
- package/lib/cjs/prompt/compact/compactor.d.ts +7 -0
- package/lib/cjs/prompt/compact/compactor.js +48 -0
- package/lib/cjs/prompt/compact/session-memory-extractor.d.ts +7 -0
- package/lib/cjs/prompt/compact/session-memory-extractor.js +139 -0
- package/lib/cjs/prompt/compact/types.d.ts +329 -0
- package/lib/cjs/prompt/compact/types.js +53 -0
- package/lib/cjs/prompt/compact/user-memory-extractor.d.ts +7 -0
- package/lib/cjs/prompt/compact/user-memory-extractor.js +120 -0
- package/lib/cjs/prompt/context/afs/history.d.ts +9 -0
- package/lib/cjs/prompt/context/afs/history.js +33 -0
- package/lib/cjs/prompt/context/afs/index.d.ts +20 -0
- package/lib/cjs/prompt/context/afs/index.js +54 -0
- package/lib/cjs/prompt/context/index.d.ts +31 -0
- package/lib/cjs/prompt/context/index.js +18 -0
- package/lib/cjs/prompt/prompt-builder.d.ts +11 -9
- package/lib/cjs/prompt/prompt-builder.js +81 -151
- package/lib/cjs/prompt/skills/afs/agent-skill/agent-skill.d.ts +18 -0
- package/lib/cjs/prompt/skills/afs/agent-skill/agent-skill.js +69 -0
- package/lib/cjs/prompt/skills/afs/agent-skill/skill-loader.d.ts +13 -0
- package/lib/cjs/prompt/skills/afs/agent-skill/skill-loader.js +62 -0
- package/lib/cjs/prompt/skills/afs/base.d.ts +4 -0
- package/lib/cjs/prompt/skills/afs/base.js +8 -0
- package/lib/cjs/prompt/skills/afs/delete.d.ts +3 -2
- package/lib/cjs/prompt/skills/afs/delete.js +17 -5
- package/lib/cjs/prompt/skills/afs/edit.d.ts +9 -11
- package/lib/cjs/prompt/skills/afs/edit.js +89 -63
- package/lib/cjs/prompt/skills/afs/exec.d.ts +4 -3
- package/lib/cjs/prompt/skills/afs/exec.js +23 -7
- package/lib/cjs/prompt/skills/afs/index.js +4 -1
- package/lib/cjs/prompt/skills/afs/list.d.ts +4 -4
- package/lib/cjs/prompt/skills/afs/list.js +38 -55
- package/lib/cjs/prompt/skills/afs/read.d.ts +10 -5
- package/lib/cjs/prompt/skills/afs/read.js +66 -19
- package/lib/cjs/prompt/skills/afs/rename.d.ts +3 -2
- package/lib/cjs/prompt/skills/afs/rename.js +20 -6
- package/lib/cjs/prompt/skills/afs/search.d.ts +4 -3
- package/lib/cjs/prompt/skills/afs/search.js +24 -8
- package/lib/cjs/prompt/skills/afs/write.d.ts +3 -2
- package/lib/cjs/prompt/skills/afs/write.js +22 -8
- package/lib/cjs/prompt/template.d.ts +84 -9
- package/lib/cjs/prompt/template.js +46 -17
- package/lib/dts/agents/agent.d.ts +42 -29
- package/lib/dts/agents/ai-agent.d.ts +63 -4
- package/lib/dts/agents/chat-model.d.ts +162 -0
- package/lib/dts/agents/image-agent.d.ts +17 -1
- package/lib/dts/agents/image-model.d.ts +17 -2
- package/lib/dts/agents/mcp-agent.d.ts +17 -0
- package/lib/dts/agents/team-agent.d.ts +55 -0
- package/lib/dts/agents/transform-agent.d.ts +12 -0
- package/lib/dts/agents/video-model.d.ts +15 -0
- package/lib/dts/aigne/context.d.ts +2 -2
- package/lib/dts/aigne/usage.d.ts +5 -0
- package/lib/dts/index.d.ts +1 -0
- package/lib/dts/loader/agent-yaml.d.ts +27 -64
- package/lib/dts/loader/agents.d.ts +4 -0
- package/lib/dts/loader/index.d.ts +16 -12
- package/lib/dts/loader/schema.d.ts +21 -6
- package/lib/dts/memory/recorder.d.ts +4 -4
- package/lib/dts/memory/retriever.d.ts +4 -4
- package/lib/dts/prompt/agent-session.d.ts +135 -0
- package/lib/dts/prompt/compact/compactor.d.ts +7 -0
- package/lib/dts/prompt/compact/session-memory-extractor.d.ts +7 -0
- package/lib/dts/prompt/compact/types.d.ts +329 -0
- package/lib/dts/prompt/compact/user-memory-extractor.d.ts +7 -0
- package/lib/dts/prompt/context/afs/history.d.ts +9 -0
- package/lib/dts/prompt/context/afs/index.d.ts +20 -0
- package/lib/dts/prompt/context/index.d.ts +31 -0
- package/lib/dts/prompt/prompt-builder.d.ts +11 -9
- package/lib/dts/prompt/skills/afs/agent-skill/agent-skill.d.ts +18 -0
- package/lib/dts/prompt/skills/afs/agent-skill/skill-loader.d.ts +13 -0
- package/lib/dts/prompt/skills/afs/base.d.ts +4 -0
- package/lib/dts/prompt/skills/afs/delete.d.ts +3 -2
- package/lib/dts/prompt/skills/afs/edit.d.ts +9 -11
- package/lib/dts/prompt/skills/afs/exec.d.ts +4 -3
- package/lib/dts/prompt/skills/afs/list.d.ts +4 -4
- package/lib/dts/prompt/skills/afs/read.d.ts +10 -5
- package/lib/dts/prompt/skills/afs/rename.d.ts +3 -2
- package/lib/dts/prompt/skills/afs/search.d.ts +4 -3
- package/lib/dts/prompt/skills/afs/write.d.ts +3 -2
- package/lib/dts/prompt/template.d.ts +84 -9
- package/lib/esm/agents/agent.d.ts +42 -29
- package/lib/esm/agents/agent.js +32 -11
- package/lib/esm/agents/ai-agent.d.ts +63 -4
- package/lib/esm/agents/ai-agent.js +148 -20
- package/lib/esm/agents/chat-model.d.ts +162 -0
- package/lib/esm/agents/chat-model.js +55 -4
- package/lib/esm/agents/image-agent.d.ts +17 -1
- package/lib/esm/agents/image-agent.js +16 -0
- package/lib/esm/agents/image-model.d.ts +17 -2
- package/lib/esm/agents/image-model.js +2 -0
- package/lib/esm/agents/mcp-agent.d.ts +17 -0
- package/lib/esm/agents/mcp-agent.js +18 -0
- package/lib/esm/agents/team-agent.d.ts +55 -0
- package/lib/esm/agents/team-agent.js +31 -0
- package/lib/esm/agents/transform-agent.d.ts +12 -0
- package/lib/esm/agents/transform-agent.js +13 -0
- package/lib/esm/agents/video-model.d.ts +15 -0
- package/lib/esm/agents/video-model.js +2 -0
- package/lib/esm/aigne/context.d.ts +2 -2
- package/lib/esm/aigne/usage.d.ts +5 -0
- package/lib/esm/aigne/usage.js +6 -0
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/loader/agent-yaml.d.ts +27 -64
- package/lib/esm/loader/agent-yaml.js +22 -128
- package/lib/esm/loader/agents.d.ts +4 -0
- package/lib/esm/loader/agents.js +14 -0
- package/lib/esm/loader/index.d.ts +16 -12
- package/lib/esm/loader/index.js +47 -82
- package/lib/esm/loader/schema.d.ts +21 -6
- package/lib/esm/loader/schema.js +57 -0
- package/lib/esm/memory/recorder.d.ts +4 -4
- package/lib/esm/memory/retriever.d.ts +4 -4
- package/lib/esm/prompt/agent-session.d.ts +135 -0
- package/lib/esm/prompt/agent-session.js +849 -0
- package/lib/esm/prompt/compact/compactor.d.ts +7 -0
- package/lib/esm/prompt/compact/compactor.js +44 -0
- package/lib/esm/prompt/compact/session-memory-extractor.d.ts +7 -0
- package/lib/esm/prompt/compact/session-memory-extractor.js +135 -0
- package/lib/esm/prompt/compact/types.d.ts +329 -0
- package/lib/esm/prompt/compact/types.js +50 -0
- package/lib/esm/prompt/compact/user-memory-extractor.d.ts +7 -0
- package/lib/esm/prompt/compact/user-memory-extractor.js +116 -0
- package/lib/esm/prompt/context/afs/history.d.ts +9 -0
- package/lib/esm/prompt/context/afs/history.js +30 -0
- package/lib/esm/prompt/context/afs/index.d.ts +20 -0
- package/lib/esm/prompt/context/afs/index.js +51 -0
- package/lib/esm/prompt/context/index.d.ts +31 -0
- package/lib/esm/prompt/context/index.js +15 -0
- package/lib/esm/prompt/prompt-builder.d.ts +11 -9
- package/lib/esm/prompt/prompt-builder.js +80 -150
- package/lib/esm/prompt/skills/afs/agent-skill/agent-skill.d.ts +18 -0
- package/lib/esm/prompt/skills/afs/agent-skill/agent-skill.js +65 -0
- package/lib/esm/prompt/skills/afs/agent-skill/skill-loader.d.ts +13 -0
- package/lib/esm/prompt/skills/afs/agent-skill/skill-loader.js +54 -0
- package/lib/esm/prompt/skills/afs/base.d.ts +4 -0
- package/lib/esm/prompt/skills/afs/base.js +4 -0
- package/lib/esm/prompt/skills/afs/delete.d.ts +3 -2
- package/lib/esm/prompt/skills/afs/delete.js +17 -5
- package/lib/esm/prompt/skills/afs/edit.d.ts +9 -11
- package/lib/esm/prompt/skills/afs/edit.js +89 -63
- package/lib/esm/prompt/skills/afs/exec.d.ts +4 -3
- package/lib/esm/prompt/skills/afs/exec.js +23 -7
- package/lib/esm/prompt/skills/afs/index.js +4 -1
- package/lib/esm/prompt/skills/afs/list.d.ts +4 -4
- package/lib/esm/prompt/skills/afs/list.js +38 -55
- package/lib/esm/prompt/skills/afs/read.d.ts +10 -5
- package/lib/esm/prompt/skills/afs/read.js +66 -19
- package/lib/esm/prompt/skills/afs/rename.d.ts +3 -2
- package/lib/esm/prompt/skills/afs/rename.js +20 -6
- package/lib/esm/prompt/skills/afs/search.d.ts +4 -3
- package/lib/esm/prompt/skills/afs/search.js +24 -8
- package/lib/esm/prompt/skills/afs/write.d.ts +3 -2
- package/lib/esm/prompt/skills/afs/write.js +22 -8
- package/lib/esm/prompt/template.d.ts +84 -9
- package/lib/esm/prompt/template.js +46 -17
- package/package.json +6 -5
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
19
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
|
+
};
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.AgentSession = void 0;
|
|
40
|
+
const afs_history_1 = require("@aigne/afs-history");
|
|
41
|
+
const uuid_1 = require("@aigne/uuid");
|
|
42
|
+
const ufo_1 = require("ufo");
|
|
43
|
+
const yaml_1 = require("yaml");
|
|
44
|
+
const token_estimator_js_1 = require("../utils/token-estimator.js");
|
|
45
|
+
const type_utils_js_1 = require("../utils/type-utils.js");
|
|
46
|
+
const types_js_1 = require("./compact/types.js");
|
|
47
|
+
__exportStar(require("./compact/types.js"), exports);
|
|
48
|
+
class AgentSession {
|
|
49
|
+
sessionId;
|
|
50
|
+
userId;
|
|
51
|
+
agentId;
|
|
52
|
+
afs;
|
|
53
|
+
historyModulePath;
|
|
54
|
+
mode;
|
|
55
|
+
compactConfig;
|
|
56
|
+
sessionMemoryConfig;
|
|
57
|
+
userMemoryConfig;
|
|
58
|
+
runtimeState;
|
|
59
|
+
initialized;
|
|
60
|
+
compactionPromise;
|
|
61
|
+
sessionMemoryUpdatePromise;
|
|
62
|
+
userMemoryUpdatePromise;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
this.sessionId = options.sessionId;
|
|
65
|
+
this.userId = options.userId;
|
|
66
|
+
this.agentId = options.agentId;
|
|
67
|
+
this.afs = options.afs;
|
|
68
|
+
this.mode = options.mode ?? types_js_1.DEFAULT_SESSION_MODE;
|
|
69
|
+
this.compactConfig = options.compact ?? {};
|
|
70
|
+
this.sessionMemoryConfig = options.sessionMemory ?? {};
|
|
71
|
+
this.userMemoryConfig = options.userMemory ?? {};
|
|
72
|
+
this.runtimeState = {
|
|
73
|
+
historyEntries: [],
|
|
74
|
+
currentEntry: null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Check if memory extraction is enabled
|
|
79
|
+
* Memory extraction requires mode to be "auto" AND AFS history module to be available
|
|
80
|
+
*/
|
|
81
|
+
get isMemoryEnabled() {
|
|
82
|
+
return this.mode === "auto" && !!this.afs && !!this.historyModulePath;
|
|
83
|
+
}
|
|
84
|
+
async setSystemMessages(...messages) {
|
|
85
|
+
await this.ensureInitialized();
|
|
86
|
+
this.runtimeState.systemMessages = messages;
|
|
87
|
+
}
|
|
88
|
+
async getMessages() {
|
|
89
|
+
await this.ensureInitialized();
|
|
90
|
+
const { systemMessages, userMemory, sessionMemory, compactSummary, historyEntries, currentEntry, currentEntryCompression, } = this.runtimeState;
|
|
91
|
+
let currentMessages = [];
|
|
92
|
+
if (currentEntry?.messages?.length) {
|
|
93
|
+
if (currentEntryCompression) {
|
|
94
|
+
const { compressedCount, summary } = currentEntryCompression;
|
|
95
|
+
const firstMsg = currentEntry.messages[0];
|
|
96
|
+
const hasSkill = firstMsg?.role === "user" &&
|
|
97
|
+
Array.isArray(firstMsg.content) &&
|
|
98
|
+
firstMsg.content.some((block) => block.type === "text" && block.isAgentSkill === true);
|
|
99
|
+
const skillMessage = hasSkill ? [firstMsg] : [];
|
|
100
|
+
const summaryMessage = {
|
|
101
|
+
role: "user",
|
|
102
|
+
content: `[Earlier messages in this conversation (${compressedCount} messages compressed)]\n${summary}`,
|
|
103
|
+
};
|
|
104
|
+
const remainingMessages = currentEntry.messages.slice(compressedCount);
|
|
105
|
+
currentMessages = [...skillMessage, summaryMessage, ...remainingMessages];
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
currentMessages = currentEntry.messages;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const messages = [
|
|
112
|
+
...(systemMessages ?? []),
|
|
113
|
+
...(userMemory && userMemory.length > 0 ? [this.formatUserMemory(userMemory)] : []),
|
|
114
|
+
...(sessionMemory && sessionMemory.length > 0
|
|
115
|
+
? [this.formatSessionMemory(sessionMemory)]
|
|
116
|
+
: []),
|
|
117
|
+
...(compactSummary
|
|
118
|
+
? [
|
|
119
|
+
{
|
|
120
|
+
role: "system",
|
|
121
|
+
content: `Previous conversation summary:\n${compactSummary}`,
|
|
122
|
+
},
|
|
123
|
+
]
|
|
124
|
+
: []),
|
|
125
|
+
...historyEntries.flatMap((entry) => entry.content?.messages ?? []),
|
|
126
|
+
...currentMessages,
|
|
127
|
+
];
|
|
128
|
+
// Filter out thinking messages and truncate large messages
|
|
129
|
+
return messages
|
|
130
|
+
.map((msg) => {
|
|
131
|
+
if (!msg.content || typeof msg.content === "string") {
|
|
132
|
+
return msg;
|
|
133
|
+
}
|
|
134
|
+
// Filter out thinking from UnionContent[]
|
|
135
|
+
const filteredContent = msg.content.filter((c) => !(c.type === "text" && c.isThinking));
|
|
136
|
+
if (filteredContent.length === 0)
|
|
137
|
+
return null;
|
|
138
|
+
return { ...msg, content: filteredContent };
|
|
139
|
+
})
|
|
140
|
+
.filter(type_utils_js_1.isNonNullable)
|
|
141
|
+
.map((msg) => this.truncateLargeMessage(msg));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Format user memory facts into a system message
|
|
145
|
+
* Applies token budget limit to ensure memory injection fits within constraints
|
|
146
|
+
*/
|
|
147
|
+
formatUserMemory(memoryEntries) {
|
|
148
|
+
const memoryRatio = this.userMemoryConfig.memoryRatio ?? types_js_1.DEFAULT_MEMORY_RATIO;
|
|
149
|
+
const maxTokens = Math.floor((this.compactConfig.maxTokens ?? types_js_1.DEFAULT_MAX_TOKENS) * memoryRatio);
|
|
150
|
+
const header = "[User Memory Facts]";
|
|
151
|
+
let currentTokens = (0, token_estimator_js_1.estimateTokens)(header);
|
|
152
|
+
const facts = [];
|
|
153
|
+
for (const entry of memoryEntries) {
|
|
154
|
+
const fact = entry.content?.fact;
|
|
155
|
+
if (!fact)
|
|
156
|
+
continue;
|
|
157
|
+
const factTokens = (0, token_estimator_js_1.estimateTokens)(fact);
|
|
158
|
+
// Check if adding this fact would exceed token budget
|
|
159
|
+
if (currentTokens + factTokens > maxTokens) {
|
|
160
|
+
break; // Stop adding facts
|
|
161
|
+
}
|
|
162
|
+
facts.push(fact);
|
|
163
|
+
currentTokens += factTokens;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
role: "system",
|
|
167
|
+
content: this.formatMemoryTemplate({ header, data: facts }),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Format session memory facts into a system message
|
|
172
|
+
* Applies token budget limit to ensure memory injection fits within constraints
|
|
173
|
+
*/
|
|
174
|
+
formatSessionMemory(memoryEntries) {
|
|
175
|
+
const memoryRatio = this.sessionMemoryConfig.memoryRatio ?? types_js_1.DEFAULT_MEMORY_RATIO;
|
|
176
|
+
const maxTokens = Math.floor((this.compactConfig.maxTokens ?? types_js_1.DEFAULT_MAX_TOKENS) * memoryRatio);
|
|
177
|
+
const header = "[Session Memory Facts]";
|
|
178
|
+
let currentTokens = (0, token_estimator_js_1.estimateTokens)(header);
|
|
179
|
+
const facts = [];
|
|
180
|
+
for (const entry of memoryEntries) {
|
|
181
|
+
const fact = entry.content?.fact;
|
|
182
|
+
if (!fact)
|
|
183
|
+
continue;
|
|
184
|
+
const factTokens = (0, token_estimator_js_1.estimateTokens)(fact);
|
|
185
|
+
// Check if adding this fact would exceed token budget
|
|
186
|
+
if (currentTokens + factTokens > maxTokens) {
|
|
187
|
+
break; // Stop adding facts
|
|
188
|
+
}
|
|
189
|
+
facts.push(fact);
|
|
190
|
+
currentTokens += factTokens;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
role: "system",
|
|
194
|
+
content: this.formatMemoryTemplate({ header, data: facts }),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
formatMemoryTemplate({ header, data }) {
|
|
198
|
+
return `\
|
|
199
|
+
${header}
|
|
200
|
+
|
|
201
|
+
${"```yaml"}
|
|
202
|
+
${(0, yaml_1.stringify)(data)}
|
|
203
|
+
${"```"}
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
async startMessage(input, message, options) {
|
|
207
|
+
await this.ensureInitialized();
|
|
208
|
+
// Only run compact if mode is not disabled
|
|
209
|
+
if (this.mode !== "disabled") {
|
|
210
|
+
await this.maybeAutoCompact(options);
|
|
211
|
+
// Always wait for compaction to complete before starting a new message
|
|
212
|
+
// This ensures data consistency even in async compact mode
|
|
213
|
+
if (this.compactionPromise)
|
|
214
|
+
await this.compactionPromise;
|
|
215
|
+
}
|
|
216
|
+
this.runtimeState.currentEntryCompression = undefined;
|
|
217
|
+
this.runtimeState.currentEntry = { input, messages: [message] };
|
|
218
|
+
}
|
|
219
|
+
async endMessage(output, message, options) {
|
|
220
|
+
await this.ensureInitialized();
|
|
221
|
+
if (!this.runtimeState.currentEntry?.input ||
|
|
222
|
+
!this.runtimeState.currentEntry.messages?.length) {
|
|
223
|
+
throw new Error("No current entry to end. Call startMessage() first.");
|
|
224
|
+
}
|
|
225
|
+
if (message)
|
|
226
|
+
this.runtimeState.currentEntry.messages.push(message);
|
|
227
|
+
this.runtimeState.currentEntry.output = output;
|
|
228
|
+
let newEntry;
|
|
229
|
+
// Only persist to AFS if mode is not disabled
|
|
230
|
+
if (this.mode !== "disabled" && this.afs && this.historyModulePath) {
|
|
231
|
+
newEntry = (await this.afs.write((0, ufo_1.joinURL)(this.historyModulePath, "by-session", this.sessionId, "new"), {
|
|
232
|
+
userId: this.userId,
|
|
233
|
+
sessionId: this.sessionId,
|
|
234
|
+
agentId: this.agentId,
|
|
235
|
+
content: this.runtimeState.currentEntry,
|
|
236
|
+
})).data;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// Create in-memory entry for runtime state
|
|
240
|
+
const id = (0, uuid_1.v7)();
|
|
241
|
+
newEntry = {
|
|
242
|
+
id,
|
|
243
|
+
path: `/history/${id}`,
|
|
244
|
+
userId: this.userId,
|
|
245
|
+
sessionId: this.sessionId,
|
|
246
|
+
agentId: this.agentId,
|
|
247
|
+
content: this.runtimeState.currentEntry,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
this.runtimeState.historyEntries.push(newEntry);
|
|
251
|
+
this.runtimeState.currentEntry = null;
|
|
252
|
+
this.runtimeState.currentEntryCompression = undefined;
|
|
253
|
+
// Only run compact and memory extraction if mode is not disabled
|
|
254
|
+
if (this.mode !== "disabled") {
|
|
255
|
+
await Promise.all([
|
|
256
|
+
// Check if auto-compact should be triggered
|
|
257
|
+
this.maybeAutoCompact(options),
|
|
258
|
+
// Check if auto-update session memory should be triggered
|
|
259
|
+
this.maybeAutoUpdateSessionMemory(options),
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Manually trigger compaction
|
|
265
|
+
*/
|
|
266
|
+
async compact(options) {
|
|
267
|
+
await this.ensureInitialized();
|
|
268
|
+
// If compaction is already in progress, wait for it to complete
|
|
269
|
+
if (this.compactionPromise) {
|
|
270
|
+
return this.compactionPromise;
|
|
271
|
+
}
|
|
272
|
+
// Start new compaction task
|
|
273
|
+
this.compactionPromise = this.doCompact(options).finally(() => {
|
|
274
|
+
this.compactionPromise = undefined;
|
|
275
|
+
});
|
|
276
|
+
return this.compactionPromise;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Internal method that performs the actual compaction
|
|
280
|
+
*/
|
|
281
|
+
async doCompact(options) {
|
|
282
|
+
const { compactor } = this.compactConfig ?? {};
|
|
283
|
+
if (!compactor) {
|
|
284
|
+
throw new Error("Cannot compact without a compactor agent configured.");
|
|
285
|
+
}
|
|
286
|
+
const historyEntries = this.runtimeState.historyEntries;
|
|
287
|
+
if (historyEntries.length === 0)
|
|
288
|
+
return;
|
|
289
|
+
const maxTokens = this.maxTokens;
|
|
290
|
+
let keepTokenBudget = this.keepTokenBudget;
|
|
291
|
+
// Calculate tokens for system messages
|
|
292
|
+
const systemTokens = (this.runtimeState.systemMessages ?? []).reduce((sum, msg) => {
|
|
293
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content ?? "");
|
|
294
|
+
return sum + (0, token_estimator_js_1.estimateTokens)(content);
|
|
295
|
+
}, 0);
|
|
296
|
+
// Calculate tokens for current entry messages
|
|
297
|
+
const currentTokens = (this.runtimeState.currentEntry?.messages ?? []).reduce((sum, msg) => {
|
|
298
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content ?? "");
|
|
299
|
+
return sum + (0, token_estimator_js_1.estimateTokens)(content);
|
|
300
|
+
}, 0);
|
|
301
|
+
// Subtract system and current tokens from budget
|
|
302
|
+
// This ensures total tokens (system + current + kept history) stays within ratio budget
|
|
303
|
+
keepTokenBudget = Math.max(0, keepTokenBudget - systemTokens - currentTokens);
|
|
304
|
+
// Find split point by iterating backwards from most recent entry
|
|
305
|
+
// The split point divides history into: [compact] | [keep]
|
|
306
|
+
let splitIndex = historyEntries.length; // Default: keep all (no compaction)
|
|
307
|
+
let accumulatedTokens = 0;
|
|
308
|
+
for (let i = historyEntries.length - 1; i >= 0; i--) {
|
|
309
|
+
const entry = historyEntries[i];
|
|
310
|
+
if (!entry)
|
|
311
|
+
continue;
|
|
312
|
+
const entryTokens = this.estimateMessagesTokens(entry.content?.messages ?? []);
|
|
313
|
+
// Check if adding this entry would exceed token budget
|
|
314
|
+
if (accumulatedTokens + entryTokens > keepTokenBudget) {
|
|
315
|
+
// Would exceed budget, split here (this entry and earlier ones will be compacted)
|
|
316
|
+
splitIndex = i + 1;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
// Can keep this entry, accumulate and continue
|
|
320
|
+
accumulatedTokens += entryTokens;
|
|
321
|
+
splitIndex = i;
|
|
322
|
+
}
|
|
323
|
+
// Split history at the found point
|
|
324
|
+
const entriesToCompact = historyEntries.slice(0, splitIndex);
|
|
325
|
+
const entriesToKeep = historyEntries.slice(splitIndex);
|
|
326
|
+
// If nothing to compact, return
|
|
327
|
+
if (entriesToCompact.length === 0) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const latestCompactedEntry = entriesToCompact.at(-1);
|
|
331
|
+
if (!latestCompactedEntry)
|
|
332
|
+
return;
|
|
333
|
+
// Split into batches to avoid context overflow
|
|
334
|
+
const batches = this.splitIntoBatches(entriesToCompact, maxTokens);
|
|
335
|
+
// Process batches incrementally, each summary becomes input for the next
|
|
336
|
+
let currentSummary = this.runtimeState.compactSummary;
|
|
337
|
+
for (const batch of batches) {
|
|
338
|
+
const messages = batch
|
|
339
|
+
.flatMap((e) => e.content?.messages ?? [])
|
|
340
|
+
.filter(type_utils_js_1.isNonNullable)
|
|
341
|
+
.map((msg) => this.truncateLargeMessage(msg));
|
|
342
|
+
const result = await options.context.invoke(compactor, {
|
|
343
|
+
previousSummary: [currentSummary].filter(type_utils_js_1.isNonNullable),
|
|
344
|
+
messages,
|
|
345
|
+
});
|
|
346
|
+
currentSummary = result.summary;
|
|
347
|
+
}
|
|
348
|
+
// Write compact entry to AFS
|
|
349
|
+
if (this.afs && this.historyModulePath) {
|
|
350
|
+
await this.afs.write((0, ufo_1.joinURL)(this.historyModulePath, "by-session", this.sessionId, "@metadata/compact/new"), {
|
|
351
|
+
userId: this.userId,
|
|
352
|
+
agentId: this.agentId,
|
|
353
|
+
content: { summary: currentSummary },
|
|
354
|
+
metadata: {
|
|
355
|
+
latestEntryId: latestCompactedEntry.id,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// Update runtime state: keep the summary and recent entries
|
|
360
|
+
this.runtimeState.compactSummary = currentSummary;
|
|
361
|
+
this.runtimeState.historyEntries = entriesToKeep;
|
|
362
|
+
}
|
|
363
|
+
async compactCurrentEntry(options) {
|
|
364
|
+
const { compactor } = this.compactConfig ?? {};
|
|
365
|
+
if (!compactor)
|
|
366
|
+
return;
|
|
367
|
+
const currentEntry = this.runtimeState.currentEntry;
|
|
368
|
+
if (!currentEntry?.messages?.length)
|
|
369
|
+
return;
|
|
370
|
+
const alreadyCompressedCount = this.runtimeState.currentEntryCompression?.compressedCount ?? 0;
|
|
371
|
+
const uncompressedMessages = currentEntry.messages.slice(alreadyCompressedCount);
|
|
372
|
+
if (uncompressedMessages.length === 0)
|
|
373
|
+
return;
|
|
374
|
+
const keepTokenBudget = this.keepTokenBudget;
|
|
375
|
+
const singleMessageLimit = this.singleMessageLimit;
|
|
376
|
+
let splitIndex = uncompressedMessages.length;
|
|
377
|
+
let accumulatedTokens = 0;
|
|
378
|
+
for (let i = uncompressedMessages.length - 1; i >= 0; i--) {
|
|
379
|
+
const msg = uncompressedMessages[i];
|
|
380
|
+
if (!msg)
|
|
381
|
+
continue;
|
|
382
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content ?? "");
|
|
383
|
+
const msgTokens = (0, token_estimator_js_1.estimateTokens)(content);
|
|
384
|
+
const effectiveTokens = msgTokens > singleMessageLimit ? singleMessageLimit : msgTokens;
|
|
385
|
+
if (accumulatedTokens + effectiveTokens > keepTokenBudget) {
|
|
386
|
+
splitIndex = i + 1;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
accumulatedTokens += effectiveTokens;
|
|
390
|
+
splitIndex = i;
|
|
391
|
+
}
|
|
392
|
+
const keptMessages = uncompressedMessages.slice(splitIndex);
|
|
393
|
+
const requiredToolCallIds = new Set();
|
|
394
|
+
for (const msg of keptMessages) {
|
|
395
|
+
if (msg.role === "tool" && msg.toolCallId) {
|
|
396
|
+
requiredToolCallIds.add(msg.toolCallId);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (requiredToolCallIds.size > 0) {
|
|
400
|
+
for (let i = splitIndex - 1; i >= 0; i--) {
|
|
401
|
+
const msg = uncompressedMessages[i];
|
|
402
|
+
if (!msg?.toolCalls)
|
|
403
|
+
continue;
|
|
404
|
+
for (const toolCall of msg.toolCalls) {
|
|
405
|
+
if (requiredToolCallIds.has(toolCall.id)) {
|
|
406
|
+
splitIndex = i;
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const messagesToCompact = uncompressedMessages
|
|
413
|
+
.slice(0, splitIndex)
|
|
414
|
+
.map((msg) => this.truncateLargeMessage(msg));
|
|
415
|
+
if (messagesToCompact.length === 0)
|
|
416
|
+
return;
|
|
417
|
+
const result = await options.context.invoke(compactor, {
|
|
418
|
+
previousSummary: this.runtimeState.currentEntryCompression?.summary
|
|
419
|
+
? [this.runtimeState.currentEntryCompression.summary]
|
|
420
|
+
: undefined,
|
|
421
|
+
messages: messagesToCompact,
|
|
422
|
+
});
|
|
423
|
+
this.runtimeState.currentEntryCompression = {
|
|
424
|
+
summary: result.summary,
|
|
425
|
+
compressedCount: alreadyCompressedCount + messagesToCompact.length,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async maybeCompactCurrentEntry(options) {
|
|
429
|
+
const currentEntry = this.runtimeState.currentEntry;
|
|
430
|
+
if (!currentEntry?.messages?.length)
|
|
431
|
+
return;
|
|
432
|
+
const compressedCount = this.runtimeState.currentEntryCompression?.compressedCount ?? 0;
|
|
433
|
+
const uncompressedMessages = currentEntry.messages.slice(compressedCount);
|
|
434
|
+
const threshold = this.keepTokenBudget;
|
|
435
|
+
const currentTokens = this.estimateMessagesTokens(uncompressedMessages, this.singleMessageLimit);
|
|
436
|
+
if (currentTokens > threshold) {
|
|
437
|
+
await this.compactCurrentEntry(options);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async maybeAutoCompact(options) {
|
|
441
|
+
if (this.compactionPromise)
|
|
442
|
+
await this.compactionPromise;
|
|
443
|
+
if (!this.compactConfig)
|
|
444
|
+
return;
|
|
445
|
+
const mode = this.compactConfig.mode ?? types_js_1.DEFAULT_COMPACT_MODE;
|
|
446
|
+
if (mode === "disabled")
|
|
447
|
+
return;
|
|
448
|
+
const { compactor } = this.compactConfig;
|
|
449
|
+
if (!compactor)
|
|
450
|
+
return;
|
|
451
|
+
const maxTokens = this.maxTokens;
|
|
452
|
+
const messages = await this.getMessages();
|
|
453
|
+
const currentTokens = this.estimateMessagesTokens(messages);
|
|
454
|
+
if (currentTokens >= maxTokens) {
|
|
455
|
+
this.compact(options);
|
|
456
|
+
const isAsync = this.compactConfig.async ?? types_js_1.DEFAULT_COMPACT_ASYNC;
|
|
457
|
+
if (!isAsync)
|
|
458
|
+
await this.compactionPromise;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Estimate token count for an array of messages
|
|
463
|
+
*/
|
|
464
|
+
estimateMessagesTokens(messages, singleMessageLimit) {
|
|
465
|
+
return messages.reduce((sum, msg) => {
|
|
466
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content ?? "");
|
|
467
|
+
const tokens = (0, token_estimator_js_1.estimateTokens)(content);
|
|
468
|
+
if (singleMessageLimit && tokens > singleMessageLimit) {
|
|
469
|
+
return sum + singleMessageLimit;
|
|
470
|
+
}
|
|
471
|
+
return sum + tokens;
|
|
472
|
+
}, 0);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Split entries into batches based on token limit
|
|
476
|
+
* Each batch will not exceed the specified maxTokens
|
|
477
|
+
*/
|
|
478
|
+
splitIntoBatches(entries, maxTokens) {
|
|
479
|
+
const batches = [];
|
|
480
|
+
let currentBatch = [];
|
|
481
|
+
let currentTokens = 0;
|
|
482
|
+
for (const entry of entries) {
|
|
483
|
+
const entryTokens = this.estimateMessagesTokens(entry.content?.messages ?? []);
|
|
484
|
+
// If adding this entry exceeds limit and we have entries in current batch, start new batch
|
|
485
|
+
if (currentTokens + entryTokens > maxTokens && currentBatch.length > 0) {
|
|
486
|
+
batches.push(currentBatch);
|
|
487
|
+
currentBatch = [entry];
|
|
488
|
+
currentTokens = entryTokens;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
currentBatch.push(entry);
|
|
492
|
+
currentTokens += entryTokens;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// Add remaining entries
|
|
496
|
+
if (currentBatch.length > 0) {
|
|
497
|
+
batches.push(currentBatch);
|
|
498
|
+
}
|
|
499
|
+
return batches;
|
|
500
|
+
}
|
|
501
|
+
async appendCurrentMessages(messages, options) {
|
|
502
|
+
await this.ensureInitialized();
|
|
503
|
+
if (!this.runtimeState.currentEntry || !this.runtimeState.currentEntry.messages?.length) {
|
|
504
|
+
throw new Error("No current entry to append messages. Call startMessage() first.");
|
|
505
|
+
}
|
|
506
|
+
this.runtimeState.currentEntry.messages.push(...[messages].flat());
|
|
507
|
+
await this.maybeCompactCurrentEntry(options);
|
|
508
|
+
}
|
|
509
|
+
truncateLargeMessage(msg) {
|
|
510
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
511
|
+
const tokens = (0, token_estimator_js_1.estimateTokens)(content);
|
|
512
|
+
const singleMessageLimit = this.singleMessageLimit;
|
|
513
|
+
if (tokens <= singleMessageLimit)
|
|
514
|
+
return msg;
|
|
515
|
+
const keepRatio = (singleMessageLimit / tokens) * 0.9;
|
|
516
|
+
const keepLength = Math.floor(content.length * keepRatio);
|
|
517
|
+
const headLength = Math.floor(keepLength * 0.7);
|
|
518
|
+
const tailLength = Math.floor(keepLength * 0.3);
|
|
519
|
+
const truncated = content.slice(0, headLength) +
|
|
520
|
+
`\n\n[... Content too large, truncated ${tokens - singleMessageLimit} tokens ...]\n\n` +
|
|
521
|
+
content.slice(-tailLength);
|
|
522
|
+
if (typeof msg.content === "string") {
|
|
523
|
+
return { ...msg, content: truncated };
|
|
524
|
+
}
|
|
525
|
+
return msg;
|
|
526
|
+
}
|
|
527
|
+
async ensureInitialized() {
|
|
528
|
+
this.initialized ??= this.initialize();
|
|
529
|
+
await this.initialized;
|
|
530
|
+
}
|
|
531
|
+
async initialize() {
|
|
532
|
+
if (this.initialized)
|
|
533
|
+
return;
|
|
534
|
+
await this.initializeDefaultCompactor();
|
|
535
|
+
await this.initializeDefaultSessionMemoryExtractor();
|
|
536
|
+
await this.initializeDefaultUserMemoryExtractor();
|
|
537
|
+
const historyModule = (await this.afs?.listModules())?.find((m) => m.module instanceof afs_history_1.AFSHistory);
|
|
538
|
+
this.historyModulePath = historyModule?.path;
|
|
539
|
+
if (this.afs && this.historyModulePath) {
|
|
540
|
+
// Load user memory, session memory, and session history in parallel
|
|
541
|
+
const [userMemory, sessionMemory, sessionHistory] = await Promise.all([
|
|
542
|
+
this.loadUserMemory(),
|
|
543
|
+
this.loadSessionMemory(),
|
|
544
|
+
this.loadSessionHistory(),
|
|
545
|
+
]);
|
|
546
|
+
// Update runtime state with loaded data
|
|
547
|
+
this.runtimeState.userMemory = userMemory;
|
|
548
|
+
this.runtimeState.sessionMemory = sessionMemory;
|
|
549
|
+
this.runtimeState.compactSummary = sessionHistory.compactSummary;
|
|
550
|
+
this.runtimeState.historyEntries = sessionHistory.historyEntries;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Load session memory facts
|
|
555
|
+
* @returns Array of memory fact entries for the current session
|
|
556
|
+
*/
|
|
557
|
+
async loadSessionMemory() {
|
|
558
|
+
if (!this.afs || !this.historyModulePath)
|
|
559
|
+
return [];
|
|
560
|
+
// Check if session memory is disabled
|
|
561
|
+
const mode = this.sessionMemoryConfig.mode ?? types_js_1.DEFAULT_SESSION_MEMORY_MODE;
|
|
562
|
+
if (mode === "disabled")
|
|
563
|
+
return [];
|
|
564
|
+
const sessionMemoryPath = (0, ufo_1.joinURL)(this.historyModulePath, "by-session", this.sessionId, "@metadata/memory");
|
|
565
|
+
const queryLimit = this.sessionMemoryConfig.queryLimit ?? types_js_1.DEFAULT_MEMORY_QUERY_LIMIT;
|
|
566
|
+
const memoryResult = await this.afs.list(sessionMemoryPath, {
|
|
567
|
+
filter: { userId: this.userId, agentId: this.agentId },
|
|
568
|
+
orderBy: [["updatedAt", "desc"]],
|
|
569
|
+
limit: queryLimit,
|
|
570
|
+
});
|
|
571
|
+
// Filter out entries without content
|
|
572
|
+
const facts = memoryResult.data
|
|
573
|
+
.reverse()
|
|
574
|
+
.filter((entry) => (0, type_utils_js_1.isNonNullable)(entry.content));
|
|
575
|
+
return facts;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Load user memory facts
|
|
579
|
+
* @returns Array of memory fact entries for the current user
|
|
580
|
+
*/
|
|
581
|
+
async loadUserMemory() {
|
|
582
|
+
if (!this.afs || !this.historyModulePath || !this.userId)
|
|
583
|
+
return [];
|
|
584
|
+
// Check if user memory is disabled
|
|
585
|
+
const mode = this.userMemoryConfig.mode ?? types_js_1.DEFAULT_USER_MEMORY_MODE;
|
|
586
|
+
if (mode === "disabled")
|
|
587
|
+
return [];
|
|
588
|
+
const userMemoryPath = (0, ufo_1.joinURL)(this.historyModulePath, "by-user", this.userId, "@metadata/memory");
|
|
589
|
+
const queryLimit = this.userMemoryConfig.queryLimit ?? types_js_1.DEFAULT_MEMORY_QUERY_LIMIT;
|
|
590
|
+
const memoryResult = await this.afs.list(userMemoryPath, {
|
|
591
|
+
filter: { userId: this.userId, agentId: this.agentId },
|
|
592
|
+
orderBy: [["updatedAt", "desc"]],
|
|
593
|
+
limit: queryLimit,
|
|
594
|
+
});
|
|
595
|
+
// Filter out entries without content
|
|
596
|
+
const facts = memoryResult.data
|
|
597
|
+
.reverse()
|
|
598
|
+
.filter((entry) => (0, type_utils_js_1.isNonNullable)(entry.content));
|
|
599
|
+
return facts;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Load session history including compact summary and history entries
|
|
603
|
+
* @returns Object containing compact summary and history entries
|
|
604
|
+
*/
|
|
605
|
+
async loadSessionHistory() {
|
|
606
|
+
if (!this.afs || !this.historyModulePath) {
|
|
607
|
+
return { historyEntries: [] };
|
|
608
|
+
}
|
|
609
|
+
// Load latest compact entry if exists
|
|
610
|
+
const compactPath = (0, ufo_1.joinURL)(this.historyModulePath, "by-session", this.sessionId, "@metadata/compact");
|
|
611
|
+
const compactResult = await this.afs.list(compactPath, {
|
|
612
|
+
filter: { userId: this.userId, agentId: this.agentId },
|
|
613
|
+
orderBy: [["createdAt", "desc"]],
|
|
614
|
+
limit: 1,
|
|
615
|
+
});
|
|
616
|
+
const latestCompact = compactResult.data[0];
|
|
617
|
+
const compactSummary = latestCompact?.content?.summary;
|
|
618
|
+
// Load history entries (after compact point if exists)
|
|
619
|
+
const afsEntries = (await this.afs.list((0, ufo_1.joinURL)(this.historyModulePath, "by-session", this.sessionId), {
|
|
620
|
+
filter: {
|
|
621
|
+
userId: this.userId,
|
|
622
|
+
agentId: this.agentId,
|
|
623
|
+
// Only load entries after the latest compact
|
|
624
|
+
after: latestCompact?.createdAt?.toISOString(),
|
|
625
|
+
},
|
|
626
|
+
orderBy: [["createdAt", "desc"]],
|
|
627
|
+
// Set a very large limit to load all history entries
|
|
628
|
+
// The default limit is 10 which would cause history truncation
|
|
629
|
+
limit: 10000,
|
|
630
|
+
})).data;
|
|
631
|
+
const historyEntries = afsEntries.reverse().filter((entry) => (0, type_utils_js_1.isNonNullable)(entry.content));
|
|
632
|
+
return {
|
|
633
|
+
compactSummary,
|
|
634
|
+
historyEntries,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Manually trigger session memory update
|
|
639
|
+
*/
|
|
640
|
+
async updateSessionMemory(options) {
|
|
641
|
+
await this.ensureInitialized();
|
|
642
|
+
// If session memory update is already in progress, wait for it to complete
|
|
643
|
+
if (this.sessionMemoryUpdatePromise) {
|
|
644
|
+
return this.sessionMemoryUpdatePromise;
|
|
645
|
+
}
|
|
646
|
+
// Start new session memory update task
|
|
647
|
+
this.sessionMemoryUpdatePromise = this.doUpdateSessionMemory(options).finally(() => {
|
|
648
|
+
this.sessionMemoryUpdatePromise = undefined;
|
|
649
|
+
// After session memory update completes, potentially trigger user memory consolidation
|
|
650
|
+
this.maybeAutoUpdateUserMemory(options);
|
|
651
|
+
});
|
|
652
|
+
return this.sessionMemoryUpdatePromise;
|
|
653
|
+
}
|
|
654
|
+
async maybeAutoUpdateSessionMemory(options) {
|
|
655
|
+
if (this.sessionMemoryUpdatePromise)
|
|
656
|
+
await this.sessionMemoryUpdatePromise;
|
|
657
|
+
// Check if memory extraction is enabled (requires AFS history module)
|
|
658
|
+
if (!this.isMemoryEnabled)
|
|
659
|
+
return;
|
|
660
|
+
if (!this.sessionMemoryConfig)
|
|
661
|
+
return;
|
|
662
|
+
// Check if mode is disabled
|
|
663
|
+
const mode = this.sessionMemoryConfig.mode ?? types_js_1.DEFAULT_SESSION_MEMORY_MODE;
|
|
664
|
+
if (mode === "disabled")
|
|
665
|
+
return;
|
|
666
|
+
// Trigger session memory update
|
|
667
|
+
this.updateSessionMemory(options);
|
|
668
|
+
const isAsync = this.sessionMemoryConfig.async ?? types_js_1.DEFAULT_SESSION_MEMORY_ASYNC;
|
|
669
|
+
if (!isAsync)
|
|
670
|
+
await this.sessionMemoryUpdatePromise;
|
|
671
|
+
}
|
|
672
|
+
async maybeAutoUpdateUserMemory(options) {
|
|
673
|
+
if (this.userMemoryUpdatePromise)
|
|
674
|
+
await this.userMemoryUpdatePromise;
|
|
675
|
+
// Check if memory extraction is enabled (requires AFS history module)
|
|
676
|
+
if (!this.isMemoryEnabled)
|
|
677
|
+
return;
|
|
678
|
+
if (!this.userMemoryConfig || !this.userId)
|
|
679
|
+
return;
|
|
680
|
+
// Check if mode is disabled
|
|
681
|
+
const mode = this.userMemoryConfig.mode ?? types_js_1.DEFAULT_USER_MEMORY_MODE;
|
|
682
|
+
if (mode === "disabled")
|
|
683
|
+
return;
|
|
684
|
+
// Wait for session memory update to complete first
|
|
685
|
+
if (this.sessionMemoryUpdatePromise)
|
|
686
|
+
await this.sessionMemoryUpdatePromise;
|
|
687
|
+
// Trigger user memory consolidation
|
|
688
|
+
this.updateUserMemory(options);
|
|
689
|
+
const isAsync = this.userMemoryConfig.async ?? types_js_1.DEFAULT_USER_MEMORY_ASYNC;
|
|
690
|
+
if (!isAsync)
|
|
691
|
+
await this.userMemoryUpdatePromise;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Internal method that performs the actual session memory update
|
|
695
|
+
*/
|
|
696
|
+
async doUpdateSessionMemory(options) {
|
|
697
|
+
const { extractor } = this.sessionMemoryConfig ?? {};
|
|
698
|
+
if (!extractor) {
|
|
699
|
+
throw new Error("Cannot update session memory without an extractor agent configured.");
|
|
700
|
+
}
|
|
701
|
+
// Get latestEntryId from the most recent memory entry's metadata
|
|
702
|
+
// This tells us which history entries have already been processed
|
|
703
|
+
const latestEntryId = this.runtimeState.sessionMemory?.at(-1)?.metadata?.latestEntryId;
|
|
704
|
+
// Filter unextracted entries based on latestEntryId
|
|
705
|
+
// Similar to compact mechanism, we find the position of the last extracted entry
|
|
706
|
+
// and only process entries after that point
|
|
707
|
+
const lastExtractedIndex = latestEntryId
|
|
708
|
+
? this.runtimeState.historyEntries.findIndex((e) => e.id === latestEntryId)
|
|
709
|
+
: -1;
|
|
710
|
+
const unextractedEntries = lastExtractedIndex >= 0
|
|
711
|
+
? this.runtimeState.historyEntries.slice(lastExtractedIndex + 1)
|
|
712
|
+
: this.runtimeState.historyEntries;
|
|
713
|
+
if (unextractedEntries.length === 0)
|
|
714
|
+
return;
|
|
715
|
+
// Get recent conversation messages for extraction
|
|
716
|
+
const recentMessages = unextractedEntries
|
|
717
|
+
.flatMap((entry) => entry.content?.messages ?? [])
|
|
718
|
+
.filter(type_utils_js_1.isNonNullable);
|
|
719
|
+
if (recentMessages.length === 0)
|
|
720
|
+
return;
|
|
721
|
+
// Get existing session memory facts for context
|
|
722
|
+
const existingFacts = this.runtimeState.sessionMemory?.map((entry) => entry.content).filter(type_utils_js_1.isNonNullable) ?? [];
|
|
723
|
+
// Get user memory facts to avoid duplication
|
|
724
|
+
const existingUserFacts = this.runtimeState.userMemory?.map((entry) => entry.content).filter(type_utils_js_1.isNonNullable) ?? [];
|
|
725
|
+
// Extract new facts from conversation
|
|
726
|
+
const result = await options.context.invoke(extractor, {
|
|
727
|
+
existingUserFacts,
|
|
728
|
+
existingFacts,
|
|
729
|
+
messages: recentMessages,
|
|
730
|
+
});
|
|
731
|
+
// If no changes, nothing to do
|
|
732
|
+
if (!result.newFacts.length && !result.removeFacts?.length) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
// Get the last entry to record its ID for metadata
|
|
736
|
+
const latestExtractedEntry = unextractedEntries.at(-1);
|
|
737
|
+
if (this.afs && this.historyModulePath) {
|
|
738
|
+
// Handle fact removal
|
|
739
|
+
if (result.removeFacts?.length && this.runtimeState.sessionMemory) {
|
|
740
|
+
const entriesToRemove = [];
|
|
741
|
+
for (const label of result.removeFacts) {
|
|
742
|
+
const entry = this.runtimeState.sessionMemory.find((e) => e.content?.label === label);
|
|
743
|
+
if (entry)
|
|
744
|
+
entriesToRemove.push(entry);
|
|
745
|
+
}
|
|
746
|
+
// Remove from AFS storage and runtime state
|
|
747
|
+
for (const entryToRemove of entriesToRemove) {
|
|
748
|
+
// Delete from AFS storage
|
|
749
|
+
const memoryEntryPath = (0, ufo_1.joinURL)(this.historyModulePath, "by-session", this.sessionId, "@metadata/memory", entryToRemove.id);
|
|
750
|
+
await this.afs.delete(memoryEntryPath);
|
|
751
|
+
// Remove from runtime state
|
|
752
|
+
const index = this.runtimeState.sessionMemory.indexOf(entryToRemove);
|
|
753
|
+
if (index !== -1) {
|
|
754
|
+
this.runtimeState.sessionMemory.splice(index, 1);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
// Handle new facts
|
|
759
|
+
if (result.newFacts.length) {
|
|
760
|
+
const sessionMemoryPath = (0, ufo_1.joinURL)(this.historyModulePath, "by-session", this.sessionId, "@metadata/memory/new");
|
|
761
|
+
for (const fact of result.newFacts) {
|
|
762
|
+
const newEntry = await this.afs.write(sessionMemoryPath, {
|
|
763
|
+
userId: this.userId,
|
|
764
|
+
sessionId: this.sessionId,
|
|
765
|
+
agentId: this.agentId,
|
|
766
|
+
content: fact,
|
|
767
|
+
metadata: {
|
|
768
|
+
latestEntryId: latestExtractedEntry?.id,
|
|
769
|
+
},
|
|
770
|
+
});
|
|
771
|
+
// Add to runtime state
|
|
772
|
+
this.runtimeState.sessionMemory ??= [];
|
|
773
|
+
this.runtimeState.sessionMemory.push(newEntry.data);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Manually trigger user memory update
|
|
780
|
+
*/
|
|
781
|
+
async updateUserMemory(options) {
|
|
782
|
+
await this.ensureInitialized();
|
|
783
|
+
// If user memory update is already in progress, wait for it to complete
|
|
784
|
+
if (this.userMemoryUpdatePromise) {
|
|
785
|
+
return this.userMemoryUpdatePromise;
|
|
786
|
+
}
|
|
787
|
+
// Start new user memory update task
|
|
788
|
+
this.userMemoryUpdatePromise = this.doUpdateUserMemory(options).finally(() => {
|
|
789
|
+
this.userMemoryUpdatePromise = undefined;
|
|
790
|
+
});
|
|
791
|
+
return this.userMemoryUpdatePromise;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Internal method that performs the actual user memory extraction
|
|
795
|
+
*/
|
|
796
|
+
async doUpdateUserMemory(options) {
|
|
797
|
+
const { extractor } = this.userMemoryConfig ?? {};
|
|
798
|
+
if (!extractor) {
|
|
799
|
+
throw new Error("Cannot update user memory without an extractor agent configured.");
|
|
800
|
+
}
|
|
801
|
+
// Get session memory facts as the source for consolidation
|
|
802
|
+
const sessionFacts = this.runtimeState.sessionMemory?.map((entry) => entry.content).filter(type_utils_js_1.isNonNullable) ?? [];
|
|
803
|
+
if (sessionFacts.length === 0)
|
|
804
|
+
return;
|
|
805
|
+
// Get existing user memory facts for context and deduplication
|
|
806
|
+
const existingUserFacts = this.runtimeState.userMemory?.map((entry) => entry.content).filter(type_utils_js_1.isNonNullable) ?? [];
|
|
807
|
+
// Extract user memory facts from session memory
|
|
808
|
+
const result = await options.context.invoke(extractor, {
|
|
809
|
+
sessionFacts,
|
|
810
|
+
existingUserFacts,
|
|
811
|
+
});
|
|
812
|
+
// If no changes, nothing to do
|
|
813
|
+
if (!result.newFacts.length && !result.removeFacts?.length) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (this.afs && this.historyModulePath && this.userId) {
|
|
817
|
+
// Handle fact removal
|
|
818
|
+
if (result.removeFacts?.length && this.runtimeState.userMemory) {
|
|
819
|
+
const entriesToRemove = [];
|
|
820
|
+
for (const label of result.removeFacts) {
|
|
821
|
+
const entry = this.runtimeState.userMemory.find((e) => e.content?.label === label);
|
|
822
|
+
if (entry)
|
|
823
|
+
entriesToRemove.push(entry);
|
|
824
|
+
}
|
|
825
|
+
// Remove from AFS storage and runtime state
|
|
826
|
+
for (const entryToRemove of entriesToRemove) {
|
|
827
|
+
const memoryEntryPath = (0, ufo_1.joinURL)(this.historyModulePath, "by-user", this.userId, "@metadata/memory", entryToRemove.id);
|
|
828
|
+
await this.afs.delete(memoryEntryPath);
|
|
829
|
+
const index = this.runtimeState.userMemory.indexOf(entryToRemove);
|
|
830
|
+
if (index !== -1) {
|
|
831
|
+
this.runtimeState.userMemory.splice(index, 1);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// Handle new/updated facts
|
|
836
|
+
// For user memory, labels are unique - replace existing facts with same label
|
|
837
|
+
if (result.newFacts.length) {
|
|
838
|
+
const userMemoryPath = (0, ufo_1.joinURL)(this.historyModulePath, "by-user", this.userId, "@metadata/memory/new");
|
|
839
|
+
for (const fact of result.newFacts) {
|
|
840
|
+
// Check if fact with same label already exists
|
|
841
|
+
const existingEntry = this.runtimeState.userMemory?.find((e) => e.content?.label === fact.label);
|
|
842
|
+
if (existingEntry) {
|
|
843
|
+
// Delete old entry
|
|
844
|
+
const oldEntryPath = (0, ufo_1.joinURL)(this.historyModulePath, "by-user", this.userId, "@metadata/memory", existingEntry.id);
|
|
845
|
+
await this.afs.delete(oldEntryPath);
|
|
846
|
+
// Remove from runtime state
|
|
847
|
+
if (this.runtimeState.userMemory) {
|
|
848
|
+
const index = this.runtimeState.userMemory.indexOf(existingEntry);
|
|
849
|
+
if (index !== -1) {
|
|
850
|
+
this.runtimeState.userMemory.splice(index, 1);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
// Create new entry
|
|
855
|
+
const newEntry = await this.afs.write(userMemoryPath, {
|
|
856
|
+
userId: this.userId,
|
|
857
|
+
agentId: this.agentId,
|
|
858
|
+
content: fact,
|
|
859
|
+
});
|
|
860
|
+
// Add to runtime state
|
|
861
|
+
this.runtimeState.userMemory ??= [];
|
|
862
|
+
this.runtimeState.userMemory.push(newEntry.data);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
async initializeDefaultCompactor() {
|
|
868
|
+
this.compactConfig.compactor ??= await Promise.resolve().then(() => __importStar(require("./compact/compactor.js"))).then((m) => new m.AISessionCompactor());
|
|
869
|
+
}
|
|
870
|
+
async initializeDefaultSessionMemoryExtractor() {
|
|
871
|
+
this.sessionMemoryConfig.extractor ??= await Promise.resolve().then(() => __importStar(require("./compact/session-memory-extractor.js"))).then((m) => new m.AISessionMemoryExtractor());
|
|
872
|
+
}
|
|
873
|
+
async initializeDefaultUserMemoryExtractor() {
|
|
874
|
+
this.userMemoryConfig.extractor ??= await Promise.resolve().then(() => __importStar(require("./compact/user-memory-extractor.js"))).then((m) => new m.AIUserMemoryExtractor());
|
|
875
|
+
}
|
|
876
|
+
get maxTokens() {
|
|
877
|
+
return this.compactConfig?.maxTokens ?? types_js_1.DEFAULT_MAX_TOKENS;
|
|
878
|
+
}
|
|
879
|
+
get keepRecentRatio() {
|
|
880
|
+
return this.compactConfig?.keepRecentRatio ?? types_js_1.DEFAULT_KEEP_RECENT_RATIO;
|
|
881
|
+
}
|
|
882
|
+
get keepTokenBudget() {
|
|
883
|
+
return Math.floor(this.maxTokens * this.keepRecentRatio);
|
|
884
|
+
}
|
|
885
|
+
get singleMessageLimit() {
|
|
886
|
+
return this.keepTokenBudget * 0.5;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
exports.AgentSession = AgentSession;
|