ak-gemini 2.0.0 → 2.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.
package/README.md CHANGED
@@ -136,7 +136,7 @@ for await (const event of agent.stream('Fetch the data')) {
136
136
 
137
137
  ### CodeAgent — Agent That Writes and Executes Code
138
138
 
139
- Instead of calling tools one by one, the model writes JavaScript that can do everything — read files, write files, run commands — in a single script. Inspired by the [code mode](https://blog.cloudflare.com/how-we-built-mcp-code-mode/) philosophy.
139
+ Instead of calling tools one by one, the model writes JavaScript that can do everything — read files, write files, run commands — in a single script. Inspired by the [code mode](https://blog.cloudflare.com/code-mode/) philosophy.
140
140
 
141
141
  ```javascript
142
142
  const agent = new CodeAgent({
package/code-agent.js CHANGED
@@ -12,8 +12,8 @@
12
12
  import BaseGemini from './base.js';
13
13
  import log from './logger.js';
14
14
  import { execFile } from 'node:child_process';
15
- import { writeFile, unlink, readdir, readFile } from 'node:fs/promises';
16
- import { join } from 'node:path';
15
+ import { writeFile, unlink, readdir, readFile, mkdir } from 'node:fs/promises';
16
+ import { join, sep } from 'node:path';
17
17
  import { randomUUID } from 'node:crypto';
18
18
 
19
19
  /**
@@ -27,7 +27,7 @@ const MAX_FILE_TREE_LINES = 500;
27
27
  const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'coverage', '.next', 'build', '__pycache__']);
28
28
 
29
29
  /**
30
- * AI agent that writes and executes JavaScript code autonomously.
30
+ * AI agent that writes and executes JavaScript code autonomously. ... what could possibly go wrong, right?
31
31
  *
32
32
  * During init, gathers codebase context (file tree + key files) and injects it
33
33
  * into the system prompt. The model uses the `execute_code` tool to run scripts
@@ -67,6 +67,11 @@ class CodeAgent extends BaseGemini {
67
67
  this.timeout = options.timeout || 30_000;
68
68
  this.onBeforeExecution = options.onBeforeExecution || null;
69
69
  this.onCodeExecution = options.onCodeExecution || null;
70
+ this.importantFiles = options.importantFiles || [];
71
+ this.writeDir = options.writeDir || join(this.workingDirectory, 'tmp');
72
+ this.keepArtifacts = options.keepArtifacts ?? false;
73
+ this.comments = options.comments ?? false;
74
+ this.maxRetries = options.maxRetries ?? 3;
70
75
 
71
76
  // ── Internal state ──
72
77
  this._codebaseContext = null;
@@ -87,6 +92,10 @@ class CodeAgent extends BaseGemini {
87
92
  code: {
88
93
  type: 'string',
89
94
  description: 'JavaScript code to execute. Use console.log() for output. You can import any built-in Node.js module.'
95
+ },
96
+ purpose: {
97
+ type: 'string',
98
+ description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs", "fetch-api-data"). Used for naming the script file.'
90
99
  }
91
100
  },
92
101
  required: ['code']
@@ -155,10 +164,50 @@ class CodeAgent extends BaseGemini {
155
164
  ];
156
165
  } catch { /* no package.json */ }
157
166
 
158
- this._codebaseContext = { fileTree, npmPackages };
167
+ // Resolve and read important files
168
+ const importantFileContents = [];
169
+ if (this.importantFiles.length > 0) {
170
+ const fileTreeLines = fileTree.split('\n').map(l => l.trim()).filter(Boolean);
171
+ for (const requested of this.importantFiles) {
172
+ const resolved = this._resolveImportantFile(requested, fileTreeLines);
173
+ if (!resolved) {
174
+ log.warn(`importantFiles: could not locate "${requested}"`);
175
+ continue;
176
+ }
177
+ try {
178
+ const fullPath = join(this.workingDirectory, resolved);
179
+ const content = await readFile(fullPath, 'utf-8');
180
+ importantFileContents.push({ path: resolved, content });
181
+ } catch (e) {
182
+ log.warn(`importantFiles: could not read "${resolved}": ${e.message}`);
183
+ }
184
+ }
185
+ }
186
+
187
+ this._codebaseContext = { fileTree, npmPackages, importantFileContents };
159
188
  this._contextGathered = true;
160
189
  }
161
190
 
191
+ /**
192
+ * Resolve an importantFiles entry against the file tree.
193
+ * Supports exact matches and partial (basename/suffix) matches.
194
+ * @private
195
+ * @param {string} filename
196
+ * @param {string[]} fileTreeLines
197
+ * @returns {string|null}
198
+ */
199
+ _resolveImportantFile(filename, fileTreeLines) {
200
+ // Exact match
201
+ const exact = fileTreeLines.find(line => line === filename);
202
+ if (exact) return exact;
203
+
204
+ // Partial match — filename matches end of path
205
+ const partial = fileTreeLines.find(line =>
206
+ line.endsWith('/' + filename) || line.endsWith(sep + filename)
207
+ );
208
+ return partial || null;
209
+ }
210
+
162
211
  /**
163
212
  * Get file tree using git ls-files.
164
213
  * @private
@@ -215,12 +264,13 @@ class CodeAgent extends BaseGemini {
215
264
  * @returns {string}
216
265
  */
217
266
  _buildSystemPrompt() {
218
- const { fileTree, npmPackages } = this._codebaseContext || { fileTree: '', npmPackages: [] };
267
+ const { fileTree, npmPackages, importantFileContents } = this._codebaseContext || { fileTree: '', npmPackages: [], importantFileContents: [] };
219
268
 
220
269
  let prompt = `You are a coding agent working in ${this.workingDirectory}.
221
270
 
222
271
  ## Instructions
223
272
  - Use the execute_code tool to accomplish tasks by writing JavaScript code
273
+ - Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config") when calling execute_code
224
274
  - Your code runs in a Node.js child process with access to all built-in modules
225
275
  - IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
226
276
  - import fs from 'fs';
@@ -235,6 +285,12 @@ class CodeAgent extends BaseGemini {
235
285
  - Top-level await is supported
236
286
  - The working directory is: ${this.workingDirectory}`;
237
287
 
288
+ if (this.comments) {
289
+ prompt += `\n- Add a JSDoc @fileoverview comment at the top of each script explaining what it does\n- Add brief JSDoc @param comments for any functions you define`;
290
+ } else {
291
+ prompt += `\n- Do NOT write any comments in your code — save tokens. The code should be self-explanatory.`;
292
+ }
293
+
238
294
  if (fileTree) {
239
295
  prompt += `\n\n## File Tree\n\`\`\`\n${fileTree}\n\`\`\``;
240
296
  }
@@ -243,6 +299,13 @@ class CodeAgent extends BaseGemini {
243
299
  prompt += `\n\n## Available Packages\nThese npm packages are installed and can be imported: ${npmPackages.join(', ')}`;
244
300
  }
245
301
 
302
+ if (importantFileContents && importantFileContents.length > 0) {
303
+ prompt += `\n\n## Key Files`;
304
+ for (const { path: filePath, content } of importantFileContents) {
305
+ prompt += `\n\n### ${filePath}\n\`\`\`javascript\n${content}\n\`\`\``;
306
+ }
307
+ }
308
+
246
309
  if (this._userSystemPrompt) {
247
310
  prompt += `\n\n## Additional Instructions\n${this._userSystemPrompt}`;
248
311
  }
@@ -252,13 +315,25 @@ class CodeAgent extends BaseGemini {
252
315
 
253
316
  // ── Code Execution ───────────────────────────────────────────────────────
254
317
 
318
+ /**
319
+ * Generate a sanitized slug from a purpose string.
320
+ * @private
321
+ * @param {string} [purpose]
322
+ * @returns {string}
323
+ */
324
+ _slugify(purpose) {
325
+ if (!purpose) return randomUUID().slice(0, 8);
326
+ return purpose.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40);
327
+ }
328
+
255
329
  /**
256
330
  * Execute a JavaScript code string in a child process.
257
331
  * @private
258
332
  * @param {string} code - JavaScript code to execute
333
+ * @param {string} [purpose] - Short description for file naming
259
334
  * @returns {Promise<{stdout: string, stderr: string, exitCode: number, denied?: boolean}>}
260
335
  */
261
- async _executeCode(code) {
336
+ async _executeCode(code, purpose) {
262
337
  // Check if stopped
263
338
  if (this._stopped) {
264
339
  return { stdout: '', stderr: 'Agent was stopped', exitCode: -1 };
@@ -276,7 +351,11 @@ class CodeAgent extends BaseGemini {
276
351
  }
277
352
  }
278
353
 
279
- const tempFile = join(this.workingDirectory, `.code-agent-tmp-${randomUUID()}.mjs`);
354
+ // Ensure writeDir exists
355
+ await mkdir(this.writeDir, { recursive: true });
356
+
357
+ const slug = this._slugify(purpose);
358
+ const tempFile = join(this.writeDir, `agent-${slug}-${Date.now()}.mjs`);
280
359
 
281
360
  try {
282
361
  // Write code to temp file
@@ -317,7 +396,10 @@ class CodeAgent extends BaseGemini {
317
396
  }
318
397
 
319
398
  // Track execution
320
- this._allExecutions.push({ code, output: result.stdout, stderr: result.stderr, exitCode: result.exitCode });
399
+ this._allExecutions.push({
400
+ code, purpose: purpose || null, output: result.stdout, stderr: result.stderr,
401
+ exitCode: result.exitCode, filePath: this.keepArtifacts ? tempFile : null
402
+ });
321
403
 
322
404
  // Fire notification callback
323
405
  if (this.onCodeExecution) {
@@ -327,9 +409,11 @@ class CodeAgent extends BaseGemini {
327
409
 
328
410
  return result;
329
411
  } finally {
330
- // Cleanup temp file
331
- try { await unlink(tempFile); }
332
- catch { /* file may already be gone */ }
412
+ // Cleanup temp file (unless keeping artifacts)
413
+ if (!this.keepArtifacts) {
414
+ try { await unlink(tempFile); }
415
+ catch { /* file may already be gone */ }
416
+ }
333
417
  }
334
418
  }
335
419
 
@@ -363,6 +447,7 @@ class CodeAgent extends BaseGemini {
363
447
  this._stopped = false;
364
448
 
365
449
  const codeExecutions = [];
450
+ let consecutiveFailures = 0;
366
451
 
367
452
  let response = await this.chatSession.sendMessage({ message });
368
453
 
@@ -377,19 +462,33 @@ class CodeAgent extends BaseGemini {
377
462
  if (this._stopped) break;
378
463
 
379
464
  const code = call.args?.code || '';
380
- const result = await this._executeCode(code);
465
+ const purpose = call.args?.purpose;
466
+ const result = await this._executeCode(code, purpose);
381
467
 
382
468
  codeExecutions.push({
383
469
  code,
470
+ purpose: this._slugify(purpose),
384
471
  output: result.stdout,
385
472
  stderr: result.stderr,
386
473
  exitCode: result.exitCode
387
474
  });
388
475
 
476
+ if (result.exitCode !== 0 && !result.denied) {
477
+ consecutiveFailures++;
478
+ } else {
479
+ consecutiveFailures = 0;
480
+ }
481
+
482
+ let output = this._formatOutput(result);
483
+
484
+ if (consecutiveFailures >= this.maxRetries) {
485
+ output += `\n\n[RETRY LIMIT REACHED] You have failed ${this.maxRetries} consecutive attempts. STOP trying to execute code. Instead, respond with: 1) What you were trying to do, 2) The errors you encountered, 3) Questions for the user about how to resolve it.`;
486
+ }
487
+
389
488
  results.push({
390
489
  id: call.id,
391
490
  name: call.name,
392
- result: this._formatOutput(result)
491
+ result: output
393
492
  });
394
493
  }
395
494
 
@@ -405,6 +504,8 @@ class CodeAgent extends BaseGemini {
405
504
  }
406
505
  }))
407
506
  });
507
+
508
+ if (consecutiveFailures >= this.maxRetries) break;
408
509
  }
409
510
 
410
511
  this._captureMetadata(response);
@@ -445,6 +546,7 @@ class CodeAgent extends BaseGemini {
445
546
 
446
547
  const codeExecutions = [];
447
548
  let fullText = '';
549
+ let consecutiveFailures = 0;
448
550
 
449
551
  let streamResponse = await this.chatSession.sendMessageStream({ message });
450
552
 
@@ -481,12 +583,14 @@ class CodeAgent extends BaseGemini {
481
583
  if (this._stopped) break;
482
584
 
483
585
  const code = call.args?.code || '';
586
+ const purpose = call.args?.purpose;
484
587
  yield { type: 'code', code };
485
588
 
486
- const result = await this._executeCode(code);
589
+ const result = await this._executeCode(code, purpose);
487
590
 
488
591
  codeExecutions.push({
489
592
  code,
593
+ purpose: this._slugify(purpose),
490
594
  output: result.stdout,
491
595
  stderr: result.stderr,
492
596
  exitCode: result.exitCode
@@ -500,10 +604,22 @@ class CodeAgent extends BaseGemini {
500
604
  exitCode: result.exitCode
501
605
  };
502
606
 
607
+ if (result.exitCode !== 0 && !result.denied) {
608
+ consecutiveFailures++;
609
+ } else {
610
+ consecutiveFailures = 0;
611
+ }
612
+
613
+ let output = this._formatOutput(result);
614
+
615
+ if (consecutiveFailures >= this.maxRetries) {
616
+ output += `\n\n[RETRY LIMIT REACHED] You have failed ${this.maxRetries} consecutive attempts. STOP trying to execute code. Instead, respond with: 1) What you were trying to do, 2) The errors you encountered, 3) Questions for the user about how to resolve it.`;
617
+ }
618
+
503
619
  results.push({
504
620
  id: call.id,
505
621
  name: call.name,
506
- result: this._formatOutput(result)
622
+ result: output
507
623
  });
508
624
  }
509
625
 
@@ -519,15 +635,21 @@ class CodeAgent extends BaseGemini {
519
635
  }
520
636
  }))
521
637
  });
638
+
639
+ if (consecutiveFailures >= this.maxRetries) break;
522
640
  }
523
641
 
524
- // Max rounds reached or stopped
642
+ // Max rounds reached, stopped, or retry limit hit
643
+ let warning = 'Max tool rounds reached';
644
+ if (this._stopped) warning = 'Agent was stopped';
645
+ else if (consecutiveFailures >= this.maxRetries) warning = 'Retry limit reached';
646
+
525
647
  yield {
526
648
  type: 'done',
527
649
  fullText,
528
650
  codeExecutions,
529
651
  usage: this.getLastUsage(),
530
- warning: this._stopped ? 'Agent was stopped' : 'Max tool rounds reached'
652
+ warning
531
653
  };
532
654
  }
533
655
 
@@ -539,8 +661,10 @@ class CodeAgent extends BaseGemini {
539
661
  */
540
662
  dump() {
541
663
  return this._allExecutions.map((exec, i) => ({
542
- fileName: `script-${i + 1}.mjs`,
543
- script: exec.code
664
+ fileName: exec.purpose ? `agent-${exec.purpose}.mjs` : `script-${i + 1}.mjs`,
665
+ purpose: exec.purpose || null,
666
+ script: exec.code,
667
+ filePath: exec.filePath || null
544
668
  }));
545
669
  }
546
670
 
package/index.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  HarmBlockThreshold: () => import_genai2.HarmBlockThreshold,
36
36
  HarmCategory: () => import_genai2.HarmCategory,
37
37
  Message: () => message_default,
38
+ RagAgent: () => rag_agent_default,
38
39
  ThinkingLevel: () => import_genai2.ThinkingLevel,
39
40
  ToolAgent: () => tool_agent_default,
40
41
  Transformer: () => transformer_default,
@@ -1209,6 +1210,7 @@ var ToolAgent = class extends base_default {
1209
1210
  this.maxToolRounds = options.maxToolRounds || 10;
1210
1211
  this.onToolCall = options.onToolCall || null;
1211
1212
  this.onBeforeExecution = options.onBeforeExecution || null;
1213
+ this.writeDir = options.writeDir || null;
1212
1214
  this._stopped = false;
1213
1215
  if (this.tools.length > 0) {
1214
1216
  this.chatConfig.tools = [{ functionDeclarations: this.tools }];
@@ -1420,6 +1422,11 @@ var CodeAgent = class extends base_default {
1420
1422
  this.timeout = options.timeout || 3e4;
1421
1423
  this.onBeforeExecution = options.onBeforeExecution || null;
1422
1424
  this.onCodeExecution = options.onCodeExecution || null;
1425
+ this.importantFiles = options.importantFiles || [];
1426
+ this.writeDir = options.writeDir || (0, import_node_path.join)(this.workingDirectory, "tmp");
1427
+ this.keepArtifacts = options.keepArtifacts ?? false;
1428
+ this.comments = options.comments ?? false;
1429
+ this.maxRetries = options.maxRetries ?? 3;
1423
1430
  this._codebaseContext = null;
1424
1431
  this._contextGathered = false;
1425
1432
  this._stopped = false;
@@ -1436,6 +1443,10 @@ var CodeAgent = class extends base_default {
1436
1443
  code: {
1437
1444
  type: "string",
1438
1445
  description: "JavaScript code to execute. Use console.log() for output. You can import any built-in Node.js module."
1446
+ },
1447
+ purpose: {
1448
+ type: "string",
1449
+ description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs", "fetch-api-data"). Used for naming the script file.'
1439
1450
  }
1440
1451
  },
1441
1452
  required: ["code"]
@@ -1489,23 +1500,57 @@ var CodeAgent = class extends base_default {
1489
1500
  ];
1490
1501
  } catch {
1491
1502
  }
1492
- this._codebaseContext = { fileTree, npmPackages };
1503
+ const importantFileContents = [];
1504
+ if (this.importantFiles.length > 0) {
1505
+ const fileTreeLines = fileTree.split("\n").map((l) => l.trim()).filter(Boolean);
1506
+ for (const requested of this.importantFiles) {
1507
+ const resolved = this._resolveImportantFile(requested, fileTreeLines);
1508
+ if (!resolved) {
1509
+ logger_default.warn(`importantFiles: could not locate "${requested}"`);
1510
+ continue;
1511
+ }
1512
+ try {
1513
+ const fullPath = (0, import_node_path.join)(this.workingDirectory, resolved);
1514
+ const content = await (0, import_promises2.readFile)(fullPath, "utf-8");
1515
+ importantFileContents.push({ path: resolved, content });
1516
+ } catch (e) {
1517
+ logger_default.warn(`importantFiles: could not read "${resolved}": ${e.message}`);
1518
+ }
1519
+ }
1520
+ }
1521
+ this._codebaseContext = { fileTree, npmPackages, importantFileContents };
1493
1522
  this._contextGathered = true;
1494
1523
  }
1524
+ /**
1525
+ * Resolve an importantFiles entry against the file tree.
1526
+ * Supports exact matches and partial (basename/suffix) matches.
1527
+ * @private
1528
+ * @param {string} filename
1529
+ * @param {string[]} fileTreeLines
1530
+ * @returns {string|null}
1531
+ */
1532
+ _resolveImportantFile(filename, fileTreeLines) {
1533
+ const exact = fileTreeLines.find((line) => line === filename);
1534
+ if (exact) return exact;
1535
+ const partial = fileTreeLines.find(
1536
+ (line) => line.endsWith("/" + filename) || line.endsWith(import_node_path.sep + filename)
1537
+ );
1538
+ return partial || null;
1539
+ }
1495
1540
  /**
1496
1541
  * Get file tree using git ls-files.
1497
1542
  * @private
1498
1543
  * @returns {Promise<string>}
1499
1544
  */
1500
1545
  async _getFileTreeGit() {
1501
- return new Promise((resolve, reject) => {
1546
+ return new Promise((resolve2, reject) => {
1502
1547
  (0, import_node_child_process.execFile)("git", ["ls-files"], {
1503
1548
  cwd: this.workingDirectory,
1504
1549
  timeout: 5e3,
1505
1550
  maxBuffer: 5 * 1024 * 1024
1506
1551
  }, (err, stdout) => {
1507
1552
  if (err) return reject(err);
1508
- resolve(stdout.trim());
1553
+ resolve2(stdout.trim());
1509
1554
  });
1510
1555
  });
1511
1556
  }
@@ -1544,11 +1589,12 @@ var CodeAgent = class extends base_default {
1544
1589
  * @returns {string}
1545
1590
  */
1546
1591
  _buildSystemPrompt() {
1547
- const { fileTree, npmPackages } = this._codebaseContext || { fileTree: "", npmPackages: [] };
1592
+ const { fileTree, npmPackages, importantFileContents } = this._codebaseContext || { fileTree: "", npmPackages: [], importantFileContents: [] };
1548
1593
  let prompt = `You are a coding agent working in ${this.workingDirectory}.
1549
1594
 
1550
1595
  ## Instructions
1551
1596
  - Use the execute_code tool to accomplish tasks by writing JavaScript code
1597
+ - Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config") when calling execute_code
1552
1598
  - Your code runs in a Node.js child process with access to all built-in modules
1553
1599
  - IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
1554
1600
  - import fs from 'fs';
@@ -1562,6 +1608,14 @@ var CodeAgent = class extends base_default {
1562
1608
  - Handle errors in your scripts with try/catch so you get useful error messages
1563
1609
  - Top-level await is supported
1564
1610
  - The working directory is: ${this.workingDirectory}`;
1611
+ if (this.comments) {
1612
+ prompt += `
1613
+ - Add a JSDoc @fileoverview comment at the top of each script explaining what it does
1614
+ - Add brief JSDoc @param comments for any functions you define`;
1615
+ } else {
1616
+ prompt += `
1617
+ - Do NOT write any comments in your code \u2014 save tokens. The code should be self-explanatory.`;
1618
+ }
1565
1619
  if (fileTree) {
1566
1620
  prompt += `
1567
1621
 
@@ -1576,6 +1630,19 @@ ${fileTree}
1576
1630
  ## Available Packages
1577
1631
  These npm packages are installed and can be imported: ${npmPackages.join(", ")}`;
1578
1632
  }
1633
+ if (importantFileContents && importantFileContents.length > 0) {
1634
+ prompt += `
1635
+
1636
+ ## Key Files`;
1637
+ for (const { path: filePath, content } of importantFileContents) {
1638
+ prompt += `
1639
+
1640
+ ### ${filePath}
1641
+ \`\`\`javascript
1642
+ ${content}
1643
+ \`\`\``;
1644
+ }
1645
+ }
1579
1646
  if (this._userSystemPrompt) {
1580
1647
  prompt += `
1581
1648
 
@@ -1585,13 +1652,24 @@ ${this._userSystemPrompt}`;
1585
1652
  return prompt;
1586
1653
  }
1587
1654
  // ── Code Execution ───────────────────────────────────────────────────────
1655
+ /**
1656
+ * Generate a sanitized slug from a purpose string.
1657
+ * @private
1658
+ * @param {string} [purpose]
1659
+ * @returns {string}
1660
+ */
1661
+ _slugify(purpose) {
1662
+ if (!purpose) return (0, import_node_crypto.randomUUID)().slice(0, 8);
1663
+ return purpose.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
1664
+ }
1588
1665
  /**
1589
1666
  * Execute a JavaScript code string in a child process.
1590
1667
  * @private
1591
1668
  * @param {string} code - JavaScript code to execute
1669
+ * @param {string} [purpose] - Short description for file naming
1592
1670
  * @returns {Promise<{stdout: string, stderr: string, exitCode: number, denied?: boolean}>}
1593
1671
  */
1594
- async _executeCode(code) {
1672
+ async _executeCode(code, purpose) {
1595
1673
  if (this._stopped) {
1596
1674
  return { stdout: "", stderr: "Agent was stopped", exitCode: -1 };
1597
1675
  }
@@ -1605,10 +1683,12 @@ ${this._userSystemPrompt}`;
1605
1683
  logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
1606
1684
  }
1607
1685
  }
1608
- const tempFile = (0, import_node_path.join)(this.workingDirectory, `.code-agent-tmp-${(0, import_node_crypto.randomUUID)()}.mjs`);
1686
+ await (0, import_promises2.mkdir)(this.writeDir, { recursive: true });
1687
+ const slug = this._slugify(purpose);
1688
+ const tempFile = (0, import_node_path.join)(this.writeDir, `agent-${slug}-${Date.now()}.mjs`);
1609
1689
  try {
1610
1690
  await (0, import_promises2.writeFile)(tempFile, code, "utf-8");
1611
- const result = await new Promise((resolve) => {
1691
+ const result = await new Promise((resolve2) => {
1612
1692
  const child = (0, import_node_child_process.execFile)("node", [tempFile], {
1613
1693
  cwd: this.workingDirectory,
1614
1694
  timeout: this.timeout,
@@ -1617,13 +1697,13 @@ ${this._userSystemPrompt}`;
1617
1697
  }, (err, stdout, stderr) => {
1618
1698
  this._activeProcess = null;
1619
1699
  if (err) {
1620
- resolve({
1700
+ resolve2({
1621
1701
  stdout: err.stdout || stdout || "",
1622
1702
  stderr: (err.stderr || stderr || "") + (err.killed ? "\n[EXECUTION TIMED OUT]" : ""),
1623
1703
  exitCode: err.code || 1
1624
1704
  });
1625
1705
  } else {
1626
- resolve({ stdout: stdout || "", stderr: stderr || "", exitCode: 0 });
1706
+ resolve2({ stdout: stdout || "", stderr: stderr || "", exitCode: 0 });
1627
1707
  }
1628
1708
  });
1629
1709
  this._activeProcess = child;
@@ -1638,7 +1718,14 @@ ${this._userSystemPrompt}`;
1638
1718
  result.stderr = result.stderr.slice(0, half) + "\n...[STDERR TRUNCATED]";
1639
1719
  }
1640
1720
  }
1641
- this._allExecutions.push({ code, output: result.stdout, stderr: result.stderr, exitCode: result.exitCode });
1721
+ this._allExecutions.push({
1722
+ code,
1723
+ purpose: purpose || null,
1724
+ output: result.stdout,
1725
+ stderr: result.stderr,
1726
+ exitCode: result.exitCode,
1727
+ filePath: this.keepArtifacts ? tempFile : null
1728
+ });
1642
1729
  if (this.onCodeExecution) {
1643
1730
  try {
1644
1731
  this.onCodeExecution(code, result);
@@ -1648,9 +1735,11 @@ ${this._userSystemPrompt}`;
1648
1735
  }
1649
1736
  return result;
1650
1737
  } finally {
1651
- try {
1652
- await (0, import_promises2.unlink)(tempFile);
1653
- } catch {
1738
+ if (!this.keepArtifacts) {
1739
+ try {
1740
+ await (0, import_promises2.unlink)(tempFile);
1741
+ } catch {
1742
+ }
1654
1743
  }
1655
1744
  }
1656
1745
  }
@@ -1681,6 +1770,7 @@ ${this._userSystemPrompt}`;
1681
1770
  if (!this.chatSession) await this.init();
1682
1771
  this._stopped = false;
1683
1772
  const codeExecutions = [];
1773
+ let consecutiveFailures = 0;
1684
1774
  let response = await this.chatSession.sendMessage({ message });
1685
1775
  for (let round = 0; round < this.maxRounds; round++) {
1686
1776
  if (this._stopped) break;
@@ -1690,17 +1780,30 @@ ${this._userSystemPrompt}`;
1690
1780
  for (const call of functionCalls) {
1691
1781
  if (this._stopped) break;
1692
1782
  const code = call.args?.code || "";
1693
- const result = await this._executeCode(code);
1783
+ const purpose = call.args?.purpose;
1784
+ const result = await this._executeCode(code, purpose);
1694
1785
  codeExecutions.push({
1695
1786
  code,
1787
+ purpose: this._slugify(purpose),
1696
1788
  output: result.stdout,
1697
1789
  stderr: result.stderr,
1698
1790
  exitCode: result.exitCode
1699
1791
  });
1792
+ if (result.exitCode !== 0 && !result.denied) {
1793
+ consecutiveFailures++;
1794
+ } else {
1795
+ consecutiveFailures = 0;
1796
+ }
1797
+ let output = this._formatOutput(result);
1798
+ if (consecutiveFailures >= this.maxRetries) {
1799
+ output += `
1800
+
1801
+ [RETRY LIMIT REACHED] You have failed ${this.maxRetries} consecutive attempts. STOP trying to execute code. Instead, respond with: 1) What you were trying to do, 2) The errors you encountered, 3) Questions for the user about how to resolve it.`;
1802
+ }
1700
1803
  results.push({
1701
1804
  id: call.id,
1702
1805
  name: call.name,
1703
- result: this._formatOutput(result)
1806
+ result: output
1704
1807
  });
1705
1808
  }
1706
1809
  if (this._stopped) break;
@@ -1713,6 +1816,7 @@ ${this._userSystemPrompt}`;
1713
1816
  }
1714
1817
  }))
1715
1818
  });
1819
+ if (consecutiveFailures >= this.maxRetries) break;
1716
1820
  }
1717
1821
  this._captureMetadata(response);
1718
1822
  this._cumulativeUsage = {
@@ -1747,6 +1851,7 @@ ${this._userSystemPrompt}`;
1747
1851
  this._stopped = false;
1748
1852
  const codeExecutions = [];
1749
1853
  let fullText = "";
1854
+ let consecutiveFailures = 0;
1750
1855
  let streamResponse = await this.chatSession.sendMessageStream({ message });
1751
1856
  for (let round = 0; round < this.maxRounds; round++) {
1752
1857
  if (this._stopped) break;
@@ -1773,10 +1878,12 @@ ${this._userSystemPrompt}`;
1773
1878
  for (const call of functionCalls) {
1774
1879
  if (this._stopped) break;
1775
1880
  const code = call.args?.code || "";
1881
+ const purpose = call.args?.purpose;
1776
1882
  yield { type: "code", code };
1777
- const result = await this._executeCode(code);
1883
+ const result = await this._executeCode(code, purpose);
1778
1884
  codeExecutions.push({
1779
1885
  code,
1886
+ purpose: this._slugify(purpose),
1780
1887
  output: result.stdout,
1781
1888
  stderr: result.stderr,
1782
1889
  exitCode: result.exitCode
@@ -1788,10 +1895,21 @@ ${this._userSystemPrompt}`;
1788
1895
  stderr: result.stderr,
1789
1896
  exitCode: result.exitCode
1790
1897
  };
1898
+ if (result.exitCode !== 0 && !result.denied) {
1899
+ consecutiveFailures++;
1900
+ } else {
1901
+ consecutiveFailures = 0;
1902
+ }
1903
+ let output = this._formatOutput(result);
1904
+ if (consecutiveFailures >= this.maxRetries) {
1905
+ output += `
1906
+
1907
+ [RETRY LIMIT REACHED] You have failed ${this.maxRetries} consecutive attempts. STOP trying to execute code. Instead, respond with: 1) What you were trying to do, 2) The errors you encountered, 3) Questions for the user about how to resolve it.`;
1908
+ }
1791
1909
  results.push({
1792
1910
  id: call.id,
1793
1911
  name: call.name,
1794
- result: this._formatOutput(result)
1912
+ result: output
1795
1913
  });
1796
1914
  }
1797
1915
  if (this._stopped) break;
@@ -1804,13 +1922,17 @@ ${this._userSystemPrompt}`;
1804
1922
  }
1805
1923
  }))
1806
1924
  });
1925
+ if (consecutiveFailures >= this.maxRetries) break;
1807
1926
  }
1927
+ let warning = "Max tool rounds reached";
1928
+ if (this._stopped) warning = "Agent was stopped";
1929
+ else if (consecutiveFailures >= this.maxRetries) warning = "Retry limit reached";
1808
1930
  yield {
1809
1931
  type: "done",
1810
1932
  fullText,
1811
1933
  codeExecutions,
1812
1934
  usage: this.getLastUsage(),
1813
- warning: this._stopped ? "Agent was stopped" : "Max tool rounds reached"
1935
+ warning
1814
1936
  };
1815
1937
  }
1816
1938
  // ── Dump ─────────────────────────────────────────────────────────────────
@@ -1820,8 +1942,10 @@ ${this._userSystemPrompt}`;
1820
1942
  */
1821
1943
  dump() {
1822
1944
  return this._allExecutions.map((exec, i) => ({
1823
- fileName: `script-${i + 1}.mjs`,
1824
- script: exec.code
1945
+ fileName: exec.purpose ? `agent-${exec.purpose}.mjs` : `script-${i + 1}.mjs`,
1946
+ purpose: exec.purpose || null,
1947
+ script: exec.code,
1948
+ filePath: exec.filePath || null
1825
1949
  }));
1826
1950
  }
1827
1951
  // ── Stop ─────────────────────────────────────────────────────────────────
@@ -1842,9 +1966,264 @@ ${this._userSystemPrompt}`;
1842
1966
  };
1843
1967
  var code_agent_default = CodeAgent;
1844
1968
 
1969
+ // rag-agent.js
1970
+ var import_node_path2 = require("node:path");
1971
+ var import_promises3 = require("node:fs/promises");
1972
+ var MIME_TYPES = {
1973
+ // Text
1974
+ ".txt": "text/plain",
1975
+ ".md": "text/plain",
1976
+ ".csv": "text/csv",
1977
+ ".html": "text/html",
1978
+ ".htm": "text/html",
1979
+ ".xml": "text/xml",
1980
+ ".json": "application/json",
1981
+ ".js": "text/javascript",
1982
+ ".mjs": "text/javascript",
1983
+ ".ts": "text/plain",
1984
+ ".css": "text/css",
1985
+ ".yaml": "text/plain",
1986
+ ".yml": "text/plain",
1987
+ ".py": "text/x-python",
1988
+ ".rb": "text/plain",
1989
+ ".sh": "text/plain",
1990
+ // Documents
1991
+ ".pdf": "application/pdf",
1992
+ ".doc": "application/msword",
1993
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1994
+ // Images
1995
+ ".png": "image/png",
1996
+ ".jpg": "image/jpeg",
1997
+ ".jpeg": "image/jpeg",
1998
+ ".gif": "image/gif",
1999
+ ".webp": "image/webp",
2000
+ ".svg": "image/svg+xml",
2001
+ // Audio
2002
+ ".mp3": "audio/mpeg",
2003
+ ".wav": "audio/wav",
2004
+ ".ogg": "audio/ogg",
2005
+ ".flac": "audio/flac",
2006
+ ".aac": "audio/aac",
2007
+ // Video
2008
+ ".mp4": "video/mp4",
2009
+ ".webm": "video/webm",
2010
+ ".avi": "video/x-msvideo",
2011
+ ".mov": "video/quicktime",
2012
+ ".mkv": "video/x-matroska"
2013
+ };
2014
+ var DEFAULT_SYSTEM_PROMPT = "You are a helpful AI assistant. Answer questions based on the provided documents and data. When referencing information, mention which document or data source it comes from.";
2015
+ var FILE_POLL_INTERVAL_MS = 2e3;
2016
+ var FILE_POLL_TIMEOUT_MS = 6e4;
2017
+ var RagAgent = class extends base_default {
2018
+ /**
2019
+ * @param {RagAgentOptions} [options={}]
2020
+ */
2021
+ constructor(options = {}) {
2022
+ if (options.systemPrompt === void 0) {
2023
+ options = { ...options, systemPrompt: DEFAULT_SYSTEM_PROMPT };
2024
+ }
2025
+ super(options);
2026
+ this.remoteFiles = options.remoteFiles || [];
2027
+ this.localFiles = options.localFiles || [];
2028
+ this.localData = options.localData || [];
2029
+ this._uploadedRemoteFiles = [];
2030
+ this._localFileContents = [];
2031
+ this._initialized = false;
2032
+ const total = this.remoteFiles.length + this.localFiles.length + this.localData.length;
2033
+ logger_default.debug(`RagAgent created with ${total} context sources`);
2034
+ }
2035
+ // ── Initialization ───────────────────────────────────────────────────────
2036
+ /**
2037
+ * Uploads remote files, reads local files, and seeds all context into the chat.
2038
+ * @param {boolean} [force=false]
2039
+ * @returns {Promise<void>}
2040
+ */
2041
+ async init(force = false) {
2042
+ if (this._initialized && !force) return;
2043
+ this._uploadedRemoteFiles = [];
2044
+ for (const filePath of this.remoteFiles) {
2045
+ const resolvedPath = (0, import_node_path2.resolve)(filePath);
2046
+ logger_default.debug(`Uploading remote file: ${resolvedPath}`);
2047
+ const ext = (0, import_node_path2.extname)(resolvedPath).toLowerCase();
2048
+ const mimeType = MIME_TYPES[ext] || "application/octet-stream";
2049
+ const uploaded = await this.genAIClient.files.upload({
2050
+ file: resolvedPath,
2051
+ config: { displayName: (0, import_node_path2.basename)(resolvedPath), mimeType }
2052
+ });
2053
+ await this._waitForFileActive(uploaded);
2054
+ this._uploadedRemoteFiles.push({
2055
+ ...uploaded,
2056
+ originalPath: resolvedPath
2057
+ });
2058
+ logger_default.debug(`File uploaded: ${uploaded.displayName} (${uploaded.mimeType})`);
2059
+ }
2060
+ this._localFileContents = [];
2061
+ for (const filePath of this.localFiles) {
2062
+ const resolvedPath = (0, import_node_path2.resolve)(filePath);
2063
+ logger_default.debug(`Reading local file: ${resolvedPath}`);
2064
+ const content = await (0, import_promises3.readFile)(resolvedPath, "utf-8");
2065
+ this._localFileContents.push({
2066
+ name: (0, import_node_path2.basename)(resolvedPath),
2067
+ content,
2068
+ path: resolvedPath
2069
+ });
2070
+ logger_default.debug(`Local file read: ${(0, import_node_path2.basename)(resolvedPath)} (${content.length} chars)`);
2071
+ }
2072
+ this.chatConfig.systemInstruction = /** @type {string} */
2073
+ this.systemPrompt;
2074
+ await super.init(force);
2075
+ const parts = [];
2076
+ for (const f of this._uploadedRemoteFiles) {
2077
+ parts.push({ fileData: { fileUri: f.uri, mimeType: f.mimeType } });
2078
+ }
2079
+ for (const lf of this._localFileContents) {
2080
+ parts.push({ text: `--- File: ${lf.name} ---
2081
+ ${lf.content}` });
2082
+ }
2083
+ for (const ld of this.localData) {
2084
+ const serialized = typeof ld.data === "string" ? ld.data : JSON.stringify(ld.data, null, 2);
2085
+ parts.push({ text: `--- Data: ${ld.name} ---
2086
+ ${serialized}` });
2087
+ }
2088
+ if (parts.length > 0) {
2089
+ parts.push({ text: "Here are the documents and data to analyze." });
2090
+ const history = [
2091
+ { role: "user", parts },
2092
+ { role: "model", parts: [{ text: "I have reviewed all the provided documents and data. I am ready to answer your questions about them." }] }
2093
+ ];
2094
+ this.chatSession = this._createChatSession(history);
2095
+ }
2096
+ this._initialized = true;
2097
+ logger_default.debug(`RagAgent initialized with ${this._uploadedRemoteFiles.length} remote files, ${this._localFileContents.length} local files, ${this.localData.length} data entries`);
2098
+ }
2099
+ // ── Non-Streaming Chat ───────────────────────────────────────────────────
2100
+ /**
2101
+ * Send a message and get a complete response grounded in the loaded context.
2102
+ *
2103
+ * @param {string} message - The user's question
2104
+ * @param {Object} [opts={}] - Per-message options
2105
+ * @param {Record<string, string>} [opts.labels] - Per-message billing labels
2106
+ * @returns {Promise<RagResponse>}
2107
+ */
2108
+ async chat(message, opts = {}) {
2109
+ if (!this._initialized) await this.init();
2110
+ const response = await this.chatSession.sendMessage({ message });
2111
+ this._captureMetadata(response);
2112
+ this._cumulativeUsage = {
2113
+ promptTokens: this.lastResponseMetadata.promptTokens,
2114
+ responseTokens: this.lastResponseMetadata.responseTokens,
2115
+ totalTokens: this.lastResponseMetadata.totalTokens,
2116
+ attempts: 1
2117
+ };
2118
+ return {
2119
+ text: response.text || "",
2120
+ usage: this.getLastUsage()
2121
+ };
2122
+ }
2123
+ // ── Streaming ────────────────────────────────────────────────────────────
2124
+ /**
2125
+ * Send a message and stream the response as events.
2126
+ *
2127
+ * @param {string} message - The user's question
2128
+ * @param {Object} [opts={}] - Per-message options
2129
+ * @yields {RagStreamEvent}
2130
+ */
2131
+ async *stream(message, opts = {}) {
2132
+ if (!this._initialized) await this.init();
2133
+ let fullText = "";
2134
+ const streamResponse = await this.chatSession.sendMessageStream({ message });
2135
+ for await (const chunk of streamResponse) {
2136
+ if (chunk.candidates?.[0]?.content?.parts?.[0]?.text) {
2137
+ const text = chunk.candidates[0].content.parts[0].text;
2138
+ fullText += text;
2139
+ yield { type: "text", text };
2140
+ }
2141
+ }
2142
+ yield {
2143
+ type: "done",
2144
+ fullText,
2145
+ usage: this.getLastUsage()
2146
+ };
2147
+ }
2148
+ // ── Context Management ──────────────────────────────────────────────────
2149
+ /**
2150
+ * Add remote files (uploaded via Files API). Triggers reinitialize.
2151
+ * @param {string[]} paths
2152
+ * @returns {Promise<void>}
2153
+ */
2154
+ async addRemoteFiles(paths) {
2155
+ this.remoteFiles.push(...paths);
2156
+ await this.init(true);
2157
+ }
2158
+ /**
2159
+ * Add local text files (read from disk). Triggers reinitialize.
2160
+ * @param {string[]} paths
2161
+ * @returns {Promise<void>}
2162
+ */
2163
+ async addLocalFiles(paths) {
2164
+ this.localFiles.push(...paths);
2165
+ await this.init(true);
2166
+ }
2167
+ /**
2168
+ * Add in-memory data entries. Triggers reinitialize.
2169
+ * @param {LocalDataEntry[]} entries
2170
+ * @returns {Promise<void>}
2171
+ */
2172
+ async addLocalData(entries) {
2173
+ this.localData.push(...entries);
2174
+ await this.init(true);
2175
+ }
2176
+ /**
2177
+ * Returns metadata about all context sources.
2178
+ * @returns {{ remoteFiles: Array<Object>, localFiles: Array<Object>, localData: Array<Object> }}
2179
+ */
2180
+ getContext() {
2181
+ return {
2182
+ remoteFiles: this._uploadedRemoteFiles.map((f) => ({
2183
+ name: f.name,
2184
+ displayName: f.displayName,
2185
+ mimeType: f.mimeType,
2186
+ sizeBytes: f.sizeBytes,
2187
+ uri: f.uri,
2188
+ originalPath: f.originalPath
2189
+ })),
2190
+ localFiles: this._localFileContents.map((lf) => ({
2191
+ name: lf.name,
2192
+ path: lf.path,
2193
+ size: lf.content.length
2194
+ })),
2195
+ localData: this.localData.map((ld) => ({
2196
+ name: ld.name,
2197
+ type: typeof ld.data === "object" && ld.data !== null ? Array.isArray(ld.data) ? "array" : "object" : typeof ld.data
2198
+ }))
2199
+ };
2200
+ }
2201
+ // ── Private Helpers ──────────────────────────────────────────────────────
2202
+ /**
2203
+ * Polls until an uploaded file reaches ACTIVE state.
2204
+ * @param {Object} file - The uploaded file object
2205
+ * @returns {Promise<void>}
2206
+ * @private
2207
+ */
2208
+ async _waitForFileActive(file) {
2209
+ if (file.state === "ACTIVE") return;
2210
+ const start = Date.now();
2211
+ while (Date.now() - start < FILE_POLL_TIMEOUT_MS) {
2212
+ const updated = await this.genAIClient.files.get({ name: file.name });
2213
+ if (updated.state === "ACTIVE") return;
2214
+ if (updated.state === "FAILED") {
2215
+ throw new Error(`File processing failed: ${file.displayName || file.name}`);
2216
+ }
2217
+ await new Promise((r) => setTimeout(r, FILE_POLL_INTERVAL_MS));
2218
+ }
2219
+ throw new Error(`File processing timed out after ${FILE_POLL_TIMEOUT_MS / 1e3}s: ${file.displayName || file.name}`);
2220
+ }
2221
+ };
2222
+ var rag_agent_default = RagAgent;
2223
+
1845
2224
  // index.js
1846
2225
  var import_genai2 = require("@google/genai");
1847
- var index_default = { Transformer: transformer_default, Chat: chat_default, Message: message_default, ToolAgent: tool_agent_default, CodeAgent: code_agent_default };
2226
+ var index_default = { Transformer: transformer_default, Chat: chat_default, Message: message_default, ToolAgent: tool_agent_default, CodeAgent: code_agent_default, RagAgent: rag_agent_default };
1848
2227
  // Annotate the CommonJS export names for ESM import in node:
1849
2228
  0 && (module.exports = {
1850
2229
  BaseGemini,
@@ -1853,6 +2232,7 @@ var index_default = { Transformer: transformer_default, Chat: chat_default, Mess
1853
2232
  HarmBlockThreshold,
1854
2233
  HarmCategory,
1855
2234
  Message,
2235
+ RagAgent,
1856
2236
  ThinkingLevel,
1857
2237
  ToolAgent,
1858
2238
  Transformer,
package/index.js CHANGED
@@ -25,6 +25,7 @@ export { default as Chat } from './chat.js';
25
25
  export { default as Message } from './message.js';
26
26
  export { default as ToolAgent } from './tool-agent.js';
27
27
  export { default as CodeAgent } from './code-agent.js';
28
+ export { default as RagAgent } from './rag-agent.js';
28
29
  export { default as BaseGemini } from './base.js';
29
30
  export { default as log } from './logger.js';
30
31
  export { ThinkingLevel, HarmCategory, HarmBlockThreshold } from '@google/genai';
@@ -37,5 +38,6 @@ import Chat from './chat.js';
37
38
  import Message from './message.js';
38
39
  import ToolAgent from './tool-agent.js';
39
40
  import CodeAgent from './code-agent.js';
41
+ import RagAgent from './rag-agent.js';
40
42
 
41
- export default { Transformer, Chat, Message, ToolAgent, CodeAgent };
43
+ export default { Transformer, Chat, Message, ToolAgent, CodeAgent, RagAgent };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "ak-gemini",
3
3
  "author": "ak@mixpanel.com",
4
4
  "description": "AK's Generative AI Helper for doing... everything",
5
- "version": "2.0.0",
5
+ "version": "2.0.1",
6
6
  "main": "index.js",
7
7
  "files": [
8
8
  "index.js",
package/tool-agent.js CHANGED
@@ -79,6 +79,7 @@ class ToolAgent extends BaseGemini {
79
79
  this.maxToolRounds = options.maxToolRounds || 10;
80
80
  this.onToolCall = options.onToolCall || null;
81
81
  this.onBeforeExecution = options.onBeforeExecution || null;
82
+ this.writeDir = options.writeDir || null;
82
83
  this._stopped = false;
83
84
 
84
85
  // ── Apply tools to chat config ──
package/types.d.ts CHANGED
@@ -181,6 +181,24 @@ export interface ToolAgentOptions extends BaseGeminiOptions {
181
181
  onToolCall?: (toolName: string, args: Record<string, any>) => void;
182
182
  /** Async callback before tool execution; return false to deny */
183
183
  onBeforeExecution?: (toolName: string, args: Record<string, any>) => Promise<boolean>;
184
+ /** Directory for tool-written files (pass-through for toolExecutor use) */
185
+ writeDir?: string;
186
+ }
187
+
188
+ export interface LocalDataEntry {
189
+ /** Label shown to the model (e.g. "users", "config") */
190
+ name: string;
191
+ /** Any JSON-serializable value */
192
+ data: any;
193
+ }
194
+
195
+ export interface RagAgentOptions extends BaseGeminiOptions {
196
+ /** Paths to files uploaded via Google Files API (PDFs, images, audio, video) */
197
+ remoteFiles?: string[];
198
+ /** Paths to local text files read from disk (md, json, csv, yaml, txt) */
199
+ localFiles?: string[];
200
+ /** In-memory data objects to include as context */
201
+ localData?: LocalDataEntry[];
184
202
  }
185
203
 
186
204
  export interface CodeAgentOptions extends BaseGeminiOptions {
@@ -194,11 +212,23 @@ export interface CodeAgentOptions extends BaseGeminiOptions {
194
212
  onBeforeExecution?: (code: string) => Promise<boolean>;
195
213
  /** Notification callback after code execution */
196
214
  onCodeExecution?: (code: string, output: { stdout: string; stderr: string; exitCode: number }) => void;
215
+ /** Files whose contents are included in the system prompt for project context */
216
+ importantFiles?: string[];
217
+ /** Directory for writing script files (default: '{workingDirectory}/tmp') */
218
+ writeDir?: string;
219
+ /** Keep script files on disk after execution (default: false) */
220
+ keepArtifacts?: boolean;
221
+ /** Instruct model to write JSDoc comments in generated code (default: false) */
222
+ comments?: boolean;
223
+ /** Max consecutive failed executions before stopping (default: 3) */
224
+ maxRetries?: number;
197
225
  }
198
226
 
199
227
  export interface CodeExecution {
200
228
  /** The JavaScript code that was executed */
201
229
  code: string;
230
+ /** Short slug describing the script's purpose */
231
+ purpose?: string;
202
232
  /** stdout from the execution */
203
233
  output: string;
204
234
  /** stderr from the execution */
@@ -276,6 +306,23 @@ export interface MessageResponse {
276
306
  usage: UsageData | null;
277
307
  }
278
308
 
309
+ export interface RagResponse {
310
+ /** The model's text response */
311
+ text: string;
312
+ /** Token usage data */
313
+ usage: UsageData | null;
314
+ }
315
+
316
+ export interface RagStreamEvent {
317
+ type: 'text' | 'done';
318
+ /** For 'text' events: the text chunk */
319
+ text?: string;
320
+ /** For 'done' events: complete accumulated text */
321
+ fullText?: string;
322
+ /** For 'done' events: token usage */
323
+ usage?: UsageData | null;
324
+ }
325
+
279
326
  export interface AgentResponse {
280
327
  /** The agent's final text response */
281
328
  text: string;
@@ -385,6 +432,8 @@ export declare class ToolAgent extends BaseGemini {
385
432
  maxToolRounds: number;
386
433
  onToolCall: ((toolName: string, args: Record<string, any>) => void) | null;
387
434
  onBeforeExecution: ((toolName: string, args: Record<string, any>) => Promise<boolean>) | null;
435
+ /** Directory for tool-written files (pass-through for toolExecutor use) */
436
+ writeDir: string | null;
388
437
 
389
438
  chat(message: string, opts?: { labels?: Record<string, string> }): Promise<AgentResponse>;
390
439
  stream(message: string, opts?: { labels?: Record<string, string> }): AsyncGenerator<AgentStreamEvent, void, unknown>;
@@ -392,6 +441,33 @@ export declare class ToolAgent extends BaseGemini {
392
441
  stop(): void;
393
442
  }
394
443
 
444
+ export declare class RagAgent extends BaseGemini {
445
+ constructor(options?: RagAgentOptions);
446
+
447
+ /** Paths to files uploaded via Google Files API */
448
+ remoteFiles: string[];
449
+ /** Paths to local text files read from disk */
450
+ localFiles: string[];
451
+ /** In-memory data objects */
452
+ localData: LocalDataEntry[];
453
+
454
+ init(force?: boolean): Promise<void>;
455
+ chat(message: string, opts?: { labels?: Record<string, string> }): Promise<RagResponse>;
456
+ stream(message: string, opts?: { labels?: Record<string, string> }): AsyncGenerator<RagStreamEvent, void, unknown>;
457
+ /** Add remote files uploaded via Files API (triggers reinitialize) */
458
+ addRemoteFiles(paths: string[]): Promise<void>;
459
+ /** Add local text files read from disk (triggers reinitialize) */
460
+ addLocalFiles(paths: string[]): Promise<void>;
461
+ /** Add in-memory data entries (triggers reinitialize) */
462
+ addLocalData(entries: LocalDataEntry[]): Promise<void>;
463
+ /** Returns metadata about all context sources */
464
+ getContext(): {
465
+ remoteFiles: Array<{ name: string; displayName: string; mimeType: string; sizeBytes: string; uri: string; originalPath: string }>;
466
+ localFiles: Array<{ name: string; path: string; size: number }>;
467
+ localData: Array<{ name: string; type: string }>;
468
+ };
469
+ }
470
+
395
471
  export declare class CodeAgent extends BaseGemini {
396
472
  constructor(options?: CodeAgentOptions);
397
473
 
@@ -400,12 +476,22 @@ export declare class CodeAgent extends BaseGemini {
400
476
  timeout: number;
401
477
  onBeforeExecution: ((code: string) => Promise<boolean>) | null;
402
478
  onCodeExecution: ((code: string, output: { stdout: string; stderr: string; exitCode: number }) => void) | null;
479
+ /** Files whose contents are included in the system prompt */
480
+ importantFiles: string[];
481
+ /** Directory for writing script files */
482
+ writeDir: string;
483
+ /** Keep script files on disk after execution */
484
+ keepArtifacts: boolean;
485
+ /** Whether the model writes comments in generated code */
486
+ comments: boolean;
487
+ /** Max consecutive failed executions before stopping */
488
+ maxRetries: number;
403
489
 
404
490
  init(force?: boolean): Promise<void>;
405
491
  chat(message: string, opts?: { labels?: Record<string, string> }): Promise<CodeAgentResponse>;
406
492
  stream(message: string, opts?: { labels?: Record<string, string> }): AsyncGenerator<CodeAgentStreamEvent, void, unknown>;
407
493
  /** Returns all code scripts written across all chat/stream calls. */
408
- dump(): Array<{ fileName: string; script: string }>;
494
+ dump(): Array<{ fileName: string; purpose: string | null; script: string; filePath: string | null }>;
409
495
  /** Stop the agent before the next code execution. Kills any running child process. */
410
496
  stop(): void;
411
497
  }
@@ -421,6 +507,7 @@ declare const _default: {
421
507
  Message: typeof Message;
422
508
  ToolAgent: typeof ToolAgent;
423
509
  CodeAgent: typeof CodeAgent;
510
+ RagAgent: typeof RagAgent;
424
511
  };
425
512
 
426
513
  export default _default;