@cuylabs/agent-core 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +33 -17
  2. package/dist/chunk-2O4MCSQS.js +780 -0
  3. package/dist/chunk-2TTOLHBT.js +198 -0
  4. package/dist/chunk-5FMSGQVX.js +281 -0
  5. package/dist/chunk-5NVVNXPQ.js +288 -0
  6. package/dist/{chunk-CAA7FHIH.js → chunk-6HZBHFOL.js} +3 -103
  7. package/dist/chunk-CJI7PVS2.js +58 -0
  8. package/dist/{chunk-N6HWIEEA.js → chunk-CMYN2RCB.js} +278 -61
  9. package/dist/chunk-FII65CN7.js +117 -0
  10. package/dist/{chunk-IVUJDISU.js → chunk-GFTW23FV.js} +5 -14
  11. package/dist/chunk-I6PKJ7XQ.js +292 -0
  12. package/dist/{chunk-BDBZ3SLK.js → chunk-ICZ66572.js} +48 -4
  13. package/dist/chunk-KYLPMBHD.js +316 -0
  14. package/dist/chunk-MXAP4UG6.js +2956 -0
  15. package/dist/{chunk-RZITT45F.js → chunk-N3VX7FEE.js} +39 -6
  16. package/dist/{chunk-YSLSEQ6B.js → chunk-NDZWXCBZ.js} +218 -95
  17. package/dist/{chunk-P6YF7USR.js → chunk-Q742PSH3.js} +23 -38
  18. package/dist/chunk-QAL3OMI3.js +943 -0
  19. package/dist/{chunk-RFEKJKTO.js → chunk-RN6WZEUF.js} +330 -280
  20. package/dist/{chunk-ZXAKHMWH.js → chunk-ROTGCYDW.js} +22 -84
  21. package/dist/chunk-SPBFQXOT.js +0 -0
  22. package/dist/{chunk-LRHOS4ZN.js → chunk-SPILYYDF.js} +3 -2
  23. package/dist/chunk-SSFBF3US.js +602 -0
  24. package/dist/chunk-SZ2XBPTW.js +8 -0
  25. package/dist/chunk-T4UIX5D7.js +115 -0
  26. package/dist/chunk-TIHPYVAJ.js +102 -0
  27. package/dist/{chunk-YUUJK53A.js → chunk-TOTDGK3P.js} +1 -1
  28. package/dist/chunk-V4RFNEET.js +563 -0
  29. package/dist/chunk-VOUEJSW6.js +0 -0
  30. package/dist/{chunk-4BDA7DQY.js → chunk-WBPOZ7CL.js} +673 -273
  31. package/dist/chunk-X4VN4GIJ.js +185 -0
  32. package/dist/dispatch/index.d.ts +93 -0
  33. package/dist/dispatch/index.js +37 -0
  34. package/dist/events/index.d.ts +93 -0
  35. package/dist/events/index.js +6 -0
  36. package/dist/{runtime → execution}/index.d.ts +120 -34
  37. package/dist/{runtime → execution}/index.js +18 -13
  38. package/dist/index-BCqEGzBj.d.ts +251 -0
  39. package/dist/index.d.ts +490 -122
  40. package/dist/index.js +2104 -615
  41. package/dist/{errors → inference/errors}/index.d.ts +2 -2
  42. package/dist/{errors → inference/errors}/index.js +1 -1
  43. package/dist/inference/index.d.ts +16 -23
  44. package/dist/inference/index.js +45 -16
  45. package/dist/instance-BqV2D5pc.d.ts +5723 -0
  46. package/dist/logger/index.d.ts +50 -0
  47. package/dist/logger/index.js +11 -0
  48. package/dist/mcp/index.d.ts +5 -9
  49. package/dist/mcp/index.js +2 -3
  50. package/dist/middleware/index.d.ts +10 -149
  51. package/dist/middleware/index.js +11 -3
  52. package/dist/model-messages-B4nK9D1-.d.ts +13 -0
  53. package/dist/models/index.d.ts +23 -18
  54. package/dist/models/index.js +48 -11
  55. package/dist/models/reasoning/index.d.ts +4 -0
  56. package/dist/{reasoning → models/reasoning}/index.js +3 -3
  57. package/dist/plugin/index.d.ts +458 -0
  58. package/dist/plugin/index.js +32 -0
  59. package/dist/profiles/index.d.ts +55 -0
  60. package/dist/profiles/index.js +30 -0
  61. package/dist/prompt/index.d.ts +8 -12
  62. package/dist/prompt/index.js +3 -2
  63. package/dist/safety/index.d.ts +109 -14
  64. package/dist/safety/index.js +59 -3
  65. package/dist/sandbox/index.d.ts +81 -0
  66. package/dist/sandbox/index.js +1 -0
  67. package/dist/skill/index.d.ts +10 -8
  68. package/dist/skill/index.js +3 -3
  69. package/dist/storage/index.d.ts +12 -4
  70. package/dist/storage/index.js +1 -1
  71. package/dist/subagents/index.d.ts +177 -0
  72. package/dist/subagents/index.js +78 -0
  73. package/dist/team/index.d.ts +544 -0
  74. package/dist/team/index.js +41 -0
  75. package/dist/tool/host/index.d.ts +41 -0
  76. package/dist/tool/host/index.js +10 -0
  77. package/dist/tool/index.d.ts +125 -21
  78. package/dist/tool/index.js +20 -13
  79. package/dist/{types-VQgymC1N.d.ts → types-Bj_J8u_W.d.ts} +44 -64
  80. package/dist/{types-CHiPh8U2.d.ts → types-C_LCeYNg.d.ts} +7 -7
  81. package/dist/types-RSCv7nQ4.d.ts +59 -0
  82. package/package.json +58 -53
  83. package/dist/builder-UpOWQMW3.d.ts +0 -34
  84. package/dist/chunk-7MUFEN4K.js +0 -559
  85. package/dist/chunk-7VKQ4WPB.js +0 -73
  86. package/dist/chunk-BFM2YHNM.js +0 -222
  87. package/dist/chunk-DWYX7ASF.js +0 -26
  88. package/dist/chunk-KUVSERLJ.js +0 -50
  89. package/dist/chunk-N7P4PN3O.js +0 -84
  90. package/dist/chunk-SDSBEQXG.js +0 -157
  91. package/dist/chunk-SQU2AJHO.js +0 -305
  92. package/dist/chunk-VBWWUHWI.js +0 -724
  93. package/dist/chunk-VEKUXUVF.js +0 -41
  94. package/dist/chunk-VNQBHPCT.js +0 -398
  95. package/dist/chunk-WWYYNWEW.js +0 -259
  96. package/dist/context/index.d.ts +0 -259
  97. package/dist/context/index.js +0 -26
  98. package/dist/events-CE72w8W4.d.ts +0 -149
  99. package/dist/host/index.d.ts +0 -45
  100. package/dist/host/index.js +0 -8
  101. package/dist/index-CWSchSql.d.ts +0 -1058
  102. package/dist/messages-BYWGn8TY.d.ts +0 -110
  103. package/dist/presets/index.d.ts +0 -53
  104. package/dist/presets/index.js +0 -28
  105. package/dist/reasoning/index.d.ts +0 -116
  106. package/dist/registry-DwYqsQkX.d.ts +0 -164
  107. package/dist/runner-e2YRcUoX.d.ts +0 -786
  108. package/dist/scope/index.d.ts +0 -10
  109. package/dist/scope/index.js +0 -14
  110. package/dist/session-manager-B_CWGTsl.d.ts +0 -274
  111. package/dist/signal/index.d.ts +0 -28
  112. package/dist/signal/index.js +0 -6
  113. package/dist/sub-agent/index.d.ts +0 -23
  114. package/dist/sub-agent/index.js +0 -15
  115. package/dist/tool-BHbyUAy3.d.ts +0 -150
  116. package/dist/tool-DLXAR9Ce.d.ts +0 -145
  117. package/dist/tracker-DClqYqTj.d.ts +0 -96
  118. package/dist/tracking/index.d.ts +0 -111
  119. package/dist/tracking/index.js +0 -20
  120. package/dist/types-BfNpU8NS.d.ts +0 -270
  121. package/dist/types-BnpEOYV-.d.ts +0 -50
  122. package/dist/types-CQL-SvTn.d.ts +0 -29
  123. package/dist/types-CWm-7rvB.d.ts +0 -55
  124. package/dist/types-KKDrdU9Y.d.ts +0 -325
  125. package/dist/types-QA4WhEfz.d.ts +0 -138
  126. package/dist/types-QKHHQLLq.d.ts +0 -336
  127. package/dist/types-YuWV4ag7.d.ts +0 -72
@@ -1,41 +0,0 @@
1
- // src/tracking/file-tracking.ts
2
- function extractFilePathsFromArgs(args, meta) {
3
- if (!meta.pathArgs || meta.pathArgs.length === 0) {
4
- return [];
5
- }
6
- const paths = [];
7
- for (const argName of meta.pathArgs) {
8
- const value = args[argName];
9
- if (typeof value === "string") {
10
- paths.push(value);
11
- } else if (Array.isArray(value)) {
12
- for (const item of value) {
13
- if (typeof item === "string") {
14
- paths.push(item);
15
- }
16
- }
17
- }
18
- }
19
- return paths;
20
- }
21
- function shouldCaptureBaseline(meta) {
22
- return meta.operationType !== "read";
23
- }
24
- function withFileTracking(tracker, meta, execute) {
25
- return (async (...args) => {
26
- const params = args[0];
27
- if (params && shouldCaptureBaseline(meta)) {
28
- const paths = extractFilePathsFromArgs(params, meta);
29
- for (const path of paths) {
30
- await tracker.beforeWrite(path);
31
- }
32
- }
33
- return await execute(...args);
34
- });
35
- }
36
-
37
- export {
38
- extractFilePathsFromArgs,
39
- shouldCaptureBaseline,
40
- withFileTracking
41
- };
@@ -1,398 +0,0 @@
1
- // src/host/local.ts
2
- import { spawn } from "child_process";
3
- import fs from "fs/promises";
4
- import { existsSync } from "fs";
5
- import path from "path";
6
- import process from "process";
7
- function getShell() {
8
- if (process.platform === "win32") {
9
- return { shell: "cmd.exe", args: ["/c"] };
10
- }
11
- const userShell = process.env.SHELL ?? "/bin/bash";
12
- return { shell: userShell, args: ["-c"] };
13
- }
14
- async function killProcessTree(pid) {
15
- try {
16
- if (process.platform !== "win32") {
17
- process.kill(-pid, "SIGKILL");
18
- } else {
19
- const { exec: execCb } = await import("child_process");
20
- execCb(`taskkill /pid ${pid} /t /f`, () => {
21
- });
22
- }
23
- } catch {
24
- try {
25
- process.kill(pid, "SIGKILL");
26
- } catch {
27
- }
28
- }
29
- }
30
- function localHost(defaultCwd) {
31
- const cwd = defaultCwd ?? process.cwd();
32
- return {
33
- name: "local",
34
- // --------------------------------------------------------------------------
35
- // File system
36
- // --------------------------------------------------------------------------
37
- async readFile(filePath) {
38
- const abs = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
39
- return fs.readFile(abs, "utf-8");
40
- },
41
- async readBytes(filePath, offset, length) {
42
- const abs = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
43
- const fh = await fs.open(abs, "r");
44
- try {
45
- const buf = Buffer.alloc(length);
46
- await fh.read(buf, 0, length, offset);
47
- return buf;
48
- } finally {
49
- await fh.close();
50
- }
51
- },
52
- async writeFile(filePath, content) {
53
- const abs = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
54
- await fs.mkdir(path.dirname(abs), { recursive: true });
55
- await fs.writeFile(abs, content, "utf-8");
56
- },
57
- async exists(filePath) {
58
- const abs = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
59
- return existsSync(abs);
60
- },
61
- async stat(filePath) {
62
- const abs = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
63
- const s = await fs.stat(abs);
64
- return {
65
- size: s.size,
66
- mtime: s.mtime,
67
- isDirectory: s.isDirectory(),
68
- isFile: s.isFile()
69
- };
70
- },
71
- async readdir(dirPath) {
72
- const abs = path.isAbsolute(dirPath) ? dirPath : path.resolve(cwd, dirPath);
73
- const entries = await fs.readdir(abs, { withFileTypes: true });
74
- return entries.map((e) => ({
75
- name: e.name,
76
- isDirectory: e.isDirectory(),
77
- isFile: e.isFile()
78
- }));
79
- },
80
- async mkdir(dirPath) {
81
- const abs = path.isAbsolute(dirPath) ? dirPath : path.resolve(cwd, dirPath);
82
- await fs.mkdir(abs, { recursive: true });
83
- },
84
- // --------------------------------------------------------------------------
85
- // Process execution
86
- // --------------------------------------------------------------------------
87
- exec(command, options) {
88
- const { shell, args } = getShell();
89
- const execCwd = options?.cwd ?? cwd;
90
- const env = {
91
- ...process.env,
92
- ...options?.env,
93
- // Disable pagers — tools collect output, not interactive
94
- PAGER: "cat",
95
- GIT_PAGER: "cat"
96
- };
97
- return new Promise((resolve, reject) => {
98
- let stdout = "";
99
- let stderr = "";
100
- let timedOut = false;
101
- let settled = false;
102
- const child = spawn(shell, [...args, command], {
103
- cwd: execCwd,
104
- detached: process.platform !== "win32",
105
- env,
106
- stdio: ["ignore", "pipe", "pipe"]
107
- });
108
- child.stdout?.on("data", (data) => {
109
- stdout += data.toString();
110
- options?.onStdout?.(data);
111
- });
112
- child.stderr?.on("data", (data) => {
113
- stderr += data.toString();
114
- options?.onStderr?.(data);
115
- });
116
- let timer;
117
- if (options?.timeout && options.timeout > 0) {
118
- timer = setTimeout(() => {
119
- timedOut = true;
120
- if (child.pid) killProcessTree(child.pid);
121
- }, options.timeout);
122
- }
123
- const onAbort = () => {
124
- if (child.pid) killProcessTree(child.pid);
125
- };
126
- options?.signal?.addEventListener("abort", onAbort, { once: true });
127
- child.on("close", (code) => {
128
- if (settled) return;
129
- settled = true;
130
- if (timer) clearTimeout(timer);
131
- options?.signal?.removeEventListener("abort", onAbort);
132
- resolve({ stdout, stderr, exitCode: code, timedOut });
133
- });
134
- child.on("error", (err) => {
135
- if (settled) return;
136
- settled = true;
137
- if (timer) clearTimeout(timer);
138
- options?.signal?.removeEventListener("abort", onAbort);
139
- reject(err);
140
- });
141
- });
142
- }
143
- };
144
- }
145
-
146
- // src/host/docker/exec.ts
147
- async function loadDockerode() {
148
- try {
149
- const module = await import("dockerode");
150
- return module.default ?? module;
151
- } catch {
152
- throw new Error(
153
- "dockerHost requires the 'dockerode' package. Install it with: npm install dockerode"
154
- );
155
- }
156
- }
157
- async function resolveDockerRuntime(options) {
158
- const Dockerode = await loadDockerode();
159
- if (typeof options.container === "string") {
160
- const docker = new Dockerode(options.dockerOptions);
161
- const container2 = docker.getContainer(options.container);
162
- return {
163
- docker,
164
- container: container2,
165
- label: options.container
166
- };
167
- }
168
- const container = options.container;
169
- return {
170
- docker: { modem: container.modem },
171
- container,
172
- label: container.id?.slice(0, 12) ?? "unknown"
173
- };
174
- }
175
- async function containerExec(runtime, command, options = {}) {
176
- const envEntries = options.env ? Object.entries(options.env).filter((entry) => entry[1] !== void 0).map(([key, value]) => `${key}=${value}`) : void 0;
177
- const exec = await runtime.container.exec({
178
- Cmd: ["sh", "-c", command],
179
- ...options.user ? { User: options.user } : {},
180
- ...options.workdir ? { WorkingDir: options.workdir } : {},
181
- ...envEntries && envEntries.length > 0 ? { Env: envEntries } : {},
182
- AttachStdout: true,
183
- AttachStderr: true
184
- });
185
- const stream = await exec.start({});
186
- let timedOut = false;
187
- let aborted = false;
188
- return await new Promise((resolve, reject) => {
189
- const stdoutChunks = [];
190
- const stderrChunks = [];
191
- runtime.docker.modem.demuxStream(
192
- stream,
193
- {
194
- write: (chunk) => {
195
- stdoutChunks.push(chunk);
196
- options.onStdout?.(chunk);
197
- return true;
198
- },
199
- end: function() {
200
- return this;
201
- }
202
- },
203
- {
204
- write: (chunk) => {
205
- stderrChunks.push(chunk);
206
- options.onStderr?.(chunk);
207
- return true;
208
- },
209
- end: function() {
210
- return this;
211
- }
212
- }
213
- );
214
- let timer;
215
- if (options.timeout && options.timeout > 0) {
216
- timer = setTimeout(() => {
217
- timedOut = true;
218
- stream.destroy?.();
219
- }, options.timeout);
220
- }
221
- const onAbort = () => {
222
- aborted = true;
223
- stream.destroy?.();
224
- };
225
- options.signal?.addEventListener("abort", onAbort, { once: true });
226
- stream.on("end", async () => {
227
- if (timer) {
228
- clearTimeout(timer);
229
- }
230
- options.signal?.removeEventListener("abort", onAbort);
231
- try {
232
- const info = await exec.inspect();
233
- resolve({
234
- stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
235
- stderr: Buffer.concat(stderrChunks).toString("utf-8"),
236
- exitCode: timedOut || aborted ? null : info.ExitCode ?? 0,
237
- timedOut
238
- });
239
- } catch (error) {
240
- reject(error);
241
- }
242
- });
243
- stream.on("error", (error) => {
244
- if (timer) {
245
- clearTimeout(timer);
246
- }
247
- options.signal?.removeEventListener("abort", onAbort);
248
- if (timedOut || aborted) {
249
- resolve({
250
- stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
251
- stderr: Buffer.concat(stderrChunks).toString("utf-8"),
252
- exitCode: null,
253
- timedOut
254
- });
255
- return;
256
- }
257
- reject(error);
258
- });
259
- });
260
- }
261
- async function runDockerCommand(options) {
262
- const { runtime, command, defaultUser, defaultWorkdir, execOptions } = options;
263
- return await containerExec(runtime, command, {
264
- user: defaultUser,
265
- workdir: execOptions?.workdir ?? execOptions?.cwd ?? defaultWorkdir,
266
- env: execOptions?.env,
267
- timeout: execOptions?.timeout,
268
- signal: execOptions?.signal,
269
- onStdout: execOptions?.onStdout,
270
- onStderr: execOptions?.onStderr
271
- });
272
- }
273
-
274
- // src/host/docker/shell.ts
275
- function sq(value) {
276
- return `'${value.replace(/'/g, `'\\''`)}'`;
277
- }
278
- function resolveDockerPath(path2, defaultWorkdir) {
279
- if (path2.startsWith("/")) {
280
- return path2;
281
- }
282
- const base = defaultWorkdir.endsWith("/") ? defaultWorkdir : `${defaultWorkdir}/`;
283
- return `${base}${path2}`;
284
- }
285
-
286
- // src/host/docker/host.ts
287
- async function dockerHost(options) {
288
- const runtime = await resolveDockerRuntime(options);
289
- const defaultUser = options.user;
290
- const defaultWorkdir = options.workdir ?? "/";
291
- async function run(command, execOptions) {
292
- return await runDockerCommand({
293
- runtime,
294
- command,
295
- defaultUser,
296
- defaultWorkdir,
297
- execOptions
298
- });
299
- }
300
- return {
301
- name: `docker:${runtime.label}`,
302
- async readFile(filePath) {
303
- const absPath = resolveDockerPath(filePath, defaultWorkdir);
304
- const result = await run(`cat ${sq(absPath)}`);
305
- if (result.exitCode !== 0) {
306
- throw new Error(`readFile failed (${absPath}): ${result.stderr.trim()}`);
307
- }
308
- return result.stdout;
309
- },
310
- async readBytes(filePath, offset, length) {
311
- const absPath = resolveDockerPath(filePath, defaultWorkdir);
312
- const result = await run(
313
- `dd if=${sq(absPath)} bs=1 skip=${offset} count=${length} 2>/dev/null`
314
- );
315
- if (result.exitCode !== 0) {
316
- throw new Error(
317
- `readBytes failed (${absPath}): ${result.stderr.trim()}`
318
- );
319
- }
320
- return Buffer.from(result.stdout, "binary");
321
- },
322
- async writeFile(filePath, content) {
323
- const absPath = resolveDockerPath(filePath, defaultWorkdir);
324
- const dir = absPath.substring(0, absPath.lastIndexOf("/")) || "/";
325
- await run(`mkdir -p ${sq(dir)}`);
326
- const encoded = Buffer.from(content, "utf-8").toString("base64");
327
- const result = await run(`echo ${sq(encoded)} | base64 -d > ${sq(absPath)}`);
328
- if (result.exitCode !== 0) {
329
- throw new Error(
330
- `writeFile failed (${absPath}): ${result.stderr.trim()}`
331
- );
332
- }
333
- },
334
- async exists(filePath) {
335
- const absPath = resolveDockerPath(filePath, defaultWorkdir);
336
- const result = await run(`test -e ${sq(absPath)}`);
337
- return result.exitCode === 0;
338
- },
339
- async stat(filePath) {
340
- const absPath = resolveDockerPath(filePath, defaultWorkdir);
341
- const result = await run(
342
- `stat -c '%s %Y %F' ${sq(absPath)} 2>/dev/null || stat -f '%z %m %HT' ${sq(absPath)}`
343
- );
344
- if (result.exitCode !== 0) {
345
- throw new Error(`stat failed (${absPath}): ${result.stderr.trim()}`);
346
- }
347
- const parts = result.stdout.trim().split(/\s+/);
348
- if (parts.length < 3) {
349
- throw new Error(
350
- `stat: unexpected output format for ${absPath}: ${result.stdout}`
351
- );
352
- }
353
- const size = parseInt(parts[0], 10);
354
- const mtimeSec = parseInt(parts[1], 10);
355
- const typeStr = parts.slice(2).join(" ").toLowerCase();
356
- return {
357
- size,
358
- mtime: new Date(mtimeSec * 1e3),
359
- isDirectory: typeStr.includes("directory"),
360
- isFile: typeStr.includes("regular") || typeStr.includes("file")
361
- };
362
- },
363
- async readdir(dirPath) {
364
- const absPath = resolveDockerPath(dirPath, defaultWorkdir);
365
- const result = await run(
366
- `find ${sq(absPath)} -maxdepth 1 -mindepth 1 -printf '%f\\t%y\\n' 2>/dev/null || for f in ${sq(absPath)}/*; do [ -e "$f" ] || continue; n=$(basename "$f"); if [ -d "$f" ]; then printf '%s\\td\\n' "$n"; else printf '%s\\tf\\n' "$n"; fi; done`
367
- );
368
- if (result.exitCode !== 0) {
369
- throw new Error(
370
- `readdir failed (${absPath}): ${result.stderr.trim()}`
371
- );
372
- }
373
- return result.stdout.trim().split("\n").filter(Boolean).map((line) => {
374
- const [name, type] = line.split(" ");
375
- return {
376
- name,
377
- isDirectory: type === "d",
378
- isFile: type === "f"
379
- };
380
- });
381
- },
382
- async mkdir(dirPath) {
383
- const absPath = resolveDockerPath(dirPath, defaultWorkdir);
384
- const result = await run(`mkdir -p ${sq(absPath)}`);
385
- if (result.exitCode !== 0) {
386
- throw new Error(`mkdir failed (${absPath}): ${result.stderr.trim()}`);
387
- }
388
- },
389
- async exec(command, execOptions) {
390
- return await run(command, execOptions);
391
- }
392
- };
393
- }
394
-
395
- export {
396
- localHost,
397
- dockerHost
398
- };
@@ -1,259 +0,0 @@
1
- // src/context/estimation.ts
2
- function estimateTokens(text) {
3
- return Math.ceil(text.length / 4);
4
- }
5
- function estimateMessageTokens(message) {
6
- if (typeof message.content === "string") {
7
- return estimateTokens(message.content);
8
- }
9
- if (Array.isArray(message.content)) {
10
- let total = 0;
11
- for (const part of message.content) {
12
- if (typeof part === "string") {
13
- total += estimateTokens(part);
14
- } else if ("text" in part && typeof part.text === "string") {
15
- total += estimateTokens(part.text);
16
- } else if ("type" in part && part.type === "image") {
17
- total += 765;
18
- }
19
- }
20
- return total;
21
- }
22
- return 0;
23
- }
24
- function estimateConversationTokens(messages) {
25
- let total = 0;
26
- for (const message of messages) {
27
- total += estimateMessageTokens(message);
28
- total += 4;
29
- }
30
- return total;
31
- }
32
-
33
- // src/agent/types/compaction.ts
34
- var PRUNE_PROTECTED_TOOLS = ["skill"];
35
-
36
- // src/context/pruning.ts
37
- var DEFAULT_CONTEXT_LIMITS = {
38
- contextWindow: 128e3,
39
- reserveTokens: 16e3,
40
- // Reserve for output
41
- protectedTokens: 4e4,
42
- // Keep recent 40 k tokens
43
- pruneMinimum: 2e4
44
- // Don't prune until 20 k tokens
45
- };
46
- function isContextOverflowing(tokens, limits = DEFAULT_CONTEXT_LIMITS) {
47
- const threshold = limits.contextWindow - limits.reserveTokens;
48
- return tokens > threshold;
49
- }
50
- function shouldPruneContext(tokens, limits = DEFAULT_CONTEXT_LIMITS) {
51
- if (tokens < limits.pruneMinimum) return false;
52
- return isContextOverflowing(tokens, limits);
53
- }
54
- function findCutPoint(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.protectedTokens) {
55
- if (messages.length === 0) return 0;
56
- let tokensFromEnd = 0;
57
- let cutIndex = messages.length;
58
- for (let i = messages.length - 1; i >= 0; i--) {
59
- tokensFromEnd += estimateMessageTokens(messages[i]);
60
- if (tokensFromEnd >= protectedTokens) {
61
- cutIndex = i;
62
- break;
63
- }
64
- }
65
- if (cutIndex <= 1) return 0;
66
- const startIndex = cutIndex >= messages.length ? messages.length - 1 : cutIndex;
67
- for (let i = startIndex; i >= 1; i--) {
68
- const msg = messages[i];
69
- const prevMsg = messages[i - 1];
70
- if (!msg || !prevMsg) continue;
71
- if (msg.role === "tool") continue;
72
- if (prevMsg.role === "assistant" || prevMsg.role === "user") {
73
- return i;
74
- }
75
- }
76
- return 0;
77
- }
78
- function pruneToolResults(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.protectedTokens, options) {
79
- const protectedToolSet = /* @__PURE__ */ new Set([
80
- ...PRUNE_PROTECTED_TOOLS,
81
- ...options?.protectedTools ?? []
82
- ]);
83
- let tokensFromEnd = 0;
84
- const tokenPositions = [];
85
- for (let i = messages.length - 1; i >= 0; i--) {
86
- tokensFromEnd += estimateMessageTokens(messages[i]);
87
- tokenPositions[i] = tokensFromEnd;
88
- }
89
- return messages.map((msg, i) => {
90
- if (tokenPositions[i] < protectedTokens) return msg;
91
- if (!("role" in msg) || msg.role !== "tool") return msg;
92
- const toolMsg = msg;
93
- if ("compactedAt" in toolMsg && toolMsg.compactedAt) return msg;
94
- if (toolMsg.toolName && protectedToolSet.has(toolMsg.toolName)) return msg;
95
- const currentTokens = estimateTokens(toolMsg.content);
96
- if (currentTokens < 500) return msg;
97
- return {
98
- ...toolMsg,
99
- content: `[Output pruned - was ${currentTokens} tokens]`,
100
- compactedAt: Date.now()
101
- };
102
- });
103
- }
104
-
105
- // src/context/summarization.ts
106
- import { generateText } from "ai";
107
- var DEFAULT_SUMMARY_PROMPT = `You are summarizing a conversation to continue it with context.
108
-
109
- Create a structured summary that captures:
110
- 1. **Goal**: What the user is trying to accomplish
111
- 2. **Progress**: What has been done so far
112
- 3. **Decisions**: Key decisions made during the conversation
113
- 4. **Current State**: Where we left off
114
- 5. **Next Steps**: What should happen next
115
-
116
- Be concise but comprehensive. Include specific file paths, function names, and technical details that would be lost otherwise.
117
-
118
- Format as a clear summary that could be given to another assistant to continue the work.`;
119
- async function generateSummary(messages, options) {
120
- const conversationText = messages.map((m) => {
121
- const role = m.role.toUpperCase();
122
- const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
123
- return `[${role}]: ${content}`;
124
- }).join("\n\n");
125
- const prompt = options.customPrompt || DEFAULT_SUMMARY_PROMPT;
126
- const { text } = await generateText({
127
- model: options.model,
128
- maxOutputTokens: options.maxTokens ?? 2e3,
129
- system: prompt,
130
- prompt: `Summarize this conversation:
131
-
132
- ${conversationText}`
133
- });
134
- return text;
135
- }
136
- async function pruneContext(messages, options = {}) {
137
- const limits = options.limits ?? DEFAULT_CONTEXT_LIMITS;
138
- let currentMessages = [...messages];
139
- let tokensRemoved = 0;
140
- let removedCount = 0;
141
- let summarized = false;
142
- let summary;
143
- const initialTokens = estimateConversationTokens(currentMessages);
144
- if (!shouldPruneContext(initialTokens, limits)) {
145
- return { messages: currentMessages, removedCount: 0, tokensRemoved: 0, summarized: false };
146
- }
147
- const prunedMessages = pruneToolResults(currentMessages, limits.protectedTokens);
148
- const afterPruneTokens = estimateConversationTokens(prunedMessages);
149
- tokensRemoved = initialTokens - afterPruneTokens;
150
- currentMessages = prunedMessages;
151
- if (!isContextOverflowing(afterPruneTokens, limits)) {
152
- return { messages: currentMessages, removedCount: 0, tokensRemoved, summarized: false };
153
- }
154
- const cutIndex = findCutPoint(currentMessages, limits.protectedTokens);
155
- if (cutIndex === 0) {
156
- return { messages: currentMessages, removedCount: 0, tokensRemoved, summarized: false };
157
- }
158
- const toSummarize = currentMessages.slice(0, cutIndex);
159
- const toKeep = currentMessages.slice(cutIndex);
160
- removedCount = toSummarize.length;
161
- tokensRemoved += estimateConversationTokens(toSummarize);
162
- if (options.model) {
163
- summary = await generateSummary(toSummarize, {
164
- model: options.model,
165
- customPrompt: options.summaryPrompt
166
- });
167
- summarized = true;
168
- const summaryMessage = {
169
- id: crypto.randomUUID(),
170
- role: "system",
171
- content: `## Previous Conversation Summary
172
-
173
- ${summary}`,
174
- createdAt: /* @__PURE__ */ new Date()
175
- };
176
- currentMessages = [summaryMessage, ...toKeep];
177
- } else {
178
- currentMessages = toKeep;
179
- }
180
- return { messages: currentMessages, removedCount, tokensRemoved, summarized, summary };
181
- }
182
-
183
- // src/context/manager.ts
184
- var ContextManager = class {
185
- limits;
186
- model;
187
- summaryPrompt;
188
- constructor(options) {
189
- this.limits = { ...DEFAULT_CONTEXT_LIMITS, ...options?.limits };
190
- this.model = options?.model;
191
- this.summaryPrompt = options?.summaryPrompt;
192
- }
193
- /** Get a copy of the current context limits. */
194
- getLimits() {
195
- return { ...this.limits };
196
- }
197
- /** Update context limits (e.g. when switching models). */
198
- setLimits(limits) {
199
- this.limits = { ...this.limits, ...limits };
200
- }
201
- /** Set the model used for summarisation. */
202
- setModel(model) {
203
- this.model = model;
204
- }
205
- /** Estimate total tokens for a message array. */
206
- estimateTokens(messages) {
207
- return estimateConversationTokens(messages);
208
- }
209
- /** Check whether the context is overflowing. */
210
- isOverflowing(messages) {
211
- const tokens = this.estimateTokens(messages);
212
- return isContextOverflowing(tokens, this.limits);
213
- }
214
- /** Check whether pruning should be triggered. */
215
- shouldPrune(messages) {
216
- const tokens = this.estimateTokens(messages);
217
- return shouldPruneContext(tokens, this.limits);
218
- }
219
- /** Prune context to fit within limits. */
220
- async prune(messages) {
221
- return pruneContext(messages, {
222
- model: this.model,
223
- limits: this.limits,
224
- summaryPrompt: this.summaryPrompt
225
- });
226
- }
227
- /**
228
- * Get a snapshot of token statistics.
229
- *
230
- * Useful for dashboards, logging, or deciding whether to prune.
231
- */
232
- getStats(messages) {
233
- const tokens = this.estimateTokens(messages);
234
- const limit = this.limits.contextWindow - this.limits.reserveTokens;
235
- return {
236
- tokens,
237
- limit,
238
- available: Math.max(0, limit - tokens),
239
- utilizationPercent: Math.round(tokens / limit * 100),
240
- isOverflowing: isContextOverflowing(tokens, this.limits),
241
- shouldPrune: shouldPruneContext(tokens, this.limits)
242
- };
243
- }
244
- };
245
-
246
- export {
247
- estimateTokens,
248
- estimateMessageTokens,
249
- estimateConversationTokens,
250
- PRUNE_PROTECTED_TOOLS,
251
- DEFAULT_CONTEXT_LIMITS,
252
- isContextOverflowing,
253
- shouldPruneContext,
254
- findCutPoint,
255
- pruneToolResults,
256
- generateSummary,
257
- pruneContext,
258
- ContextManager
259
- };