@lleverage-ai/agent-sdk 0.0.1

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 (327) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2321 -0
  3. package/dist/agent.d.ts +52 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +2122 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/backend.d.ts +378 -0
  8. package/dist/backend.d.ts.map +1 -0
  9. package/dist/backend.js +71 -0
  10. package/dist/backend.js.map +1 -0
  11. package/dist/backends/composite.d.ts +258 -0
  12. package/dist/backends/composite.d.ts.map +1 -0
  13. package/dist/backends/composite.js +437 -0
  14. package/dist/backends/composite.js.map +1 -0
  15. package/dist/backends/filesystem.d.ts +268 -0
  16. package/dist/backends/filesystem.d.ts.map +1 -0
  17. package/dist/backends/filesystem.js +623 -0
  18. package/dist/backends/filesystem.js.map +1 -0
  19. package/dist/backends/index.d.ts +14 -0
  20. package/dist/backends/index.d.ts.map +1 -0
  21. package/dist/backends/index.js +14 -0
  22. package/dist/backends/index.js.map +1 -0
  23. package/dist/backends/persistent.d.ts +312 -0
  24. package/dist/backends/persistent.d.ts.map +1 -0
  25. package/dist/backends/persistent.js +519 -0
  26. package/dist/backends/persistent.js.map +1 -0
  27. package/dist/backends/sandbox.d.ts +315 -0
  28. package/dist/backends/sandbox.d.ts.map +1 -0
  29. package/dist/backends/sandbox.js +490 -0
  30. package/dist/backends/sandbox.js.map +1 -0
  31. package/dist/backends/state.d.ts +225 -0
  32. package/dist/backends/state.d.ts.map +1 -0
  33. package/dist/backends/state.js +396 -0
  34. package/dist/backends/state.js.map +1 -0
  35. package/dist/checkpointer/file-saver.d.ts +182 -0
  36. package/dist/checkpointer/file-saver.d.ts.map +1 -0
  37. package/dist/checkpointer/file-saver.js +298 -0
  38. package/dist/checkpointer/file-saver.js.map +1 -0
  39. package/dist/checkpointer/index.d.ts +40 -0
  40. package/dist/checkpointer/index.d.ts.map +1 -0
  41. package/dist/checkpointer/index.js +40 -0
  42. package/dist/checkpointer/index.js.map +1 -0
  43. package/dist/checkpointer/kv-saver.d.ts +142 -0
  44. package/dist/checkpointer/kv-saver.d.ts.map +1 -0
  45. package/dist/checkpointer/kv-saver.js +176 -0
  46. package/dist/checkpointer/kv-saver.js.map +1 -0
  47. package/dist/checkpointer/memory-saver.d.ts +158 -0
  48. package/dist/checkpointer/memory-saver.d.ts.map +1 -0
  49. package/dist/checkpointer/memory-saver.js +222 -0
  50. package/dist/checkpointer/memory-saver.js.map +1 -0
  51. package/dist/checkpointer/types.d.ts +353 -0
  52. package/dist/checkpointer/types.d.ts.map +1 -0
  53. package/dist/checkpointer/types.js +159 -0
  54. package/dist/checkpointer/types.js.map +1 -0
  55. package/dist/context-manager.d.ts +627 -0
  56. package/dist/context-manager.d.ts.map +1 -0
  57. package/dist/context-manager.js +1039 -0
  58. package/dist/context-manager.js.map +1 -0
  59. package/dist/context.d.ts +57 -0
  60. package/dist/context.d.ts.map +1 -0
  61. package/dist/context.js +76 -0
  62. package/dist/context.js.map +1 -0
  63. package/dist/errors/index.d.ts +611 -0
  64. package/dist/errors/index.d.ts.map +1 -0
  65. package/dist/errors/index.js +1023 -0
  66. package/dist/errors/index.js.map +1 -0
  67. package/dist/generation-helpers.d.ts +126 -0
  68. package/dist/generation-helpers.d.ts.map +1 -0
  69. package/dist/generation-helpers.js +181 -0
  70. package/dist/generation-helpers.js.map +1 -0
  71. package/dist/hooks/audit.d.ts +210 -0
  72. package/dist/hooks/audit.d.ts.map +1 -0
  73. package/dist/hooks/audit.js +305 -0
  74. package/dist/hooks/audit.js.map +1 -0
  75. package/dist/hooks/cache.d.ts +180 -0
  76. package/dist/hooks/cache.d.ts.map +1 -0
  77. package/dist/hooks/cache.js +273 -0
  78. package/dist/hooks/cache.js.map +1 -0
  79. package/dist/hooks/guardrails.d.ts +145 -0
  80. package/dist/hooks/guardrails.d.ts.map +1 -0
  81. package/dist/hooks/guardrails.js +326 -0
  82. package/dist/hooks/guardrails.js.map +1 -0
  83. package/dist/hooks/index.d.ts +18 -0
  84. package/dist/hooks/index.d.ts.map +1 -0
  85. package/dist/hooks/index.js +32 -0
  86. package/dist/hooks/index.js.map +1 -0
  87. package/dist/hooks/logging.d.ts +193 -0
  88. package/dist/hooks/logging.d.ts.map +1 -0
  89. package/dist/hooks/logging.js +345 -0
  90. package/dist/hooks/logging.js.map +1 -0
  91. package/dist/hooks/parallel-guardrails.d.ts +268 -0
  92. package/dist/hooks/parallel-guardrails.d.ts.map +1 -0
  93. package/dist/hooks/parallel-guardrails.js +416 -0
  94. package/dist/hooks/parallel-guardrails.js.map +1 -0
  95. package/dist/hooks/rate-limit.d.ts +305 -0
  96. package/dist/hooks/rate-limit.d.ts.map +1 -0
  97. package/dist/hooks/rate-limit.js +372 -0
  98. package/dist/hooks/rate-limit.js.map +1 -0
  99. package/dist/hooks/retry.d.ts +144 -0
  100. package/dist/hooks/retry.d.ts.map +1 -0
  101. package/dist/hooks/retry.js +210 -0
  102. package/dist/hooks/retry.js.map +1 -0
  103. package/dist/hooks/secrets.d.ts +174 -0
  104. package/dist/hooks/secrets.d.ts.map +1 -0
  105. package/dist/hooks/secrets.js +306 -0
  106. package/dist/hooks/secrets.js.map +1 -0
  107. package/dist/hooks.d.ts +229 -0
  108. package/dist/hooks.d.ts.map +1 -0
  109. package/dist/hooks.js +352 -0
  110. package/dist/hooks.js.map +1 -0
  111. package/dist/index.d.ts +97 -0
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.js +182 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/mcp/env.d.ts +25 -0
  116. package/dist/mcp/env.d.ts.map +1 -0
  117. package/dist/mcp/env.js +18 -0
  118. package/dist/mcp/env.js.map +1 -0
  119. package/dist/mcp/index.d.ts +16 -0
  120. package/dist/mcp/index.d.ts.map +1 -0
  121. package/dist/mcp/index.js +17 -0
  122. package/dist/mcp/index.js.map +1 -0
  123. package/dist/mcp/manager.d.ts +184 -0
  124. package/dist/mcp/manager.d.ts.map +1 -0
  125. package/dist/mcp/manager.js +446 -0
  126. package/dist/mcp/manager.js.map +1 -0
  127. package/dist/mcp/types.d.ts +58 -0
  128. package/dist/mcp/types.d.ts.map +1 -0
  129. package/dist/mcp/types.js +7 -0
  130. package/dist/mcp/types.js.map +1 -0
  131. package/dist/mcp/validation.d.ts +119 -0
  132. package/dist/mcp/validation.d.ts.map +1 -0
  133. package/dist/mcp/validation.js +407 -0
  134. package/dist/mcp/validation.js.map +1 -0
  135. package/dist/mcp/virtual-server.d.ts +78 -0
  136. package/dist/mcp/virtual-server.d.ts.map +1 -0
  137. package/dist/mcp/virtual-server.js +137 -0
  138. package/dist/mcp/virtual-server.js.map +1 -0
  139. package/dist/memory/filesystem-store.d.ts +217 -0
  140. package/dist/memory/filesystem-store.d.ts.map +1 -0
  141. package/dist/memory/filesystem-store.js +343 -0
  142. package/dist/memory/filesystem-store.js.map +1 -0
  143. package/dist/memory/index.d.ts +46 -0
  144. package/dist/memory/index.d.ts.map +1 -0
  145. package/dist/memory/index.js +46 -0
  146. package/dist/memory/index.js.map +1 -0
  147. package/dist/memory/loader.d.ts +396 -0
  148. package/dist/memory/loader.d.ts.map +1 -0
  149. package/dist/memory/loader.js +419 -0
  150. package/dist/memory/loader.js.map +1 -0
  151. package/dist/memory/permissions.d.ts +282 -0
  152. package/dist/memory/permissions.d.ts.map +1 -0
  153. package/dist/memory/permissions.js +297 -0
  154. package/dist/memory/permissions.js.map +1 -0
  155. package/dist/memory/rules.d.ts +249 -0
  156. package/dist/memory/rules.d.ts.map +1 -0
  157. package/dist/memory/rules.js +362 -0
  158. package/dist/memory/rules.js.map +1 -0
  159. package/dist/memory/store.d.ts +286 -0
  160. package/dist/memory/store.d.ts.map +1 -0
  161. package/dist/memory/store.js +263 -0
  162. package/dist/memory/store.js.map +1 -0
  163. package/dist/middleware/apply.d.ts +73 -0
  164. package/dist/middleware/apply.d.ts.map +1 -0
  165. package/dist/middleware/apply.js +219 -0
  166. package/dist/middleware/apply.js.map +1 -0
  167. package/dist/middleware/context.d.ts +33 -0
  168. package/dist/middleware/context.d.ts.map +1 -0
  169. package/dist/middleware/context.js +176 -0
  170. package/dist/middleware/context.js.map +1 -0
  171. package/dist/middleware/index.d.ts +31 -0
  172. package/dist/middleware/index.d.ts.map +1 -0
  173. package/dist/middleware/index.js +32 -0
  174. package/dist/middleware/index.js.map +1 -0
  175. package/dist/middleware/logging.d.ts +137 -0
  176. package/dist/middleware/logging.d.ts.map +1 -0
  177. package/dist/middleware/logging.js +374 -0
  178. package/dist/middleware/logging.js.map +1 -0
  179. package/dist/middleware/types.d.ts +183 -0
  180. package/dist/middleware/types.d.ts.map +1 -0
  181. package/dist/middleware/types.js +11 -0
  182. package/dist/middleware/types.js.map +1 -0
  183. package/dist/observability/events.d.ts +183 -0
  184. package/dist/observability/events.d.ts.map +1 -0
  185. package/dist/observability/events.js +305 -0
  186. package/dist/observability/events.js.map +1 -0
  187. package/dist/observability/index.d.ts +55 -0
  188. package/dist/observability/index.d.ts.map +1 -0
  189. package/dist/observability/index.js +87 -0
  190. package/dist/observability/index.js.map +1 -0
  191. package/dist/observability/logger.d.ts +318 -0
  192. package/dist/observability/logger.d.ts.map +1 -0
  193. package/dist/observability/logger.js +436 -0
  194. package/dist/observability/logger.js.map +1 -0
  195. package/dist/observability/metrics.d.ts +341 -0
  196. package/dist/observability/metrics.d.ts.map +1 -0
  197. package/dist/observability/metrics.js +490 -0
  198. package/dist/observability/metrics.js.map +1 -0
  199. package/dist/observability/preset.d.ts +161 -0
  200. package/dist/observability/preset.d.ts.map +1 -0
  201. package/dist/observability/preset.js +133 -0
  202. package/dist/observability/preset.js.map +1 -0
  203. package/dist/observability/streaming.d.ts +113 -0
  204. package/dist/observability/streaming.d.ts.map +1 -0
  205. package/dist/observability/streaming.js +114 -0
  206. package/dist/observability/streaming.js.map +1 -0
  207. package/dist/observability/tracing.d.ts +378 -0
  208. package/dist/observability/tracing.d.ts.map +1 -0
  209. package/dist/observability/tracing.js +539 -0
  210. package/dist/observability/tracing.js.map +1 -0
  211. package/dist/plugins.d.ts +55 -0
  212. package/dist/plugins.d.ts.map +1 -0
  213. package/dist/plugins.js +63 -0
  214. package/dist/plugins.js.map +1 -0
  215. package/dist/presets/index.d.ts +7 -0
  216. package/dist/presets/index.d.ts.map +1 -0
  217. package/dist/presets/index.js +7 -0
  218. package/dist/presets/index.js.map +1 -0
  219. package/dist/presets/production.d.ts +262 -0
  220. package/dist/presets/production.d.ts.map +1 -0
  221. package/dist/presets/production.js +295 -0
  222. package/dist/presets/production.js.map +1 -0
  223. package/dist/security/index.d.ts +179 -0
  224. package/dist/security/index.d.ts.map +1 -0
  225. package/dist/security/index.js +323 -0
  226. package/dist/security/index.js.map +1 -0
  227. package/dist/subagents/advanced.d.ts +413 -0
  228. package/dist/subagents/advanced.d.ts.map +1 -0
  229. package/dist/subagents/advanced.js +396 -0
  230. package/dist/subagents/advanced.js.map +1 -0
  231. package/dist/subagents/index.d.ts +14 -0
  232. package/dist/subagents/index.d.ts.map +1 -0
  233. package/dist/subagents/index.js +15 -0
  234. package/dist/subagents/index.js.map +1 -0
  235. package/dist/subagents.d.ts +73 -0
  236. package/dist/subagents.d.ts.map +1 -0
  237. package/dist/subagents.js +213 -0
  238. package/dist/subagents.js.map +1 -0
  239. package/dist/task-store/file-store.d.ts +76 -0
  240. package/dist/task-store/file-store.d.ts.map +1 -0
  241. package/dist/task-store/file-store.js +190 -0
  242. package/dist/task-store/file-store.js.map +1 -0
  243. package/dist/task-store/index.d.ts +11 -0
  244. package/dist/task-store/index.d.ts.map +1 -0
  245. package/dist/task-store/index.js +10 -0
  246. package/dist/task-store/index.js.map +1 -0
  247. package/dist/task-store/kv-store.d.ts +140 -0
  248. package/dist/task-store/kv-store.d.ts.map +1 -0
  249. package/dist/task-store/kv-store.js +169 -0
  250. package/dist/task-store/kv-store.js.map +1 -0
  251. package/dist/task-store/memory-store.d.ts +66 -0
  252. package/dist/task-store/memory-store.d.ts.map +1 -0
  253. package/dist/task-store/memory-store.js +125 -0
  254. package/dist/task-store/memory-store.js.map +1 -0
  255. package/dist/task-store/types.d.ts +235 -0
  256. package/dist/task-store/types.d.ts.map +1 -0
  257. package/dist/task-store/types.js +110 -0
  258. package/dist/task-store/types.js.map +1 -0
  259. package/dist/testing/assertions.d.ts +401 -0
  260. package/dist/testing/assertions.d.ts.map +1 -0
  261. package/dist/testing/assertions.js +630 -0
  262. package/dist/testing/assertions.js.map +1 -0
  263. package/dist/testing/index.d.ts +343 -0
  264. package/dist/testing/index.d.ts.map +1 -0
  265. package/dist/testing/index.js +360 -0
  266. package/dist/testing/index.js.map +1 -0
  267. package/dist/testing/mock-agent.d.ts +214 -0
  268. package/dist/testing/mock-agent.d.ts.map +1 -0
  269. package/dist/testing/mock-agent.js +448 -0
  270. package/dist/testing/mock-agent.js.map +1 -0
  271. package/dist/testing/recorder.d.ts +288 -0
  272. package/dist/testing/recorder.d.ts.map +1 -0
  273. package/dist/testing/recorder.js +499 -0
  274. package/dist/testing/recorder.js.map +1 -0
  275. package/dist/tools/execute.d.ts +104 -0
  276. package/dist/tools/execute.d.ts.map +1 -0
  277. package/dist/tools/execute.js +191 -0
  278. package/dist/tools/execute.js.map +1 -0
  279. package/dist/tools/factory.d.ts +260 -0
  280. package/dist/tools/factory.d.ts.map +1 -0
  281. package/dist/tools/factory.js +241 -0
  282. package/dist/tools/factory.js.map +1 -0
  283. package/dist/tools/filesystem.d.ts +215 -0
  284. package/dist/tools/filesystem.d.ts.map +1 -0
  285. package/dist/tools/filesystem.js +311 -0
  286. package/dist/tools/filesystem.js.map +1 -0
  287. package/dist/tools/index.d.ts +33 -0
  288. package/dist/tools/index.d.ts.map +1 -0
  289. package/dist/tools/index.js +33 -0
  290. package/dist/tools/index.js.map +1 -0
  291. package/dist/tools/search.d.ts +59 -0
  292. package/dist/tools/search.d.ts.map +1 -0
  293. package/dist/tools/search.js +94 -0
  294. package/dist/tools/search.js.map +1 -0
  295. package/dist/tools/skills.d.ts +354 -0
  296. package/dist/tools/skills.d.ts.map +1 -0
  297. package/dist/tools/skills.js +413 -0
  298. package/dist/tools/skills.js.map +1 -0
  299. package/dist/tools/task.d.ts +272 -0
  300. package/dist/tools/task.d.ts.map +1 -0
  301. package/dist/tools/task.js +521 -0
  302. package/dist/tools/task.js.map +1 -0
  303. package/dist/tools/todos.d.ts +131 -0
  304. package/dist/tools/todos.d.ts.map +1 -0
  305. package/dist/tools/todos.js +120 -0
  306. package/dist/tools/todos.js.map +1 -0
  307. package/dist/tools/tool-registry.d.ts +424 -0
  308. package/dist/tools/tool-registry.d.ts.map +1 -0
  309. package/dist/tools/tool-registry.js +607 -0
  310. package/dist/tools/tool-registry.js.map +1 -0
  311. package/dist/tools/user-interaction.d.ts +116 -0
  312. package/dist/tools/user-interaction.d.ts.map +1 -0
  313. package/dist/tools/user-interaction.js +147 -0
  314. package/dist/tools/user-interaction.js.map +1 -0
  315. package/dist/tools/utils.d.ts +124 -0
  316. package/dist/tools/utils.d.ts.map +1 -0
  317. package/dist/tools/utils.js +189 -0
  318. package/dist/tools/utils.js.map +1 -0
  319. package/dist/tools.d.ts +74 -0
  320. package/dist/tools.d.ts.map +1 -0
  321. package/dist/tools.js +73 -0
  322. package/dist/tools.js.map +1 -0
  323. package/dist/types.d.ts +2421 -0
  324. package/dist/types.d.ts.map +1 -0
  325. package/dist/types.js +55 -0
  326. package/dist/types.js.map +1 -0
  327. package/package.json +81 -0
@@ -0,0 +1,1039 @@
1
+ /**
2
+ * Context management for token tracking and auto-compaction.
3
+ *
4
+ * This module provides tools for managing conversation context, including:
5
+ * - Token counting and budget tracking
6
+ * - Automatic context compaction/summarization
7
+ * - Message history management
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ /**
12
+ * Helper function to create a hash for a message for caching purposes.
13
+ * Uses a simple hash of the serialized message content.
14
+ *
15
+ * @param message - The message to hash
16
+ * @returns A hash string for the message
17
+ */
18
+ function hashMessage(message) {
19
+ // Create a stable string representation
20
+ let content;
21
+ if (typeof message.content === "string") {
22
+ content = message.content;
23
+ }
24
+ else if (Array.isArray(message.content)) {
25
+ // For array content, create a hash-friendly representation
26
+ // For images/files, include type and a marker (not the full data)
27
+ content = message.content
28
+ .map((part) => {
29
+ if ("text" in part)
30
+ return `text:${part.text}`;
31
+ if ("image" in part)
32
+ return `image:${part.type}`;
33
+ if ("data" in part && part.type === "file")
34
+ return `file:${part.type}`;
35
+ if ("toolName" in part)
36
+ return `tool:${part.toolName}`;
37
+ if ("result" in part)
38
+ return `result:${JSON.stringify(part.result)}`;
39
+ if ("output" in part)
40
+ return `output:${JSON.stringify(part.output)}`;
41
+ return JSON.stringify(part);
42
+ })
43
+ .join("|");
44
+ }
45
+ else {
46
+ content = JSON.stringify(message.content);
47
+ }
48
+ // Simple hash function (djb2)
49
+ let hash = 5381;
50
+ for (let i = 0; i < content.length; i++) {
51
+ hash = (hash << 5) + hash + content.charCodeAt(i); /* hash * 33 + c */
52
+ }
53
+ return `${message.role}:${hash}`;
54
+ }
55
+ /**
56
+ * Creates a simple token counter using character approximation.
57
+ *
58
+ * Uses the common approximation of 4 characters per token.
59
+ * This is faster but less accurate than model-specific tokenizers.
60
+ * Includes a message-level cache to avoid re-counting identical messages.
61
+ *
62
+ * @returns A token counter using character approximation
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const counter = createApproximateTokenCounter();
67
+ * const tokens = counter.count("Hello, world!"); // ~3 tokens
68
+ * ```
69
+ *
70
+ * @category Context
71
+ */
72
+ export function createApproximateTokenCounter() {
73
+ const CHARS_PER_TOKEN = 4;
74
+ const messageCache = new Map();
75
+ const count = (text) => {
76
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
77
+ };
78
+ const countSingleMessage = (message) => {
79
+ const hash = hashMessage(message);
80
+ // Check cache
81
+ const cached = messageCache.get(hash);
82
+ if (cached !== undefined) {
83
+ return cached;
84
+ }
85
+ let total = 0;
86
+ // Count message overhead (role, structure)
87
+ total += 4; // Approximate overhead per message
88
+ // Count content
89
+ if (typeof message.content === "string") {
90
+ total += count(message.content);
91
+ }
92
+ else if (Array.isArray(message.content)) {
93
+ for (const part of message.content) {
94
+ if ("text" in part && typeof part.text === "string") {
95
+ total += count(part.text);
96
+ }
97
+ else if ("toolName" in part) {
98
+ // Tool call - count name and args
99
+ total += count(part.toolName);
100
+ if ("args" in part) {
101
+ total += count(JSON.stringify(part.args));
102
+ }
103
+ if ("input" in part) {
104
+ total += count(JSON.stringify(part.input));
105
+ }
106
+ }
107
+ else if ("result" in part || "output" in part) {
108
+ // Tool result - count output
109
+ const output = "result" in part ? part.result : part.output;
110
+ total += count(typeof output === "string" ? output : JSON.stringify(output));
111
+ }
112
+ else if ("image" in part) {
113
+ // Image part - count ~1000 tokens for image (approximate vision model cost)
114
+ // Images are expensive in terms of tokens, varies by size and model
115
+ total += 1000;
116
+ }
117
+ else if ("data" in part && part.type === "file") {
118
+ // File part - count as ~500 tokens per file (placeholder)
119
+ // Actual token count depends on file size and content
120
+ total += 500;
121
+ }
122
+ }
123
+ }
124
+ // Store in cache
125
+ messageCache.set(hash, total);
126
+ return total;
127
+ };
128
+ const countMessages = (messages) => {
129
+ let total = 0;
130
+ for (const message of messages) {
131
+ total += countSingleMessage(message);
132
+ }
133
+ return total;
134
+ };
135
+ const invalidateCache = () => {
136
+ messageCache.clear();
137
+ };
138
+ return { count, countMessages, invalidateCache };
139
+ }
140
+ /**
141
+ * Creates a token counter with a custom counting function.
142
+ *
143
+ * Use this to integrate model-specific tokenizers for more accurate counts.
144
+ * Includes a message-level cache to avoid re-counting identical messages.
145
+ *
146
+ * @param options - Configuration for the token counter
147
+ * @returns A token counter using the custom function
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * import { encoding_for_model } from "tiktoken";
152
+ *
153
+ * const encoder = encoding_for_model("gpt-4");
154
+ * const counter = createCustomTokenCounter({
155
+ * countFn: (text) => encoder.encode(text).length,
156
+ * });
157
+ * ```
158
+ *
159
+ * @category Context
160
+ */
161
+ export function createCustomTokenCounter(options) {
162
+ const { countFn, messageOverhead = 4 } = options;
163
+ const messageCache = new Map();
164
+ const countSingleMessage = (message) => {
165
+ const hash = hashMessage(message);
166
+ // Check cache
167
+ const cached = messageCache.get(hash);
168
+ if (cached !== undefined) {
169
+ return cached;
170
+ }
171
+ let total = messageOverhead;
172
+ if (typeof message.content === "string") {
173
+ total += countFn(message.content);
174
+ }
175
+ else if (Array.isArray(message.content)) {
176
+ for (const part of message.content) {
177
+ if ("text" in part && typeof part.text === "string") {
178
+ total += countFn(part.text);
179
+ }
180
+ else if ("toolName" in part) {
181
+ total += countFn(part.toolName);
182
+ if ("args" in part) {
183
+ total += countFn(JSON.stringify(part.args));
184
+ }
185
+ if ("input" in part) {
186
+ total += countFn(JSON.stringify(part.input));
187
+ }
188
+ }
189
+ else if ("result" in part || "output" in part) {
190
+ const output = "result" in part ? part.result : part.output;
191
+ total += countFn(typeof output === "string" ? output : JSON.stringify(output));
192
+ }
193
+ else if ("image" in part) {
194
+ // Image part - count ~1000 tokens for image (approximate vision model cost)
195
+ // Images are expensive in terms of tokens, varies by size and model
196
+ total += 1000;
197
+ }
198
+ else if ("data" in part && part.type === "file") {
199
+ // File part - count as ~500 tokens per file (placeholder)
200
+ // Actual token count depends on file size and content
201
+ total += 500;
202
+ }
203
+ }
204
+ }
205
+ // Store in cache
206
+ messageCache.set(hash, total);
207
+ return total;
208
+ };
209
+ const countMessages = (messages) => {
210
+ let total = 0;
211
+ for (const message of messages) {
212
+ total += countSingleMessage(message);
213
+ }
214
+ return total;
215
+ };
216
+ const invalidateCache = () => {
217
+ messageCache.clear();
218
+ };
219
+ return { count: countFn, countMessages, invalidateCache };
220
+ }
221
+ /**
222
+ * Creates a token budget from current usage.
223
+ *
224
+ * @param maxTokens - Maximum tokens allowed
225
+ * @param currentTokens - Current token count
226
+ * @param isActual - Whether this is based on actual model usage (default: false)
227
+ * @returns A token budget object
228
+ *
229
+ * @category Context
230
+ */
231
+ export function createTokenBudget(maxTokens, currentTokens, isActual = false) {
232
+ return {
233
+ maxTokens,
234
+ currentTokens,
235
+ usage: currentTokens / maxTokens,
236
+ remaining: Math.max(0, maxTokens - currentTokens),
237
+ isActual,
238
+ };
239
+ }
240
+ /**
241
+ * Default compaction policy.
242
+ *
243
+ * @category Context
244
+ */
245
+ export const DEFAULT_COMPACTION_POLICY = {
246
+ enabled: true,
247
+ tokenThreshold: 0.8,
248
+ hardCapThreshold: 0.95,
249
+ enableGrowthRatePrediction: false,
250
+ enableErrorFallback: true,
251
+ };
252
+ /**
253
+ * Default summarization configuration.
254
+ *
255
+ * @category Context
256
+ */
257
+ export const DEFAULT_SUMMARIZATION_CONFIG = {
258
+ keepMessageCount: 10,
259
+ keepToolResultCount: 5,
260
+ strategy: "rollup",
261
+ enableTieredSummaries: false,
262
+ maxSummaryTiers: 3,
263
+ messagesPerTier: 5,
264
+ enableStructuredSummary: false,
265
+ };
266
+ /**
267
+ * Creates a compaction scheduler for managing background compaction tasks.
268
+ *
269
+ * @param contextManager - The context manager to use for compaction
270
+ * @param options - Scheduler configuration options
271
+ * @returns A compaction scheduler instance
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * const scheduler = createCompactionScheduler(contextManager, {
276
+ * enableBackgroundCompaction: true,
277
+ * debounceDelayMs: 5000,
278
+ * onTaskComplete: (task) => {
279
+ * console.log(`Compaction saved ${task.result?.tokensBefore - task.result?.tokensAfter} tokens`);
280
+ * },
281
+ * });
282
+ *
283
+ * // Schedule a compaction
284
+ * const taskId = scheduler.schedule(messages, agent, "token_threshold");
285
+ *
286
+ * // Later, get the result
287
+ * const result = scheduler.getLatestResult();
288
+ * if (result) {
289
+ * messages = result.newMessages;
290
+ * }
291
+ * ```
292
+ *
293
+ * @category Context
294
+ */
295
+ export function createCompactionScheduler(contextManager, options = {}) {
296
+ const { enableBackgroundCompaction = false, debounceDelayMs = 5000, maxPendingTasks = 3, onTaskComplete, onTaskError, } = options;
297
+ const tasks = new Map();
298
+ const taskAgents = new Map(); // Store agents for each task
299
+ let taskIdCounter = 0;
300
+ let debounceTimer = null;
301
+ let isShutdown = false;
302
+ const schedule = (messages, agent, trigger) => {
303
+ if (isShutdown) {
304
+ throw new Error("Scheduler has been shut down");
305
+ }
306
+ if (!enableBackgroundCompaction) {
307
+ // When background compaction is disabled, return a placeholder ID
308
+ // The caller should handle compaction synchronously
309
+ return `sync-${Date.now()}`;
310
+ }
311
+ const taskId = `task-${++taskIdCounter}`;
312
+ const task = {
313
+ id: taskId,
314
+ status: "pending",
315
+ messages: [...messages], // Clone to avoid mutation
316
+ createdAt: Date.now(),
317
+ trigger,
318
+ };
319
+ // Enforce max pending tasks - drop oldest pending task
320
+ const pendingTasks = Array.from(tasks.values())
321
+ .filter((t) => t.status === "pending")
322
+ .sort((a, b) => a.createdAt - b.createdAt);
323
+ if (pendingTasks.length >= maxPendingTasks) {
324
+ const oldestTask = pendingTasks[0];
325
+ if (oldestTask) {
326
+ tasks.delete(oldestTask.id);
327
+ }
328
+ }
329
+ tasks.set(taskId, task);
330
+ taskAgents.set(taskId, agent); // Store agent for this task
331
+ // Debounce execution
332
+ if (debounceTimer) {
333
+ clearTimeout(debounceTimer);
334
+ }
335
+ debounceTimer = setTimeout(() => {
336
+ executeNextTask();
337
+ debounceTimer = null;
338
+ }, debounceDelayMs);
339
+ return taskId;
340
+ };
341
+ const executeNextTask = async () => {
342
+ if (isShutdown) {
343
+ return;
344
+ }
345
+ // Find the oldest pending task
346
+ const pendingTask = Array.from(tasks.values())
347
+ .filter((t) => t.status === "pending")
348
+ .sort((a, b) => a.createdAt - b.createdAt)[0];
349
+ if (!pendingTask) {
350
+ return;
351
+ }
352
+ // Get the agent for this task
353
+ const agent = taskAgents.get(pendingTask.id);
354
+ if (!agent) {
355
+ // This shouldn't happen, but handle gracefully
356
+ pendingTask.status = "failed";
357
+ pendingTask.completedAt = Date.now();
358
+ pendingTask.error = new Error("Agent not found for task");
359
+ onTaskError?.(pendingTask);
360
+ return;
361
+ }
362
+ // Mark as running
363
+ pendingTask.status = "running";
364
+ pendingTask.startedAt = Date.now();
365
+ try {
366
+ // Execute compaction
367
+ const result = await contextManager.compact(pendingTask.messages, agent, pendingTask.trigger);
368
+ // Mark as completed
369
+ pendingTask.status = "completed";
370
+ pendingTask.completedAt = Date.now();
371
+ pendingTask.result = result;
372
+ onTaskComplete?.(pendingTask);
373
+ }
374
+ catch (error) {
375
+ // Mark as failed
376
+ pendingTask.status = "failed";
377
+ pendingTask.completedAt = Date.now();
378
+ pendingTask.error = error instanceof Error ? error : new Error(String(error));
379
+ onTaskError?.(pendingTask);
380
+ }
381
+ // Execute next task if any
382
+ const hasMorePending = Array.from(tasks.values()).some((t) => t.status === "pending");
383
+ if (hasMorePending) {
384
+ // Schedule next task with debounce
385
+ if (debounceTimer) {
386
+ clearTimeout(debounceTimer);
387
+ }
388
+ debounceTimer = setTimeout(() => {
389
+ executeNextTask();
390
+ debounceTimer = null;
391
+ }, debounceDelayMs);
392
+ }
393
+ };
394
+ const getTask = (id) => {
395
+ return tasks.get(id);
396
+ };
397
+ const getPendingTasks = () => {
398
+ return Array.from(tasks.values())
399
+ .filter((t) => t.status === "pending")
400
+ .sort((a, b) => a.createdAt - b.createdAt);
401
+ };
402
+ const getLatestResult = () => {
403
+ // Find the most recent completed task
404
+ const completedTasks = Array.from(tasks.values())
405
+ .filter((t) => t.status === "completed" && t.result)
406
+ .sort((a, b) => (b.completedAt ?? 0) - (a.completedAt ?? 0));
407
+ return completedTasks[0]?.result;
408
+ };
409
+ const cancel = (id) => {
410
+ const task = tasks.get(id);
411
+ if (!task || task.status !== "pending") {
412
+ return false;
413
+ }
414
+ tasks.delete(id);
415
+ taskAgents.delete(id); // Clean up agent reference
416
+ return true;
417
+ };
418
+ const cleanup = () => {
419
+ // Remove completed and failed tasks
420
+ for (const [id, task] of tasks.entries()) {
421
+ if (task.status === "completed" || task.status === "failed") {
422
+ tasks.delete(id);
423
+ taskAgents.delete(id); // Clean up agent reference
424
+ }
425
+ }
426
+ };
427
+ const shutdown = () => {
428
+ isShutdown = true;
429
+ // Clear debounce timer
430
+ if (debounceTimer) {
431
+ clearTimeout(debounceTimer);
432
+ debounceTimer = null;
433
+ }
434
+ // Cancel all pending tasks
435
+ for (const [_id, task] of tasks.entries()) {
436
+ if (task.status === "pending") {
437
+ task.status = "failed";
438
+ task.error = new Error("Scheduler shut down");
439
+ task.completedAt = Date.now();
440
+ }
441
+ }
442
+ };
443
+ return {
444
+ schedule,
445
+ getTask,
446
+ getPendingTasks,
447
+ getLatestResult,
448
+ cancel,
449
+ cleanup,
450
+ shutdown,
451
+ };
452
+ }
453
+ /**
454
+ * Default prompt for generating conversation summaries.
455
+ */
456
+ const DEFAULT_SUMMARY_PROMPT = `You are summarizing a conversation for context management. Create a concise summary that:
457
+
458
+ 1. Preserves key decisions, facts, and user preferences
459
+ 2. Maintains important technical details and code references
460
+ 3. Notes any pending tasks or unresolved questions
461
+ 4. Captures references to images, files, and media (e.g., "discussed screenshot of X", "analyzed document Y")
462
+ 5. Uses bullet points for clarity
463
+ 6. Is approximately 200-500 words
464
+
465
+ Do not include:
466
+ - Pleasantries or greetings
467
+ - Redundant information
468
+ - Tool call details (just outcomes)
469
+ - Verbose explanations
470
+ - Full image/file contents (just references and context)
471
+
472
+ Format:
473
+ ## Conversation Summary
474
+ [Your summary here]`;
475
+ /**
476
+ * Prompt for generating structured summaries with sections.
477
+ */
478
+ const STRUCTURED_SUMMARY_PROMPT = `You are summarizing a conversation for context management. Generate a structured summary in JSON format with the following sections:
479
+
480
+ {
481
+ "decisions": ["Key decision 1", "Key decision 2", ...],
482
+ "preferences": ["User preference 1", "User preference 2", ...],
483
+ "currentState": ["Current state fact 1", "Current state fact 2", ...],
484
+ "openQuestions": ["Unresolved question 1", "Unresolved question 2", ...],
485
+ "references": ["file.ts:123", "userId: abc123", "URL: https://...", "Image: screenshot.png showing X", "File: document.pdf containing Y", ...]
486
+ }
487
+
488
+ Guidelines:
489
+ - **decisions**: Important choices made (architecture, approach, tools)
490
+ - **preferences**: User requirements, constraints, style preferences
491
+ - **currentState**: What's been implemented, current progress, known issues
492
+ - **openQuestions**: Unresolved issues, pending decisions, items to revisit
493
+ - **references**: File paths, identifiers, URLs, config values, images, documents that may be needed later
494
+
495
+ Keep each item concise (1-2 sentences). Focus on information that will be useful for continuing the conversation.
496
+ For images and files, include what they contained or depicted, not just that they were present.
497
+ Respond ONLY with valid JSON, no additional text.`;
498
+ /**
499
+ * Prompt for generating tiered summaries (summary of summaries).
500
+ */
501
+ const TIERED_SUMMARY_PROMPT = `You are creating a higher-level summary from multiple conversation summaries. Consolidate the key information from these summaries:
502
+
503
+ Guidelines:
504
+ 1. Merge related decisions and facts
505
+ 2. Preserve the most important details
506
+ 3. Remove redundancy across summaries
507
+ 4. Maintain chronological context where relevant
508
+ 5. Keep the summary concise but comprehensive
509
+
510
+ Format:
511
+ ## Tiered Summary (Level {tier})
512
+ [Your consolidated summary here]`;
513
+ /**
514
+ * Creates a context manager for tracking and managing conversation context.
515
+ *
516
+ * @param options - Configuration options
517
+ * @returns A context manager instance
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * const contextManager = createContextManager({
522
+ * maxTokens: 100000,
523
+ * summarization: {
524
+ * tokenThreshold: 0.75,
525
+ * keepMessageCount: 15,
526
+ * },
527
+ * onBudgetUpdate: (budget) => {
528
+ * console.log(`Context usage: ${(budget.usage * 100).toFixed(1)}%`);
529
+ * },
530
+ * });
531
+ *
532
+ * // Check budget
533
+ * const budget = contextManager.getBudget(messages);
534
+ *
535
+ * // Process messages (auto-compacts if needed)
536
+ * const processedMessages = await contextManager.process(messages, agent);
537
+ * ```
538
+ *
539
+ * @category Context
540
+ */
541
+ export function createContextManager(options) {
542
+ const tokenCounter = options.tokenCounter ?? createApproximateTokenCounter();
543
+ const policy = {
544
+ ...DEFAULT_COMPACTION_POLICY,
545
+ ...options.policy,
546
+ };
547
+ const summarizationConfig = {
548
+ ...DEFAULT_SUMMARIZATION_CONFIG,
549
+ ...options.summarization,
550
+ };
551
+ const { maxTokens, onBudgetUpdate, onCompact } = options;
552
+ // Track actual usage from model responses
553
+ let lastActualUsage = null;
554
+ // Track pinned messages
555
+ const pinnedMessages = [];
556
+ // Track summary tiers for tiered strategy
557
+ let currentSummaryTier = 0;
558
+ // Create scheduler if background compaction is enabled
559
+ let scheduler;
560
+ if (options.scheduler?.enableBackgroundCompaction) {
561
+ // We'll create the scheduler after defining the manager, to avoid circular dependency
562
+ // For now, set it to undefined and assign it later
563
+ scheduler = undefined;
564
+ }
565
+ const getBudget = (messages) => {
566
+ let currentTokens;
567
+ let isActual = false;
568
+ // If we have actual usage data from the last generation, use it
569
+ if (lastActualUsage && lastActualUsage.totalTokens !== undefined) {
570
+ currentTokens = lastActualUsage.totalTokens;
571
+ isActual = true;
572
+ }
573
+ else {
574
+ // Fall back to estimation
575
+ currentTokens = tokenCounter.countMessages(messages);
576
+ }
577
+ const budget = createTokenBudget(maxTokens, currentTokens, isActual);
578
+ onBudgetUpdate?.(budget);
579
+ return budget;
580
+ };
581
+ const shouldCompact = (messages) => {
582
+ if (!policy.enabled) {
583
+ return { trigger: false };
584
+ }
585
+ const budget = getBudget(messages);
586
+ // Custom policy override
587
+ if (policy.shouldCompact) {
588
+ return policy.shouldCompact(budget, messages);
589
+ }
590
+ // Hard cap safety - force compaction at 95% to prevent errors
591
+ if (budget.usage >= policy.hardCapThreshold) {
592
+ return { trigger: true, reason: "hard_cap" };
593
+ }
594
+ // Standard token threshold
595
+ if (budget.usage >= policy.tokenThreshold) {
596
+ return { trigger: true, reason: "token_threshold" };
597
+ }
598
+ // Growth rate prediction (simplified version)
599
+ if (policy.enableGrowthRatePrediction && messages.length >= 2) {
600
+ // Estimate growth: if last message was large relative to average
601
+ const lastMessage = messages[messages.length - 1];
602
+ if (lastMessage) {
603
+ const lastMessageTokens = tokenCounter.countMessages([lastMessage]);
604
+ const avgMessageTokens = budget.currentTokens / messages.length;
605
+ // If last message is significantly larger than average, predict growth
606
+ if (lastMessageTokens > avgMessageTokens * 2) {
607
+ const predictedNext = budget.currentTokens + lastMessageTokens;
608
+ const predictedUsage = predictedNext / maxTokens;
609
+ if (predictedUsage >= policy.tokenThreshold) {
610
+ return { trigger: true, reason: "growth_rate" };
611
+ }
612
+ }
613
+ }
614
+ }
615
+ return { trigger: false };
616
+ };
617
+ const compact = async (messages, agent, trigger = "token_threshold") => {
618
+ const tokensBefore = tokenCounter.countMessages(messages);
619
+ const messagesBefore = messages.length;
620
+ const { keepMessageCount, strategy = "rollup", enableStructuredSummary, enableTieredSummaries, } = summarizationConfig;
621
+ // Always keep system messages
622
+ const systemMessages = messages.filter((m) => m.role === "system");
623
+ const _nonSystemMessages = messages.filter((m) => m.role !== "system");
624
+ // Filter out pinned messages (keep them separate)
625
+ // pinnedMessages indices refer to the original messages array
626
+ const pinnedIndices = new Set(pinnedMessages.map((p) => p.messageIndex));
627
+ const pinnedMessagesArray = [];
628
+ const unpinnedMessages = [];
629
+ messages.forEach((msg, idx) => {
630
+ // Skip system messages (already handled)
631
+ if (msg.role === "system")
632
+ return;
633
+ if (pinnedIndices.has(idx)) {
634
+ pinnedMessagesArray.push(msg);
635
+ }
636
+ else {
637
+ unpinnedMessages.push(msg);
638
+ }
639
+ });
640
+ // Keep recent messages from unpinned messages
641
+ const recentMessages = unpinnedMessages.slice(-keepMessageCount);
642
+ const oldMessages = unpinnedMessages.slice(0, Math.max(0, unpinnedMessages.length - keepMessageCount));
643
+ // If nothing to compact, return unchanged
644
+ if (oldMessages.length === 0) {
645
+ return {
646
+ messagesBefore,
647
+ messagesAfter: messages.length,
648
+ tokensBefore,
649
+ tokensAfter: tokensBefore,
650
+ summary: "",
651
+ compactedMessages: [],
652
+ newMessages: messages,
653
+ trigger,
654
+ strategy,
655
+ };
656
+ }
657
+ // Execute strategy-specific compaction
658
+ let summary;
659
+ let structuredSummary;
660
+ let summaryTier;
661
+ if (strategy === "tiered" && enableTieredSummaries) {
662
+ // Tiered strategy: check if we have existing summaries to tier
663
+ const existingSummaries = messages.filter((m) => m.role === "assistant" &&
664
+ typeof m.content === "string" &&
665
+ m.content.includes("[Previous conversation summary]"));
666
+ if (existingSummaries.length >= (summarizationConfig.messagesPerTier ?? 5)) {
667
+ // Create a higher-tier summary
668
+ currentSummaryTier++;
669
+ const summariesContent = existingSummaries.map((m) => m.content).join("\n\n");
670
+ const tierPrompt = TIERED_SUMMARY_PROMPT.replace("{tier}", String(currentSummaryTier));
671
+ const summaryResult = await agent.generate({
672
+ messages: [
673
+ { role: "system", content: tierPrompt },
674
+ {
675
+ role: "user",
676
+ content: `Consolidate these summaries:\n\n${summariesContent}`,
677
+ },
678
+ ],
679
+ maxTokens: 1000,
680
+ _skipCompaction: true, // Prevent recursive compaction during summary generation
681
+ });
682
+ summary = summaryResult.status === "complete" ? summaryResult.text : "";
683
+ summaryTier = currentSummaryTier;
684
+ }
685
+ else {
686
+ // Create a first-tier summary
687
+ const contentToSummarize = formatMessagesForSummary(oldMessages);
688
+ const summaryPrompt = enableStructuredSummary
689
+ ? STRUCTURED_SUMMARY_PROMPT
690
+ : (summarizationConfig.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT);
691
+ const summaryResult = await agent.generate({
692
+ messages: [
693
+ { role: "system", content: summaryPrompt },
694
+ {
695
+ role: "user",
696
+ content: `Please summarize this conversation history:\n\n${contentToSummarize}`,
697
+ },
698
+ ],
699
+ maxTokens: 1000,
700
+ _skipCompaction: true, // Prevent recursive compaction during summary generation
701
+ });
702
+ summary = summaryResult.status === "complete" ? summaryResult.text : "";
703
+ summaryTier = 0;
704
+ // Try to parse structured summary if enabled
705
+ if (enableStructuredSummary) {
706
+ try {
707
+ structuredSummary = JSON.parse(summary);
708
+ }
709
+ catch {
710
+ // If parsing fails, keep as plain text
711
+ }
712
+ }
713
+ }
714
+ }
715
+ else if (strategy === "structured" || enableStructuredSummary) {
716
+ // Structured strategy: generate JSON with sections
717
+ const contentToSummarize = formatMessagesForSummary(oldMessages);
718
+ const summaryResult = await agent.generate({
719
+ messages: [
720
+ { role: "system", content: STRUCTURED_SUMMARY_PROMPT },
721
+ {
722
+ role: "user",
723
+ content: `Please summarize this conversation history:\n\n${contentToSummarize}`,
724
+ },
725
+ ],
726
+ maxTokens: 1000,
727
+ _skipCompaction: true, // Prevent recursive compaction during summary generation
728
+ });
729
+ summary = summaryResult.status === "complete" ? summaryResult.text : "";
730
+ // Try to parse structured summary
731
+ try {
732
+ structuredSummary = JSON.parse(summary);
733
+ }
734
+ catch {
735
+ // If parsing fails, keep as plain text
736
+ }
737
+ }
738
+ else {
739
+ // Rollup strategy (default): single summary of old messages
740
+ const contentToSummarize = formatMessagesForSummary(oldMessages);
741
+ const summaryPrompt = summarizationConfig.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT;
742
+ const summaryResult = await agent.generate({
743
+ messages: [
744
+ { role: "system", content: summaryPrompt },
745
+ {
746
+ role: "user",
747
+ content: `Please summarize this conversation history:\n\n${contentToSummarize}`,
748
+ },
749
+ ],
750
+ maxTokens: 1000,
751
+ _skipCompaction: true, // Prevent recursive compaction during summary generation
752
+ });
753
+ summary = summaryResult.status === "complete" ? summaryResult.text : "";
754
+ }
755
+ // Build new message history
756
+ const summaryPrefix = summaryTier !== undefined
757
+ ? `[Previous conversation summary - Tier ${summaryTier}]`
758
+ : "[Previous conversation summary]";
759
+ const summaryMessage = {
760
+ role: "assistant",
761
+ content: structuredSummary
762
+ ? `${summaryPrefix}\n\n${formatStructuredSummary(structuredSummary)}`
763
+ : `${summaryPrefix}\n\n${summary}`,
764
+ };
765
+ const newMessages = [
766
+ ...systemMessages,
767
+ summaryMessage,
768
+ ...pinnedMessagesArray, // Include pinned messages
769
+ ...recentMessages,
770
+ ];
771
+ const tokensAfter = tokenCounter.countMessages(newMessages);
772
+ const result = {
773
+ messagesBefore,
774
+ messagesAfter: newMessages.length,
775
+ tokensBefore,
776
+ tokensAfter,
777
+ summary,
778
+ compactedMessages: oldMessages,
779
+ newMessages,
780
+ trigger,
781
+ strategy,
782
+ structuredSummary,
783
+ summaryTier,
784
+ };
785
+ onCompact?.(result);
786
+ return result;
787
+ };
788
+ const process = async (messages, agent) => {
789
+ const { trigger, reason } = shouldCompact(messages);
790
+ if (trigger && reason) {
791
+ // If background compaction is enabled and scheduler exists
792
+ if (scheduler && options.scheduler?.enableBackgroundCompaction) {
793
+ // Check if there's a pending result from a previous background compaction
794
+ const latestResult = scheduler.getLatestResult();
795
+ if (latestResult) {
796
+ // Apply the background compaction result
797
+ scheduler.cleanup(); // Clean up old completed tasks
798
+ return latestResult.newMessages;
799
+ }
800
+ // Schedule a new background compaction for next time
801
+ scheduler.schedule(messages, agent, reason);
802
+ // Return original messages (compaction will happen in background)
803
+ return messages;
804
+ }
805
+ else {
806
+ // Synchronous compaction (blocking)
807
+ const result = await compact(messages, agent, reason);
808
+ return result.newMessages;
809
+ }
810
+ }
811
+ return messages;
812
+ };
813
+ const updateUsage = (usage) => {
814
+ // Store actual usage for next budget calculation
815
+ // Only store if totalTokens is defined
816
+ if (usage.totalTokens !== undefined) {
817
+ lastActualUsage = {
818
+ inputTokens: usage.inputTokens,
819
+ totalTokens: usage.totalTokens,
820
+ };
821
+ }
822
+ };
823
+ const pinMessage = (messageIndex, reason) => {
824
+ // Check if already pinned
825
+ if (pinnedMessages.some((p) => p.messageIndex === messageIndex)) {
826
+ return;
827
+ }
828
+ pinnedMessages.push({
829
+ messageIndex,
830
+ reason,
831
+ pinnedAt: Date.now(),
832
+ });
833
+ };
834
+ const unpinMessage = (messageIndex) => {
835
+ const index = pinnedMessages.findIndex((p) => p.messageIndex === messageIndex);
836
+ if (index !== -1) {
837
+ pinnedMessages.splice(index, 1);
838
+ }
839
+ };
840
+ const isPinned = (messageIndex) => {
841
+ return pinnedMessages.some((p) => p.messageIndex === messageIndex);
842
+ };
843
+ // Create the manager object (needed for scheduler creation)
844
+ const manager = {
845
+ tokenCounter,
846
+ policy,
847
+ summarizationConfig,
848
+ maxTokens,
849
+ scheduler: undefined, // Will be set below if needed
850
+ pinnedMessages,
851
+ getBudget,
852
+ shouldCompact,
853
+ compact,
854
+ process,
855
+ updateUsage,
856
+ pinMessage,
857
+ unpinMessage,
858
+ isPinned,
859
+ };
860
+ // Create scheduler after manager is defined (to pass to createCompactionScheduler)
861
+ if (options.scheduler?.enableBackgroundCompaction) {
862
+ scheduler = createCompactionScheduler(manager, options.scheduler);
863
+ manager.scheduler = scheduler;
864
+ }
865
+ return manager;
866
+ }
867
+ // =============================================================================
868
+ // Helper Functions
869
+ // =============================================================================
870
+ /**
871
+ * Formats messages into a string suitable for summarization.
872
+ *
873
+ * @param messages - Messages to format
874
+ * @returns Formatted string representation
875
+ */
876
+ /**
877
+ * Helper function to extract metadata from image parts for summarization.
878
+ */
879
+ function extractImageMetadata(part) {
880
+ const image = part.image;
881
+ // Handle URL case
882
+ if (typeof image === "object" && image !== null && "toString" in image) {
883
+ const url = image.toString();
884
+ if (url.startsWith("http://") || url.startsWith("https://")) {
885
+ return `[Image: ${url}]`;
886
+ }
887
+ }
888
+ // Handle data content case (base64, Buffer, etc.)
889
+ if (typeof image === "string") {
890
+ // Check if it's a data URL
891
+ if (image.startsWith("data:")) {
892
+ const mimeMatch = image.match(/^data:([^;,]+)/);
893
+ const mimeType = mimeMatch ? mimeMatch[1] : "unknown";
894
+ return `[Image: ${mimeType}, base64 data]`;
895
+ }
896
+ }
897
+ return "[Image: embedded data]";
898
+ }
899
+ /**
900
+ * Helper function to extract metadata from file parts for summarization.
901
+ */
902
+ function extractFileMetadata(part) {
903
+ const mimeType = part.mimeType ?? "unknown";
904
+ // Handle URL case
905
+ const data = part.data;
906
+ if (typeof data === "object" && data !== null && "toString" in data) {
907
+ const url = data.toString();
908
+ if (url.startsWith("http://") || url.startsWith("https://")) {
909
+ return `[File: ${mimeType}, URL: ${url}]`;
910
+ }
911
+ }
912
+ // Handle data content case
913
+ return `[File: ${mimeType}, embedded data]`;
914
+ }
915
+ function formatMessagesForSummary(messages) {
916
+ const lines = [];
917
+ for (const message of messages) {
918
+ const role = message.role.toUpperCase();
919
+ if (typeof message.content === "string") {
920
+ lines.push(`${role}: ${message.content}`);
921
+ }
922
+ else if (Array.isArray(message.content)) {
923
+ const parts = [];
924
+ for (const part of message.content) {
925
+ if ("text" in part && typeof part.text === "string") {
926
+ parts.push(part.text);
927
+ }
928
+ else if ("toolName" in part) {
929
+ parts.push(`[Tool call: ${part.toolName}]`);
930
+ }
931
+ else if ("result" in part || "output" in part) {
932
+ const output = "result" in part ? part.result : part.output;
933
+ const outputStr = typeof output === "string"
934
+ ? output.slice(0, 200)
935
+ : JSON.stringify(output).slice(0, 200);
936
+ parts.push(`[Tool result: ${outputStr}...]`);
937
+ }
938
+ else if ("image" in part && part.type === "image") {
939
+ parts.push(extractImageMetadata(part));
940
+ }
941
+ else if ("data" in part && part.type === "file") {
942
+ parts.push(extractFileMetadata(part));
943
+ }
944
+ }
945
+ if (parts.length > 0) {
946
+ lines.push(`${role}: ${parts.join("\n")}`);
947
+ }
948
+ }
949
+ }
950
+ return lines.join("\n\n");
951
+ }
952
+ /**
953
+ * Extracts tool results from messages.
954
+ *
955
+ * @param messages - Messages to search
956
+ * @returns Array of tool results with their context
957
+ */
958
+ export function extractToolResults(messages) {
959
+ const results = [];
960
+ messages.forEach((message, index) => {
961
+ if (Array.isArray(message.content)) {
962
+ for (const part of message.content) {
963
+ if ("result" in part || "output" in part) {
964
+ results.push({
965
+ toolName: "toolName" in part ? String(part.toolName) : "unknown",
966
+ output: "result" in part ? part.result : part.output,
967
+ messageIndex: index,
968
+ });
969
+ }
970
+ }
971
+ }
972
+ });
973
+ return results;
974
+ }
975
+ /**
976
+ * Finds the last user message in the conversation.
977
+ *
978
+ * @param messages - Messages to search
979
+ * @returns The last user message content, or undefined
980
+ */
981
+ export function findLastUserMessage(messages) {
982
+ for (let i = messages.length - 1; i >= 0; i--) {
983
+ const message = messages[i];
984
+ if (!message)
985
+ continue;
986
+ if (message.role === "user") {
987
+ if (typeof message.content === "string") {
988
+ return message.content;
989
+ }
990
+ else if (Array.isArray(message.content)) {
991
+ for (const part of message.content) {
992
+ if ("text" in part && typeof part.text === "string") {
993
+ return part.text;
994
+ }
995
+ }
996
+ }
997
+ }
998
+ }
999
+ return undefined;
1000
+ }
1001
+ /**
1002
+ * Counts messages by role.
1003
+ *
1004
+ * @param messages - Messages to count
1005
+ * @returns Object with counts per role
1006
+ */
1007
+ export function countMessagesByRole(messages) {
1008
+ const counts = {};
1009
+ for (const message of messages) {
1010
+ counts[message.role] = (counts[message.role] ?? 0) + 1;
1011
+ }
1012
+ return counts;
1013
+ }
1014
+ /**
1015
+ * Formats a structured summary into a readable markdown string.
1016
+ *
1017
+ * @param summary - Structured summary to format
1018
+ * @returns Formatted markdown string
1019
+ */
1020
+ function formatStructuredSummary(summary) {
1021
+ const sections = [];
1022
+ if (summary.decisions.length > 0) {
1023
+ sections.push(`## Decisions\n${summary.decisions.map((d) => `- ${d}`).join("\n")}`);
1024
+ }
1025
+ if (summary.preferences.length > 0) {
1026
+ sections.push(`## Preferences\n${summary.preferences.map((p) => `- ${p}`).join("\n")}`);
1027
+ }
1028
+ if (summary.currentState.length > 0) {
1029
+ sections.push(`## Current State\n${summary.currentState.map((s) => `- ${s}`).join("\n")}`);
1030
+ }
1031
+ if (summary.openQuestions.length > 0) {
1032
+ sections.push(`## Open Questions\n${summary.openQuestions.map((q) => `- ${q}`).join("\n")}`);
1033
+ }
1034
+ if (summary.references.length > 0) {
1035
+ sections.push(`## References\n${summary.references.map((r) => `- ${r}`).join("\n")}`);
1036
+ }
1037
+ return sections.join("\n\n");
1038
+ }
1039
+ //# sourceMappingURL=context-manager.js.map