ak-gemini 2.1.3 → 2.2.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/code-agent.js +234 -43
- package/index.cjs +216 -44
- package/package.json +1 -1
- package/types.d.ts +29 -2
package/code-agent.js
CHANGED
|
@@ -8,7 +8,7 @@ import BaseGemini from './base.js';
|
|
|
8
8
|
import log from './logger.js';
|
|
9
9
|
import { execFile } from 'node:child_process';
|
|
10
10
|
import { writeFile, unlink, readdir, readFile, mkdir } from 'node:fs/promises';
|
|
11
|
-
import { join, sep, basename } from 'node:path';
|
|
11
|
+
import { join, sep, basename, isAbsolute } from 'node:path';
|
|
12
12
|
import { randomUUID } from 'node:crypto';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -19,7 +19,54 @@ import { randomUUID } from 'node:crypto';
|
|
|
19
19
|
|
|
20
20
|
const MAX_OUTPUT_CHARS = 50_000;
|
|
21
21
|
const MAX_FILE_TREE_LINES = 500;
|
|
22
|
-
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'coverage', '.next', 'build', '__pycache__']);
|
|
22
|
+
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'coverage', '.next', 'build', '__pycache__', '.venv']);
|
|
23
|
+
|
|
24
|
+
const LANG_CONFIG = {
|
|
25
|
+
javascript: {
|
|
26
|
+
toolDescExecute: 'Execute a given piece of JavaScript code in a Node.js child process. Use this when you already have code to run — e.g., running code from a previous write_code call, re-running a snippet, or executing code the user provided. Use console.log() for output.',
|
|
27
|
+
toolDescWriteAndRun: 'Write a fresh solution from scratch and execute it in one step. Use this when you need to figure out the code AND run it — the autonomous, end-to-end tool for solving problems with code.',
|
|
28
|
+
codeParamDesc: 'JavaScript code to execute. Use console.log() for output. Use import syntax (ES modules).',
|
|
29
|
+
codeRules: `- Your code runs in a Node.js child process with access to all built-in modules
|
|
30
|
+
- IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
|
|
31
|
+
- import fs from 'fs';
|
|
32
|
+
- import path from 'path';
|
|
33
|
+
- import { execSync } from 'child_process';
|
|
34
|
+
- Use console.log() to produce output — that's how results are returned to you
|
|
35
|
+
- Write efficient scripts that do multiple things per execution when possible
|
|
36
|
+
- For parallel async operations, use Promise.all()
|
|
37
|
+
- Handle errors in your scripts with try/catch so you get useful error messages
|
|
38
|
+
- Top-level await is supported`,
|
|
39
|
+
commentsEnabled: `- 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`,
|
|
40
|
+
commentsDisabled: `- Do NOT write any comments in your code — save tokens. The code should be self-explanatory.`,
|
|
41
|
+
packageLabel: 'Available Packages',
|
|
42
|
+
packageIntro: 'These npm packages are installed and can be imported',
|
|
43
|
+
bashExamples: 'ls, grep, curl, git, npm, cat',
|
|
44
|
+
codeBlockLang: 'javascript',
|
|
45
|
+
execToolSummary: 'Run a given piece of JavaScript code. Use when you already have code to run — e.g., from a previous write_code call, re-running a snippet, or executing user-provided code.',
|
|
46
|
+
writeRunToolSummary: 'Write a fresh solution from scratch and execute it in one step. The autonomous, end-to-end tool for solving problems with code.',
|
|
47
|
+
},
|
|
48
|
+
python: {
|
|
49
|
+
toolDescExecute: 'Execute a given piece of Python code in a child process. Use this when you already have code to run — e.g., running code from a previous write_code call, re-running a snippet, or executing code the user provided. Use print() for output.',
|
|
50
|
+
toolDescWriteAndRun: 'Write a fresh Python solution from scratch and execute it in one step. Use this when you need to figure out the code AND run it — the autonomous, end-to-end tool for solving problems with code.',
|
|
51
|
+
codeParamDesc: 'Python code to execute. Use print() for output.',
|
|
52
|
+
codeRules: `- Your code runs in a Python 3 child process
|
|
53
|
+
- Use print() to produce output — that's how results are returned to you
|
|
54
|
+
- Use standard Python imports (import os, import json, from pathlib import Path, etc.)
|
|
55
|
+
- Write efficient scripts; prefer list comprehensions and built-in functions
|
|
56
|
+
- For async operations, use asyncio
|
|
57
|
+
- Handle errors with try/except so you get useful error messages
|
|
58
|
+
- A virtual environment is active — you can install packages using run_bash with: pip install <package>
|
|
59
|
+
- Installed packages persist across executions in this session`,
|
|
60
|
+
commentsEnabled: `- Add a module-level docstring at the top of each script explaining what it does\n- Add brief docstrings for any functions you define`,
|
|
61
|
+
commentsDisabled: `- Do NOT write any comments in your code — save tokens. The code should be self-explanatory.`,
|
|
62
|
+
packageLabel: 'Available Packages',
|
|
63
|
+
packageIntro: 'These Python packages are available for import',
|
|
64
|
+
bashExamples: 'ls, grep, curl, git, pip, cat',
|
|
65
|
+
codeBlockLang: 'python',
|
|
66
|
+
execToolSummary: 'Run a given piece of Python code. Use when you already have code to run — e.g., from a previous write_code call, re-running a snippet, or executing user-provided code.',
|
|
67
|
+
writeRunToolSummary: 'Write a fresh Python solution from scratch and execute it in one step. The autonomous, end-to-end tool for solving problems with code.',
|
|
68
|
+
}
|
|
69
|
+
};
|
|
23
70
|
|
|
24
71
|
/** Tools that execute code/commands and can fail */
|
|
25
72
|
const EXECUTING_TOOLS = new Set(['execute_code', 'write_and_run_code', 'run_bash']);
|
|
@@ -37,6 +84,8 @@ class CodeAgent extends BaseGemini {
|
|
|
37
84
|
|
|
38
85
|
// ── Agent config ──
|
|
39
86
|
this.workingDirectory = options.workingDirectory || process.cwd();
|
|
87
|
+
this.language = options.language || 'javascript';
|
|
88
|
+
this.pythonPath = options.pythonPath || null;
|
|
40
89
|
this.maxRounds = options.maxRounds || 10;
|
|
41
90
|
this.timeout = options.timeout || 30_000;
|
|
42
91
|
this.onBeforeExecution = options.onBeforeExecution || null;
|
|
@@ -49,6 +98,22 @@ class CodeAgent extends BaseGemini {
|
|
|
49
98
|
this.skills = options.skills || [];
|
|
50
99
|
this.envOverview = options.envOverview || '';
|
|
51
100
|
|
|
101
|
+
// ── Python state (resolved during init) ──
|
|
102
|
+
this._pythonBinary = null;
|
|
103
|
+
this._venvPath = null;
|
|
104
|
+
this._venvEnv = null;
|
|
105
|
+
|
|
106
|
+
// ── Custom tools ──
|
|
107
|
+
this.customTools = (options.tools || []).map(t => ({
|
|
108
|
+
name: t.name,
|
|
109
|
+
description: t.description,
|
|
110
|
+
parametersJsonSchema: t.parametersJsonSchema || t.parameters || t.input_schema || t.inputSchema
|
|
111
|
+
}));
|
|
112
|
+
this.toolExecutor = options.toolExecutor || null;
|
|
113
|
+
if (this.customTools.length > 0 && !this.toolExecutor) {
|
|
114
|
+
throw new Error('CodeAgent: tools provided without a toolExecutor.');
|
|
115
|
+
}
|
|
116
|
+
|
|
52
117
|
// ── Internal state ──
|
|
53
118
|
this._codebaseContext = null;
|
|
54
119
|
this._contextGathered = false;
|
|
@@ -74,6 +139,8 @@ class CodeAgent extends BaseGemini {
|
|
|
74
139
|
* @returns {{ functionDeclarations: Array<Object> }}
|
|
75
140
|
*/
|
|
76
141
|
_buildToolDefinitions() {
|
|
142
|
+
const lang = LANG_CONFIG[this.language] || LANG_CONFIG.javascript;
|
|
143
|
+
|
|
77
144
|
/** @type {Array<Object>} */
|
|
78
145
|
const declarations = [
|
|
79
146
|
{
|
|
@@ -91,11 +158,11 @@ class CodeAgent extends BaseGemini {
|
|
|
91
158
|
},
|
|
92
159
|
{
|
|
93
160
|
name: 'execute_code',
|
|
94
|
-
description:
|
|
161
|
+
description: lang.toolDescExecute,
|
|
95
162
|
parametersJsonSchema: {
|
|
96
163
|
type: 'object',
|
|
97
164
|
properties: {
|
|
98
|
-
code: { type: 'string', description:
|
|
165
|
+
code: { type: 'string', description: lang.codeParamDesc },
|
|
99
166
|
purpose: { type: 'string', description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs").' }
|
|
100
167
|
},
|
|
101
168
|
required: ['code']
|
|
@@ -103,11 +170,11 @@ class CodeAgent extends BaseGemini {
|
|
|
103
170
|
},
|
|
104
171
|
{
|
|
105
172
|
name: 'write_and_run_code',
|
|
106
|
-
description:
|
|
173
|
+
description: lang.toolDescWriteAndRun,
|
|
107
174
|
parametersJsonSchema: {
|
|
108
175
|
type: 'object',
|
|
109
176
|
properties: {
|
|
110
|
-
code: { type: 'string', description:
|
|
177
|
+
code: { type: 'string', description: lang.codeParamDesc },
|
|
111
178
|
purpose: { type: 'string', description: 'A short 2-4 word slug describing what this script does (e.g., "fetch-api-data", "generate-report").' }
|
|
112
179
|
},
|
|
113
180
|
required: ['code']
|
|
@@ -129,7 +196,7 @@ class CodeAgent extends BaseGemini {
|
|
|
129
196
|
},
|
|
130
197
|
{
|
|
131
198
|
name: 'run_bash',
|
|
132
|
-
description:
|
|
199
|
+
description: `Execute a shell command in the working directory. Use this for file operations, git commands, installing packages, or any shell task (e.g., ${lang.bashExamples}). Prefer this over execute_code for simple shell operations.`,
|
|
133
200
|
parametersJsonSchema: {
|
|
134
201
|
type: 'object',
|
|
135
202
|
properties: {
|
|
@@ -156,6 +223,11 @@ class CodeAgent extends BaseGemini {
|
|
|
156
223
|
});
|
|
157
224
|
}
|
|
158
225
|
|
|
226
|
+
// Append custom tools
|
|
227
|
+
for (const t of this.customTools) {
|
|
228
|
+
declarations.push({ name: t.name, description: t.description, parametersJsonSchema: t.parametersJsonSchema });
|
|
229
|
+
}
|
|
230
|
+
|
|
159
231
|
return { functionDeclarations: declarations };
|
|
160
232
|
}
|
|
161
233
|
|
|
@@ -173,6 +245,12 @@ class CodeAgent extends BaseGemini {
|
|
|
173
245
|
await this._loadSkills();
|
|
174
246
|
}
|
|
175
247
|
|
|
248
|
+
// Resolve Python and set up venv if needed
|
|
249
|
+
if (this.language === 'python' && (!this._pythonBinary || force)) {
|
|
250
|
+
await this._resolvePython();
|
|
251
|
+
await this._setupVenv();
|
|
252
|
+
}
|
|
253
|
+
|
|
176
254
|
// Rebuild tools (use_skill may now be included)
|
|
177
255
|
this.chatConfig.tools = [this._buildToolDefinitions()];
|
|
178
256
|
|
|
@@ -211,6 +289,73 @@ class CodeAgent extends BaseGemini {
|
|
|
211
289
|
}
|
|
212
290
|
}
|
|
213
291
|
|
|
292
|
+
// ── Python Resolution ───────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Resolve the Python 3 binary path.
|
|
296
|
+
* @private
|
|
297
|
+
*/
|
|
298
|
+
async _resolvePython() {
|
|
299
|
+
const tryBinary = (bin) => new Promise((resolve) => {
|
|
300
|
+
execFile(bin, ['--version'], { timeout: 5000 }, (err, stdout, stderr) => {
|
|
301
|
+
if (err) return resolve(null);
|
|
302
|
+
const output = (stdout || '') + (stderr || '');
|
|
303
|
+
if (output.includes('Python 3.')) return resolve(bin);
|
|
304
|
+
resolve(null);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (this.pythonPath) {
|
|
309
|
+
const result = await tryBinary(this.pythonPath);
|
|
310
|
+
if (result) { this._pythonBinary = result; return; }
|
|
311
|
+
throw new Error(`CodeAgent: pythonPath "${this.pythonPath}" is not a valid Python 3 binary.`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const python3 = await tryBinary('python3');
|
|
315
|
+
if (python3) { this._pythonBinary = python3; return; }
|
|
316
|
+
|
|
317
|
+
const python = await tryBinary('python');
|
|
318
|
+
if (python) { this._pythonBinary = python; return; }
|
|
319
|
+
|
|
320
|
+
throw new Error('CodeAgent: language is "python" but python3 was not found on PATH. Install Python 3 or provide the pythonPath option.');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Create a virtual environment for Python execution.
|
|
325
|
+
* @private
|
|
326
|
+
*/
|
|
327
|
+
async _setupVenv() {
|
|
328
|
+
await mkdir(this.writeDir, { recursive: true });
|
|
329
|
+
this._venvPath = join(this.writeDir, '.venv');
|
|
330
|
+
const isWin = process.platform === 'win32';
|
|
331
|
+
const venvBin = isWin ? join(this._venvPath, 'Scripts') : join(this._venvPath, 'bin');
|
|
332
|
+
const venvPython = join(venvBin, isWin ? 'python.exe' : 'python');
|
|
333
|
+
|
|
334
|
+
// Create venv if it doesn't exist
|
|
335
|
+
try {
|
|
336
|
+
await readFile(venvPython);
|
|
337
|
+
} catch {
|
|
338
|
+
log.debug(`Creating Python venv at ${this._venvPath}`);
|
|
339
|
+
await new Promise((resolve, reject) => {
|
|
340
|
+
execFile(this._pythonBinary, ['-m', 'venv', this._venvPath], {
|
|
341
|
+
timeout: 30_000
|
|
342
|
+
}, (err) => {
|
|
343
|
+
if (err) return reject(new Error(`CodeAgent: failed to create venv: ${err.message}`));
|
|
344
|
+
resolve();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Build env with venv activated
|
|
350
|
+
const env = Object.assign({}, process.env);
|
|
351
|
+
env.VIRTUAL_ENV = this._venvPath;
|
|
352
|
+
env.PATH = venvBin + (isWin ? ';' : ':') + (process.env.PATH || '');
|
|
353
|
+
this._venvEnv = env;
|
|
354
|
+
this._pythonBinary = venvPython;
|
|
355
|
+
|
|
356
|
+
log.debug(`Python venv ready at ${this._venvPath}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
214
359
|
// ── Context Gathering ────────────────────────────────────────────────────
|
|
215
360
|
|
|
216
361
|
/**
|
|
@@ -232,15 +377,37 @@ class CodeAgent extends BaseGemini {
|
|
|
232
377
|
fileTree = `${truncated}\n... (${lines.length - MAX_FILE_TREE_LINES} more files)`;
|
|
233
378
|
}
|
|
234
379
|
|
|
235
|
-
let
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
380
|
+
let packages = [];
|
|
381
|
+
if (this.language === 'python') {
|
|
382
|
+
try {
|
|
383
|
+
const reqPath = join(this.workingDirectory, 'requirements.txt');
|
|
384
|
+
const content = await readFile(reqPath, 'utf-8');
|
|
385
|
+
packages = content.split('\n')
|
|
386
|
+
.map(l => l.trim())
|
|
387
|
+
.filter(l => l && !l.startsWith('#') && !l.startsWith('-'))
|
|
388
|
+
.map(l => l.split(/[>=<!\[;\s]/)[0]);
|
|
389
|
+
} catch { /* no requirements.txt */ }
|
|
390
|
+
if (packages.length === 0) {
|
|
391
|
+
try {
|
|
392
|
+
const ppPath = join(this.workingDirectory, 'pyproject.toml');
|
|
393
|
+
const content = await readFile(ppPath, 'utf-8');
|
|
394
|
+
const depMatch = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
395
|
+
if (depMatch) {
|
|
396
|
+
packages = (depMatch[1].match(/"([^"]+)"/g) || [])
|
|
397
|
+
.map(s => s.replace(/"/g, '').split(/[>=<!\[;\s]/)[0]);
|
|
398
|
+
}
|
|
399
|
+
} catch { /* no pyproject.toml */ }
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
try {
|
|
403
|
+
const pkgPath = join(this.workingDirectory, 'package.json');
|
|
404
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
|
|
405
|
+
packages = [
|
|
406
|
+
...Object.keys(pkg.dependencies || {}),
|
|
407
|
+
...Object.keys(pkg.devDependencies || {})
|
|
408
|
+
];
|
|
409
|
+
} catch { /* no package.json */ }
|
|
410
|
+
}
|
|
244
411
|
|
|
245
412
|
const importantFileContents = [];
|
|
246
413
|
if (this.importantFiles.length > 0) {
|
|
@@ -252,7 +419,7 @@ class CodeAgent extends BaseGemini {
|
|
|
252
419
|
continue;
|
|
253
420
|
}
|
|
254
421
|
try {
|
|
255
|
-
const fullPath = join(this.workingDirectory, resolved);
|
|
422
|
+
const fullPath = isAbsolute(resolved) ? resolved : join(this.workingDirectory, resolved);
|
|
256
423
|
const content = await readFile(fullPath, 'utf-8');
|
|
257
424
|
importantFileContents.push({ path: resolved, content });
|
|
258
425
|
} catch (e) {
|
|
@@ -261,7 +428,7 @@ class CodeAgent extends BaseGemini {
|
|
|
261
428
|
}
|
|
262
429
|
}
|
|
263
430
|
|
|
264
|
-
this._codebaseContext = { fileTree, npmPackages, importantFileContents };
|
|
431
|
+
this._codebaseContext = { fileTree, npmPackages: packages, packages, importantFileContents };
|
|
265
432
|
this._contextGathered = true;
|
|
266
433
|
}
|
|
267
434
|
|
|
@@ -269,6 +436,8 @@ class CodeAgent extends BaseGemini {
|
|
|
269
436
|
* @private
|
|
270
437
|
*/
|
|
271
438
|
_resolveImportantFile(filename, fileTreeLines) {
|
|
439
|
+
if (isAbsolute(filename)) return filename;
|
|
440
|
+
|
|
272
441
|
const exact = fileTreeLines.find(line => line === filename);
|
|
273
442
|
if (exact) return exact;
|
|
274
443
|
|
|
@@ -325,7 +494,8 @@ class CodeAgent extends BaseGemini {
|
|
|
325
494
|
* @private
|
|
326
495
|
*/
|
|
327
496
|
_buildSystemPrompt() {
|
|
328
|
-
const { fileTree,
|
|
497
|
+
const { fileTree, packages, importantFileContents } = this._codebaseContext || { fileTree: '', packages: [], importantFileContents: [] };
|
|
498
|
+
const lang = LANG_CONFIG[this.language] || LANG_CONFIG.javascript;
|
|
329
499
|
|
|
330
500
|
let prompt = `You are a coding agent working in ${this.workingDirectory}.
|
|
331
501
|
|
|
@@ -335,16 +505,16 @@ class CodeAgent extends BaseGemini {
|
|
|
335
505
|
Output code without executing it. Use when showing, proposing, or presenting code to the user.
|
|
336
506
|
|
|
337
507
|
### execute_code
|
|
338
|
-
|
|
508
|
+
${lang.execToolSummary}
|
|
339
509
|
|
|
340
510
|
### write_and_run_code
|
|
341
|
-
|
|
511
|
+
${lang.writeRunToolSummary}
|
|
342
512
|
|
|
343
513
|
### fix_code
|
|
344
514
|
Fix broken code by providing original and fixed versions. Set execute=true to verify the fix works.
|
|
345
515
|
|
|
346
516
|
### run_bash
|
|
347
|
-
Run shell commands directly (e.g.,
|
|
517
|
+
Run shell commands directly (e.g., ${lang.bashExamples}). Prefer this over execute_code for simple shell operations.`;
|
|
348
518
|
|
|
349
519
|
if (this._skillRegistry.size > 0) {
|
|
350
520
|
prompt += `
|
|
@@ -358,36 +528,27 @@ Load a skill by name to get detailed instructions and templates. Available skill
|
|
|
358
528
|
## Code Execution Rules
|
|
359
529
|
These rules apply when using execute_code, write_and_run_code, or fix_code (with execute=true):
|
|
360
530
|
- Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config")
|
|
361
|
-
|
|
362
|
-
- IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
|
|
363
|
-
- import fs from 'fs';
|
|
364
|
-
- import path from 'path';
|
|
365
|
-
- import { execSync } from 'child_process';
|
|
366
|
-
- Use console.log() to produce output — that's how results are returned to you
|
|
367
|
-
- Write efficient scripts that do multiple things per execution when possible
|
|
368
|
-
- For parallel async operations, use Promise.all()
|
|
369
|
-
- Handle errors in your scripts with try/catch so you get useful error messages
|
|
370
|
-
- Top-level await is supported
|
|
531
|
+
${lang.codeRules}
|
|
371
532
|
- The working directory is: ${this.workingDirectory}`;
|
|
372
533
|
|
|
373
534
|
if (this.comments) {
|
|
374
|
-
prompt += `\n
|
|
535
|
+
prompt += `\n${lang.commentsEnabled}`;
|
|
375
536
|
} else {
|
|
376
|
-
prompt += `\n
|
|
537
|
+
prompt += `\n${lang.commentsDisabled}`;
|
|
377
538
|
}
|
|
378
539
|
|
|
379
540
|
if (fileTree) {
|
|
380
541
|
prompt += `\n\n## File Tree\n\`\`\`\n${fileTree}\n\`\`\``;
|
|
381
542
|
}
|
|
382
543
|
|
|
383
|
-
if (
|
|
384
|
-
prompt += `\n\n##
|
|
544
|
+
if (packages && packages.length > 0) {
|
|
545
|
+
prompt += `\n\n## ${lang.packageLabel}\n${lang.packageIntro}: ${packages.join(', ')}`;
|
|
385
546
|
}
|
|
386
547
|
|
|
387
548
|
if (importantFileContents && importantFileContents.length > 0) {
|
|
388
549
|
prompt += `\n\n## Key Files`;
|
|
389
550
|
for (const { path: filePath, content } of importantFileContents) {
|
|
390
|
-
prompt += `\n\n### ${filePath}\n
|
|
551
|
+
prompt += `\n\n### ${filePath}\n\`\`\`${lang.codeBlockLang}\n${content}\n\`\`\``;
|
|
391
552
|
}
|
|
392
553
|
}
|
|
393
554
|
|
|
@@ -434,16 +595,20 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
|
|
|
434
595
|
await mkdir(this.writeDir, { recursive: true });
|
|
435
596
|
|
|
436
597
|
const slug = this._slugify(purpose);
|
|
437
|
-
const
|
|
598
|
+
const ext = this.language === 'python' ? '.py' : '.mjs';
|
|
599
|
+
const tempFile = join(this.writeDir, `agent-${slug}-${Date.now()}${ext}`);
|
|
438
600
|
|
|
439
601
|
try {
|
|
440
602
|
await writeFile(tempFile, code, 'utf-8');
|
|
441
603
|
|
|
604
|
+
const binary = this.language === 'python' ? this._pythonBinary : 'node';
|
|
605
|
+
const execEnv = this.language === 'python' && this._venvEnv ? this._venvEnv : process.env;
|
|
606
|
+
|
|
442
607
|
const result = await new Promise((resolve) => {
|
|
443
|
-
const child = execFile(
|
|
608
|
+
const child = execFile(binary, [tempFile], {
|
|
444
609
|
cwd: this.workingDirectory,
|
|
445
610
|
timeout: this.timeout,
|
|
446
|
-
env:
|
|
611
|
+
env: execEnv,
|
|
447
612
|
maxBuffer: 10 * 1024 * 1024
|
|
448
613
|
}, (err, stdout, stderr) => {
|
|
449
614
|
this._activeProcess = null;
|
|
@@ -513,11 +678,13 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
|
|
|
513
678
|
}
|
|
514
679
|
}
|
|
515
680
|
|
|
681
|
+
const execEnv = this.language === 'python' && this._venvEnv ? this._venvEnv : process.env;
|
|
682
|
+
|
|
516
683
|
const result = await new Promise((resolve) => {
|
|
517
684
|
const child = execFile('bash', ['-c', command], {
|
|
518
685
|
cwd: this.workingDirectory,
|
|
519
686
|
timeout: this.timeout,
|
|
520
|
-
env:
|
|
687
|
+
env: execEnv,
|
|
521
688
|
maxBuffer: 10 * 1024 * 1024
|
|
522
689
|
}, (err, stdout, stderr) => {
|
|
523
690
|
this._activeProcess = null;
|
|
@@ -651,12 +818,30 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
|
|
|
651
818
|
data: { tool: 'use_skill', skillName: skill.name, content: skill.content, found: true }
|
|
652
819
|
};
|
|
653
820
|
}
|
|
654
|
-
default:
|
|
821
|
+
default: {
|
|
822
|
+
if (this.toolExecutor) {
|
|
823
|
+
try {
|
|
824
|
+
const result = await this.toolExecutor(name, input);
|
|
825
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
826
|
+
return {
|
|
827
|
+
output: resultStr,
|
|
828
|
+
type: 'tool',
|
|
829
|
+
data: { tool: name, args: input, result }
|
|
830
|
+
};
|
|
831
|
+
} catch (err) {
|
|
832
|
+
return {
|
|
833
|
+
output: `Tool "${name}" failed: ${err.message}`,
|
|
834
|
+
type: 'tool',
|
|
835
|
+
data: { tool: name, args: input, error: err.message }
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
655
839
|
return {
|
|
656
840
|
output: `Unknown tool: ${name}`,
|
|
657
841
|
type: 'unknown',
|
|
658
842
|
data: { tool: name }
|
|
659
843
|
};
|
|
844
|
+
}
|
|
660
845
|
}
|
|
661
846
|
}
|
|
662
847
|
|
|
@@ -859,6 +1044,11 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
|
|
|
859
1044
|
yield { type: 'skill', skillName: data.skillName, content: data.content, found: data.found };
|
|
860
1045
|
}
|
|
861
1046
|
|
|
1047
|
+
// Emit custom tool event
|
|
1048
|
+
if (type === 'tool') {
|
|
1049
|
+
yield { type: 'tool', toolName, args: data.args, result: data.result, error: data.error };
|
|
1050
|
+
}
|
|
1051
|
+
|
|
862
1052
|
// Track consecutive failures
|
|
863
1053
|
const isExecutingTool = EXECUTING_TOOLS.has(toolName) || (toolName === 'fix_code' && toolInput.execute);
|
|
864
1054
|
if (isExecutingTool) {
|
|
@@ -922,8 +1112,9 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
|
|
|
922
1112
|
* @returns {Array<{fileName: string, purpose: string|null, script: string, filePath: string|null, tool: string}>}
|
|
923
1113
|
*/
|
|
924
1114
|
dump() {
|
|
1115
|
+
const ext = this.language === 'python' ? '.py' : '.mjs';
|
|
925
1116
|
return this._allExecutions.map((exec, i) => ({
|
|
926
|
-
fileName: exec.purpose ? `agent-${exec.purpose}
|
|
1117
|
+
fileName: exec.purpose ? `agent-${exec.purpose}${ext}` : `script-${i + 1}${ext}`,
|
|
927
1118
|
purpose: exec.purpose || null,
|
|
928
1119
|
script: exec.code,
|
|
929
1120
|
filePath: exec.filePath || null,
|
package/index.cjs
CHANGED
|
@@ -1626,7 +1626,55 @@ var import_node_path = require("node:path");
|
|
|
1626
1626
|
var import_node_crypto = require("node:crypto");
|
|
1627
1627
|
var MAX_OUTPUT_CHARS = 5e4;
|
|
1628
1628
|
var MAX_FILE_TREE_LINES = 500;
|
|
1629
|
-
var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "coverage", ".next", "build", "__pycache__"]);
|
|
1629
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "coverage", ".next", "build", "__pycache__", ".venv"]);
|
|
1630
|
+
var LANG_CONFIG = {
|
|
1631
|
+
javascript: {
|
|
1632
|
+
toolDescExecute: "Execute a given piece of JavaScript code in a Node.js child process. Use this when you already have code to run \u2014 e.g., running code from a previous write_code call, re-running a snippet, or executing code the user provided. Use console.log() for output.",
|
|
1633
|
+
toolDescWriteAndRun: "Write a fresh solution from scratch and execute it in one step. Use this when you need to figure out the code AND run it \u2014 the autonomous, end-to-end tool for solving problems with code.",
|
|
1634
|
+
codeParamDesc: "JavaScript code to execute. Use console.log() for output. Use import syntax (ES modules).",
|
|
1635
|
+
codeRules: `- Your code runs in a Node.js child process with access to all built-in modules
|
|
1636
|
+
- IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
|
|
1637
|
+
- import fs from 'fs';
|
|
1638
|
+
- import path from 'path';
|
|
1639
|
+
- import { execSync } from 'child_process';
|
|
1640
|
+
- Use console.log() to produce output \u2014 that's how results are returned to you
|
|
1641
|
+
- Write efficient scripts that do multiple things per execution when possible
|
|
1642
|
+
- For parallel async operations, use Promise.all()
|
|
1643
|
+
- Handle errors in your scripts with try/catch so you get useful error messages
|
|
1644
|
+
- Top-level await is supported`,
|
|
1645
|
+
commentsEnabled: `- Add a JSDoc @fileoverview comment at the top of each script explaining what it does
|
|
1646
|
+
- Add brief JSDoc @param comments for any functions you define`,
|
|
1647
|
+
commentsDisabled: `- Do NOT write any comments in your code \u2014 save tokens. The code should be self-explanatory.`,
|
|
1648
|
+
packageLabel: "Available Packages",
|
|
1649
|
+
packageIntro: "These npm packages are installed and can be imported",
|
|
1650
|
+
bashExamples: "ls, grep, curl, git, npm, cat",
|
|
1651
|
+
codeBlockLang: "javascript",
|
|
1652
|
+
execToolSummary: "Run a given piece of JavaScript code. Use when you already have code to run \u2014 e.g., from a previous write_code call, re-running a snippet, or executing user-provided code.",
|
|
1653
|
+
writeRunToolSummary: "Write a fresh solution from scratch and execute it in one step. The autonomous, end-to-end tool for solving problems with code."
|
|
1654
|
+
},
|
|
1655
|
+
python: {
|
|
1656
|
+
toolDescExecute: "Execute a given piece of Python code in a child process. Use this when you already have code to run \u2014 e.g., running code from a previous write_code call, re-running a snippet, or executing code the user provided. Use print() for output.",
|
|
1657
|
+
toolDescWriteAndRun: "Write a fresh Python solution from scratch and execute it in one step. Use this when you need to figure out the code AND run it \u2014 the autonomous, end-to-end tool for solving problems with code.",
|
|
1658
|
+
codeParamDesc: "Python code to execute. Use print() for output.",
|
|
1659
|
+
codeRules: `- Your code runs in a Python 3 child process
|
|
1660
|
+
- Use print() to produce output \u2014 that's how results are returned to you
|
|
1661
|
+
- Use standard Python imports (import os, import json, from pathlib import Path, etc.)
|
|
1662
|
+
- Write efficient scripts; prefer list comprehensions and built-in functions
|
|
1663
|
+
- For async operations, use asyncio
|
|
1664
|
+
- Handle errors with try/except so you get useful error messages
|
|
1665
|
+
- A virtual environment is active \u2014 you can install packages using run_bash with: pip install <package>
|
|
1666
|
+
- Installed packages persist across executions in this session`,
|
|
1667
|
+
commentsEnabled: `- Add a module-level docstring at the top of each script explaining what it does
|
|
1668
|
+
- Add brief docstrings for any functions you define`,
|
|
1669
|
+
commentsDisabled: `- Do NOT write any comments in your code \u2014 save tokens. The code should be self-explanatory.`,
|
|
1670
|
+
packageLabel: "Available Packages",
|
|
1671
|
+
packageIntro: "These Python packages are available for import",
|
|
1672
|
+
bashExamples: "ls, grep, curl, git, pip, cat",
|
|
1673
|
+
codeBlockLang: "python",
|
|
1674
|
+
execToolSummary: "Run a given piece of Python code. Use when you already have code to run \u2014 e.g., from a previous write_code call, re-running a snippet, or executing user-provided code.",
|
|
1675
|
+
writeRunToolSummary: "Write a fresh Python solution from scratch and execute it in one step. The autonomous, end-to-end tool for solving problems with code."
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1630
1678
|
var EXECUTING_TOOLS = /* @__PURE__ */ new Set(["execute_code", "write_and_run_code", "run_bash"]);
|
|
1631
1679
|
var CodeAgent = class extends base_default {
|
|
1632
1680
|
/**
|
|
@@ -1638,6 +1686,8 @@ var CodeAgent = class extends base_default {
|
|
|
1638
1686
|
}
|
|
1639
1687
|
super(options);
|
|
1640
1688
|
this.workingDirectory = options.workingDirectory || process.cwd();
|
|
1689
|
+
this.language = options.language || "javascript";
|
|
1690
|
+
this.pythonPath = options.pythonPath || null;
|
|
1641
1691
|
this.maxRounds = options.maxRounds || 10;
|
|
1642
1692
|
this.timeout = options.timeout || 3e4;
|
|
1643
1693
|
this.onBeforeExecution = options.onBeforeExecution || null;
|
|
@@ -1649,6 +1699,18 @@ var CodeAgent = class extends base_default {
|
|
|
1649
1699
|
this.maxRetries = options.maxRetries ?? 3;
|
|
1650
1700
|
this.skills = options.skills || [];
|
|
1651
1701
|
this.envOverview = options.envOverview || "";
|
|
1702
|
+
this._pythonBinary = null;
|
|
1703
|
+
this._venvPath = null;
|
|
1704
|
+
this._venvEnv = null;
|
|
1705
|
+
this.customTools = (options.tools || []).map((t) => ({
|
|
1706
|
+
name: t.name,
|
|
1707
|
+
description: t.description,
|
|
1708
|
+
parametersJsonSchema: t.parametersJsonSchema || t.parameters || t.input_schema || t.inputSchema
|
|
1709
|
+
}));
|
|
1710
|
+
this.toolExecutor = options.toolExecutor || null;
|
|
1711
|
+
if (this.customTools.length > 0 && !this.toolExecutor) {
|
|
1712
|
+
throw new Error("CodeAgent: tools provided without a toolExecutor.");
|
|
1713
|
+
}
|
|
1652
1714
|
this._codebaseContext = null;
|
|
1653
1715
|
this._contextGathered = false;
|
|
1654
1716
|
this._stopped = false;
|
|
@@ -1668,6 +1730,7 @@ var CodeAgent = class extends base_default {
|
|
|
1668
1730
|
* @returns {{ functionDeclarations: Array<Object> }}
|
|
1669
1731
|
*/
|
|
1670
1732
|
_buildToolDefinitions() {
|
|
1733
|
+
const lang = LANG_CONFIG[this.language] || LANG_CONFIG.javascript;
|
|
1671
1734
|
const declarations = [
|
|
1672
1735
|
{
|
|
1673
1736
|
name: "write_code",
|
|
@@ -1684,11 +1747,11 @@ var CodeAgent = class extends base_default {
|
|
|
1684
1747
|
},
|
|
1685
1748
|
{
|
|
1686
1749
|
name: "execute_code",
|
|
1687
|
-
description:
|
|
1750
|
+
description: lang.toolDescExecute,
|
|
1688
1751
|
parametersJsonSchema: {
|
|
1689
1752
|
type: "object",
|
|
1690
1753
|
properties: {
|
|
1691
|
-
code: { type: "string", description:
|
|
1754
|
+
code: { type: "string", description: lang.codeParamDesc },
|
|
1692
1755
|
purpose: { type: "string", description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs").' }
|
|
1693
1756
|
},
|
|
1694
1757
|
required: ["code"]
|
|
@@ -1696,11 +1759,11 @@ var CodeAgent = class extends base_default {
|
|
|
1696
1759
|
},
|
|
1697
1760
|
{
|
|
1698
1761
|
name: "write_and_run_code",
|
|
1699
|
-
description:
|
|
1762
|
+
description: lang.toolDescWriteAndRun,
|
|
1700
1763
|
parametersJsonSchema: {
|
|
1701
1764
|
type: "object",
|
|
1702
1765
|
properties: {
|
|
1703
|
-
code: { type: "string", description:
|
|
1766
|
+
code: { type: "string", description: lang.codeParamDesc },
|
|
1704
1767
|
purpose: { type: "string", description: 'A short 2-4 word slug describing what this script does (e.g., "fetch-api-data", "generate-report").' }
|
|
1705
1768
|
},
|
|
1706
1769
|
required: ["code"]
|
|
@@ -1722,7 +1785,7 @@ var CodeAgent = class extends base_default {
|
|
|
1722
1785
|
},
|
|
1723
1786
|
{
|
|
1724
1787
|
name: "run_bash",
|
|
1725
|
-
description:
|
|
1788
|
+
description: `Execute a shell command in the working directory. Use this for file operations, git commands, installing packages, or any shell task (e.g., ${lang.bashExamples}). Prefer this over execute_code for simple shell operations.`,
|
|
1726
1789
|
parametersJsonSchema: {
|
|
1727
1790
|
type: "object",
|
|
1728
1791
|
properties: {
|
|
@@ -1746,6 +1809,9 @@ var CodeAgent = class extends base_default {
|
|
|
1746
1809
|
}
|
|
1747
1810
|
});
|
|
1748
1811
|
}
|
|
1812
|
+
for (const t of this.customTools) {
|
|
1813
|
+
declarations.push({ name: t.name, description: t.description, parametersJsonSchema: t.parametersJsonSchema });
|
|
1814
|
+
}
|
|
1749
1815
|
return { functionDeclarations: declarations };
|
|
1750
1816
|
}
|
|
1751
1817
|
// ── Init ─────────────────────────────────────────────────────────────────
|
|
@@ -1758,6 +1824,10 @@ var CodeAgent = class extends base_default {
|
|
|
1758
1824
|
if (this.skills.length > 0 && (this._skillRegistry.size === 0 || force)) {
|
|
1759
1825
|
await this._loadSkills();
|
|
1760
1826
|
}
|
|
1827
|
+
if (this.language === "python" && (!this._pythonBinary || force)) {
|
|
1828
|
+
await this._resolvePython();
|
|
1829
|
+
await this._setupVenv();
|
|
1830
|
+
}
|
|
1761
1831
|
this.chatConfig.tools = [this._buildToolDefinitions()];
|
|
1762
1832
|
if (!this._contextGathered || force) {
|
|
1763
1833
|
await this._gatherCodebaseContext();
|
|
@@ -1785,6 +1855,70 @@ var CodeAgent = class extends base_default {
|
|
|
1785
1855
|
}
|
|
1786
1856
|
}
|
|
1787
1857
|
}
|
|
1858
|
+
// ── Python Resolution ───────────────────────────────────────────────────
|
|
1859
|
+
/**
|
|
1860
|
+
* Resolve the Python 3 binary path.
|
|
1861
|
+
* @private
|
|
1862
|
+
*/
|
|
1863
|
+
async _resolvePython() {
|
|
1864
|
+
const tryBinary = (bin) => new Promise((resolve2) => {
|
|
1865
|
+
(0, import_node_child_process.execFile)(bin, ["--version"], { timeout: 5e3 }, (err, stdout, stderr) => {
|
|
1866
|
+
if (err) return resolve2(null);
|
|
1867
|
+
const output = (stdout || "") + (stderr || "");
|
|
1868
|
+
if (output.includes("Python 3.")) return resolve2(bin);
|
|
1869
|
+
resolve2(null);
|
|
1870
|
+
});
|
|
1871
|
+
});
|
|
1872
|
+
if (this.pythonPath) {
|
|
1873
|
+
const result = await tryBinary(this.pythonPath);
|
|
1874
|
+
if (result) {
|
|
1875
|
+
this._pythonBinary = result;
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
throw new Error(`CodeAgent: pythonPath "${this.pythonPath}" is not a valid Python 3 binary.`);
|
|
1879
|
+
}
|
|
1880
|
+
const python3 = await tryBinary("python3");
|
|
1881
|
+
if (python3) {
|
|
1882
|
+
this._pythonBinary = python3;
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
const python = await tryBinary("python");
|
|
1886
|
+
if (python) {
|
|
1887
|
+
this._pythonBinary = python;
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
throw new Error('CodeAgent: language is "python" but python3 was not found on PATH. Install Python 3 or provide the pythonPath option.');
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Create a virtual environment for Python execution.
|
|
1894
|
+
* @private
|
|
1895
|
+
*/
|
|
1896
|
+
async _setupVenv() {
|
|
1897
|
+
await (0, import_promises2.mkdir)(this.writeDir, { recursive: true });
|
|
1898
|
+
this._venvPath = (0, import_node_path.join)(this.writeDir, ".venv");
|
|
1899
|
+
const isWin = process.platform === "win32";
|
|
1900
|
+
const venvBin = isWin ? (0, import_node_path.join)(this._venvPath, "Scripts") : (0, import_node_path.join)(this._venvPath, "bin");
|
|
1901
|
+
const venvPython = (0, import_node_path.join)(venvBin, isWin ? "python.exe" : "python");
|
|
1902
|
+
try {
|
|
1903
|
+
await (0, import_promises2.readFile)(venvPython);
|
|
1904
|
+
} catch {
|
|
1905
|
+
logger_default.debug(`Creating Python venv at ${this._venvPath}`);
|
|
1906
|
+
await new Promise((resolve2, reject) => {
|
|
1907
|
+
(0, import_node_child_process.execFile)(this._pythonBinary, ["-m", "venv", this._venvPath], {
|
|
1908
|
+
timeout: 3e4
|
|
1909
|
+
}, (err) => {
|
|
1910
|
+
if (err) return reject(new Error(`CodeAgent: failed to create venv: ${err.message}`));
|
|
1911
|
+
resolve2();
|
|
1912
|
+
});
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
const env = Object.assign({}, process.env);
|
|
1916
|
+
env.VIRTUAL_ENV = this._venvPath;
|
|
1917
|
+
env.PATH = venvBin + (isWin ? ";" : ":") + (process.env.PATH || "");
|
|
1918
|
+
this._venvEnv = env;
|
|
1919
|
+
this._pythonBinary = venvPython;
|
|
1920
|
+
logger_default.debug(`Python venv ready at ${this._venvPath}`);
|
|
1921
|
+
}
|
|
1788
1922
|
// ── Context Gathering ────────────────────────────────────────────────────
|
|
1789
1923
|
/**
|
|
1790
1924
|
* @private
|
|
@@ -1803,15 +1937,35 @@ var CodeAgent = class extends base_default {
|
|
|
1803
1937
|
fileTree = `${truncated}
|
|
1804
1938
|
... (${lines.length - MAX_FILE_TREE_LINES} more files)`;
|
|
1805
1939
|
}
|
|
1806
|
-
let
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1940
|
+
let packages = [];
|
|
1941
|
+
if (this.language === "python") {
|
|
1942
|
+
try {
|
|
1943
|
+
const reqPath = (0, import_node_path.join)(this.workingDirectory, "requirements.txt");
|
|
1944
|
+
const content = await (0, import_promises2.readFile)(reqPath, "utf-8");
|
|
1945
|
+
packages = content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && !l.startsWith("-")).map((l) => l.split(/[>=<!\[;\s]/)[0]);
|
|
1946
|
+
} catch {
|
|
1947
|
+
}
|
|
1948
|
+
if (packages.length === 0) {
|
|
1949
|
+
try {
|
|
1950
|
+
const ppPath = (0, import_node_path.join)(this.workingDirectory, "pyproject.toml");
|
|
1951
|
+
const content = await (0, import_promises2.readFile)(ppPath, "utf-8");
|
|
1952
|
+
const depMatch = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
1953
|
+
if (depMatch) {
|
|
1954
|
+
packages = (depMatch[1].match(/"([^"]+)"/g) || []).map((s) => s.replace(/"/g, "").split(/[>=<!\[;\s]/)[0]);
|
|
1955
|
+
}
|
|
1956
|
+
} catch {
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
} else {
|
|
1960
|
+
try {
|
|
1961
|
+
const pkgPath = (0, import_node_path.join)(this.workingDirectory, "package.json");
|
|
1962
|
+
const pkg = JSON.parse(await (0, import_promises2.readFile)(pkgPath, "utf-8"));
|
|
1963
|
+
packages = [
|
|
1964
|
+
...Object.keys(pkg.dependencies || {}),
|
|
1965
|
+
...Object.keys(pkg.devDependencies || {})
|
|
1966
|
+
];
|
|
1967
|
+
} catch {
|
|
1968
|
+
}
|
|
1815
1969
|
}
|
|
1816
1970
|
const importantFileContents = [];
|
|
1817
1971
|
if (this.importantFiles.length > 0) {
|
|
@@ -1823,7 +1977,7 @@ var CodeAgent = class extends base_default {
|
|
|
1823
1977
|
continue;
|
|
1824
1978
|
}
|
|
1825
1979
|
try {
|
|
1826
|
-
const fullPath = (0, import_node_path.join)(this.workingDirectory, resolved);
|
|
1980
|
+
const fullPath = (0, import_node_path.isAbsolute)(resolved) ? resolved : (0, import_node_path.join)(this.workingDirectory, resolved);
|
|
1827
1981
|
const content = await (0, import_promises2.readFile)(fullPath, "utf-8");
|
|
1828
1982
|
importantFileContents.push({ path: resolved, content });
|
|
1829
1983
|
} catch (e) {
|
|
@@ -1831,13 +1985,14 @@ var CodeAgent = class extends base_default {
|
|
|
1831
1985
|
}
|
|
1832
1986
|
}
|
|
1833
1987
|
}
|
|
1834
|
-
this._codebaseContext = { fileTree, npmPackages, importantFileContents };
|
|
1988
|
+
this._codebaseContext = { fileTree, npmPackages: packages, packages, importantFileContents };
|
|
1835
1989
|
this._contextGathered = true;
|
|
1836
1990
|
}
|
|
1837
1991
|
/**
|
|
1838
1992
|
* @private
|
|
1839
1993
|
*/
|
|
1840
1994
|
_resolveImportantFile(filename, fileTreeLines) {
|
|
1995
|
+
if ((0, import_node_path.isAbsolute)(filename)) return filename;
|
|
1841
1996
|
const exact = fileTreeLines.find((line) => line === filename);
|
|
1842
1997
|
if (exact) return exact;
|
|
1843
1998
|
const partial = fileTreeLines.find(
|
|
@@ -1888,7 +2043,8 @@ var CodeAgent = class extends base_default {
|
|
|
1888
2043
|
* @private
|
|
1889
2044
|
*/
|
|
1890
2045
|
_buildSystemPrompt() {
|
|
1891
|
-
const { fileTree,
|
|
2046
|
+
const { fileTree, packages, importantFileContents } = this._codebaseContext || { fileTree: "", packages: [], importantFileContents: [] };
|
|
2047
|
+
const lang = LANG_CONFIG[this.language] || LANG_CONFIG.javascript;
|
|
1892
2048
|
let prompt = `You are a coding agent working in ${this.workingDirectory}.
|
|
1893
2049
|
|
|
1894
2050
|
## Available Tools
|
|
@@ -1897,16 +2053,16 @@ var CodeAgent = class extends base_default {
|
|
|
1897
2053
|
Output code without executing it. Use when showing, proposing, or presenting code to the user.
|
|
1898
2054
|
|
|
1899
2055
|
### execute_code
|
|
1900
|
-
|
|
2056
|
+
${lang.execToolSummary}
|
|
1901
2057
|
|
|
1902
2058
|
### write_and_run_code
|
|
1903
|
-
|
|
2059
|
+
${lang.writeRunToolSummary}
|
|
1904
2060
|
|
|
1905
2061
|
### fix_code
|
|
1906
2062
|
Fix broken code by providing original and fixed versions. Set execute=true to verify the fix works.
|
|
1907
2063
|
|
|
1908
2064
|
### run_bash
|
|
1909
|
-
Run shell commands directly (e.g.,
|
|
2065
|
+
Run shell commands directly (e.g., ${lang.bashExamples}). Prefer this over execute_code for simple shell operations.`;
|
|
1910
2066
|
if (this._skillRegistry.size > 0) {
|
|
1911
2067
|
prompt += `
|
|
1912
2068
|
|
|
@@ -1918,24 +2074,14 @@ Load a skill by name to get detailed instructions and templates. Available skill
|
|
|
1918
2074
|
## Code Execution Rules
|
|
1919
2075
|
These rules apply when using execute_code, write_and_run_code, or fix_code (with execute=true):
|
|
1920
2076
|
- Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config")
|
|
1921
|
-
|
|
1922
|
-
- IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
|
|
1923
|
-
- import fs from 'fs';
|
|
1924
|
-
- import path from 'path';
|
|
1925
|
-
- import { execSync } from 'child_process';
|
|
1926
|
-
- Use console.log() to produce output \u2014 that's how results are returned to you
|
|
1927
|
-
- Write efficient scripts that do multiple things per execution when possible
|
|
1928
|
-
- For parallel async operations, use Promise.all()
|
|
1929
|
-
- Handle errors in your scripts with try/catch so you get useful error messages
|
|
1930
|
-
- Top-level await is supported
|
|
2077
|
+
${lang.codeRules}
|
|
1931
2078
|
- The working directory is: ${this.workingDirectory}`;
|
|
1932
2079
|
if (this.comments) {
|
|
1933
2080
|
prompt += `
|
|
1934
|
-
|
|
1935
|
-
- Add brief JSDoc @param comments for any functions you define`;
|
|
2081
|
+
${lang.commentsEnabled}`;
|
|
1936
2082
|
} else {
|
|
1937
2083
|
prompt += `
|
|
1938
|
-
|
|
2084
|
+
${lang.commentsDisabled}`;
|
|
1939
2085
|
}
|
|
1940
2086
|
if (fileTree) {
|
|
1941
2087
|
prompt += `
|
|
@@ -1945,11 +2091,11 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
|
|
|
1945
2091
|
${fileTree}
|
|
1946
2092
|
\`\`\``;
|
|
1947
2093
|
}
|
|
1948
|
-
if (
|
|
2094
|
+
if (packages && packages.length > 0) {
|
|
1949
2095
|
prompt += `
|
|
1950
2096
|
|
|
1951
|
-
##
|
|
1952
|
-
|
|
2097
|
+
## ${lang.packageLabel}
|
|
2098
|
+
${lang.packageIntro}: ${packages.join(", ")}`;
|
|
1953
2099
|
}
|
|
1954
2100
|
if (importantFileContents && importantFileContents.length > 0) {
|
|
1955
2101
|
prompt += `
|
|
@@ -1959,7 +2105,7 @@ These npm packages are installed and can be imported: ${npmPackages.join(", ")}`
|
|
|
1959
2105
|
prompt += `
|
|
1960
2106
|
|
|
1961
2107
|
### ${filePath}
|
|
1962
|
-
|
|
2108
|
+
\`\`\`${lang.codeBlockLang}
|
|
1963
2109
|
${content}
|
|
1964
2110
|
\`\`\``;
|
|
1965
2111
|
}
|
|
@@ -2005,14 +2151,17 @@ ${this.envOverview}`;
|
|
|
2005
2151
|
}
|
|
2006
2152
|
await (0, import_promises2.mkdir)(this.writeDir, { recursive: true });
|
|
2007
2153
|
const slug = this._slugify(purpose);
|
|
2008
|
-
const
|
|
2154
|
+
const ext = this.language === "python" ? ".py" : ".mjs";
|
|
2155
|
+
const tempFile = (0, import_node_path.join)(this.writeDir, `agent-${slug}-${Date.now()}${ext}`);
|
|
2009
2156
|
try {
|
|
2010
2157
|
await (0, import_promises2.writeFile)(tempFile, code, "utf-8");
|
|
2158
|
+
const binary = this.language === "python" ? this._pythonBinary : "node";
|
|
2159
|
+
const execEnv = this.language === "python" && this._venvEnv ? this._venvEnv : process.env;
|
|
2011
2160
|
const result = await new Promise((resolve2) => {
|
|
2012
|
-
const child = (0, import_node_child_process.execFile)(
|
|
2161
|
+
const child = (0, import_node_child_process.execFile)(binary, [tempFile], {
|
|
2013
2162
|
cwd: this.workingDirectory,
|
|
2014
2163
|
timeout: this.timeout,
|
|
2015
|
-
env:
|
|
2164
|
+
env: execEnv,
|
|
2016
2165
|
maxBuffer: 10 * 1024 * 1024
|
|
2017
2166
|
}, (err, stdout, stderr) => {
|
|
2018
2167
|
this._activeProcess = null;
|
|
@@ -2083,11 +2232,12 @@ ${this.envOverview}`;
|
|
|
2083
2232
|
logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
|
|
2084
2233
|
}
|
|
2085
2234
|
}
|
|
2235
|
+
const execEnv = this.language === "python" && this._venvEnv ? this._venvEnv : process.env;
|
|
2086
2236
|
const result = await new Promise((resolve2) => {
|
|
2087
2237
|
const child = (0, import_node_child_process.execFile)("bash", ["-c", command], {
|
|
2088
2238
|
cwd: this.workingDirectory,
|
|
2089
2239
|
timeout: this.timeout,
|
|
2090
|
-
env:
|
|
2240
|
+
env: execEnv,
|
|
2091
2241
|
maxBuffer: 10 * 1024 * 1024
|
|
2092
2242
|
}, (err, stdout, stderr) => {
|
|
2093
2243
|
this._activeProcess = null;
|
|
@@ -2234,12 +2384,30 @@ ${this.envOverview}`;
|
|
|
2234
2384
|
data: { tool: "use_skill", skillName: skill.name, content: skill.content, found: true }
|
|
2235
2385
|
};
|
|
2236
2386
|
}
|
|
2237
|
-
default:
|
|
2387
|
+
default: {
|
|
2388
|
+
if (this.toolExecutor) {
|
|
2389
|
+
try {
|
|
2390
|
+
const result = await this.toolExecutor(name, input);
|
|
2391
|
+
const resultStr = typeof result === "string" ? result : JSON.stringify(result);
|
|
2392
|
+
return {
|
|
2393
|
+
output: resultStr,
|
|
2394
|
+
type: "tool",
|
|
2395
|
+
data: { tool: name, args: input, result }
|
|
2396
|
+
};
|
|
2397
|
+
} catch (err) {
|
|
2398
|
+
return {
|
|
2399
|
+
output: `Tool "${name}" failed: ${err.message}`,
|
|
2400
|
+
type: "tool",
|
|
2401
|
+
data: { tool: name, args: input, error: err.message }
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2238
2405
|
return {
|
|
2239
2406
|
output: `Unknown tool: ${name}`,
|
|
2240
2407
|
type: "unknown",
|
|
2241
2408
|
data: { tool: name }
|
|
2242
2409
|
};
|
|
2410
|
+
}
|
|
2243
2411
|
}
|
|
2244
2412
|
}
|
|
2245
2413
|
// ── Non-Streaming Chat ───────────────────────────────────────────────────
|
|
@@ -2396,6 +2564,9 @@ ${this.envOverview}`;
|
|
|
2396
2564
|
if (toolName === "use_skill") {
|
|
2397
2565
|
yield { type: "skill", skillName: data.skillName, content: data.content, found: data.found };
|
|
2398
2566
|
}
|
|
2567
|
+
if (type === "tool") {
|
|
2568
|
+
yield { type: "tool", toolName, args: data.args, result: data.result, error: data.error };
|
|
2569
|
+
}
|
|
2399
2570
|
const isExecutingTool = EXECUTING_TOOLS.has(toolName) || toolName === "fix_code" && toolInput.execute;
|
|
2400
2571
|
if (isExecutingTool) {
|
|
2401
2572
|
if (data.exitCode !== 0 && !data.denied) {
|
|
@@ -2446,8 +2617,9 @@ ${this.envOverview}`;
|
|
|
2446
2617
|
* @returns {Array<{fileName: string, purpose: string|null, script: string, filePath: string|null, tool: string}>}
|
|
2447
2618
|
*/
|
|
2448
2619
|
dump() {
|
|
2620
|
+
const ext = this.language === "python" ? ".py" : ".mjs";
|
|
2449
2621
|
return this._allExecutions.map((exec, i) => ({
|
|
2450
|
-
fileName: exec.purpose ? `agent-${exec.purpose}
|
|
2622
|
+
fileName: exec.purpose ? `agent-${exec.purpose}${ext}` : `script-${i + 1}${ext}`,
|
|
2451
2623
|
purpose: exec.purpose || null,
|
|
2452
2624
|
script: exec.code,
|
|
2453
2625
|
filePath: exec.filePath || null,
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -302,6 +302,10 @@ export interface RagAgentOptions extends BaseGeminiOptions {
|
|
|
302
302
|
export interface CodeAgentOptions extends BaseGeminiOptions {
|
|
303
303
|
/** Working directory for code execution (default: process.cwd()) */
|
|
304
304
|
workingDirectory?: string;
|
|
305
|
+
/** Programming language for code execution: 'javascript' (default) or 'python' */
|
|
306
|
+
language?: 'javascript' | 'python';
|
|
307
|
+
/** Path to the Python binary (only used when language is 'python'; default: auto-detect python3/python) */
|
|
308
|
+
pythonPath?: string;
|
|
305
309
|
/** Max code execution loop iterations (default: 10) */
|
|
306
310
|
maxRounds?: number;
|
|
307
311
|
/** Per-execution timeout in milliseconds (default: 30000) */
|
|
@@ -324,6 +328,17 @@ export interface CodeAgentOptions extends BaseGeminiOptions {
|
|
|
324
328
|
skills?: string[];
|
|
325
329
|
/** Plain text environment overview appended to the system prompt — describe the project, stack, conventions, etc. */
|
|
326
330
|
envOverview?: string;
|
|
331
|
+
/** Custom tool declarations to add alongside built-in CodeAgent tools. Accepts Gemini, Claude, or OpenAI tool formats (auto-mapped). */
|
|
332
|
+
tools?: Array<{
|
|
333
|
+
name: string;
|
|
334
|
+
description: string;
|
|
335
|
+
parametersJsonSchema?: any;
|
|
336
|
+
parameters?: any;
|
|
337
|
+
input_schema?: any;
|
|
338
|
+
inputSchema?: any;
|
|
339
|
+
}>;
|
|
340
|
+
/** Function to execute custom tool calls: (toolName, args) => result */
|
|
341
|
+
toolExecutor?: (toolName: string, args: Record<string, any>) => Promise<any>;
|
|
327
342
|
}
|
|
328
343
|
|
|
329
344
|
export interface CodeExecution {
|
|
@@ -340,7 +355,7 @@ export interface CodeExecution {
|
|
|
340
355
|
}
|
|
341
356
|
|
|
342
357
|
export interface ToolCallResult {
|
|
343
|
-
tool: 'write_code' | 'execute_code' | 'write_and_run_code' | 'fix_code' | 'run_bash' | 'use_skill';
|
|
358
|
+
tool: 'write_code' | 'execute_code' | 'write_and_run_code' | 'fix_code' | 'run_bash' | 'use_skill' | string;
|
|
344
359
|
code?: string;
|
|
345
360
|
purpose?: string;
|
|
346
361
|
language?: string;
|
|
@@ -370,7 +385,7 @@ export interface CodeAgentResponse {
|
|
|
370
385
|
}
|
|
371
386
|
|
|
372
387
|
export interface CodeAgentStreamEvent {
|
|
373
|
-
type: 'text' | 'code' | 'output' | 'write' | 'fix' | 'bash' | 'skill' | 'done';
|
|
388
|
+
type: 'text' | 'code' | 'output' | 'write' | 'fix' | 'bash' | 'skill' | 'tool' | 'done';
|
|
374
389
|
text?: string;
|
|
375
390
|
code?: string;
|
|
376
391
|
stdout?: string;
|
|
@@ -390,6 +405,14 @@ export interface CodeAgentStreamEvent {
|
|
|
390
405
|
skillName?: string;
|
|
391
406
|
content?: string;
|
|
392
407
|
found?: boolean;
|
|
408
|
+
/** custom tool: tool name */
|
|
409
|
+
toolName?: string;
|
|
410
|
+
/** custom tool: arguments passed */
|
|
411
|
+
args?: Record<string, any>;
|
|
412
|
+
/** custom tool: result returned */
|
|
413
|
+
result?: any;
|
|
414
|
+
/** custom tool: error message (if failed) */
|
|
415
|
+
error?: string;
|
|
393
416
|
}
|
|
394
417
|
|
|
395
418
|
// ── Per-Message Options ──────────────────────────────────────────────────────
|
|
@@ -614,6 +637,8 @@ export declare class CodeAgent extends BaseGemini {
|
|
|
614
637
|
constructor(options?: CodeAgentOptions);
|
|
615
638
|
|
|
616
639
|
workingDirectory: string;
|
|
640
|
+
language: 'javascript' | 'python';
|
|
641
|
+
pythonPath: string | null;
|
|
617
642
|
maxRounds: number;
|
|
618
643
|
timeout: number;
|
|
619
644
|
onBeforeExecution: ((content: string, toolName: string) => Promise<boolean> | boolean) | null;
|
|
@@ -625,6 +650,8 @@ export declare class CodeAgent extends BaseGemini {
|
|
|
625
650
|
maxRetries: number;
|
|
626
651
|
skills: string[];
|
|
627
652
|
envOverview: string;
|
|
653
|
+
customTools: Array<{ name: string; description: string; parametersJsonSchema: any }>;
|
|
654
|
+
toolExecutor: ((toolName: string, args: Record<string, any>) => Promise<any>) | null;
|
|
628
655
|
|
|
629
656
|
init(force?: boolean): Promise<void>;
|
|
630
657
|
chat(message: string, opts?: { labels?: Record<string, string> }): Promise<CodeAgentResponse>;
|