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.
- package/code-agent.js +435 -173
- package/index.cjs +407 -121
- package/package.json +1 -1
- 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.
|
|
1632
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
1652
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
1791
|
-
|
|
1792
|
-
|
|
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
|
|
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>}
|
|
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
|
|
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
|
|
1978
|
-
|
|
1979
|
-
const
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
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
|
|
2254
|
+
let toolOutput = output;
|
|
1993
2255
|
if (consecutiveFailures >= this.maxRetries) {
|
|
1994
|
-
|
|
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:
|
|
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={}]
|
|
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
|
|
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
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
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
|
|
2076
|
-
const
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
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
|
|
2383
|
+
let toolOutput = output;
|
|
2099
2384
|
if (consecutiveFailures >= this.maxRetries) {
|
|
2100
|
-
|
|
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:
|
|
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
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
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
|
|
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
|
|
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;
|