ak-gemini 2.0.9 → 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.
Files changed (4) hide show
  1. package/code-agent.js +435 -173
  2. package/index.cjs +407 -121
  3. package/package.json +1 -1
  4. package/types.d.ts +44 -23
package/index.cjs CHANGED
@@ -1603,6 +1603,7 @@ var import_node_crypto = require("node:crypto");
1603
1603
  var MAX_OUTPUT_CHARS = 5e4;
1604
1604
  var MAX_FILE_TREE_LINES = 500;
1605
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"]);
1606
1607
  var CodeAgent = class extends base_default {
1607
1608
  /**
1608
1609
  * @param {CodeAgentOptions} [options={}]
@@ -1622,53 +1623,146 @@ var CodeAgent = class extends base_default {
1622
1623
  this.keepArtifacts = options.keepArtifacts ?? false;
1623
1624
  this.comments = options.comments ?? false;
1624
1625
  this.maxRetries = options.maxRetries ?? 3;
1626
+ this.skills = options.skills || [];
1627
+ this.envOverview = options.envOverview || "";
1625
1628
  this._codebaseContext = null;
1626
1629
  this._contextGathered = false;
1627
1630
  this._stopped = false;
1628
1631
  this._activeProcess = null;
1629
1632
  this._userSystemPrompt = options.systemPrompt || "";
1630
1633
  this._allExecutions = [];
1631
- this.chatConfig.tools = [{
1632
- 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
+ {
1633
1662
  name: "execute_code",
1634
- 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.",
1635
1664
  parametersJsonSchema: {
1636
1665
  type: "object",
1637
1666
  properties: {
1638
- code: {
1639
- type: "string",
1640
- description: "JavaScript code to execute. Use console.log() for output. You can import any built-in Node.js module."
1641
- },
1642
- purpose: {
1643
- type: "string",
1644
- 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.'
1645
- }
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").' }
1646
1669
  },
1647
1670
  required: ["code"]
1648
1671
  }
1649
- }]
1650
- }];
1651
- this.chatConfig.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
1652
- 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 };
1653
1726
  }
1654
1727
  // ── Init ─────────────────────────────────────────────────────────────────
1655
1728
  /**
1656
- * Initialize the agent: gather codebase context, build system prompt,
1657
- * and create the chat session.
1729
+ * Initialize the agent: load skills, gather codebase context, and build system prompt.
1658
1730
  * @param {boolean} [force=false]
1659
1731
  */
1660
1732
  async init(force = false) {
1661
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()];
1662
1738
  if (!this._contextGathered || force) {
1663
1739
  await this._gatherCodebaseContext();
1664
1740
  }
1665
- const systemPrompt = this._buildSystemPrompt();
1666
- this.chatConfig.systemInstruction = systemPrompt;
1741
+ this.chatConfig.systemInstruction = this._buildSystemPrompt();
1667
1742
  await super.init(force);
1668
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
+ }
1669
1764
  // ── Context Gathering ────────────────────────────────────────────────────
1670
1765
  /**
1671
- * Gather file tree and key file contents from the working directory.
1672
1766
  * @private
1673
1767
  */
1674
1768
  async _gatherCodebaseContext() {
@@ -1717,12 +1811,7 @@ var CodeAgent = class extends base_default {
1717
1811
  this._contextGathered = true;
1718
1812
  }
1719
1813
  /**
1720
- * Resolve an importantFiles entry against the file tree.
1721
- * Supports exact matches and partial (basename/suffix) matches.
1722
1814
  * @private
1723
- * @param {string} filename
1724
- * @param {string[]} fileTreeLines
1725
- * @returns {string|null}
1726
1815
  */
1727
1816
  _resolveImportantFile(filename, fileTreeLines) {
1728
1817
  const exact = fileTreeLines.find((line) => line === filename);
@@ -1733,9 +1822,7 @@ var CodeAgent = class extends base_default {
1733
1822
  return partial || null;
1734
1823
  }
1735
1824
  /**
1736
- * Get file tree using git ls-files.
1737
1825
  * @private
1738
- * @returns {Promise<string>}
1739
1826
  */
1740
1827
  async _getFileTreeGit() {
1741
1828
  return new Promise((resolve2, reject) => {
@@ -1750,12 +1837,7 @@ var CodeAgent = class extends base_default {
1750
1837
  });
1751
1838
  }
1752
1839
  /**
1753
- * Fallback file tree via recursive readdir.
1754
1840
  * @private
1755
- * @param {string} dir
1756
- * @param {number} depth
1757
- * @param {number} maxDepth
1758
- * @returns {Promise<string>}
1759
1841
  */
1760
1842
  async _getFileTreeReaddir(dir, depth, maxDepth) {
1761
1843
  if (depth >= maxDepth) return "";
@@ -1779,17 +1861,39 @@ var CodeAgent = class extends base_default {
1779
1861
  return entries.join("\n");
1780
1862
  }
1781
1863
  /**
1782
- * Build the full system prompt with codebase context.
1783
1864
  * @private
1784
- * @returns {string}
1785
1865
  */
1786
1866
  _buildSystemPrompt() {
1787
1867
  const { fileTree, npmPackages, importantFileContents } = this._codebaseContext || { fileTree: "", npmPackages: [], importantFileContents: [] };
1788
1868
  let prompt = `You are a coding agent working in ${this.workingDirectory}.
1789
1869
 
1790
- ## Instructions
1791
- - Use the execute_code tool to accomplish tasks by writing JavaScript code
1792
- - 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")
1793
1897
  - Your code runs in a Node.js child process with access to all built-in modules
1794
1898
  - IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
1795
1899
  - import fs from 'fs';
@@ -1797,9 +1901,7 @@ var CodeAgent = class extends base_default {
1797
1901
  - import { execSync } from 'child_process';
1798
1902
  - Use console.log() to produce output \u2014 that's how results are returned to you
1799
1903
  - Write efficient scripts that do multiple things per execution when possible
1800
- - For parallel async operations, use Promise.all():
1801
- const [a, b] = await Promise.all([fetchA(), fetchB()]);
1802
- - Read files with fs.readFileSync() when you need to understand their contents
1904
+ - For parallel async operations, use Promise.all()
1803
1905
  - Handle errors in your scripts with try/catch so you get useful error messages
1804
1906
  - Top-level await is supported
1805
1907
  - The working directory is: ${this.workingDirectory}`;
@@ -1843,34 +1945,33 @@ ${content}
1843
1945
 
1844
1946
  ## Additional Instructions
1845
1947
  ${this._userSystemPrompt}`;
1948
+ }
1949
+ if (this.envOverview) {
1950
+ prompt += `
1951
+
1952
+ ## Environment Overview
1953
+ ${this.envOverview}`;
1846
1954
  }
1847
1955
  return prompt;
1848
1956
  }
1849
1957
  // ── Code Execution ───────────────────────────────────────────────────────
1850
1958
  /**
1851
- * Generate a sanitized slug from a purpose string.
1852
1959
  * @private
1853
- * @param {string} [purpose]
1854
- * @returns {string}
1855
1960
  */
1856
1961
  _slugify(purpose) {
1857
1962
  if (!purpose) return (0, import_node_crypto.randomUUID)().slice(0, 8);
1858
1963
  return purpose.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
1859
1964
  }
1860
1965
  /**
1861
- * Execute a JavaScript code string in a child process.
1862
1966
  * @private
1863
- * @param {string} code - JavaScript code to execute
1864
- * @param {string} [purpose] - Short description for file naming
1865
- * @returns {Promise<{stdout: string, stderr: string, exitCode: number, denied?: boolean}>}
1866
1967
  */
1867
- async _executeCode(code, purpose) {
1968
+ async _executeCode(code, purpose, toolName) {
1868
1969
  if (this._stopped) {
1869
1970
  return { stdout: "", stderr: "Agent was stopped", exitCode: -1 };
1870
1971
  }
1871
1972
  if (this.onBeforeExecution) {
1872
1973
  try {
1873
- const allowed = await this.onBeforeExecution(code);
1974
+ const allowed = await this.onBeforeExecution(code, toolName || "execute_code");
1874
1975
  if (allowed === false) {
1875
1976
  return { stdout: "", stderr: "Execution denied by onBeforeExecution callback", exitCode: -1, denied: true };
1876
1977
  }
@@ -1919,7 +2020,8 @@ ${this._userSystemPrompt}`;
1919
2020
  output: result.stdout,
1920
2021
  stderr: result.stderr,
1921
2022
  exitCode: result.exitCode,
1922
- filePath: this.keepArtifacts ? tempFile : null
2023
+ filePath: this.keepArtifacts ? tempFile : null,
2024
+ tool: toolName || "execute_code"
1923
2025
  });
1924
2026
  if (this.onCodeExecution) {
1925
2027
  try {
@@ -1938,11 +2040,75 @@ ${this._userSystemPrompt}`;
1938
2040
  }
1939
2041
  }
1940
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
+ }
1941
2110
  /**
1942
- * Format execution result as a string for the model.
1943
2111
  * @private
1944
- * @param {{stdout: string, stderr: string, exitCode: number}} result
1945
- * @returns {string}
1946
2112
  */
1947
2113
  _formatOutput(result) {
1948
2114
  let output = "";
@@ -1951,20 +2117,121 @@ ${this._userSystemPrompt}`;
1951
2117
  if (result.exitCode !== 0) output += (output ? "\n" : "") + `[EXIT CODE]: ${result.exitCode}`;
1952
2118
  return output || "(no output)";
1953
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
+ }
1954
2221
  // ── Non-Streaming Chat ───────────────────────────────────────────────────
1955
2222
  /**
1956
2223
  * Send a message and get a complete response (non-streaming).
1957
- * Automatically handles the code execution loop.
2224
+ * Automatically handles the multi-tool execution loop.
1958
2225
  *
1959
2226
  * @param {string} message - The user's message
1960
2227
  * @param {Object} [opts={}] - Per-message options
1961
2228
  * @param {Record<string, string>} [opts.labels] - Per-message billing labels
1962
- * @returns {Promise<CodeAgentResponse>} Response with text, codeExecutions, and usage
2229
+ * @returns {Promise<CodeAgentResponse>}
1963
2230
  */
1964
2231
  async chat(message, opts = {}) {
1965
2232
  if (!this.chatSession) await this.init();
1966
2233
  this._stopped = false;
1967
- const codeExecutions = [];
2234
+ const toolCalls = [];
1968
2235
  let consecutiveFailures = 0;
1969
2236
  let response = await this._withRetry(() => this.chatSession.sendMessage({ message }));
1970
2237
  for (let round = 0; round < this.maxRounds; round++) {
@@ -1974,31 +2241,26 @@ ${this._userSystemPrompt}`;
1974
2241
  const results = [];
1975
2242
  for (const call of functionCalls) {
1976
2243
  if (this._stopped) break;
1977
- const code = call.args?.code || "";
1978
- const purpose = call.args?.purpose;
1979
- const result = await this._executeCode(code, purpose);
1980
- codeExecutions.push({
1981
- code,
1982
- purpose: this._slugify(purpose),
1983
- output: result.stdout,
1984
- stderr: result.stderr,
1985
- exitCode: result.exitCode
1986
- });
1987
- if (result.exitCode !== 0 && !result.denied) {
1988
- consecutiveFailures++;
1989
- } else {
1990
- 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
+ }
1991
2253
  }
1992
- let output = this._formatOutput(result);
2254
+ let toolOutput = output;
1993
2255
  if (consecutiveFailures >= this.maxRetries) {
1994
- output += `
2256
+ toolOutput += `
1995
2257
 
1996
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.`;
1997
2259
  }
1998
2260
  results.push({
1999
2261
  id: call.id,
2000
2262
  name: call.name,
2001
- result: output
2263
+ result: toolOutput
2002
2264
  });
2003
2265
  }
2004
2266
  if (this._stopped) break;
@@ -2020,31 +2282,42 @@ ${this._userSystemPrompt}`;
2020
2282
  totalTokens: this.lastResponseMetadata.totalTokens,
2021
2283
  attempts: 1
2022
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
+ }));
2023
2292
  return {
2024
2293
  text: response.text || "",
2025
2294
  codeExecutions,
2295
+ toolCalls,
2026
2296
  usage: this.getLastUsage()
2027
2297
  };
2028
2298
  }
2029
2299
  // ── Streaming ────────────────────────────────────────────────────────────
2030
2300
  /**
2031
2301
  * Send a message and stream the response as events.
2032
- * Automatically handles the code execution loop between streamed rounds.
2033
2302
  *
2034
2303
  * Event types:
2035
2304
  * - `text` — A chunk of the agent's text response
2036
- * - `code` — The agent is about to execute code
2037
- * - `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
2038
2311
  * - `done` — The agent finished
2039
2312
  *
2040
2313
  * @param {string} message - The user's message
2041
- * @param {Object} [opts={}] - Per-message options
2314
+ * @param {Object} [opts={}]
2042
2315
  * @yields {CodeAgentStreamEvent}
2043
2316
  */
2044
2317
  async *stream(message, opts = {}) {
2045
2318
  if (!this.chatSession) await this.init();
2046
2319
  this._stopped = false;
2047
- const codeExecutions = [];
2320
+ const toolCalls = [];
2048
2321
  let fullText = "";
2049
2322
  let consecutiveFailures = 0;
2050
2323
  let streamResponse = await this._withRetry(() => this.chatSession.sendMessageStream({ message }));
@@ -2061,50 +2334,62 @@ ${this._userSystemPrompt}`;
2061
2334
  }
2062
2335
  }
2063
2336
  if (functionCalls.length === 0) {
2064
- yield {
2065
- type: "done",
2066
- fullText,
2067
- codeExecutions,
2068
- usage: this.getLastUsage()
2069
- };
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() };
2070
2345
  return;
2071
2346
  }
2072
2347
  const results = [];
2073
2348
  for (const call of functionCalls) {
2074
2349
  if (this._stopped) break;
2075
- const code = call.args?.code || "";
2076
- const purpose = call.args?.purpose;
2077
- yield { type: "code", code };
2078
- const result = await this._executeCode(code, purpose);
2079
- codeExecutions.push({
2080
- code,
2081
- purpose: this._slugify(purpose),
2082
- output: result.stdout,
2083
- stderr: result.stderr,
2084
- exitCode: result.exitCode
2085
- });
2086
- yield {
2087
- type: "output",
2088
- code,
2089
- stdout: result.stdout,
2090
- stderr: result.stderr,
2091
- exitCode: result.exitCode
2092
- };
2093
- if (result.exitCode !== 0 && !result.denied) {
2094
- consecutiveFailures++;
2095
- } else {
2096
- 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
+ }
2097
2382
  }
2098
- let output = this._formatOutput(result);
2383
+ let toolOutput = output;
2099
2384
  if (consecutiveFailures >= this.maxRetries) {
2100
- output += `
2385
+ toolOutput += `
2101
2386
 
2102
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.`;
2103
2388
  }
2104
2389
  results.push({
2105
2390
  id: call.id,
2106
2391
  name: call.name,
2107
- result: output
2392
+ result: toolOutput
2108
2393
  });
2109
2394
  }
2110
2395
  if (this._stopped) break;
@@ -2122,31 +2407,32 @@ ${this._userSystemPrompt}`;
2122
2407
  let warning = "Max tool rounds reached";
2123
2408
  if (this._stopped) warning = "Agent was stopped";
2124
2409
  else if (consecutiveFailures >= this.maxRetries) warning = "Retry limit reached";
2125
- yield {
2126
- type: "done",
2127
- fullText,
2128
- codeExecutions,
2129
- usage: this.getLastUsage(),
2130
- warning
2131
- };
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 };
2132
2418
  }
2133
2419
  // ── Dump ─────────────────────────────────────────────────────────────────
2134
2420
  /**
2135
- * Returns all code scripts the agent has written across all chat/stream calls.
2136
- * @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}>}
2137
2423
  */
2138
2424
  dump() {
2139
2425
  return this._allExecutions.map((exec, i) => ({
2140
2426
  fileName: exec.purpose ? `agent-${exec.purpose}.mjs` : `script-${i + 1}.mjs`,
2141
2427
  purpose: exec.purpose || null,
2142
2428
  script: exec.code,
2143
- filePath: exec.filePath || null
2429
+ filePath: exec.filePath || null,
2430
+ tool: exec.tool || "execute_code"
2144
2431
  }));
2145
2432
  }
2146
2433
  // ── Stop ─────────────────────────────────────────────────────────────────
2147
2434
  /**
2148
- * Stop the agent before the next code execution.
2149
- * If a child process is currently running, it will be killed.
2435
+ * Stop the agent. Kills any running child process.
2150
2436
  */
2151
2437
  stop() {
2152
2438
  this._stopped = true;