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