ak-gemini 2.1.5 → 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 CHANGED
@@ -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,11 @@ 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
+
52
106
  // ── Custom tools ──
53
107
  this.customTools = (options.tools || []).map(t => ({
54
108
  name: t.name,
@@ -85,6 +139,8 @@ class CodeAgent extends BaseGemini {
85
139
  * @returns {{ functionDeclarations: Array<Object> }}
86
140
  */
87
141
  _buildToolDefinitions() {
142
+ const lang = LANG_CONFIG[this.language] || LANG_CONFIG.javascript;
143
+
88
144
  /** @type {Array<Object>} */
89
145
  const declarations = [
90
146
  {
@@ -102,11 +158,11 @@ class CodeAgent extends BaseGemini {
102
158
  },
103
159
  {
104
160
  name: 'execute_code',
105
- description: '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.',
161
+ description: lang.toolDescExecute,
106
162
  parametersJsonSchema: {
107
163
  type: 'object',
108
164
  properties: {
109
- code: { type: 'string', description: 'JavaScript code to execute. Use console.log() for output. Use import syntax (ES modules).' },
165
+ code: { type: 'string', description: lang.codeParamDesc },
110
166
  purpose: { type: 'string', description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs").' }
111
167
  },
112
168
  required: ['code']
@@ -114,11 +170,11 @@ class CodeAgent extends BaseGemini {
114
170
  },
115
171
  {
116
172
  name: 'write_and_run_code',
117
- description: '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.',
173
+ description: lang.toolDescWriteAndRun,
118
174
  parametersJsonSchema: {
119
175
  type: 'object',
120
176
  properties: {
121
- code: { type: 'string', description: 'JavaScript code to write and execute. Use console.log() for output. Use import syntax (ES modules).' },
177
+ code: { type: 'string', description: lang.codeParamDesc },
122
178
  purpose: { type: 'string', description: 'A short 2-4 word slug describing what this script does (e.g., "fetch-api-data", "generate-report").' }
123
179
  },
124
180
  required: ['code']
@@ -140,7 +196,7 @@ class CodeAgent extends BaseGemini {
140
196
  },
141
197
  {
142
198
  name: 'run_bash',
143
- description: 'Execute a shell command in the working directory. Use this for file operations, git commands, installing packages, or any shell task. Prefer this over execute_code for simple shell operations.',
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.`,
144
200
  parametersJsonSchema: {
145
201
  type: 'object',
146
202
  properties: {
@@ -189,6 +245,12 @@ class CodeAgent extends BaseGemini {
189
245
  await this._loadSkills();
190
246
  }
191
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
+
192
254
  // Rebuild tools (use_skill may now be included)
193
255
  this.chatConfig.tools = [this._buildToolDefinitions()];
194
256
 
@@ -227,6 +289,73 @@ class CodeAgent extends BaseGemini {
227
289
  }
228
290
  }
229
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
+
230
359
  // ── Context Gathering ────────────────────────────────────────────────────
231
360
 
232
361
  /**
@@ -248,15 +377,37 @@ class CodeAgent extends BaseGemini {
248
377
  fileTree = `${truncated}\n... (${lines.length - MAX_FILE_TREE_LINES} more files)`;
249
378
  }
250
379
 
251
- let npmPackages = [];
252
- try {
253
- const pkgPath = join(this.workingDirectory, 'package.json');
254
- const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
255
- npmPackages = [
256
- ...Object.keys(pkg.dependencies || {}),
257
- ...Object.keys(pkg.devDependencies || {})
258
- ];
259
- } catch { /* no package.json */ }
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
+ }
260
411
 
261
412
  const importantFileContents = [];
262
413
  if (this.importantFiles.length > 0) {
@@ -277,7 +428,7 @@ class CodeAgent extends BaseGemini {
277
428
  }
278
429
  }
279
430
 
280
- this._codebaseContext = { fileTree, npmPackages, importantFileContents };
431
+ this._codebaseContext = { fileTree, npmPackages: packages, packages, importantFileContents };
281
432
  this._contextGathered = true;
282
433
  }
283
434
 
@@ -343,7 +494,8 @@ class CodeAgent extends BaseGemini {
343
494
  * @private
344
495
  */
345
496
  _buildSystemPrompt() {
346
- const { fileTree, npmPackages, importantFileContents } = this._codebaseContext || { fileTree: '', npmPackages: [], importantFileContents: [] };
497
+ const { fileTree, packages, importantFileContents } = this._codebaseContext || { fileTree: '', packages: [], importantFileContents: [] };
498
+ const lang = LANG_CONFIG[this.language] || LANG_CONFIG.javascript;
347
499
 
348
500
  let prompt = `You are a coding agent working in ${this.workingDirectory}.
349
501
 
@@ -353,16 +505,16 @@ class CodeAgent extends BaseGemini {
353
505
  Output code without executing it. Use when showing, proposing, or presenting code to the user.
354
506
 
355
507
  ### execute_code
356
- 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.
508
+ ${lang.execToolSummary}
357
509
 
358
510
  ### write_and_run_code
359
- Write a fresh solution from scratch and execute it in one step. The autonomous, end-to-end tool for solving problems with code.
511
+ ${lang.writeRunToolSummary}
360
512
 
361
513
  ### fix_code
362
514
  Fix broken code by providing original and fixed versions. Set execute=true to verify the fix works.
363
515
 
364
516
  ### run_bash
365
- Run shell commands directly (e.g., ls, grep, curl, git, npm, cat). Prefer this over execute_code for simple shell operations.`;
517
+ Run shell commands directly (e.g., ${lang.bashExamples}). Prefer this over execute_code for simple shell operations.`;
366
518
 
367
519
  if (this._skillRegistry.size > 0) {
368
520
  prompt += `
@@ -376,36 +528,27 @@ Load a skill by name to get detailed instructions and templates. Available skill
376
528
  ## Code Execution Rules
377
529
  These rules apply when using execute_code, write_and_run_code, or fix_code (with execute=true):
378
530
  - Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config")
379
- - Your code runs in a Node.js child process with access to all built-in modules
380
- - IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
381
- - import fs from 'fs';
382
- - import path from 'path';
383
- - import { execSync } from 'child_process';
384
- - Use console.log() to produce output — that's how results are returned to you
385
- - Write efficient scripts that do multiple things per execution when possible
386
- - For parallel async operations, use Promise.all()
387
- - Handle errors in your scripts with try/catch so you get useful error messages
388
- - Top-level await is supported
531
+ ${lang.codeRules}
389
532
  - The working directory is: ${this.workingDirectory}`;
390
533
 
391
534
  if (this.comments) {
392
- 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`;
535
+ prompt += `\n${lang.commentsEnabled}`;
393
536
  } else {
394
- prompt += `\n- Do NOT write any comments in your code — save tokens. The code should be self-explanatory.`;
537
+ prompt += `\n${lang.commentsDisabled}`;
395
538
  }
396
539
 
397
540
  if (fileTree) {
398
541
  prompt += `\n\n## File Tree\n\`\`\`\n${fileTree}\n\`\`\``;
399
542
  }
400
543
 
401
- if (npmPackages.length > 0) {
402
- prompt += `\n\n## Available Packages\nThese npm packages are installed and can be imported: ${npmPackages.join(', ')}`;
544
+ if (packages && packages.length > 0) {
545
+ prompt += `\n\n## ${lang.packageLabel}\n${lang.packageIntro}: ${packages.join(', ')}`;
403
546
  }
404
547
 
405
548
  if (importantFileContents && importantFileContents.length > 0) {
406
549
  prompt += `\n\n## Key Files`;
407
550
  for (const { path: filePath, content } of importantFileContents) {
408
- prompt += `\n\n### ${filePath}\n\`\`\`javascript\n${content}\n\`\`\``;
551
+ prompt += `\n\n### ${filePath}\n\`\`\`${lang.codeBlockLang}\n${content}\n\`\`\``;
409
552
  }
410
553
  }
411
554
 
@@ -452,16 +595,20 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
452
595
  await mkdir(this.writeDir, { recursive: true });
453
596
 
454
597
  const slug = this._slugify(purpose);
455
- const tempFile = join(this.writeDir, `agent-${slug}-${Date.now()}.mjs`);
598
+ const ext = this.language === 'python' ? '.py' : '.mjs';
599
+ const tempFile = join(this.writeDir, `agent-${slug}-${Date.now()}${ext}`);
456
600
 
457
601
  try {
458
602
  await writeFile(tempFile, code, 'utf-8');
459
603
 
604
+ const binary = this.language === 'python' ? this._pythonBinary : 'node';
605
+ const execEnv = this.language === 'python' && this._venvEnv ? this._venvEnv : process.env;
606
+
460
607
  const result = await new Promise((resolve) => {
461
- const child = execFile('node', [tempFile], {
608
+ const child = execFile(binary, [tempFile], {
462
609
  cwd: this.workingDirectory,
463
610
  timeout: this.timeout,
464
- env: process.env,
611
+ env: execEnv,
465
612
  maxBuffer: 10 * 1024 * 1024
466
613
  }, (err, stdout, stderr) => {
467
614
  this._activeProcess = null;
@@ -531,11 +678,13 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
531
678
  }
532
679
  }
533
680
 
681
+ const execEnv = this.language === 'python' && this._venvEnv ? this._venvEnv : process.env;
682
+
534
683
  const result = await new Promise((resolve) => {
535
684
  const child = execFile('bash', ['-c', command], {
536
685
  cwd: this.workingDirectory,
537
686
  timeout: this.timeout,
538
- env: process.env,
687
+ env: execEnv,
539
688
  maxBuffer: 10 * 1024 * 1024
540
689
  }, (err, stdout, stderr) => {
541
690
  this._activeProcess = null;
@@ -963,8 +1112,9 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
963
1112
  * @returns {Array<{fileName: string, purpose: string|null, script: string, filePath: string|null, tool: string}>}
964
1113
  */
965
1114
  dump() {
1115
+ const ext = this.language === 'python' ? '.py' : '.mjs';
966
1116
  return this._allExecutions.map((exec, i) => ({
967
- fileName: exec.purpose ? `agent-${exec.purpose}.mjs` : `script-${i + 1}.mjs`,
1117
+ fileName: exec.purpose ? `agent-${exec.purpose}${ext}` : `script-${i + 1}${ext}`,
968
1118
  purpose: exec.purpose || null,
969
1119
  script: exec.code,
970
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,9 @@ 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;
1652
1705
  this.customTools = (options.tools || []).map((t) => ({
1653
1706
  name: t.name,
1654
1707
  description: t.description,
@@ -1677,6 +1730,7 @@ var CodeAgent = class extends base_default {
1677
1730
  * @returns {{ functionDeclarations: Array<Object> }}
1678
1731
  */
1679
1732
  _buildToolDefinitions() {
1733
+ const lang = LANG_CONFIG[this.language] || LANG_CONFIG.javascript;
1680
1734
  const declarations = [
1681
1735
  {
1682
1736
  name: "write_code",
@@ -1693,11 +1747,11 @@ var CodeAgent = class extends base_default {
1693
1747
  },
1694
1748
  {
1695
1749
  name: "execute_code",
1696
- description: "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.",
1750
+ description: lang.toolDescExecute,
1697
1751
  parametersJsonSchema: {
1698
1752
  type: "object",
1699
1753
  properties: {
1700
- code: { type: "string", description: "JavaScript code to execute. Use console.log() for output. Use import syntax (ES modules)." },
1754
+ code: { type: "string", description: lang.codeParamDesc },
1701
1755
  purpose: { type: "string", description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs").' }
1702
1756
  },
1703
1757
  required: ["code"]
@@ -1705,11 +1759,11 @@ var CodeAgent = class extends base_default {
1705
1759
  },
1706
1760
  {
1707
1761
  name: "write_and_run_code",
1708
- description: "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.",
1762
+ description: lang.toolDescWriteAndRun,
1709
1763
  parametersJsonSchema: {
1710
1764
  type: "object",
1711
1765
  properties: {
1712
- code: { type: "string", description: "JavaScript code to write and execute. Use console.log() for output. Use import syntax (ES modules)." },
1766
+ code: { type: "string", description: lang.codeParamDesc },
1713
1767
  purpose: { type: "string", description: 'A short 2-4 word slug describing what this script does (e.g., "fetch-api-data", "generate-report").' }
1714
1768
  },
1715
1769
  required: ["code"]
@@ -1731,7 +1785,7 @@ var CodeAgent = class extends base_default {
1731
1785
  },
1732
1786
  {
1733
1787
  name: "run_bash",
1734
- description: "Execute a shell command in the working directory. Use this for file operations, git commands, installing packages, or any shell task. Prefer this over execute_code for simple shell operations.",
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.`,
1735
1789
  parametersJsonSchema: {
1736
1790
  type: "object",
1737
1791
  properties: {
@@ -1770,6 +1824,10 @@ var CodeAgent = class extends base_default {
1770
1824
  if (this.skills.length > 0 && (this._skillRegistry.size === 0 || force)) {
1771
1825
  await this._loadSkills();
1772
1826
  }
1827
+ if (this.language === "python" && (!this._pythonBinary || force)) {
1828
+ await this._resolvePython();
1829
+ await this._setupVenv();
1830
+ }
1773
1831
  this.chatConfig.tools = [this._buildToolDefinitions()];
1774
1832
  if (!this._contextGathered || force) {
1775
1833
  await this._gatherCodebaseContext();
@@ -1797,6 +1855,70 @@ var CodeAgent = class extends base_default {
1797
1855
  }
1798
1856
  }
1799
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
+ }
1800
1922
  // ── Context Gathering ────────────────────────────────────────────────────
1801
1923
  /**
1802
1924
  * @private
@@ -1815,15 +1937,35 @@ var CodeAgent = class extends base_default {
1815
1937
  fileTree = `${truncated}
1816
1938
  ... (${lines.length - MAX_FILE_TREE_LINES} more files)`;
1817
1939
  }
1818
- let npmPackages = [];
1819
- try {
1820
- const pkgPath = (0, import_node_path.join)(this.workingDirectory, "package.json");
1821
- const pkg = JSON.parse(await (0, import_promises2.readFile)(pkgPath, "utf-8"));
1822
- npmPackages = [
1823
- ...Object.keys(pkg.dependencies || {}),
1824
- ...Object.keys(pkg.devDependencies || {})
1825
- ];
1826
- } catch {
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
+ }
1827
1969
  }
1828
1970
  const importantFileContents = [];
1829
1971
  if (this.importantFiles.length > 0) {
@@ -1843,7 +1985,7 @@ var CodeAgent = class extends base_default {
1843
1985
  }
1844
1986
  }
1845
1987
  }
1846
- this._codebaseContext = { fileTree, npmPackages, importantFileContents };
1988
+ this._codebaseContext = { fileTree, npmPackages: packages, packages, importantFileContents };
1847
1989
  this._contextGathered = true;
1848
1990
  }
1849
1991
  /**
@@ -1901,7 +2043,8 @@ var CodeAgent = class extends base_default {
1901
2043
  * @private
1902
2044
  */
1903
2045
  _buildSystemPrompt() {
1904
- const { fileTree, npmPackages, importantFileContents } = this._codebaseContext || { fileTree: "", npmPackages: [], importantFileContents: [] };
2046
+ const { fileTree, packages, importantFileContents } = this._codebaseContext || { fileTree: "", packages: [], importantFileContents: [] };
2047
+ const lang = LANG_CONFIG[this.language] || LANG_CONFIG.javascript;
1905
2048
  let prompt = `You are a coding agent working in ${this.workingDirectory}.
1906
2049
 
1907
2050
  ## Available Tools
@@ -1910,16 +2053,16 @@ var CodeAgent = class extends base_default {
1910
2053
  Output code without executing it. Use when showing, proposing, or presenting code to the user.
1911
2054
 
1912
2055
  ### execute_code
1913
- 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.
2056
+ ${lang.execToolSummary}
1914
2057
 
1915
2058
  ### write_and_run_code
1916
- Write a fresh solution from scratch and execute it in one step. The autonomous, end-to-end tool for solving problems with code.
2059
+ ${lang.writeRunToolSummary}
1917
2060
 
1918
2061
  ### fix_code
1919
2062
  Fix broken code by providing original and fixed versions. Set execute=true to verify the fix works.
1920
2063
 
1921
2064
  ### run_bash
1922
- Run shell commands directly (e.g., ls, grep, curl, git, npm, cat). Prefer this over execute_code for simple shell operations.`;
2065
+ Run shell commands directly (e.g., ${lang.bashExamples}). Prefer this over execute_code for simple shell operations.`;
1923
2066
  if (this._skillRegistry.size > 0) {
1924
2067
  prompt += `
1925
2068
 
@@ -1931,24 +2074,14 @@ Load a skill by name to get detailed instructions and templates. Available skill
1931
2074
  ## Code Execution Rules
1932
2075
  These rules apply when using execute_code, write_and_run_code, or fix_code (with execute=true):
1933
2076
  - Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config")
1934
- - Your code runs in a Node.js child process with access to all built-in modules
1935
- - IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
1936
- - import fs from 'fs';
1937
- - import path from 'path';
1938
- - import { execSync } from 'child_process';
1939
- - Use console.log() to produce output \u2014 that's how results are returned to you
1940
- - Write efficient scripts that do multiple things per execution when possible
1941
- - For parallel async operations, use Promise.all()
1942
- - Handle errors in your scripts with try/catch so you get useful error messages
1943
- - Top-level await is supported
2077
+ ${lang.codeRules}
1944
2078
  - The working directory is: ${this.workingDirectory}`;
1945
2079
  if (this.comments) {
1946
2080
  prompt += `
1947
- - Add a JSDoc @fileoverview comment at the top of each script explaining what it does
1948
- - Add brief JSDoc @param comments for any functions you define`;
2081
+ ${lang.commentsEnabled}`;
1949
2082
  } else {
1950
2083
  prompt += `
1951
- - Do NOT write any comments in your code \u2014 save tokens. The code should be self-explanatory.`;
2084
+ ${lang.commentsDisabled}`;
1952
2085
  }
1953
2086
  if (fileTree) {
1954
2087
  prompt += `
@@ -1958,11 +2091,11 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
1958
2091
  ${fileTree}
1959
2092
  \`\`\``;
1960
2093
  }
1961
- if (npmPackages.length > 0) {
2094
+ if (packages && packages.length > 0) {
1962
2095
  prompt += `
1963
2096
 
1964
- ## Available Packages
1965
- These npm packages are installed and can be imported: ${npmPackages.join(", ")}`;
2097
+ ## ${lang.packageLabel}
2098
+ ${lang.packageIntro}: ${packages.join(", ")}`;
1966
2099
  }
1967
2100
  if (importantFileContents && importantFileContents.length > 0) {
1968
2101
  prompt += `
@@ -1972,7 +2105,7 @@ These npm packages are installed and can be imported: ${npmPackages.join(", ")}`
1972
2105
  prompt += `
1973
2106
 
1974
2107
  ### ${filePath}
1975
- \`\`\`javascript
2108
+ \`\`\`${lang.codeBlockLang}
1976
2109
  ${content}
1977
2110
  \`\`\``;
1978
2111
  }
@@ -2018,14 +2151,17 @@ ${this.envOverview}`;
2018
2151
  }
2019
2152
  await (0, import_promises2.mkdir)(this.writeDir, { recursive: true });
2020
2153
  const slug = this._slugify(purpose);
2021
- const tempFile = (0, import_node_path.join)(this.writeDir, `agent-${slug}-${Date.now()}.mjs`);
2154
+ const ext = this.language === "python" ? ".py" : ".mjs";
2155
+ const tempFile = (0, import_node_path.join)(this.writeDir, `agent-${slug}-${Date.now()}${ext}`);
2022
2156
  try {
2023
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;
2024
2160
  const result = await new Promise((resolve2) => {
2025
- const child = (0, import_node_child_process.execFile)("node", [tempFile], {
2161
+ const child = (0, import_node_child_process.execFile)(binary, [tempFile], {
2026
2162
  cwd: this.workingDirectory,
2027
2163
  timeout: this.timeout,
2028
- env: process.env,
2164
+ env: execEnv,
2029
2165
  maxBuffer: 10 * 1024 * 1024
2030
2166
  }, (err, stdout, stderr) => {
2031
2167
  this._activeProcess = null;
@@ -2096,11 +2232,12 @@ ${this.envOverview}`;
2096
2232
  logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
2097
2233
  }
2098
2234
  }
2235
+ const execEnv = this.language === "python" && this._venvEnv ? this._venvEnv : process.env;
2099
2236
  const result = await new Promise((resolve2) => {
2100
2237
  const child = (0, import_node_child_process.execFile)("bash", ["-c", command], {
2101
2238
  cwd: this.workingDirectory,
2102
2239
  timeout: this.timeout,
2103
- env: process.env,
2240
+ env: execEnv,
2104
2241
  maxBuffer: 10 * 1024 * 1024
2105
2242
  }, (err, stdout, stderr) => {
2106
2243
  this._activeProcess = null;
@@ -2480,8 +2617,9 @@ ${this.envOverview}`;
2480
2617
  * @returns {Array<{fileName: string, purpose: string|null, script: string, filePath: string|null, tool: string}>}
2481
2618
  */
2482
2619
  dump() {
2620
+ const ext = this.language === "python" ? ".py" : ".mjs";
2483
2621
  return this._allExecutions.map((exec, i) => ({
2484
- fileName: exec.purpose ? `agent-${exec.purpose}.mjs` : `script-${i + 1}.mjs`,
2622
+ fileName: exec.purpose ? `agent-${exec.purpose}${ext}` : `script-${i + 1}${ext}`,
2485
2623
  purpose: exec.purpose || null,
2486
2624
  script: exec.code,
2487
2625
  filePath: exec.filePath || null,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "ak-gemini",
3
3
  "author": "ak@mixpanel.com",
4
4
  "description": "AK's Generative AI Helper for doing... everything",
5
- "version": "2.1.5",
5
+ "version": "2.2.1",
6
6
  "main": "index.js",
7
7
  "files": [
8
8
  "index.js",
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) */
@@ -633,6 +637,8 @@ export declare class CodeAgent extends BaseGemini {
633
637
  constructor(options?: CodeAgentOptions);
634
638
 
635
639
  workingDirectory: string;
640
+ language: 'javascript' | 'python';
641
+ pythonPath: string | null;
636
642
  maxRounds: number;
637
643
  timeout: number;
638
644
  onBeforeExecution: ((content: string, toolName: string) => Promise<boolean> | boolean) | null;