ak-gemini 2.0.7 → 2.1.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/index.cjs CHANGED
@@ -1325,6 +1325,24 @@ var Message = class extends base_default {
1325
1325
  var message_default = Message;
1326
1326
 
1327
1327
  // tool-agent.js
1328
+ async function runWithConcurrency(tasks, concurrency) {
1329
+ if (concurrency === Infinity) return Promise.all(tasks.map((t) => t()));
1330
+ if (concurrency === 1) {
1331
+ const results2 = [];
1332
+ for (const t of tasks) results2.push(await t());
1333
+ return results2;
1334
+ }
1335
+ const results = new Array(tasks.length);
1336
+ let next = 0;
1337
+ async function worker() {
1338
+ while (next < tasks.length) {
1339
+ const i = next++;
1340
+ results[i] = await tasks[i]();
1341
+ }
1342
+ }
1343
+ await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker()));
1344
+ return results;
1345
+ }
1328
1346
  var ToolAgent = class extends base_default {
1329
1347
  /**
1330
1348
  * @param {ToolAgentOptions} [options={}]
@@ -1342,6 +1360,8 @@ var ToolAgent = class extends base_default {
1342
1360
  if (this.toolExecutor && this.tools.length === 0) {
1343
1361
  throw new Error("ToolAgent: toolExecutor provided without tools. Provide tool declarations so the model knows what tools are available.");
1344
1362
  }
1363
+ this.parallelToolCalls = options.parallelToolCalls ?? true;
1364
+ this._concurrency = this.parallelToolCalls === true ? Infinity : this.parallelToolCalls === false ? 1 : this.parallelToolCalls;
1345
1365
  this.maxToolRounds = options.maxToolRounds || 10;
1346
1366
  this.onToolCall = options.onToolCall || null;
1347
1367
  this.onBeforeExecution = options.onBeforeExecution || null;
@@ -1372,38 +1392,36 @@ var ToolAgent = class extends base_default {
1372
1392
  if (this._stopped) break;
1373
1393
  const functionCalls = response.functionCalls;
1374
1394
  if (!functionCalls || functionCalls.length === 0) break;
1375
- const toolResults = await Promise.all(
1376
- functionCalls.map(async (call) => {
1377
- if (this.onToolCall) {
1378
- try {
1379
- this.onToolCall(call.name, call.args);
1380
- } catch (e) {
1381
- logger_default.warn(`onToolCall callback error: ${e.message}`);
1382
- }
1383
- }
1384
- if (this.onBeforeExecution) {
1385
- try {
1386
- const allowed = await this.onBeforeExecution(call.name, call.args);
1387
- if (allowed === false) {
1388
- const result2 = { error: "Execution denied by onBeforeExecution callback" };
1389
- allToolCalls.push({ name: call.name, args: call.args, result: result2 });
1390
- return { id: call.id, name: call.name, result: result2 };
1391
- }
1392
- } catch (e) {
1393
- logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
1394
- }
1395
+ const tasks = functionCalls.map((call) => async () => {
1396
+ if (this.onToolCall) {
1397
+ try {
1398
+ this.onToolCall(call.name, call.args);
1399
+ } catch (e) {
1400
+ logger_default.warn(`onToolCall callback error: ${e.message}`);
1395
1401
  }
1396
- let result;
1402
+ }
1403
+ if (this.onBeforeExecution) {
1397
1404
  try {
1398
- result = await this.toolExecutor(call.name, call.args);
1399
- } catch (err) {
1400
- logger_default.warn(`Tool ${call.name} failed: ${err.message}`);
1401
- result = { error: err.message };
1405
+ const allowed = await this.onBeforeExecution(call.name, call.args);
1406
+ if (allowed === false) {
1407
+ const result2 = { error: "Execution denied by onBeforeExecution callback" };
1408
+ return { id: call.id, name: call.name, args: call.args, result: result2 };
1409
+ }
1410
+ } catch (e) {
1411
+ logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
1402
1412
  }
1403
- allToolCalls.push({ name: call.name, args: call.args, result });
1404
- return { id: call.id, name: call.name, result };
1405
- })
1406
- );
1413
+ }
1414
+ let result;
1415
+ try {
1416
+ result = await this.toolExecutor(call.name, call.args);
1417
+ } catch (err) {
1418
+ logger_default.warn(`Tool ${call.name} failed: ${err.message}`);
1419
+ result = { error: err.message };
1420
+ }
1421
+ return { id: call.id, name: call.name, args: call.args, result };
1422
+ });
1423
+ const toolResults = await runWithConcurrency(tasks, this._concurrency);
1424
+ for (const r of toolResults) allToolCalls.push({ name: r.name, args: r.args, result: r.result });
1407
1425
  response = await this._withRetry(() => this.chatSession.sendMessage({
1408
1426
  message: toolResults.map((r) => ({
1409
1427
  functionResponse: {
@@ -1471,39 +1489,81 @@ var ToolAgent = class extends base_default {
1471
1489
  return;
1472
1490
  }
1473
1491
  const toolResults = [];
1474
- for (const call of functionCalls) {
1475
- if (this._stopped) break;
1476
- yield { type: "tool_call", toolName: call.name, args: call.args };
1477
- if (this.onToolCall) {
1478
- try {
1479
- this.onToolCall(call.name, call.args);
1480
- } catch (e) {
1481
- logger_default.warn(`onToolCall callback error: ${e.message}`);
1492
+ if (this._concurrency === 1) {
1493
+ for (const call of functionCalls) {
1494
+ if (this._stopped) break;
1495
+ yield { type: "tool_call", toolName: call.name, args: call.args };
1496
+ if (this.onToolCall) {
1497
+ try {
1498
+ this.onToolCall(call.name, call.args);
1499
+ } catch (e) {
1500
+ logger_default.warn(`onToolCall callback error: ${e.message}`);
1501
+ }
1482
1502
  }
1483
- }
1484
- let denied = false;
1485
- if (this.onBeforeExecution) {
1486
- try {
1487
- const allowed = await this.onBeforeExecution(call.name, call.args);
1488
- if (allowed === false) denied = true;
1489
- } catch (e) {
1490
- logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
1503
+ let denied = false;
1504
+ if (this.onBeforeExecution) {
1505
+ try {
1506
+ const allowed = await this.onBeforeExecution(call.name, call.args);
1507
+ if (allowed === false) denied = true;
1508
+ } catch (e) {
1509
+ logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
1510
+ }
1491
1511
  }
1512
+ let result;
1513
+ if (denied) {
1514
+ result = { error: "Execution denied by onBeforeExecution callback" };
1515
+ } else {
1516
+ try {
1517
+ result = await this.toolExecutor(call.name, call.args);
1518
+ } catch (err) {
1519
+ logger_default.warn(`Tool ${call.name} failed: ${err.message}`);
1520
+ result = { error: err.message };
1521
+ }
1522
+ }
1523
+ allToolCalls.push({ name: call.name, args: call.args, result });
1524
+ yield { type: "tool_result", toolName: call.name, result };
1525
+ toolResults.push({ id: call.id, name: call.name, result });
1492
1526
  }
1493
- let result;
1494
- if (denied) {
1495
- result = { error: "Execution denied by onBeforeExecution callback" };
1496
- } else {
1497
- try {
1498
- result = await this.toolExecutor(call.name, call.args);
1499
- } catch (err) {
1500
- logger_default.warn(`Tool ${call.name} failed: ${err.message}`);
1501
- result = { error: err.message };
1527
+ } else {
1528
+ for (const call of functionCalls) {
1529
+ yield { type: "tool_call", toolName: call.name, args: call.args };
1530
+ }
1531
+ const tasks = functionCalls.map((call) => async () => {
1532
+ if (this.onToolCall) {
1533
+ try {
1534
+ this.onToolCall(call.name, call.args);
1535
+ } catch (e) {
1536
+ logger_default.warn(`onToolCall callback error: ${e.message}`);
1537
+ }
1538
+ }
1539
+ let denied = false;
1540
+ if (this.onBeforeExecution) {
1541
+ try {
1542
+ const allowed = await this.onBeforeExecution(call.name, call.args);
1543
+ if (allowed === false) denied = true;
1544
+ } catch (e) {
1545
+ logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
1546
+ }
1547
+ }
1548
+ let result;
1549
+ if (denied) {
1550
+ result = { error: "Execution denied by onBeforeExecution callback" };
1551
+ } else {
1552
+ try {
1553
+ result = await this.toolExecutor(call.name, call.args);
1554
+ } catch (err) {
1555
+ logger_default.warn(`Tool ${call.name} failed: ${err.message}`);
1556
+ result = { error: err.message };
1557
+ }
1502
1558
  }
1559
+ return { id: call.id, name: call.name, args: call.args, result };
1560
+ });
1561
+ const results = await runWithConcurrency(tasks, this._concurrency);
1562
+ for (const r of results) {
1563
+ allToolCalls.push({ name: r.name, args: r.args, result: r.result });
1564
+ yield { type: "tool_result", toolName: r.name, result: r.result };
1565
+ toolResults.push({ id: r.id, name: r.name, result: r.result });
1503
1566
  }
1504
- allToolCalls.push({ name: call.name, args: call.args, result });
1505
- yield { type: "tool_result", toolName: call.name, result };
1506
- toolResults.push({ id: call.id, name: call.name, result });
1507
1567
  }
1508
1568
  streamResponse = await this._withRetry(() => this.chatSession.sendMessageStream({
1509
1569
  message: toolResults.map((r) => ({
@@ -1543,6 +1603,7 @@ var import_node_crypto = require("node:crypto");
1543
1603
  var MAX_OUTPUT_CHARS = 5e4;
1544
1604
  var MAX_FILE_TREE_LINES = 500;
1545
1605
  var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "coverage", ".next", "build", "__pycache__"]);
1606
+ var EXECUTING_TOOLS = /* @__PURE__ */ new Set(["execute_code", "write_and_run_code", "run_bash"]);
1546
1607
  var CodeAgent = class extends base_default {
1547
1608
  /**
1548
1609
  * @param {CodeAgentOptions} [options={}]
@@ -1562,53 +1623,146 @@ var CodeAgent = class extends base_default {
1562
1623
  this.keepArtifacts = options.keepArtifacts ?? false;
1563
1624
  this.comments = options.comments ?? false;
1564
1625
  this.maxRetries = options.maxRetries ?? 3;
1626
+ this.skills = options.skills || [];
1627
+ this.envOverview = options.envOverview || "";
1565
1628
  this._codebaseContext = null;
1566
1629
  this._contextGathered = false;
1567
1630
  this._stopped = false;
1568
1631
  this._activeProcess = null;
1569
1632
  this._userSystemPrompt = options.systemPrompt || "";
1570
1633
  this._allExecutions = [];
1571
- this.chatConfig.tools = [{
1572
- functionDeclarations: [{
1634
+ this._skillRegistry = /* @__PURE__ */ new Map();
1635
+ this.chatConfig.tools = [this._buildToolDefinitions()];
1636
+ this.chatConfig.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
1637
+ logger_default.debug(`CodeAgent created for directory: ${this.workingDirectory}`);
1638
+ }
1639
+ // ── Tool Definitions ─────────────────────────────────────────────────────
1640
+ /**
1641
+ * Build tool definitions in Gemini format.
1642
+ * use_skill is only included when skills are registered.
1643
+ * @private
1644
+ * @returns {{ functionDeclarations: Array<Object> }}
1645
+ */
1646
+ _buildToolDefinitions() {
1647
+ const declarations = [
1648
+ {
1649
+ name: "write_code",
1650
+ description: "Output code without executing it. Use this when you want to show, propose, or present code to the user without running it.",
1651
+ parametersJsonSchema: {
1652
+ type: "object",
1653
+ properties: {
1654
+ code: { type: "string", description: "The code to output." },
1655
+ purpose: { type: "string", description: 'A short 2-4 word slug describing the code (e.g., "api-client", "data-parser").' },
1656
+ language: { type: "string", description: 'Programming language of the code (default: "javascript").' }
1657
+ },
1658
+ required: ["code"]
1659
+ }
1660
+ },
1661
+ {
1573
1662
  name: "execute_code",
1574
- description: "Execute JavaScript code in a Node.js child process. The code has access to all Node.js built-in modules (fs, path, child_process, http, etc.). Use console.log() to produce output that will be returned to you. The code runs in the working directory with the same environment variables as the parent process.",
1663
+ 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.",
1575
1664
  parametersJsonSchema: {
1576
1665
  type: "object",
1577
1666
  properties: {
1578
- code: {
1579
- type: "string",
1580
- description: "JavaScript code to execute. Use console.log() for output. You can import any built-in Node.js module."
1581
- },
1582
- purpose: {
1583
- type: "string",
1584
- description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs", "fetch-api-data"). Used for naming the script file.'
1585
- }
1667
+ code: { type: "string", description: "JavaScript code to execute. Use console.log() for output. Use import syntax (ES modules)." },
1668
+ purpose: { type: "string", description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs").' }
1586
1669
  },
1587
1670
  required: ["code"]
1588
1671
  }
1589
- }]
1590
- }];
1591
- this.chatConfig.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
1592
- logger_default.debug(`CodeAgent created for directory: ${this.workingDirectory}`);
1672
+ },
1673
+ {
1674
+ name: "write_and_run_code",
1675
+ 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.",
1676
+ parametersJsonSchema: {
1677
+ type: "object",
1678
+ properties: {
1679
+ code: { type: "string", description: "JavaScript code to write and execute. Use console.log() for output. Use import syntax (ES modules)." },
1680
+ purpose: { type: "string", description: 'A short 2-4 word slug describing what this script does (e.g., "fetch-api-data", "generate-report").' }
1681
+ },
1682
+ required: ["code"]
1683
+ }
1684
+ },
1685
+ {
1686
+ name: "fix_code",
1687
+ description: "Fix broken code. Provide the original and fixed versions with an explanation. Optionally execute the fix to verify it works.",
1688
+ parametersJsonSchema: {
1689
+ type: "object",
1690
+ properties: {
1691
+ original_code: { type: "string", description: "The original broken code." },
1692
+ fixed_code: { type: "string", description: "The corrected code." },
1693
+ explanation: { type: "string", description: "Brief explanation of what was wrong and how it was fixed." },
1694
+ execute: { type: "boolean", description: "If true, execute the fixed code to verify it works (default: false)." }
1695
+ },
1696
+ required: ["original_code", "fixed_code"]
1697
+ }
1698
+ },
1699
+ {
1700
+ name: "run_bash",
1701
+ 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.",
1702
+ parametersJsonSchema: {
1703
+ type: "object",
1704
+ properties: {
1705
+ command: { type: "string", description: "The shell command to execute." },
1706
+ purpose: { type: "string", description: 'A short 2-4 word slug describing the command (e.g., "list-files", "install-deps").' }
1707
+ },
1708
+ required: ["command"]
1709
+ }
1710
+ }
1711
+ ];
1712
+ if (this._skillRegistry && this._skillRegistry.size > 0) {
1713
+ declarations.push({
1714
+ name: "use_skill",
1715
+ description: `Load a skill by name to get instructions, templates, or patterns. Available skills: ${[...this._skillRegistry.keys()].join(", ")}`,
1716
+ parametersJsonSchema: {
1717
+ type: "object",
1718
+ properties: {
1719
+ skill_name: { type: "string", description: "The name of the skill to load." }
1720
+ },
1721
+ required: ["skill_name"]
1722
+ }
1723
+ });
1724
+ }
1725
+ return { functionDeclarations: declarations };
1593
1726
  }
1594
1727
  // ── Init ─────────────────────────────────────────────────────────────────
1595
1728
  /**
1596
- * Initialize the agent: gather codebase context, build system prompt,
1597
- * and create the chat session.
1729
+ * Initialize the agent: load skills, gather codebase context, and build system prompt.
1598
1730
  * @param {boolean} [force=false]
1599
1731
  */
1600
1732
  async init(force = false) {
1601
1733
  if (this.chatSession && !force) return;
1734
+ if (this.skills.length > 0 && (this._skillRegistry.size === 0 || force)) {
1735
+ await this._loadSkills();
1736
+ }
1737
+ this.chatConfig.tools = [this._buildToolDefinitions()];
1602
1738
  if (!this._contextGathered || force) {
1603
1739
  await this._gatherCodebaseContext();
1604
1740
  }
1605
- const systemPrompt = this._buildSystemPrompt();
1606
- this.chatConfig.systemInstruction = systemPrompt;
1741
+ this.chatConfig.systemInstruction = this._buildSystemPrompt();
1607
1742
  await super.init(force);
1608
1743
  }
1744
+ // ── Skill Loading ────────────────────────────────────────────────────────
1745
+ /**
1746
+ * Load skill files into the skill registry.
1747
+ * @private
1748
+ */
1749
+ async _loadSkills() {
1750
+ this._skillRegistry.clear();
1751
+ for (const filePath of this.skills) {
1752
+ try {
1753
+ const content = await (0, import_promises2.readFile)(filePath, "utf-8");
1754
+ let name = (0, import_node_path.basename)(filePath).replace(/\.md$/i, "");
1755
+ const fmMatch = content.match(/^---\s*\n[\s\S]*?^name:\s*(.+)$/m);
1756
+ if (fmMatch) name = fmMatch[1].trim();
1757
+ this._skillRegistry.set(name, { name, content, path: filePath });
1758
+ logger_default.debug(`Loaded skill: ${name} from ${filePath}`);
1759
+ } catch (e) {
1760
+ logger_default.warn(`skills: could not load "${filePath}": ${e.message}`);
1761
+ }
1762
+ }
1763
+ }
1609
1764
  // ── Context Gathering ────────────────────────────────────────────────────
1610
1765
  /**
1611
- * Gather file tree and key file contents from the working directory.
1612
1766
  * @private
1613
1767
  */
1614
1768
  async _gatherCodebaseContext() {
@@ -1657,12 +1811,7 @@ var CodeAgent = class extends base_default {
1657
1811
  this._contextGathered = true;
1658
1812
  }
1659
1813
  /**
1660
- * Resolve an importantFiles entry against the file tree.
1661
- * Supports exact matches and partial (basename/suffix) matches.
1662
1814
  * @private
1663
- * @param {string} filename
1664
- * @param {string[]} fileTreeLines
1665
- * @returns {string|null}
1666
1815
  */
1667
1816
  _resolveImportantFile(filename, fileTreeLines) {
1668
1817
  const exact = fileTreeLines.find((line) => line === filename);
@@ -1673,9 +1822,7 @@ var CodeAgent = class extends base_default {
1673
1822
  return partial || null;
1674
1823
  }
1675
1824
  /**
1676
- * Get file tree using git ls-files.
1677
1825
  * @private
1678
- * @returns {Promise<string>}
1679
1826
  */
1680
1827
  async _getFileTreeGit() {
1681
1828
  return new Promise((resolve2, reject) => {
@@ -1690,12 +1837,7 @@ var CodeAgent = class extends base_default {
1690
1837
  });
1691
1838
  }
1692
1839
  /**
1693
- * Fallback file tree via recursive readdir.
1694
1840
  * @private
1695
- * @param {string} dir
1696
- * @param {number} depth
1697
- * @param {number} maxDepth
1698
- * @returns {Promise<string>}
1699
1841
  */
1700
1842
  async _getFileTreeReaddir(dir, depth, maxDepth) {
1701
1843
  if (depth >= maxDepth) return "";
@@ -1719,17 +1861,39 @@ var CodeAgent = class extends base_default {
1719
1861
  return entries.join("\n");
1720
1862
  }
1721
1863
  /**
1722
- * Build the full system prompt with codebase context.
1723
1864
  * @private
1724
- * @returns {string}
1725
1865
  */
1726
1866
  _buildSystemPrompt() {
1727
1867
  const { fileTree, npmPackages, importantFileContents } = this._codebaseContext || { fileTree: "", npmPackages: [], importantFileContents: [] };
1728
1868
  let prompt = `You are a coding agent working in ${this.workingDirectory}.
1729
1869
 
1730
- ## Instructions
1731
- - Use the execute_code tool to accomplish tasks by writing JavaScript code
1732
- - Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config") when calling execute_code
1870
+ ## Available Tools
1871
+
1872
+ ### write_code
1873
+ Output code without executing it. Use when showing, proposing, or presenting code to the user.
1874
+
1875
+ ### execute_code
1876
+ 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.
1877
+
1878
+ ### write_and_run_code
1879
+ Write a fresh solution from scratch and execute it in one step. The autonomous, end-to-end tool for solving problems with code.
1880
+
1881
+ ### fix_code
1882
+ Fix broken code by providing original and fixed versions. Set execute=true to verify the fix works.
1883
+
1884
+ ### run_bash
1885
+ Run shell commands directly (e.g., ls, grep, curl, git, npm, cat). Prefer this over execute_code for simple shell operations.`;
1886
+ if (this._skillRegistry.size > 0) {
1887
+ prompt += `
1888
+
1889
+ ### use_skill
1890
+ Load a skill by name to get detailed instructions and templates. Available skills: ${[...this._skillRegistry.keys()].join(", ")}`;
1891
+ }
1892
+ prompt += `
1893
+
1894
+ ## Code Execution Rules
1895
+ These rules apply when using execute_code, write_and_run_code, or fix_code (with execute=true):
1896
+ - Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config")
1733
1897
  - Your code runs in a Node.js child process with access to all built-in modules
1734
1898
  - IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
1735
1899
  - import fs from 'fs';
@@ -1737,9 +1901,7 @@ var CodeAgent = class extends base_default {
1737
1901
  - import { execSync } from 'child_process';
1738
1902
  - Use console.log() to produce output \u2014 that's how results are returned to you
1739
1903
  - Write efficient scripts that do multiple things per execution when possible
1740
- - For parallel async operations, use Promise.all():
1741
- const [a, b] = await Promise.all([fetchA(), fetchB()]);
1742
- - Read files with fs.readFileSync() when you need to understand their contents
1904
+ - For parallel async operations, use Promise.all()
1743
1905
  - Handle errors in your scripts with try/catch so you get useful error messages
1744
1906
  - Top-level await is supported
1745
1907
  - The working directory is: ${this.workingDirectory}`;
@@ -1783,34 +1945,33 @@ ${content}
1783
1945
 
1784
1946
  ## Additional Instructions
1785
1947
  ${this._userSystemPrompt}`;
1948
+ }
1949
+ if (this.envOverview) {
1950
+ prompt += `
1951
+
1952
+ ## Environment Overview
1953
+ ${this.envOverview}`;
1786
1954
  }
1787
1955
  return prompt;
1788
1956
  }
1789
1957
  // ── Code Execution ───────────────────────────────────────────────────────
1790
1958
  /**
1791
- * Generate a sanitized slug from a purpose string.
1792
1959
  * @private
1793
- * @param {string} [purpose]
1794
- * @returns {string}
1795
1960
  */
1796
1961
  _slugify(purpose) {
1797
1962
  if (!purpose) return (0, import_node_crypto.randomUUID)().slice(0, 8);
1798
1963
  return purpose.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
1799
1964
  }
1800
1965
  /**
1801
- * Execute a JavaScript code string in a child process.
1802
1966
  * @private
1803
- * @param {string} code - JavaScript code to execute
1804
- * @param {string} [purpose] - Short description for file naming
1805
- * @returns {Promise<{stdout: string, stderr: string, exitCode: number, denied?: boolean}>}
1806
1967
  */
1807
- async _executeCode(code, purpose) {
1968
+ async _executeCode(code, purpose, toolName) {
1808
1969
  if (this._stopped) {
1809
1970
  return { stdout: "", stderr: "Agent was stopped", exitCode: -1 };
1810
1971
  }
1811
1972
  if (this.onBeforeExecution) {
1812
1973
  try {
1813
- const allowed = await this.onBeforeExecution(code);
1974
+ const allowed = await this.onBeforeExecution(code, toolName || "execute_code");
1814
1975
  if (allowed === false) {
1815
1976
  return { stdout: "", stderr: "Execution denied by onBeforeExecution callback", exitCode: -1, denied: true };
1816
1977
  }
@@ -1859,7 +2020,8 @@ ${this._userSystemPrompt}`;
1859
2020
  output: result.stdout,
1860
2021
  stderr: result.stderr,
1861
2022
  exitCode: result.exitCode,
1862
- filePath: this.keepArtifacts ? tempFile : null
2023
+ filePath: this.keepArtifacts ? tempFile : null,
2024
+ tool: toolName || "execute_code"
1863
2025
  });
1864
2026
  if (this.onCodeExecution) {
1865
2027
  try {
@@ -1878,11 +2040,75 @@ ${this._userSystemPrompt}`;
1878
2040
  }
1879
2041
  }
1880
2042
  }
2043
+ // ── Bash Execution ───────────────────────────────────────────────────────
2044
+ /**
2045
+ * Execute a bash command in the working directory.
2046
+ * @private
2047
+ */
2048
+ async _executeBash(command, purpose) {
2049
+ if (this._stopped) {
2050
+ return { stdout: "", stderr: "Agent was stopped", exitCode: -1 };
2051
+ }
2052
+ if (this.onBeforeExecution) {
2053
+ try {
2054
+ const allowed = await this.onBeforeExecution(command, "run_bash");
2055
+ if (allowed === false) {
2056
+ return { stdout: "", stderr: "Execution denied by onBeforeExecution callback", exitCode: -1, denied: true };
2057
+ }
2058
+ } catch (e) {
2059
+ logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
2060
+ }
2061
+ }
2062
+ const result = await new Promise((resolve2) => {
2063
+ const child = (0, import_node_child_process.execFile)("bash", ["-c", command], {
2064
+ cwd: this.workingDirectory,
2065
+ timeout: this.timeout,
2066
+ env: process.env,
2067
+ maxBuffer: 10 * 1024 * 1024
2068
+ }, (err, stdout, stderr) => {
2069
+ this._activeProcess = null;
2070
+ if (err) {
2071
+ resolve2({
2072
+ stdout: err.stdout || stdout || "",
2073
+ stderr: (err.stderr || stderr || "") + (err.killed ? "\n[EXECUTION TIMED OUT]" : ""),
2074
+ exitCode: err.code || 1
2075
+ });
2076
+ } else {
2077
+ resolve2({ stdout: stdout || "", stderr: stderr || "", exitCode: 0 });
2078
+ }
2079
+ });
2080
+ this._activeProcess = child;
2081
+ });
2082
+ const totalLen = result.stdout.length + result.stderr.length;
2083
+ if (totalLen > MAX_OUTPUT_CHARS) {
2084
+ const half = Math.floor(MAX_OUTPUT_CHARS / 2);
2085
+ if (result.stdout.length > half) {
2086
+ result.stdout = result.stdout.slice(0, half) + "\n...[OUTPUT TRUNCATED]";
2087
+ }
2088
+ if (result.stderr.length > half) {
2089
+ result.stderr = result.stderr.slice(0, half) + "\n...[STDERR TRUNCATED]";
2090
+ }
2091
+ }
2092
+ this._allExecutions.push({
2093
+ code: command,
2094
+ purpose: purpose || null,
2095
+ output: result.stdout,
2096
+ stderr: result.stderr,
2097
+ exitCode: result.exitCode,
2098
+ filePath: null,
2099
+ tool: "run_bash"
2100
+ });
2101
+ if (this.onCodeExecution) {
2102
+ try {
2103
+ this.onCodeExecution(command, result);
2104
+ } catch (e) {
2105
+ logger_default.warn(`onCodeExecution callback error: ${e.message}`);
2106
+ }
2107
+ }
2108
+ return result;
2109
+ }
1881
2110
  /**
1882
- * Format execution result as a string for the model.
1883
2111
  * @private
1884
- * @param {{stdout: string, stderr: string, exitCode: number}} result
1885
- * @returns {string}
1886
2112
  */
1887
2113
  _formatOutput(result) {
1888
2114
  let output = "";
@@ -1891,20 +2117,121 @@ ${this._userSystemPrompt}`;
1891
2117
  if (result.exitCode !== 0) output += (output ? "\n" : "") + `[EXIT CODE]: ${result.exitCode}`;
1892
2118
  return output || "(no output)";
1893
2119
  }
2120
+ // ── Tool Call Dispatch ───────────────────────────────────────────────────
2121
+ /**
2122
+ * Handle a tool call by name, dispatching to the appropriate handler.
2123
+ * @private
2124
+ * @param {string} name - Tool name
2125
+ * @param {Object} input - Tool arguments
2126
+ * @returns {Promise<{output: string, type: string, data: Object}>}
2127
+ */
2128
+ async _handleToolCall(name, input) {
2129
+ switch (name) {
2130
+ case "execute_code":
2131
+ case "write_and_run_code": {
2132
+ const result = await this._executeCode(input.code || "", input.purpose, name);
2133
+ return {
2134
+ output: this._formatOutput(result),
2135
+ type: "code_execution",
2136
+ data: {
2137
+ tool: name,
2138
+ code: input.code || "",
2139
+ purpose: input.purpose,
2140
+ stdout: result.stdout,
2141
+ stderr: result.stderr,
2142
+ exitCode: result.exitCode,
2143
+ denied: result.denied
2144
+ }
2145
+ };
2146
+ }
2147
+ case "write_code": {
2148
+ return {
2149
+ output: "Code written successfully.",
2150
+ type: "write",
2151
+ data: {
2152
+ tool: "write_code",
2153
+ code: input.code || "",
2154
+ purpose: input.purpose,
2155
+ language: input.language || "javascript"
2156
+ }
2157
+ };
2158
+ }
2159
+ case "fix_code": {
2160
+ let execResult = null;
2161
+ if (input.execute) {
2162
+ execResult = await this._executeCode(input.fixed_code || "", "fix", "fix_code");
2163
+ }
2164
+ return {
2165
+ output: input.execute ? this._formatOutput(execResult) : "Fix recorded.",
2166
+ type: "fix",
2167
+ data: {
2168
+ tool: "fix_code",
2169
+ originalCode: input.original_code || "",
2170
+ fixedCode: input.fixed_code || "",
2171
+ explanation: input.explanation,
2172
+ executed: !!input.execute,
2173
+ stdout: execResult?.stdout,
2174
+ stderr: execResult?.stderr,
2175
+ exitCode: execResult?.exitCode,
2176
+ denied: execResult?.denied
2177
+ }
2178
+ };
2179
+ }
2180
+ case "run_bash": {
2181
+ const result = await this._executeBash(input.command || "", input.purpose);
2182
+ return {
2183
+ output: this._formatOutput(result),
2184
+ type: "bash",
2185
+ data: {
2186
+ tool: "run_bash",
2187
+ command: input.command || "",
2188
+ purpose: input.purpose,
2189
+ stdout: result.stdout,
2190
+ stderr: result.stderr,
2191
+ exitCode: result.exitCode,
2192
+ denied: result.denied
2193
+ }
2194
+ };
2195
+ }
2196
+ case "use_skill": {
2197
+ const skillName = input.skill_name || "";
2198
+ const skill = this._skillRegistry.get(skillName);
2199
+ if (!skill) {
2200
+ const available = [...this._skillRegistry.keys()].join(", ");
2201
+ return {
2202
+ output: `Skill "${skillName}" not found. Available skills: ${available || "(none)"}`,
2203
+ type: "skill",
2204
+ data: { tool: "use_skill", skillName, found: false }
2205
+ };
2206
+ }
2207
+ return {
2208
+ output: skill.content,
2209
+ type: "skill",
2210
+ data: { tool: "use_skill", skillName: skill.name, content: skill.content, found: true }
2211
+ };
2212
+ }
2213
+ default:
2214
+ return {
2215
+ output: `Unknown tool: ${name}`,
2216
+ type: "unknown",
2217
+ data: { tool: name }
2218
+ };
2219
+ }
2220
+ }
1894
2221
  // ── Non-Streaming Chat ───────────────────────────────────────────────────
1895
2222
  /**
1896
2223
  * Send a message and get a complete response (non-streaming).
1897
- * Automatically handles the code execution loop.
2224
+ * Automatically handles the multi-tool execution loop.
1898
2225
  *
1899
2226
  * @param {string} message - The user's message
1900
2227
  * @param {Object} [opts={}] - Per-message options
1901
2228
  * @param {Record<string, string>} [opts.labels] - Per-message billing labels
1902
- * @returns {Promise<CodeAgentResponse>} Response with text, codeExecutions, and usage
2229
+ * @returns {Promise<CodeAgentResponse>}
1903
2230
  */
1904
2231
  async chat(message, opts = {}) {
1905
2232
  if (!this.chatSession) await this.init();
1906
2233
  this._stopped = false;
1907
- const codeExecutions = [];
2234
+ const toolCalls = [];
1908
2235
  let consecutiveFailures = 0;
1909
2236
  let response = await this._withRetry(() => this.chatSession.sendMessage({ message }));
1910
2237
  for (let round = 0; round < this.maxRounds; round++) {
@@ -1914,31 +2241,26 @@ ${this._userSystemPrompt}`;
1914
2241
  const results = [];
1915
2242
  for (const call of functionCalls) {
1916
2243
  if (this._stopped) break;
1917
- const code = call.args?.code || "";
1918
- const purpose = call.args?.purpose;
1919
- const result = await this._executeCode(code, purpose);
1920
- codeExecutions.push({
1921
- code,
1922
- purpose: this._slugify(purpose),
1923
- output: result.stdout,
1924
- stderr: result.stderr,
1925
- exitCode: result.exitCode
1926
- });
1927
- if (result.exitCode !== 0 && !result.denied) {
1928
- consecutiveFailures++;
1929
- } else {
1930
- consecutiveFailures = 0;
2244
+ const { output, type, data } = await this._handleToolCall(call.name, call.args || {});
2245
+ toolCalls.push(data);
2246
+ const isExecutingTool = EXECUTING_TOOLS.has(call.name) || call.name === "fix_code" && call.args?.execute;
2247
+ if (isExecutingTool) {
2248
+ if (data.exitCode !== 0 && !data.denied) {
2249
+ consecutiveFailures++;
2250
+ } else {
2251
+ consecutiveFailures = 0;
2252
+ }
1931
2253
  }
1932
- let output = this._formatOutput(result);
2254
+ let toolOutput = output;
1933
2255
  if (consecutiveFailures >= this.maxRetries) {
1934
- output += `
2256
+ toolOutput += `
1935
2257
 
1936
2258
  [RETRY LIMIT REACHED] You have failed ${this.maxRetries} consecutive attempts. STOP trying to execute code. Instead, respond with: 1) What you were trying to do, 2) The errors you encountered, 3) Questions for the user about how to resolve it.`;
1937
2259
  }
1938
2260
  results.push({
1939
2261
  id: call.id,
1940
2262
  name: call.name,
1941
- result: output
2263
+ result: toolOutput
1942
2264
  });
1943
2265
  }
1944
2266
  if (this._stopped) break;
@@ -1960,31 +2282,42 @@ ${this._userSystemPrompt}`;
1960
2282
  totalTokens: this.lastResponseMetadata.totalTokens,
1961
2283
  attempts: 1
1962
2284
  };
2285
+ const codeExecutions = toolCalls.filter((tc) => tc.tool === "execute_code" || tc.tool === "write_and_run_code" || tc.tool === "fix_code" && tc.executed).map((tc) => ({
2286
+ code: tc.code || tc.fixedCode,
2287
+ purpose: this._slugify(tc.purpose),
2288
+ output: tc.stdout || "",
2289
+ stderr: tc.stderr || "",
2290
+ exitCode: tc.exitCode ?? 0
2291
+ }));
1963
2292
  return {
1964
2293
  text: response.text || "",
1965
2294
  codeExecutions,
2295
+ toolCalls,
1966
2296
  usage: this.getLastUsage()
1967
2297
  };
1968
2298
  }
1969
2299
  // ── Streaming ────────────────────────────────────────────────────────────
1970
2300
  /**
1971
2301
  * Send a message and stream the response as events.
1972
- * Automatically handles the code execution loop between streamed rounds.
1973
2302
  *
1974
2303
  * Event types:
1975
2304
  * - `text` — A chunk of the agent's text response
1976
- * - `code` — The agent is about to execute code
1977
- * - `output` — Code finished executing
2305
+ * - `code` — The agent is about to execute code (execute_code or write_and_run_code)
2306
+ * - `output` — Code/bash finished executing
2307
+ * - `write` — The agent wrote code without executing (write_code)
2308
+ * - `fix` — The agent fixed code (fix_code)
2309
+ * - `bash` — The agent is about to run a bash command
2310
+ * - `skill` — The agent loaded a skill
1978
2311
  * - `done` — The agent finished
1979
2312
  *
1980
2313
  * @param {string} message - The user's message
1981
- * @param {Object} [opts={}] - Per-message options
2314
+ * @param {Object} [opts={}]
1982
2315
  * @yields {CodeAgentStreamEvent}
1983
2316
  */
1984
2317
  async *stream(message, opts = {}) {
1985
2318
  if (!this.chatSession) await this.init();
1986
2319
  this._stopped = false;
1987
- const codeExecutions = [];
2320
+ const toolCalls = [];
1988
2321
  let fullText = "";
1989
2322
  let consecutiveFailures = 0;
1990
2323
  let streamResponse = await this._withRetry(() => this.chatSession.sendMessageStream({ message }));
@@ -2001,50 +2334,62 @@ ${this._userSystemPrompt}`;
2001
2334
  }
2002
2335
  }
2003
2336
  if (functionCalls.length === 0) {
2004
- yield {
2005
- type: "done",
2006
- fullText,
2007
- codeExecutions,
2008
- usage: this.getLastUsage()
2009
- };
2337
+ const codeExecutions2 = toolCalls.filter((tc) => tc.tool === "execute_code" || tc.tool === "write_and_run_code" || tc.tool === "fix_code" && tc.executed).map((tc) => ({
2338
+ code: tc.code || tc.fixedCode,
2339
+ purpose: this._slugify(tc.purpose),
2340
+ output: tc.stdout || "",
2341
+ stderr: tc.stderr || "",
2342
+ exitCode: tc.exitCode ?? 0
2343
+ }));
2344
+ yield { type: "done", fullText, codeExecutions: codeExecutions2, toolCalls, usage: this.getLastUsage() };
2010
2345
  return;
2011
2346
  }
2012
2347
  const results = [];
2013
2348
  for (const call of functionCalls) {
2014
2349
  if (this._stopped) break;
2015
- const code = call.args?.code || "";
2016
- const purpose = call.args?.purpose;
2017
- yield { type: "code", code };
2018
- const result = await this._executeCode(code, purpose);
2019
- codeExecutions.push({
2020
- code,
2021
- purpose: this._slugify(purpose),
2022
- output: result.stdout,
2023
- stderr: result.stderr,
2024
- exitCode: result.exitCode
2025
- });
2026
- yield {
2027
- type: "output",
2028
- code,
2029
- stdout: result.stdout,
2030
- stderr: result.stderr,
2031
- exitCode: result.exitCode
2032
- };
2033
- if (result.exitCode !== 0 && !result.denied) {
2034
- consecutiveFailures++;
2035
- } else {
2036
- consecutiveFailures = 0;
2350
+ const toolName = call.name;
2351
+ const toolInput = call.args || {};
2352
+ if (toolName === "write_code") {
2353
+ yield { type: "write", code: toolInput.code, purpose: toolInput.purpose, language: toolInput.language || "javascript" };
2354
+ } else if (toolName === "fix_code") {
2355
+ yield { type: "fix", originalCode: toolInput.original_code, fixedCode: toolInput.fixed_code, explanation: toolInput.explanation };
2356
+ } else if (toolName === "run_bash") {
2357
+ yield { type: "bash", command: toolInput.command };
2358
+ } else if (toolName === "execute_code" || toolName === "write_and_run_code") {
2359
+ yield { type: "code", code: toolInput.code };
2360
+ }
2361
+ const { output, type, data } = await this._handleToolCall(toolName, toolInput);
2362
+ toolCalls.push(data);
2363
+ if (data.stdout !== void 0 || data.stderr !== void 0) {
2364
+ yield {
2365
+ type: "output",
2366
+ code: data.code || data.command || data.fixedCode,
2367
+ stdout: data.stdout || "",
2368
+ stderr: data.stderr || "",
2369
+ exitCode: data.exitCode ?? 0
2370
+ };
2371
+ }
2372
+ if (toolName === "use_skill") {
2373
+ yield { type: "skill", skillName: data.skillName, content: data.content, found: data.found };
2374
+ }
2375
+ const isExecutingTool = EXECUTING_TOOLS.has(toolName) || toolName === "fix_code" && toolInput.execute;
2376
+ if (isExecutingTool) {
2377
+ if (data.exitCode !== 0 && !data.denied) {
2378
+ consecutiveFailures++;
2379
+ } else {
2380
+ consecutiveFailures = 0;
2381
+ }
2037
2382
  }
2038
- let output = this._formatOutput(result);
2383
+ let toolOutput = output;
2039
2384
  if (consecutiveFailures >= this.maxRetries) {
2040
- output += `
2385
+ toolOutput += `
2041
2386
 
2042
2387
  [RETRY LIMIT REACHED] You have failed ${this.maxRetries} consecutive attempts. STOP trying to execute code. Instead, respond with: 1) What you were trying to do, 2) The errors you encountered, 3) Questions for the user about how to resolve it.`;
2043
2388
  }
2044
2389
  results.push({
2045
2390
  id: call.id,
2046
2391
  name: call.name,
2047
- result: output
2392
+ result: toolOutput
2048
2393
  });
2049
2394
  }
2050
2395
  if (this._stopped) break;
@@ -2062,31 +2407,32 @@ ${this._userSystemPrompt}`;
2062
2407
  let warning = "Max tool rounds reached";
2063
2408
  if (this._stopped) warning = "Agent was stopped";
2064
2409
  else if (consecutiveFailures >= this.maxRetries) warning = "Retry limit reached";
2065
- yield {
2066
- type: "done",
2067
- fullText,
2068
- codeExecutions,
2069
- usage: this.getLastUsage(),
2070
- warning
2071
- };
2410
+ const codeExecutions = toolCalls.filter((tc) => tc.tool === "execute_code" || tc.tool === "write_and_run_code" || tc.tool === "fix_code" && tc.executed).map((tc) => ({
2411
+ code: tc.code || tc.fixedCode,
2412
+ purpose: this._slugify(tc.purpose),
2413
+ output: tc.stdout || "",
2414
+ stderr: tc.stderr || "",
2415
+ exitCode: tc.exitCode ?? 0
2416
+ }));
2417
+ yield { type: "done", fullText, codeExecutions, toolCalls, usage: this.getLastUsage(), warning };
2072
2418
  }
2073
2419
  // ── Dump ─────────────────────────────────────────────────────────────────
2074
2420
  /**
2075
- * Returns all code scripts the agent has written across all chat/stream calls.
2076
- * @returns {Array<{fileName: string, script: string}>}
2421
+ * Returns all code scripts and bash commands the agent has executed.
2422
+ * @returns {Array<{fileName: string, purpose: string|null, script: string, filePath: string|null, tool: string}>}
2077
2423
  */
2078
2424
  dump() {
2079
2425
  return this._allExecutions.map((exec, i) => ({
2080
2426
  fileName: exec.purpose ? `agent-${exec.purpose}.mjs` : `script-${i + 1}.mjs`,
2081
2427
  purpose: exec.purpose || null,
2082
2428
  script: exec.code,
2083
- filePath: exec.filePath || null
2429
+ filePath: exec.filePath || null,
2430
+ tool: exec.tool || "execute_code"
2084
2431
  }));
2085
2432
  }
2086
2433
  // ── Stop ─────────────────────────────────────────────────────────────────
2087
2434
  /**
2088
- * Stop the agent before the next code execution.
2089
- * If a child process is currently running, it will be killed.
2435
+ * Stop the agent. Kills any running child process.
2090
2436
  */
2091
2437
  stop() {
2092
2438
  this._stopped = true;