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 +52 -13
- package/code-agent.js +190 -40
- package/image-generator.js +186 -0
- package/index.cjs +368 -54
- package/index.js +3 -1
- package/package.json +3 -2
- package/types.d.ts +78 -3
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
|
|
31
|
-
/^gemini-3
|
|
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
|
-
/**
|
|
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
|
-
|
|
41
|
-
'gemini-
|
|
42
|
-
'gemini-
|
|
43
|
-
|
|
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
|
-
|
|
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-
|
|
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:
|
|
161
|
+
description: lang.toolDescExecute,
|
|
106
162
|
parametersJsonSchema: {
|
|
107
163
|
type: 'object',
|
|
108
164
|
properties: {
|
|
109
|
-
code: { type: 'string', description:
|
|
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:
|
|
173
|
+
description: lang.toolDescWriteAndRun,
|
|
118
174
|
parametersJsonSchema: {
|
|
119
175
|
type: 'object',
|
|
120
176
|
properties: {
|
|
121
|
-
code: { type: 'string', description:
|
|
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:
|
|
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
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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,
|
|
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
|
-
|
|
508
|
+
${lang.execToolSummary}
|
|
357
509
|
|
|
358
510
|
### write_and_run_code
|
|
359
|
-
|
|
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.,
|
|
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
|
-
|
|
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
|
|
535
|
+
prompt += `\n${lang.commentsEnabled}`;
|
|
393
536
|
} else {
|
|
394
|
-
prompt += `\n
|
|
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 (
|
|
402
|
-
prompt += `\n\n##
|
|
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
|
|
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
|
|
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(
|
|
608
|
+
const child = execFile(binary, [tempFile], {
|
|
462
609
|
cwd: this.workingDirectory,
|
|
463
610
|
timeout: this.timeout,
|
|
464
|
-
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:
|
|
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}
|
|
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,
|