ak-gemini 2.1.5 → 2.3.0

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/base.js CHANGED
@@ -25,26 +25,47 @@ const DEFAULT_THINKING_CONFIG = {
25
25
 
26
26
  const DEFAULT_MAX_OUTPUT_TOKENS = 50_000;
27
27
 
28
- /** Models that support thinking features */
28
+ /** Models that support thinking features. Image / live / tts variants intentionally excluded. */
29
29
  const THINKING_SUPPORTED_MODELS = [
30
- /^gemini-3-flash(-preview)?$/,
31
- /^gemini-3-pro(-preview|-image-preview)?$/,
30
+ /^gemini-3(\.\d+)?-pro(-preview)?$/,
31
+ /^gemini-3(\.\d+)?-flash(-preview)?$/,
32
+ /^gemini-3(\.\d+)?-flash-lite(-preview)?$/,
32
33
  /^gemini-2\.5-pro/,
33
34
  /^gemini-2\.5-flash(-preview)?$/,
34
35
  /^gemini-2\.5-flash-lite(-preview)?$/,
35
36
  /^gemini-2\.0-flash$/
36
37
  ];
37
38
 
38
- /** Model pricing per million tokens (as of Dec 2025) */
39
+ /**
40
+ * Model pricing per million tokens (Paid Tier Standard, base rate, as of May 2026).
41
+ * Source: https://ai.google.dev/gemini-api/docs/pricing
42
+ *
43
+ * NOTES:
44
+ * - Pro models use tiered pricing (≤200k vs >200k context). Listed rate is ≤200k base tier.
45
+ * - Image-output tokens on Nano Banana models bill at $60/M (1.5 Flash Image) or $120/M (3 Pro Image).
46
+ * Only text-input/text-output rates are modelled here; image-output cost is NOT included in estimateCost().
47
+ * - Audio input is more expensive on most models — listed rate covers text/image/video input.
48
+ */
39
49
  const MODEL_PRICING = {
40
- 'gemini-2.5-flash': { input: 0.15, output: 0.60 },
41
- 'gemini-2.5-flash-lite': { input: 0.02, output: 0.10 },
42
- 'gemini-2.5-pro': { input: 2.50, output: 10.00 },
43
- 'gemini-3-pro': { input: 2.00, output: 12.00 },
44
- 'gemini-3-pro-preview': { input: 2.00, output: 12.00 },
50
+ // Gemini 3.x stable
51
+ 'gemini-3.5-flash': { input: 1.50, output: 9.00 },
52
+ 'gemini-3.1-flash-lite': { input: 0.25, output: 1.50 },
53
+ // Gemini 3.x preview
54
+ 'gemini-3.1-pro-preview': { input: 2.00, output: 12.00 }, // ≤200k tier
55
+ 'gemini-3-flash-preview': { input: 0.50, output: 3.00 },
56
+ 'gemini-3.1-flash-lite-preview': { input: 0.25, output: 1.50 },
57
+ 'gemini-3.1-flash-image-preview': { input: 0.50, output: 3.00 }, // text-only; image-output is $60/M
58
+ 'gemini-3-pro-image-preview': { input: 2.00, output: 12.00 }, // text-only; image-output is $120/M
59
+ // Gemini 2.5 stable
60
+ 'gemini-2.5-flash': { input: 0.30, output: 2.50 },
61
+ 'gemini-2.5-flash-lite': { input: 0.10, output: 0.40 },
62
+ 'gemini-2.5-pro': { input: 1.25, output: 10.00 }, // ≤200k tier
63
+ 'gemini-2.5-flash-image': { input: 0.30, output: 0 }, // image-output is ~$0.039/image (1290 tokens)
64
+ // Deprecated but kept for back-compat (shut down June 2026)
45
65
  'gemini-2.0-flash': { input: 0.10, output: 0.40 },
46
66
  'gemini-2.0-flash-lite': { input: 0.02, output: 0.10 },
47
- 'gemini-embedding-001': { input: 0.006, output: 0 }
67
+ // Embeddings
68
+ 'gemini-embedding-001': { input: 0.15, output: 0 }
48
69
  };
49
70
 
50
71
  export { DEFAULT_SAFETY_SETTINGS, DEFAULT_THINKING_CONFIG, THINKING_SUPPORTED_MODELS, MODEL_PRICING, DEFAULT_MAX_OUTPUT_TOKENS };
@@ -70,7 +91,7 @@ class BaseGemini {
70
91
  */
71
92
  constructor(options = {}) {
72
93
  // ── Model ──
73
- this.modelName = options.modelName || 'gemini-2.5-flash';
94
+ this.modelName = options.modelName || 'gemini-3-flash-preview';
74
95
 
75
96
  // ── System Prompt ──
76
97
  // Subclasses set their own default if options.systemPrompt is undefined
@@ -114,6 +135,14 @@ class BaseGemini {
114
135
  // ── Caching ──
115
136
  this.cachedContent = options.cachedContent || null;
116
137
 
138
+ // ── Service Tier (Gemini API / Vertex AI 2026+) ──
139
+ // Allowed values: 'STANDARD' | 'FLEX' | 'PRIORITY' — cost vs latency trade.
140
+ this.serviceTier = options.serviceTier || null;
141
+
142
+ // ── Server-Side Tool Invocation Visibility (1.46.0+) ──
143
+ // When grounding is on, surface the server's tool calls (e.g. Google Search) in the response.
144
+ this.includeServerSideToolInvocations = options.includeServerSideToolInvocations ?? false;
145
+
117
146
  // ── Chat Config ──
118
147
  this.chatConfig = {
119
148
  temperature: 0.7,
@@ -123,6 +152,9 @@ class BaseGemini {
123
152
  ...options.chatConfig
124
153
  };
125
154
 
155
+ if (this.serviceTier) this.chatConfig['serviceTier'] = this.serviceTier;
156
+ if (this.includeServerSideToolInvocations) this.chatConfig['includeServerSideToolInvocations'] = true;
157
+
126
158
  // Apply systemPrompt to chatConfig
127
159
  if (this.systemPrompt) {
128
160
  this.chatConfig.systemInstruction = this.systemPrompt;
@@ -365,6 +397,7 @@ class BaseGemini {
365
397
  * @protected
366
398
  */
367
399
  _captureMetadata(response) {
400
+ const modelStatus = response?.modelStatus || null;
368
401
  this.lastResponseMetadata = {
369
402
  modelVersion: response.modelVersion || null,
370
403
  requestedModel: this.modelName,
@@ -372,8 +405,13 @@ class BaseGemini {
372
405
  responseTokens: response.usageMetadata?.candidatesTokenCount || 0,
373
406
  totalTokens: response.usageMetadata?.totalTokenCount || 0,
374
407
  timestamp: Date.now(),
375
- groundingMetadata: response.candidates?.[0]?.groundingMetadata || null
408
+ groundingMetadata: response.candidates?.[0]?.groundingMetadata || null,
409
+ modelStatus
376
410
  };
411
+ if (modelStatus === 'DEPRECATED' && !this._deprecationWarned) {
412
+ log.warn(`Model "${this.modelName}" is marked DEPRECATED by Google. Plan migration.`);
413
+ this._deprecationWarned = true;
414
+ }
377
415
  }
378
416
 
379
417
  /**
@@ -396,7 +434,8 @@ class BaseGemini {
396
434
  modelVersion: meta.modelVersion,
397
435
  requestedModel: meta.requestedModel,
398
436
  timestamp: meta.timestamp,
399
- groundingMetadata: meta.groundingMetadata || null
437
+ groundingMetadata: meta.groundingMetadata || null,
438
+ modelStatus: meta.modelStatus || null
400
439
  };
401
440
  }
402
441
 
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,