ak-gemini 2.0.0 → 2.0.2
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/GUIDE.md +994 -0
- package/README.md +1 -1
- package/code-agent.js +143 -19
- package/index.cjs +401 -21
- package/index.js +3 -1
- package/package.json +4 -2
- package/rag-agent.js +340 -0
- 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
|
|