ak-gemini 2.0.9 → 2.1.3

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 (5) hide show
  1. package/chat.js +29 -0
  2. package/code-agent.js +435 -173
  3. package/index.cjs +431 -121
  4. package/package.json +1 -1
  5. package/types.d.ts +52 -23
package/index.cjs CHANGED
@@ -1216,6 +1216,30 @@ var Chat = class extends base_default {
1216
1216
  usage: this.getLastUsage()
1217
1217
  };
1218
1218
  }
1219
+ /**
1220
+ * Send a message and stream the response as events.
1221
+ *
1222
+ * @param {string} message - The user's message
1223
+ * @param {Object} [opts={}] - Per-message options
1224
+ * @yields {ChatStreamEvent}
1225
+ */
1226
+ async *stream(message, opts = {}) {
1227
+ if (!this.chatSession) await this.init();
1228
+ let fullText = "";
1229
+ const streamResponse = await this._withRetry(() => this.chatSession.sendMessageStream({ message }));
1230
+ for await (const chunk of streamResponse) {
1231
+ if (chunk.candidates?.[0]?.content?.parts?.[0]?.text) {
1232
+ const text = chunk.candidates[0].content.parts[0].text;
1233
+ fullText += text;
1234
+ yield { type: "text", text };
1235
+ }
1236
+ }
1237
+ yield {
1238
+ type: "done",
1239
+ fullText,
1240
+ usage: this.getLastUsage()
1241
+ };
1242
+ }
1219
1243
  };
1220
1244
  var chat_default = Chat;
1221
1245
 
@@ -1603,6 +1627,7 @@ var import_node_crypto = require("node:crypto");
1603
1627
  var MAX_OUTPUT_CHARS = 5e4;
1604
1628
  var MAX_FILE_TREE_LINES = 500;
1605
1629
  var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "coverage", ".next", "build", "__pycache__"]);
1630
+ var EXECUTING_TOOLS = /* @__PURE__ */ new Set(["execute_code", "write_and_run_code", "run_bash"]);
1606
1631
  var CodeAgent = class extends base_default {
1607
1632
  /**
1608
1633
  * @param {CodeAgentOptions} [options={}]
@@ -1622,53 +1647,146 @@ var CodeAgent = class extends base_default {
1622
1647
  this.keepArtifacts = options.keepArtifacts ?? false;
1623
1648
  this.comments = options.comments ?? false;
1624
1649
  this.maxRetries = options.maxRetries ?? 3;
1650
+ this.skills = options.skills || [];
1651
+ this.envOverview = options.envOverview || "";
1625
1652
  this._codebaseContext = null;
1626
1653
  this._contextGathered = false;
1627
1654
  this._stopped = false;
1628
1655
  this._activeProcess = null;
1629
1656
  this._userSystemPrompt = options.systemPrompt || "";
1630
1657
  this._allExecutions = [];
1631
- this.chatConfig.tools = [{
1632
- functionDeclarations: [{
1658
+ this._skillRegistry = /* @__PURE__ */ new Map();
1659
+ this.chatConfig.tools = [this._buildToolDefinitions()];
1660
+ this.chatConfig.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
1661
+ logger_default.debug(`CodeAgent created for directory: ${this.workingDirectory}`);
1662
+ }
1663
+ // ── Tool Definitions ─────────────────────────────────────────────────────
1664
+ /**
1665
+ * Build tool definitions in Gemini format.
1666
+ * use_skill is only included when skills are registered.
1667
+ * @private
1668
+ * @returns {{ functionDeclarations: Array<Object> }}
1669
+ */
1670
+ _buildToolDefinitions() {
1671
+ const declarations = [
1672
+ {
1673
+ name: "write_code",
1674
+ description: "Output code without executing it. Use this when you want to show, propose, or present code to the user without running it.",
1675
+ parametersJsonSchema: {
1676
+ type: "object",
1677
+ properties: {
1678
+ code: { type: "string", description: "The code to output." },
1679
+ purpose: { type: "string", description: 'A short 2-4 word slug describing the code (e.g., "api-client", "data-parser").' },
1680
+ language: { type: "string", description: 'Programming language of the code (default: "javascript").' }
1681
+ },
1682
+ required: ["code"]
1683
+ }
1684
+ },
1685
+ {
1633
1686
  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.",
1687
+ 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
1688
  parametersJsonSchema: {
1636
1689
  type: "object",
1637
1690
  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
- }
1691
+ code: { type: "string", description: "JavaScript code to execute. Use console.log() for output. Use import syntax (ES modules)." },
1692
+ purpose: { type: "string", description: 'A short 2-4 word slug describing what this script does (e.g., "read-config", "parse-logs").' }
1646
1693
  },
1647
1694
  required: ["code"]
1648
1695
  }
1649
- }]
1650
- }];
1651
- this.chatConfig.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
1652
- logger_default.debug(`CodeAgent created for directory: ${this.workingDirectory}`);
1696
+ },
1697
+ {
1698
+ name: "write_and_run_code",
1699
+ 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.",
1700
+ parametersJsonSchema: {
1701
+ type: "object",
1702
+ properties: {
1703
+ code: { type: "string", description: "JavaScript code to write and execute. Use console.log() for output. Use import syntax (ES modules)." },
1704
+ purpose: { type: "string", description: 'A short 2-4 word slug describing what this script does (e.g., "fetch-api-data", "generate-report").' }
1705
+ },
1706
+ required: ["code"]
1707
+ }
1708
+ },
1709
+ {
1710
+ name: "fix_code",
1711
+ description: "Fix broken code. Provide the original and fixed versions with an explanation. Optionally execute the fix to verify it works.",
1712
+ parametersJsonSchema: {
1713
+ type: "object",
1714
+ properties: {
1715
+ original_code: { type: "string", description: "The original broken code." },
1716
+ fixed_code: { type: "string", description: "The corrected code." },
1717
+ explanation: { type: "string", description: "Brief explanation of what was wrong and how it was fixed." },
1718
+ execute: { type: "boolean", description: "If true, execute the fixed code to verify it works (default: false)." }
1719
+ },
1720
+ required: ["original_code", "fixed_code"]
1721
+ }
1722
+ },
1723
+ {
1724
+ name: "run_bash",
1725
+ 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.",
1726
+ parametersJsonSchema: {
1727
+ type: "object",
1728
+ properties: {
1729
+ command: { type: "string", description: "The shell command to execute." },
1730
+ purpose: { type: "string", description: 'A short 2-4 word slug describing the command (e.g., "list-files", "install-deps").' }
1731
+ },
1732
+ required: ["command"]
1733
+ }
1734
+ }
1735
+ ];
1736
+ if (this._skillRegistry && this._skillRegistry.size > 0) {
1737
+ declarations.push({
1738
+ name: "use_skill",
1739
+ description: `Load a skill by name to get instructions, templates, or patterns. Available skills: ${[...this._skillRegistry.keys()].join(", ")}`,
1740
+ parametersJsonSchema: {
1741
+ type: "object",
1742
+ properties: {
1743
+ skill_name: { type: "string", description: "The name of the skill to load." }
1744
+ },
1745
+ required: ["skill_name"]
1746
+ }
1747
+ });
1748
+ }
1749
+ return { functionDeclarations: declarations };
1653
1750
  }
1654
1751
  // ── Init ─────────────────────────────────────────────────────────────────
1655
1752
  /**
1656
- * Initialize the agent: gather codebase context, build system prompt,
1657
- * and create the chat session.
1753
+ * Initialize the agent: load skills, gather codebase context, and build system prompt.
1658
1754
  * @param {boolean} [force=false]
1659
1755
  */
1660
1756
  async init(force = false) {
1661
1757
  if (this.chatSession && !force) return;
1758
+ if (this.skills.length > 0 && (this._skillRegistry.size === 0 || force)) {
1759
+ await this._loadSkills();
1760
+ }
1761
+ this.chatConfig.tools = [this._buildToolDefinitions()];
1662
1762
  if (!this._contextGathered || force) {
1663
1763
  await this._gatherCodebaseContext();
1664
1764
  }
1665
- const systemPrompt = this._buildSystemPrompt();
1666
- this.chatConfig.systemInstruction = systemPrompt;
1765
+ this.chatConfig.systemInstruction = this._buildSystemPrompt();
1667
1766
  await super.init(force);
1668
1767
  }
1768
+ // ── Skill Loading ────────────────────────────────────────────────────────
1769
+ /**
1770
+ * Load skill files into the skill registry.
1771
+ * @private
1772
+ */
1773
+ async _loadSkills() {
1774
+ this._skillRegistry.clear();
1775
+ for (const filePath of this.skills) {
1776
+ try {
1777
+ const content = await (0, import_promises2.readFile)(filePath, "utf-8");
1778
+ let name = (0, import_node_path.basename)(filePath).replace(/\.md$/i, "");
1779
+ const fmMatch = content.match(/^---\s*\n[\s\S]*?^name:\s*(.+)$/m);
1780
+ if (fmMatch) name = fmMatch[1].trim();
1781
+ this._skillRegistry.set(name, { name, content, path: filePath });
1782
+ logger_default.debug(`Loaded skill: ${name} from ${filePath}`);
1783
+ } catch (e) {
1784
+ logger_default.warn(`skills: could not load "${filePath}": ${e.message}`);
1785
+ }
1786
+ }
1787
+ }
1669
1788
  // ── Context Gathering ────────────────────────────────────────────────────
1670
1789
  /**
1671
- * Gather file tree and key file contents from the working directory.
1672
1790
  * @private
1673
1791
  */
1674
1792
  async _gatherCodebaseContext() {
@@ -1717,12 +1835,7 @@ var CodeAgent = class extends base_default {
1717
1835
  this._contextGathered = true;
1718
1836
  }
1719
1837
  /**
1720
- * Resolve an importantFiles entry against the file tree.
1721
- * Supports exact matches and partial (basename/suffix) matches.
1722
1838
  * @private
1723
- * @param {string} filename
1724
- * @param {string[]} fileTreeLines
1725
- * @returns {string|null}
1726
1839
  */
1727
1840
  _resolveImportantFile(filename, fileTreeLines) {
1728
1841
  const exact = fileTreeLines.find((line) => line === filename);
@@ -1733,9 +1846,7 @@ var CodeAgent = class extends base_default {
1733
1846
  return partial || null;
1734
1847
  }
1735
1848
  /**
1736
- * Get file tree using git ls-files.
1737
1849
  * @private
1738
- * @returns {Promise<string>}
1739
1850
  */
1740
1851
  async _getFileTreeGit() {
1741
1852
  return new Promise((resolve2, reject) => {
@@ -1750,12 +1861,7 @@ var CodeAgent = class extends base_default {
1750
1861
  });
1751
1862
  }
1752
1863
  /**
1753
- * Fallback file tree via recursive readdir.
1754
1864
  * @private
1755
- * @param {string} dir
1756
- * @param {number} depth
1757
- * @param {number} maxDepth
1758
- * @returns {Promise<string>}
1759
1865
  */
1760
1866
  async _getFileTreeReaddir(dir, depth, maxDepth) {
1761
1867
  if (depth >= maxDepth) return "";
@@ -1779,17 +1885,39 @@ var CodeAgent = class extends base_default {
1779
1885
  return entries.join("\n");
1780
1886
  }
1781
1887
  /**
1782
- * Build the full system prompt with codebase context.
1783
1888
  * @private
1784
- * @returns {string}
1785
1889
  */
1786
1890
  _buildSystemPrompt() {
1787
1891
  const { fileTree, npmPackages, importantFileContents } = this._codebaseContext || { fileTree: "", npmPackages: [], importantFileContents: [] };
1788
1892
  let prompt = `You are a coding agent working in ${this.workingDirectory}.
1789
1893
 
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
1894
+ ## Available Tools
1895
+
1896
+ ### write_code
1897
+ Output code without executing it. Use when showing, proposing, or presenting code to the user.
1898
+
1899
+ ### execute_code
1900
+ 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.
1901
+
1902
+ ### write_and_run_code
1903
+ Write a fresh solution from scratch and execute it in one step. The autonomous, end-to-end tool for solving problems with code.
1904
+
1905
+ ### fix_code
1906
+ Fix broken code by providing original and fixed versions. Set execute=true to verify the fix works.
1907
+
1908
+ ### run_bash
1909
+ Run shell commands directly (e.g., ls, grep, curl, git, npm, cat). Prefer this over execute_code for simple shell operations.`;
1910
+ if (this._skillRegistry.size > 0) {
1911
+ prompt += `
1912
+
1913
+ ### use_skill
1914
+ Load a skill by name to get detailed instructions and templates. Available skills: ${[...this._skillRegistry.keys()].join(", ")}`;
1915
+ }
1916
+ prompt += `
1917
+
1918
+ ## Code Execution Rules
1919
+ These rules apply when using execute_code, write_and_run_code, or fix_code (with execute=true):
1920
+ - Always provide a short descriptive \`purpose\` parameter (2-4 word slug like "read-config")
1793
1921
  - Your code runs in a Node.js child process with access to all built-in modules
1794
1922
  - IMPORTANT: Your code runs as an ES module (.mjs). Use import syntax, NOT require():
1795
1923
  - import fs from 'fs';
@@ -1797,9 +1925,7 @@ var CodeAgent = class extends base_default {
1797
1925
  - import { execSync } from 'child_process';
1798
1926
  - Use console.log() to produce output \u2014 that's how results are returned to you
1799
1927
  - 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
1928
+ - For parallel async operations, use Promise.all()
1803
1929
  - Handle errors in your scripts with try/catch so you get useful error messages
1804
1930
  - Top-level await is supported
1805
1931
  - The working directory is: ${this.workingDirectory}`;
@@ -1843,34 +1969,33 @@ ${content}
1843
1969
 
1844
1970
  ## Additional Instructions
1845
1971
  ${this._userSystemPrompt}`;
1972
+ }
1973
+ if (this.envOverview) {
1974
+ prompt += `
1975
+
1976
+ ## Environment Overview
1977
+ ${this.envOverview}`;
1846
1978
  }
1847
1979
  return prompt;
1848
1980
  }
1849
1981
  // ── Code Execution ───────────────────────────────────────────────────────
1850
1982
  /**
1851
- * Generate a sanitized slug from a purpose string.
1852
1983
  * @private
1853
- * @param {string} [purpose]
1854
- * @returns {string}
1855
1984
  */
1856
1985
  _slugify(purpose) {
1857
1986
  if (!purpose) return (0, import_node_crypto.randomUUID)().slice(0, 8);
1858
1987
  return purpose.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
1859
1988
  }
1860
1989
  /**
1861
- * Execute a JavaScript code string in a child process.
1862
1990
  * @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
1991
  */
1867
- async _executeCode(code, purpose) {
1992
+ async _executeCode(code, purpose, toolName) {
1868
1993
  if (this._stopped) {
1869
1994
  return { stdout: "", stderr: "Agent was stopped", exitCode: -1 };
1870
1995
  }
1871
1996
  if (this.onBeforeExecution) {
1872
1997
  try {
1873
- const allowed = await this.onBeforeExecution(code);
1998
+ const allowed = await this.onBeforeExecution(code, toolName || "execute_code");
1874
1999
  if (allowed === false) {
1875
2000
  return { stdout: "", stderr: "Execution denied by onBeforeExecution callback", exitCode: -1, denied: true };
1876
2001
  }
@@ -1919,7 +2044,8 @@ ${this._userSystemPrompt}`;
1919
2044
  output: result.stdout,
1920
2045
  stderr: result.stderr,
1921
2046
  exitCode: result.exitCode,
1922
- filePath: this.keepArtifacts ? tempFile : null
2047
+ filePath: this.keepArtifacts ? tempFile : null,
2048
+ tool: toolName || "execute_code"
1923
2049
  });
1924
2050
  if (this.onCodeExecution) {
1925
2051
  try {
@@ -1938,11 +2064,75 @@ ${this._userSystemPrompt}`;
1938
2064
  }
1939
2065
  }
1940
2066
  }
2067
+ // ── Bash Execution ───────────────────────────────────────────────────────
2068
+ /**
2069
+ * Execute a bash command in the working directory.
2070
+ * @private
2071
+ */
2072
+ async _executeBash(command, purpose) {
2073
+ if (this._stopped) {
2074
+ return { stdout: "", stderr: "Agent was stopped", exitCode: -1 };
2075
+ }
2076
+ if (this.onBeforeExecution) {
2077
+ try {
2078
+ const allowed = await this.onBeforeExecution(command, "run_bash");
2079
+ if (allowed === false) {
2080
+ return { stdout: "", stderr: "Execution denied by onBeforeExecution callback", exitCode: -1, denied: true };
2081
+ }
2082
+ } catch (e) {
2083
+ logger_default.warn(`onBeforeExecution callback error: ${e.message}`);
2084
+ }
2085
+ }
2086
+ const result = await new Promise((resolve2) => {
2087
+ const child = (0, import_node_child_process.execFile)("bash", ["-c", command], {
2088
+ cwd: this.workingDirectory,
2089
+ timeout: this.timeout,
2090
+ env: process.env,
2091
+ maxBuffer: 10 * 1024 * 1024
2092
+ }, (err, stdout, stderr) => {
2093
+ this._activeProcess = null;
2094
+ if (err) {
2095
+ resolve2({
2096
+ stdout: err.stdout || stdout || "",
2097
+ stderr: (err.stderr || stderr || "") + (err.killed ? "\n[EXECUTION TIMED OUT]" : ""),
2098
+ exitCode: err.code || 1
2099
+ });
2100
+ } else {
2101
+ resolve2({ stdout: stdout || "", stderr: stderr || "", exitCode: 0 });
2102
+ }
2103
+ });
2104
+ this._activeProcess = child;
2105
+ });
2106
+ const totalLen = result.stdout.length + result.stderr.length;
2107
+ if (totalLen > MAX_OUTPUT_CHARS) {
2108
+ const half = Math.floor(MAX_OUTPUT_CHARS / 2);
2109
+ if (result.stdout.length > half) {
2110
+ result.stdout = result.stdout.slice(0, half) + "\n...[OUTPUT TRUNCATED]";
2111
+ }
2112
+ if (result.stderr.length > half) {
2113
+ result.stderr = result.stderr.slice(0, half) + "\n...[STDERR TRUNCATED]";
2114
+ }
2115
+ }
2116
+ this._allExecutions.push({
2117
+ code: command,
2118
+ purpose: purpose || null,
2119
+ output: result.stdout,
2120
+ stderr: result.stderr,
2121
+ exitCode: result.exitCode,
2122
+ filePath: null,
2123
+ tool: "run_bash"
2124
+ });
2125
+ if (this.onCodeExecution) {
2126
+ try {
2127
+ this.onCodeExecution(command, result);
2128
+ } catch (e) {
2129
+ logger_default.warn(`onCodeExecution callback error: ${e.message}`);
2130
+ }
2131
+ }
2132
+ return result;
2133
+ }
1941
2134
  /**
1942
- * Format execution result as a string for the model.
1943
2135
  * @private
1944
- * @param {{stdout: string, stderr: string, exitCode: number}} result
1945
- * @returns {string}
1946
2136
  */
1947
2137
  _formatOutput(result) {
1948
2138
  let output = "";
@@ -1951,20 +2141,121 @@ ${this._userSystemPrompt}`;
1951
2141
  if (result.exitCode !== 0) output += (output ? "\n" : "") + `[EXIT CODE]: ${result.exitCode}`;
1952
2142
  return output || "(no output)";
1953
2143
  }
2144
+ // ── Tool Call Dispatch ───────────────────────────────────────────────────
2145
+ /**
2146
+ * Handle a tool call by name, dispatching to the appropriate handler.
2147
+ * @private
2148
+ * @param {string} name - Tool name
2149
+ * @param {Object} input - Tool arguments
2150
+ * @returns {Promise<{output: string, type: string, data: Object}>}
2151
+ */
2152
+ async _handleToolCall(name, input) {
2153
+ switch (name) {
2154
+ case "execute_code":
2155
+ case "write_and_run_code": {
2156
+ const result = await this._executeCode(input.code || "", input.purpose, name);
2157
+ return {
2158
+ output: this._formatOutput(result),
2159
+ type: "code_execution",
2160
+ data: {
2161
+ tool: name,
2162
+ code: input.code || "",
2163
+ purpose: input.purpose,
2164
+ stdout: result.stdout,
2165
+ stderr: result.stderr,
2166
+ exitCode: result.exitCode,
2167
+ denied: result.denied
2168
+ }
2169
+ };
2170
+ }
2171
+ case "write_code": {
2172
+ return {
2173
+ output: "Code written successfully.",
2174
+ type: "write",
2175
+ data: {
2176
+ tool: "write_code",
2177
+ code: input.code || "",
2178
+ purpose: input.purpose,
2179
+ language: input.language || "javascript"
2180
+ }
2181
+ };
2182
+ }
2183
+ case "fix_code": {
2184
+ let execResult = null;
2185
+ if (input.execute) {
2186
+ execResult = await this._executeCode(input.fixed_code || "", "fix", "fix_code");
2187
+ }
2188
+ return {
2189
+ output: input.execute ? this._formatOutput(execResult) : "Fix recorded.",
2190
+ type: "fix",
2191
+ data: {
2192
+ tool: "fix_code",
2193
+ originalCode: input.original_code || "",
2194
+ fixedCode: input.fixed_code || "",
2195
+ explanation: input.explanation,
2196
+ executed: !!input.execute,
2197
+ stdout: execResult?.stdout,
2198
+ stderr: execResult?.stderr,
2199
+ exitCode: execResult?.exitCode,
2200
+ denied: execResult?.denied
2201
+ }
2202
+ };
2203
+ }
2204
+ case "run_bash": {
2205
+ const result = await this._executeBash(input.command || "", input.purpose);
2206
+ return {
2207
+ output: this._formatOutput(result),
2208
+ type: "bash",
2209
+ data: {
2210
+ tool: "run_bash",
2211
+ command: input.command || "",
2212
+ purpose: input.purpose,
2213
+ stdout: result.stdout,
2214
+ stderr: result.stderr,
2215
+ exitCode: result.exitCode,
2216
+ denied: result.denied
2217
+ }
2218
+ };
2219
+ }
2220
+ case "use_skill": {
2221
+ const skillName = input.skill_name || "";
2222
+ const skill = this._skillRegistry.get(skillName);
2223
+ if (!skill) {
2224
+ const available = [...this._skillRegistry.keys()].join(", ");
2225
+ return {
2226
+ output: `Skill "${skillName}" not found. Available skills: ${available || "(none)"}`,
2227
+ type: "skill",
2228
+ data: { tool: "use_skill", skillName, found: false }
2229
+ };
2230
+ }
2231
+ return {
2232
+ output: skill.content,
2233
+ type: "skill",
2234
+ data: { tool: "use_skill", skillName: skill.name, content: skill.content, found: true }
2235
+ };
2236
+ }
2237
+ default:
2238
+ return {
2239
+ output: `Unknown tool: ${name}`,
2240
+ type: "unknown",
2241
+ data: { tool: name }
2242
+ };
2243
+ }
2244
+ }
1954
2245
  // ── Non-Streaming Chat ───────────────────────────────────────────────────
1955
2246
  /**
1956
2247
  * Send a message and get a complete response (non-streaming).
1957
- * Automatically handles the code execution loop.
2248
+ * Automatically handles the multi-tool execution loop.
1958
2249
  *
1959
2250
  * @param {string} message - The user's message
1960
2251
  * @param {Object} [opts={}] - Per-message options
1961
2252
  * @param {Record<string, string>} [opts.labels] - Per-message billing labels
1962
- * @returns {Promise<CodeAgentResponse>} Response with text, codeExecutions, and usage
2253
+ * @returns {Promise<CodeAgentResponse>}
1963
2254
  */
1964
2255
  async chat(message, opts = {}) {
1965
2256
  if (!this.chatSession) await this.init();
1966
2257
  this._stopped = false;
1967
- const codeExecutions = [];
2258
+ const toolCalls = [];
1968
2259
  let consecutiveFailures = 0;
1969
2260
  let response = await this._withRetry(() => this.chatSession.sendMessage({ message }));
1970
2261
  for (let round = 0; round < this.maxRounds; round++) {
@@ -1974,31 +2265,26 @@ ${this._userSystemPrompt}`;
1974
2265
  const results = [];
1975
2266
  for (const call of functionCalls) {
1976
2267
  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;
2268
+ const { output, type, data } = await this._handleToolCall(call.name, call.args || {});
2269
+ toolCalls.push(data);
2270
+ const isExecutingTool = EXECUTING_TOOLS.has(call.name) || call.name === "fix_code" && call.args?.execute;
2271
+ if (isExecutingTool) {
2272
+ if (data.exitCode !== 0 && !data.denied) {
2273
+ consecutiveFailures++;
2274
+ } else {
2275
+ consecutiveFailures = 0;
2276
+ }
1991
2277
  }
1992
- let output = this._formatOutput(result);
2278
+ let toolOutput = output;
1993
2279
  if (consecutiveFailures >= this.maxRetries) {
1994
- output += `
2280
+ toolOutput += `
1995
2281
 
1996
2282
  [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
2283
  }
1998
2284
  results.push({
1999
2285
  id: call.id,
2000
2286
  name: call.name,
2001
- result: output
2287
+ result: toolOutput
2002
2288
  });
2003
2289
  }
2004
2290
  if (this._stopped) break;
@@ -2020,31 +2306,42 @@ ${this._userSystemPrompt}`;
2020
2306
  totalTokens: this.lastResponseMetadata.totalTokens,
2021
2307
  attempts: 1
2022
2308
  };
2309
+ const codeExecutions = toolCalls.filter((tc) => tc.tool === "execute_code" || tc.tool === "write_and_run_code" || tc.tool === "fix_code" && tc.executed).map((tc) => ({
2310
+ code: tc.code || tc.fixedCode,
2311
+ purpose: this._slugify(tc.purpose),
2312
+ output: tc.stdout || "",
2313
+ stderr: tc.stderr || "",
2314
+ exitCode: tc.exitCode ?? 0
2315
+ }));
2023
2316
  return {
2024
2317
  text: response.text || "",
2025
2318
  codeExecutions,
2319
+ toolCalls,
2026
2320
  usage: this.getLastUsage()
2027
2321
  };
2028
2322
  }
2029
2323
  // ── Streaming ────────────────────────────────────────────────────────────
2030
2324
  /**
2031
2325
  * Send a message and stream the response as events.
2032
- * Automatically handles the code execution loop between streamed rounds.
2033
2326
  *
2034
2327
  * Event types:
2035
2328
  * - `text` — A chunk of the agent's text response
2036
- * - `code` — The agent is about to execute code
2037
- * - `output` — Code finished executing
2329
+ * - `code` — The agent is about to execute code (execute_code or write_and_run_code)
2330
+ * - `output` — Code/bash finished executing
2331
+ * - `write` — The agent wrote code without executing (write_code)
2332
+ * - `fix` — The agent fixed code (fix_code)
2333
+ * - `bash` — The agent is about to run a bash command
2334
+ * - `skill` — The agent loaded a skill
2038
2335
  * - `done` — The agent finished
2039
2336
  *
2040
2337
  * @param {string} message - The user's message
2041
- * @param {Object} [opts={}] - Per-message options
2338
+ * @param {Object} [opts={}]
2042
2339
  * @yields {CodeAgentStreamEvent}
2043
2340
  */
2044
2341
  async *stream(message, opts = {}) {
2045
2342
  if (!this.chatSession) await this.init();
2046
2343
  this._stopped = false;
2047
- const codeExecutions = [];
2344
+ const toolCalls = [];
2048
2345
  let fullText = "";
2049
2346
  let consecutiveFailures = 0;
2050
2347
  let streamResponse = await this._withRetry(() => this.chatSession.sendMessageStream({ message }));
@@ -2061,50 +2358,62 @@ ${this._userSystemPrompt}`;
2061
2358
  }
2062
2359
  }
2063
2360
  if (functionCalls.length === 0) {
2064
- yield {
2065
- type: "done",
2066
- fullText,
2067
- codeExecutions,
2068
- usage: this.getLastUsage()
2069
- };
2361
+ const codeExecutions2 = toolCalls.filter((tc) => tc.tool === "execute_code" || tc.tool === "write_and_run_code" || tc.tool === "fix_code" && tc.executed).map((tc) => ({
2362
+ code: tc.code || tc.fixedCode,
2363
+ purpose: this._slugify(tc.purpose),
2364
+ output: tc.stdout || "",
2365
+ stderr: tc.stderr || "",
2366
+ exitCode: tc.exitCode ?? 0
2367
+ }));
2368
+ yield { type: "done", fullText, codeExecutions: codeExecutions2, toolCalls, usage: this.getLastUsage() };
2070
2369
  return;
2071
2370
  }
2072
2371
  const results = [];
2073
2372
  for (const call of functionCalls) {
2074
2373
  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;
2374
+ const toolName = call.name;
2375
+ const toolInput = call.args || {};
2376
+ if (toolName === "write_code") {
2377
+ yield { type: "write", code: toolInput.code, purpose: toolInput.purpose, language: toolInput.language || "javascript" };
2378
+ } else if (toolName === "fix_code") {
2379
+ yield { type: "fix", originalCode: toolInput.original_code, fixedCode: toolInput.fixed_code, explanation: toolInput.explanation };
2380
+ } else if (toolName === "run_bash") {
2381
+ yield { type: "bash", command: toolInput.command };
2382
+ } else if (toolName === "execute_code" || toolName === "write_and_run_code") {
2383
+ yield { type: "code", code: toolInput.code };
2384
+ }
2385
+ const { output, type, data } = await this._handleToolCall(toolName, toolInput);
2386
+ toolCalls.push(data);
2387
+ if (data.stdout !== void 0 || data.stderr !== void 0) {
2388
+ yield {
2389
+ type: "output",
2390
+ code: data.code || data.command || data.fixedCode,
2391
+ stdout: data.stdout || "",
2392
+ stderr: data.stderr || "",
2393
+ exitCode: data.exitCode ?? 0
2394
+ };
2395
+ }
2396
+ if (toolName === "use_skill") {
2397
+ yield { type: "skill", skillName: data.skillName, content: data.content, found: data.found };
2097
2398
  }
2098
- let output = this._formatOutput(result);
2399
+ const isExecutingTool = EXECUTING_TOOLS.has(toolName) || toolName === "fix_code" && toolInput.execute;
2400
+ if (isExecutingTool) {
2401
+ if (data.exitCode !== 0 && !data.denied) {
2402
+ consecutiveFailures++;
2403
+ } else {
2404
+ consecutiveFailures = 0;
2405
+ }
2406
+ }
2407
+ let toolOutput = output;
2099
2408
  if (consecutiveFailures >= this.maxRetries) {
2100
- output += `
2409
+ toolOutput += `
2101
2410
 
2102
2411
  [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
2412
  }
2104
2413
  results.push({
2105
2414
  id: call.id,
2106
2415
  name: call.name,
2107
- result: output
2416
+ result: toolOutput
2108
2417
  });
2109
2418
  }
2110
2419
  if (this._stopped) break;
@@ -2122,31 +2431,32 @@ ${this._userSystemPrompt}`;
2122
2431
  let warning = "Max tool rounds reached";
2123
2432
  if (this._stopped) warning = "Agent was stopped";
2124
2433
  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
- };
2434
+ const codeExecutions = toolCalls.filter((tc) => tc.tool === "execute_code" || tc.tool === "write_and_run_code" || tc.tool === "fix_code" && tc.executed).map((tc) => ({
2435
+ code: tc.code || tc.fixedCode,
2436
+ purpose: this._slugify(tc.purpose),
2437
+ output: tc.stdout || "",
2438
+ stderr: tc.stderr || "",
2439
+ exitCode: tc.exitCode ?? 0
2440
+ }));
2441
+ yield { type: "done", fullText, codeExecutions, toolCalls, usage: this.getLastUsage(), warning };
2132
2442
  }
2133
2443
  // ── Dump ─────────────────────────────────────────────────────────────────
2134
2444
  /**
2135
- * Returns all code scripts the agent has written across all chat/stream calls.
2136
- * @returns {Array<{fileName: string, script: string}>}
2445
+ * Returns all code scripts and bash commands the agent has executed.
2446
+ * @returns {Array<{fileName: string, purpose: string|null, script: string, filePath: string|null, tool: string}>}
2137
2447
  */
2138
2448
  dump() {
2139
2449
  return this._allExecutions.map((exec, i) => ({
2140
2450
  fileName: exec.purpose ? `agent-${exec.purpose}.mjs` : `script-${i + 1}.mjs`,
2141
2451
  purpose: exec.purpose || null,
2142
2452
  script: exec.code,
2143
- filePath: exec.filePath || null
2453
+ filePath: exec.filePath || null,
2454
+ tool: exec.tool || "execute_code"
2144
2455
  }));
2145
2456
  }
2146
2457
  // ── Stop ─────────────────────────────────────────────────────────────────
2147
2458
  /**
2148
- * Stop the agent before the next code execution.
2149
- * If a child process is currently running, it will be killed.
2459
+ * Stop the agent. Kills any running child process.
2150
2460
  */
2151
2461
  stop() {
2152
2462
  this._stopped = true;