@aigne/core 1.72.0-beta.2 → 1.72.0-beta.23

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