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 +1 -1
- package/code-agent.js +143 -19
- package/index.cjs +401 -21
- package/index.js +3 -1
- package/package.json +1 -1
- package/tool-agent.js +1 -0
- package/types.d.ts +88 -1
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/
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
332
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
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;
|