@samrahimi/smol-js 0.7.0 → 0.7.2
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/dist/cli.js +414 -55
- package/dist/cli.js.map +1 -1
- package/dist/index.d.mts +94 -16
- package/dist/index.d.ts +94 -16
- package/dist/index.js +413 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +411 -51
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/toolHarness.ts +73 -0
package/dist/index.js
CHANGED
|
@@ -49,6 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
Orchestrator: () => Orchestrator,
|
|
50
50
|
ProxyTool: () => ProxyTool,
|
|
51
51
|
ReadFileTool: () => ReadFileTool,
|
|
52
|
+
TerminalAgent: () => TerminalAgent,
|
|
52
53
|
Tool: () => Tool,
|
|
53
54
|
ToolUseAgent: () => ToolUseAgent,
|
|
54
55
|
UserInputTool: () => UserInputTool,
|
|
@@ -792,7 +793,7 @@ Total time: ${(duration / 1e3).toFixed(2)}s`);
|
|
|
792
793
|
}
|
|
793
794
|
/** Sleep for a specified duration */
|
|
794
795
|
sleep(ms) {
|
|
795
|
-
return new Promise((
|
|
796
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
796
797
|
}
|
|
797
798
|
};
|
|
798
799
|
|
|
@@ -1461,12 +1462,12 @@ var UserInputTool = class extends Tool {
|
|
|
1461
1462
|
input: process.stdin,
|
|
1462
1463
|
output: process.stdout
|
|
1463
1464
|
});
|
|
1464
|
-
return new Promise((
|
|
1465
|
+
return new Promise((resolve7) => {
|
|
1465
1466
|
rl.question(`
|
|
1466
1467
|
[Agent asks]: ${question}
|
|
1467
1468
|
Your response: `, (answer) => {
|
|
1468
1469
|
rl.close();
|
|
1469
|
-
|
|
1470
|
+
resolve7(answer);
|
|
1470
1471
|
});
|
|
1471
1472
|
});
|
|
1472
1473
|
}
|
|
@@ -2108,6 +2109,369 @@ Please try a different approach.`
|
|
|
2108
2109
|
}
|
|
2109
2110
|
};
|
|
2110
2111
|
|
|
2112
|
+
// src/agents/TerminalAgent.ts
|
|
2113
|
+
var import_child_process = require("child_process");
|
|
2114
|
+
|
|
2115
|
+
// src/prompts/terminalAgent.ts
|
|
2116
|
+
function generateTerminalAgentSystemPrompt(variables) {
|
|
2117
|
+
const { customInstructions, hasSubAgents, subAgentDescriptions } = variables;
|
|
2118
|
+
let delegationSection = "";
|
|
2119
|
+
if (hasSubAgents) {
|
|
2120
|
+
delegationSection = `
|
|
2121
|
+
## Delegation to Sub-Agents
|
|
2122
|
+
|
|
2123
|
+
You can delegate tasks to specialized sub-agents when terminal commands alone
|
|
2124
|
+
are not sufficient. Available sub-agents:
|
|
2125
|
+
|
|
2126
|
+
${subAgentDescriptions}
|
|
2127
|
+
|
|
2128
|
+
To delegate, call the sub-agent tool with a clear task description. Wait for
|
|
2129
|
+
its result before continuing. Sub-agents handle their own tool calls internally.
|
|
2130
|
+
`;
|
|
2131
|
+
}
|
|
2132
|
+
return `You are a terminal operations agent running on macOS. You accomplish tasks
|
|
2133
|
+
by reasoning about what shell commands to run and executing them one or more at
|
|
2134
|
+
a time.
|
|
2135
|
+
|
|
2136
|
+
## How You Work
|
|
2137
|
+
|
|
2138
|
+
You follow a ReAct (Reasoning + Acting) loop:
|
|
2139
|
+
1. **Think**: Analyze the task and decide what commands to run next.
|
|
2140
|
+
2. **Act**: Emit one or more shell commands inside a fenced code block.
|
|
2141
|
+
3. **Observe**: Review the command output (stdout, stderr, exit code) that the
|
|
2142
|
+
framework feeds back to you.
|
|
2143
|
+
4. Repeat until the task is complete, then signal your final answer (see below).
|
|
2144
|
+
|
|
2145
|
+
## Emitting Commands
|
|
2146
|
+
|
|
2147
|
+
Place your shell commands inside a fenced sh code block. You may include
|
|
2148
|
+
multiple commands separated by newlines. Each command is executed
|
|
2149
|
+
sequentially in its own shell invocation on the user's macOS terminal:
|
|
2150
|
+
|
|
2151
|
+
\`\`\`sh
|
|
2152
|
+
echo "hello"
|
|
2153
|
+
\`\`\`
|
|
2154
|
+
|
|
2155
|
+
\`\`\`sh
|
|
2156
|
+
ls -la ~/Documents
|
|
2157
|
+
pwd
|
|
2158
|
+
\`\`\`
|
|
2159
|
+
|
|
2160
|
+
**Important**: Each code block is treated as a batch. Commands within a single
|
|
2161
|
+
block run sequentially. The output of the entire block is returned as one
|
|
2162
|
+
observation. If you need to inspect intermediate output before proceeding,
|
|
2163
|
+
use separate blocks across steps.
|
|
2164
|
+
|
|
2165
|
+
## Rules
|
|
2166
|
+
|
|
2167
|
+
1. **Safety first**: Before running anything destructive (rm, mv on important
|
|
2168
|
+
files, format commands, etc.), explain what you are about to do in your
|
|
2169
|
+
reasoning so the user can read it during the 5-second delay and abort with
|
|
2170
|
+
Ctrl+C if needed.
|
|
2171
|
+
|
|
2172
|
+
2. **Signalling completion**: When the task is done, do NOT emit any more
|
|
2173
|
+
\`\`\`sh blocks. Instead, write your summary on a line that starts with
|
|
2174
|
+
the exact marker:
|
|
2175
|
+
|
|
2176
|
+
FINAL_ANSWER: <your summary here>
|
|
2177
|
+
|
|
2178
|
+
Everything after "FINAL_ANSWER: " (including multiple lines) is captured
|
|
2179
|
+
as your final answer. This is how you tell the framework you are finished.
|
|
2180
|
+
|
|
2181
|
+
3. **Handle errors**: If a command fails (non-zero exit code or stderr output),
|
|
2182
|
+
analyze what went wrong and try a corrective approach. Do not repeat the
|
|
2183
|
+
exact same command.
|
|
2184
|
+
|
|
2185
|
+
4. **Be explicit about paths**: Use absolute paths or \`cd\` explicitly. Do not
|
|
2186
|
+
assume the working directory persists between steps (it does not).
|
|
2187
|
+
|
|
2188
|
+
5. **macOS conventions**: Use macOS / BSD variants of commands (e.g. \`gstat\`
|
|
2189
|
+
may not exist; use \`stat\` with the right flags). Prefer Homebrew paths
|
|
2190
|
+
(\`/opt/homebrew/bin\`) for installed tools. Use \`sw_vers\` for OS info.
|
|
2191
|
+
|
|
2192
|
+
6. **Prefer streaming-friendly commands**: Avoid commands that buffer all output
|
|
2193
|
+
until completion. Prefer tools that print as they go.
|
|
2194
|
+
|
|
2195
|
+
7. **No interactive prompts**: Do not run commands that wait for user input
|
|
2196
|
+
(e.g. \`ssh\` without key auth, interactive installers). If a command would
|
|
2197
|
+
prompt, pass flags to make it non-interactive or use \`yes |\` piping.
|
|
2198
|
+
${delegationSection}
|
|
2199
|
+
${customInstructions ? `## Additional Instructions
|
|
2200
|
+
|
|
2201
|
+
${customInstructions}` : ""}
|
|
2202
|
+
|
|
2203
|
+
Begin. Think about the task, then emit your first shell command(s).`;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// src/agents/TerminalAgent.ts
|
|
2207
|
+
var SH_BLOCK_REGEX = /```(?:sh|bash|shell|zsh)\n([\s\S]*?)```/g;
|
|
2208
|
+
var THOUGHT_REGEX2 = /(?:Thought|Reasoning|Think):\s*([\s\S]*?)(?=```|FINAL_ANSWER:|$)/i;
|
|
2209
|
+
var FINAL_ANSWER_MARKER = /^FINAL_ANSWER:\s*([\s\S]*)$/m;
|
|
2210
|
+
var NEWLINE = "\n";
|
|
2211
|
+
var TerminalAgent = class extends Agent {
|
|
2212
|
+
commandDelay;
|
|
2213
|
+
maxOutputLength;
|
|
2214
|
+
constructor(config) {
|
|
2215
|
+
super({ ...config, verboseLevel: 2 /* DEBUG */ });
|
|
2216
|
+
this.commandDelay = config.commandDelay ?? 5;
|
|
2217
|
+
this.maxOutputLength = config.maxOutputLength ?? 8e3;
|
|
2218
|
+
const keepTools = /* @__PURE__ */ new Map();
|
|
2219
|
+
for (const [name, tool] of this.tools) {
|
|
2220
|
+
if (tool.constructor.name === "AgentTool") {
|
|
2221
|
+
keepTools.set(name, tool);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
this.tools = keepTools;
|
|
2225
|
+
if (!this.tools.has("final_answer")) {
|
|
2226
|
+
this.tools.set("final_answer", new FinalAnswerTool());
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* Build the system prompt. Includes delegation info if sub-agents are present.
|
|
2231
|
+
*/
|
|
2232
|
+
initializeSystemPrompt() {
|
|
2233
|
+
const agentTools = Array.from(this.tools.values()).filter(
|
|
2234
|
+
(t) => t.constructor.name === "AgentTool"
|
|
2235
|
+
);
|
|
2236
|
+
const hasSubAgents = agentTools.length > 0;
|
|
2237
|
+
const subAgentDescriptions = agentTools.map((t) => `- **${t.name}**: ${t.description}`).join("\n");
|
|
2238
|
+
return generateTerminalAgentSystemPrompt({
|
|
2239
|
+
customInstructions: this.config.customInstructions,
|
|
2240
|
+
hasSubAgents,
|
|
2241
|
+
subAgentDescriptions
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Execute one step of the ReAct loop:
|
|
2246
|
+
* 1. Send messages to LLM (with tool defs for final_answer / delegation)
|
|
2247
|
+
* 2. Extract reasoning and ```sh blocks from the response
|
|
2248
|
+
* 3. If tool calls present (final_answer or delegation), process them
|
|
2249
|
+
* 4. Otherwise execute shell commands with the pre-execution delay
|
|
2250
|
+
* 5. Feed stdout/stderr/exit-code back as observation
|
|
2251
|
+
*/
|
|
2252
|
+
async executeStep(memoryStep) {
|
|
2253
|
+
const messages = this.memory.toMessages();
|
|
2254
|
+
memoryStep.modelInputMessages = [...messages];
|
|
2255
|
+
const actionSteps = this.memory.getActionSteps();
|
|
2256
|
+
const prevStep = actionSteps.length >= 2 ? actionSteps[actionSteps.length - 2] : void 0;
|
|
2257
|
+
if (prevStep?.error) {
|
|
2258
|
+
messages.push({
|
|
2259
|
+
role: "user",
|
|
2260
|
+
content: `Your previous action encountered an error: ${prevStep.error.message}
|
|
2261
|
+
Please try a different approach.`
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
const delegationTools = Array.from(this.tools.values()).filter((t) => t.constructor.name === "AgentTool");
|
|
2265
|
+
const toolDefinitions = delegationTools.map((t) => t.toOpenAITool());
|
|
2266
|
+
this.logger.subheader("Agent thinking...");
|
|
2267
|
+
const response = await this.generateResponse(messages, toolDefinitions);
|
|
2268
|
+
memoryStep.modelOutputMessage = response;
|
|
2269
|
+
memoryStep.tokenUsage = response.tokenUsage;
|
|
2270
|
+
const content = response.content ?? "";
|
|
2271
|
+
const thoughtMatch = content.match(THOUGHT_REGEX2);
|
|
2272
|
+
if (thoughtMatch) {
|
|
2273
|
+
this.logger.reasoning(thoughtMatch[1].trim());
|
|
2274
|
+
this.emitEvent("agent_thinking", { step: this.currentStep, content: thoughtMatch[1].trim() });
|
|
2275
|
+
}
|
|
2276
|
+
const finalMatch = content.match(FINAL_ANSWER_MARKER);
|
|
2277
|
+
if (finalMatch) {
|
|
2278
|
+
const answer = finalMatch[1].trim();
|
|
2279
|
+
this.logger.finalAnswer(answer);
|
|
2280
|
+
return { output: answer, isFinalAnswer: true };
|
|
2281
|
+
}
|
|
2282
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
2283
|
+
memoryStep.toolCalls = response.toolCalls;
|
|
2284
|
+
const results = await this.processToolCalls(response.toolCalls);
|
|
2285
|
+
memoryStep.toolResults = results;
|
|
2286
|
+
for (const result of results) {
|
|
2287
|
+
if (result.error) {
|
|
2288
|
+
this.logger.error(`Tool ${result.toolName} failed: ${result.error}`);
|
|
2289
|
+
} else {
|
|
2290
|
+
const str = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
|
|
2291
|
+
this.logger.output(`[${result.toolName}]: ${str.slice(0, 500)}${str.length > 500 ? "..." : ""}`);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
const obs = results.map((r) => r.error ? `[${r.toolName}] Error: ${r.error}` : `[${r.toolName}] Result: ${typeof r.result === "string" ? r.result : JSON.stringify(r.result)}`).join("\n");
|
|
2295
|
+
memoryStep.observation = `Observation:
|
|
2296
|
+
${obs}`;
|
|
2297
|
+
return { output: null, isFinalAnswer: false };
|
|
2298
|
+
}
|
|
2299
|
+
const blocks = [];
|
|
2300
|
+
let match;
|
|
2301
|
+
const regex = new RegExp(SH_BLOCK_REGEX.source, "g");
|
|
2302
|
+
while ((match = regex.exec(content)) !== null) {
|
|
2303
|
+
blocks.push(match[1].trim());
|
|
2304
|
+
}
|
|
2305
|
+
if (blocks.length === 0) {
|
|
2306
|
+
this.logger.warn("No shell commands or tool calls in response.");
|
|
2307
|
+
memoryStep.observation = "No shell command block was found in your response. Emit commands inside a ```sh code block, or call final_answer if the task is complete.";
|
|
2308
|
+
return { output: null, isFinalAnswer: false };
|
|
2309
|
+
}
|
|
2310
|
+
const allCommands = blocks.join("\n---\n");
|
|
2311
|
+
this.logger.code(allCommands, "sh");
|
|
2312
|
+
this.emitEvent("agent_observation", {
|
|
2313
|
+
step: this.currentStep,
|
|
2314
|
+
observation: `Pending commands:
|
|
2315
|
+
${allCommands}`
|
|
2316
|
+
});
|
|
2317
|
+
this.logger.waiting(this.commandDelay);
|
|
2318
|
+
await this.sleep(this.commandDelay * 1e3);
|
|
2319
|
+
const observations = [];
|
|
2320
|
+
let hitError = false;
|
|
2321
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
2322
|
+
const block = blocks[i];
|
|
2323
|
+
const commands = block.split(NEWLINE).filter((l) => l.trim() && !l.trim().startsWith("#"));
|
|
2324
|
+
this.logger.subheader(`Executing command block ${blocks.length > 1 ? `${i + 1}/${blocks.length}` : ""}...`);
|
|
2325
|
+
for (const cmd of commands) {
|
|
2326
|
+
this.logger.info(` $ ${cmd}`);
|
|
2327
|
+
const result = this.runCommand(cmd);
|
|
2328
|
+
if (result.stdout) {
|
|
2329
|
+
this.logger.logs(result.stdout);
|
|
2330
|
+
}
|
|
2331
|
+
if (result.stderr) {
|
|
2332
|
+
this.logger.error(`stderr: ${result.stderr}`);
|
|
2333
|
+
}
|
|
2334
|
+
observations.push(
|
|
2335
|
+
`$ ${cmd}
|
|
2336
|
+
` + (result.stdout ? `stdout:
|
|
2337
|
+
${result.stdout}` : "") + (result.stderr ? `stderr:
|
|
2338
|
+
${result.stderr}` : "") + `exit code: ${result.exitCode}`
|
|
2339
|
+
);
|
|
2340
|
+
if (result.exitCode !== 0) {
|
|
2341
|
+
hitError = true;
|
|
2342
|
+
break;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
if (hitError) break;
|
|
2346
|
+
}
|
|
2347
|
+
const observation = observations.join("\n\n");
|
|
2348
|
+
memoryStep.observation = `Observation:
|
|
2349
|
+
${observation}`;
|
|
2350
|
+
if (hitError) {
|
|
2351
|
+
memoryStep.error = new Error("Command exited with non-zero status. See observation for details.");
|
|
2352
|
+
}
|
|
2353
|
+
return { output: observation, isFinalAnswer: false };
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Run a single shell command, capture stdout/stderr, return structured result.
|
|
2357
|
+
*/
|
|
2358
|
+
runCommand(cmd) {
|
|
2359
|
+
try {
|
|
2360
|
+
const stdout = (0, import_child_process.execSync)(cmd, {
|
|
2361
|
+
encoding: "utf8",
|
|
2362
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2363
|
+
shell: "/bin/zsh",
|
|
2364
|
+
timeout: 12e4,
|
|
2365
|
+
// 2-minute per-command timeout
|
|
2366
|
+
maxBuffer: 50 * 1024 * 1024
|
|
2367
|
+
// 50 MB
|
|
2368
|
+
});
|
|
2369
|
+
return { stdout: this.truncateOutput(stdout), stderr: "", exitCode: 0 };
|
|
2370
|
+
} catch (err) {
|
|
2371
|
+
const e = err;
|
|
2372
|
+
return {
|
|
2373
|
+
stdout: this.truncateOutput(e.stdout ?? ""),
|
|
2374
|
+
stderr: this.truncateOutput(e.stderr ?? e.message),
|
|
2375
|
+
exitCode: e.status ?? 1
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Truncate long output, preserving head and tail so context stays useful.
|
|
2381
|
+
*/
|
|
2382
|
+
truncateOutput(output) {
|
|
2383
|
+
if (output.length <= this.maxOutputLength) return output;
|
|
2384
|
+
const half = Math.floor(this.maxOutputLength / 2);
|
|
2385
|
+
const head = output.slice(0, half);
|
|
2386
|
+
const tail = output.slice(output.length - half);
|
|
2387
|
+
const omitted = output.length - this.maxOutputLength;
|
|
2388
|
+
return `${head}
|
|
2389
|
+
|
|
2390
|
+
... [${omitted} characters omitted] ...
|
|
2391
|
+
|
|
2392
|
+
${tail}`;
|
|
2393
|
+
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Generate response, with streaming if available.
|
|
2396
|
+
*/
|
|
2397
|
+
async generateResponse(messages, toolDefinitions) {
|
|
2398
|
+
if (this.config.streamOutputs && this.model.supportsStreaming() && this.model.generateStream) {
|
|
2399
|
+
let fullContent = "";
|
|
2400
|
+
const generator = this.model.generateStream(messages, {
|
|
2401
|
+
toolDefinitions,
|
|
2402
|
+
maxTokens: this.config.maxTokens,
|
|
2403
|
+
temperature: this.config.temperature
|
|
2404
|
+
});
|
|
2405
|
+
for await (const chunk of generator) {
|
|
2406
|
+
this.logger.streamChar(chunk);
|
|
2407
|
+
fullContent += chunk;
|
|
2408
|
+
}
|
|
2409
|
+
this.logger.streamEnd();
|
|
2410
|
+
return { role: "assistant", content: fullContent };
|
|
2411
|
+
}
|
|
2412
|
+
return this.model.generate(messages, {
|
|
2413
|
+
toolDefinitions,
|
|
2414
|
+
maxTokens: this.config.maxTokens,
|
|
2415
|
+
temperature: this.config.temperature
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Process tool calls (final_answer or AgentTool delegation).
|
|
2420
|
+
*/
|
|
2421
|
+
async processToolCalls(toolCalls) {
|
|
2422
|
+
const results = [];
|
|
2423
|
+
for (const tc of toolCalls) {
|
|
2424
|
+
const toolName = tc.function.name;
|
|
2425
|
+
const tool = this.tools.get(toolName);
|
|
2426
|
+
if (!tool) {
|
|
2427
|
+
results.push({ toolCallId: tc.id, toolName, result: null, error: `Unknown tool: ${toolName}` });
|
|
2428
|
+
continue;
|
|
2429
|
+
}
|
|
2430
|
+
let args;
|
|
2431
|
+
try {
|
|
2432
|
+
args = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
|
|
2433
|
+
} catch {
|
|
2434
|
+
results.push({ toolCallId: tc.id, toolName, result: null, error: "Failed to parse tool arguments" });
|
|
2435
|
+
continue;
|
|
2436
|
+
}
|
|
2437
|
+
this.logger.info(` Calling tool: ${toolName}(${JSON.stringify(args).slice(0, 100)}...)`);
|
|
2438
|
+
this.emitEvent("agent_tool_call", { step: this.currentStep, toolCallId: tc.id, toolName, arguments: args });
|
|
2439
|
+
try {
|
|
2440
|
+
const result = await tool.call(args);
|
|
2441
|
+
this.emitEvent("agent_tool_result", { step: this.currentStep, toolCallId: tc.id, toolName, result, duration: 0 });
|
|
2442
|
+
results.push({ toolCallId: tc.id, toolName, result });
|
|
2443
|
+
} catch (error) {
|
|
2444
|
+
const msg = `Tool execution error: ${error.message}`;
|
|
2445
|
+
this.emitEvent("agent_tool_result", { step: this.currentStep, toolCallId: tc.id, toolName, result: null, error: msg, duration: 0 });
|
|
2446
|
+
results.push({ toolCallId: tc.id, toolName, result: null, error: msg });
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
return results;
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Override: force final answer via tool call format when max steps hit.
|
|
2453
|
+
*/
|
|
2454
|
+
async provideFinalAnswer(task) {
|
|
2455
|
+
this.logger.subheader("Generating final answer from accumulated context");
|
|
2456
|
+
const messages = this.memory.toMessages();
|
|
2457
|
+
messages.push({
|
|
2458
|
+
role: "user",
|
|
2459
|
+
content: `You have reached the maximum number of steps. Based on your work so far, provide the best answer for the task: "${task}". Call the final_answer tool with your response.`
|
|
2460
|
+
});
|
|
2461
|
+
const toolDefinitions = [new FinalAnswerTool().toOpenAITool()];
|
|
2462
|
+
const response = await this.model.generate(messages, { toolDefinitions, maxTokens: this.config.maxTokens, temperature: this.config.temperature });
|
|
2463
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
2464
|
+
try {
|
|
2465
|
+
const args = typeof response.toolCalls[0].function.arguments === "string" ? JSON.parse(response.toolCalls[0].function.arguments) : response.toolCalls[0].function.arguments;
|
|
2466
|
+
return args.answer;
|
|
2467
|
+
} catch {
|
|
2468
|
+
return response.content;
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
return response.content;
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
|
|
2111
2475
|
// src/models/Model.ts
|
|
2112
2476
|
var Model = class {
|
|
2113
2477
|
/**
|
|
@@ -2795,7 +3159,7 @@ var ExaResearchTool = class extends Tool {
|
|
|
2795
3159
|
while (Date.now() - startTime < this.maxPollTime) {
|
|
2796
3160
|
attempts++;
|
|
2797
3161
|
if (attempts > 1) {
|
|
2798
|
-
await new Promise((
|
|
3162
|
+
await new Promise((resolve7) => setTimeout(resolve7, this.pollInterval));
|
|
2799
3163
|
}
|
|
2800
3164
|
const statusResponse = await fetch(`https://api.exa.ai/research/v1/${researchId}`, {
|
|
2801
3165
|
method: "GET",
|
|
@@ -2847,10 +3211,11 @@ var ExaResearchTool = class extends Tool {
|
|
|
2847
3211
|
};
|
|
2848
3212
|
|
|
2849
3213
|
// src/tools/ProxyTool.ts
|
|
2850
|
-
var
|
|
3214
|
+
var import_child_process3 = require("child_process");
|
|
3215
|
+
var path6 = __toESM(require("path"));
|
|
2851
3216
|
|
|
2852
3217
|
// src/utils/bunInstaller.ts
|
|
2853
|
-
var
|
|
3218
|
+
var import_child_process2 = require("child_process");
|
|
2854
3219
|
var path5 = __toESM(require("path"));
|
|
2855
3220
|
var fs5 = __toESM(require("fs"));
|
|
2856
3221
|
var os3 = __toESM(require("os"));
|
|
@@ -2871,7 +3236,7 @@ async function ensureBunAvailable() {
|
|
|
2871
3236
|
"\n[smol-js] Bun is required to run custom tools but was not found. Installing Bun automatically...\n"
|
|
2872
3237
|
);
|
|
2873
3238
|
try {
|
|
2874
|
-
(0,
|
|
3239
|
+
(0, import_child_process2.execSync)("curl --proto =https --tlsv1.2 -sSf https://bun.sh | bash", {
|
|
2875
3240
|
stdio: "inherit",
|
|
2876
3241
|
shell: "/bin/bash",
|
|
2877
3242
|
env: { ...process.env, HOME: os3.homedir() }
|
|
@@ -2896,7 +3261,7 @@ Details: ${err.message}`
|
|
|
2896
3261
|
function whichBun() {
|
|
2897
3262
|
try {
|
|
2898
3263
|
const cmd = process.platform === "win32" ? "where bun" : "which bun";
|
|
2899
|
-
const result = (0,
|
|
3264
|
+
const result = (0, import_child_process2.execSync)(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
|
|
2900
3265
|
const first = result.split("\n")[0]?.trim();
|
|
2901
3266
|
if (first && fs5.existsSync(first)) return first;
|
|
2902
3267
|
return null;
|
|
@@ -2906,10 +3271,12 @@ function whichBun() {
|
|
|
2906
3271
|
}
|
|
2907
3272
|
|
|
2908
3273
|
// src/tools/ProxyTool.ts
|
|
2909
|
-
var TOOL_OUTPUT_PREFIX = "[TOOL_OUTPUT]";
|
|
2910
3274
|
var TOOL_RESULT_PREFIX = "[TOOL_RESULT]";
|
|
2911
3275
|
var TOOL_ERROR_PREFIX = "[TOOL_ERROR]";
|
|
2912
3276
|
var DEFAULT_TOOL_TIMEOUT_MS = 6e4;
|
|
3277
|
+
function resolveHarnessPath() {
|
|
3278
|
+
return path6.resolve(__dirname, "..", "toolHarness.ts");
|
|
3279
|
+
}
|
|
2913
3280
|
var ProxyTool = class extends Tool {
|
|
2914
3281
|
name;
|
|
2915
3282
|
description;
|
|
@@ -2918,6 +3285,7 @@ var ProxyTool = class extends Tool {
|
|
|
2918
3285
|
toolPath;
|
|
2919
3286
|
timeout;
|
|
2920
3287
|
bunPath = null;
|
|
3288
|
+
harnessPath = null;
|
|
2921
3289
|
constructor(config) {
|
|
2922
3290
|
super();
|
|
2923
3291
|
this.name = config.name;
|
|
@@ -2928,23 +3296,25 @@ var ProxyTool = class extends Tool {
|
|
|
2928
3296
|
this.timeout = config.timeout ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
2929
3297
|
}
|
|
2930
3298
|
/**
|
|
2931
|
-
* Ensure Bun is available before first invocation.
|
|
3299
|
+
* Ensure Bun is available and locate the harness before first invocation.
|
|
2932
3300
|
*/
|
|
2933
3301
|
async setup() {
|
|
2934
3302
|
this.bunPath = await ensureBunAvailable();
|
|
3303
|
+
this.harnessPath = resolveHarnessPath();
|
|
2935
3304
|
this.isSetup = true;
|
|
2936
3305
|
}
|
|
2937
3306
|
/**
|
|
2938
|
-
* Spawn the
|
|
2939
|
-
*
|
|
3307
|
+
* Spawn the harness in a Bun child process. The harness imports the tool,
|
|
3308
|
+
* calls execute(args), and writes the protocol lines. Any console.log from
|
|
3309
|
+
* the tool flows through stdout as plain lines.
|
|
2940
3310
|
*/
|
|
2941
3311
|
async execute(args) {
|
|
2942
|
-
if (!this.bunPath) {
|
|
3312
|
+
if (!this.bunPath || !this.harnessPath) {
|
|
2943
3313
|
await this.setup();
|
|
2944
3314
|
}
|
|
2945
3315
|
const serializedArgs = JSON.stringify(args);
|
|
2946
|
-
return new Promise((
|
|
2947
|
-
const child = (0,
|
|
3316
|
+
return new Promise((resolve7, reject) => {
|
|
3317
|
+
const child = (0, import_child_process3.spawn)(this.bunPath, ["run", this.harnessPath, this.toolPath, serializedArgs], {
|
|
2948
3318
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2949
3319
|
env: { ...process.env }
|
|
2950
3320
|
});
|
|
@@ -3009,12 +3379,12 @@ ${logBuffer.join("\n")}
|
|
|
3009
3379
|
[Tool result]
|
|
3010
3380
|
`;
|
|
3011
3381
|
if (typeof result === "string") {
|
|
3012
|
-
|
|
3382
|
+
resolve7(logPrefix + result);
|
|
3013
3383
|
} else {
|
|
3014
|
-
|
|
3384
|
+
resolve7({ logs: logBuffer.join("\n"), result });
|
|
3015
3385
|
}
|
|
3016
3386
|
} else {
|
|
3017
|
-
|
|
3387
|
+
resolve7(result);
|
|
3018
3388
|
}
|
|
3019
3389
|
return;
|
|
3020
3390
|
}
|
|
@@ -3024,7 +3394,7 @@ ${logBuffer.join("\n")}
|
|
|
3024
3394
|
`Custom tool "${this.name}" exited with code ${code}. Output: ${combined || "(none)"}`
|
|
3025
3395
|
));
|
|
3026
3396
|
} else {
|
|
3027
|
-
|
|
3397
|
+
resolve7(combined || `Tool "${this.name}" produced no output.`);
|
|
3028
3398
|
}
|
|
3029
3399
|
});
|
|
3030
3400
|
child.on("error", (err) => {
|
|
@@ -3035,7 +3405,7 @@ ${logBuffer.join("\n")}
|
|
|
3035
3405
|
});
|
|
3036
3406
|
});
|
|
3037
3407
|
}
|
|
3038
|
-
// ---
|
|
3408
|
+
// --- line parser: protocol is spoken by harness, interpreted here ---
|
|
3039
3409
|
processLine(line, handlers) {
|
|
3040
3410
|
const trimmed = line.trimEnd();
|
|
3041
3411
|
if (!trimmed) return;
|
|
@@ -3048,8 +3418,6 @@ ${logBuffer.join("\n")}
|
|
|
3048
3418
|
}
|
|
3049
3419
|
} else if (trimmed.startsWith(TOOL_ERROR_PREFIX)) {
|
|
3050
3420
|
handlers.onError(trimmed.slice(TOOL_ERROR_PREFIX.length).trim());
|
|
3051
|
-
} else if (trimmed.startsWith(TOOL_OUTPUT_PREFIX)) {
|
|
3052
|
-
handlers.onOutput(trimmed.slice(TOOL_OUTPUT_PREFIX.length).trim());
|
|
3053
3421
|
} else {
|
|
3054
3422
|
handlers.onOutput(trimmed);
|
|
3055
3423
|
}
|
|
@@ -3058,7 +3426,7 @@ ${logBuffer.join("\n")}
|
|
|
3058
3426
|
|
|
3059
3427
|
// src/tools/CustomToolScanner.ts
|
|
3060
3428
|
var fs6 = __toESM(require("fs"));
|
|
3061
|
-
var
|
|
3429
|
+
var path7 = __toESM(require("path"));
|
|
3062
3430
|
var METADATA_REGEX = /export\s+const\s+TOOL_METADATA\s*=\s*(\{[\s\S]*?\});\s*$/m;
|
|
3063
3431
|
function scanCustomTools(folderPath) {
|
|
3064
3432
|
if (!fs6.existsSync(folderPath)) {
|
|
@@ -3070,10 +3438,10 @@ function scanCustomTools(folderPath) {
|
|
|
3070
3438
|
const discovered = [];
|
|
3071
3439
|
for (const entry of entries) {
|
|
3072
3440
|
if (entry.isDirectory()) continue;
|
|
3073
|
-
const ext =
|
|
3441
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
3074
3442
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
3075
|
-
const filePath =
|
|
3076
|
-
const baseName =
|
|
3443
|
+
const filePath = path7.resolve(folderPath, entry.name);
|
|
3444
|
+
const baseName = path7.basename(entry.name, ext);
|
|
3077
3445
|
let metadata;
|
|
3078
3446
|
try {
|
|
3079
3447
|
metadata = extractMetadata(filePath);
|
|
@@ -3141,7 +3509,7 @@ function loadCustomTools(folderPath) {
|
|
|
3141
3509
|
|
|
3142
3510
|
// src/orchestrator/YAMLLoader.ts
|
|
3143
3511
|
var fs7 = __toESM(require("fs"));
|
|
3144
|
-
var
|
|
3512
|
+
var path8 = __toESM(require("path"));
|
|
3145
3513
|
var import_yaml = __toESM(require("yaml"));
|
|
3146
3514
|
var TOOL_REGISTRY = {
|
|
3147
3515
|
read_file: ReadFileTool,
|
|
@@ -3173,7 +3541,7 @@ var YAMLLoader = class {
|
|
|
3173
3541
|
* Load a workflow from a YAML file path.
|
|
3174
3542
|
*/
|
|
3175
3543
|
loadFromFile(filePath) {
|
|
3176
|
-
const absolutePath =
|
|
3544
|
+
const absolutePath = path8.isAbsolute(filePath) ? filePath : path8.resolve(process.cwd(), filePath);
|
|
3177
3545
|
if (!fs7.existsSync(absolutePath)) {
|
|
3178
3546
|
throw new Error(`Workflow file not found: ${absolutePath}`);
|
|
3179
3547
|
}
|
|
@@ -3317,32 +3685,24 @@ var YAMLLoader = class {
|
|
|
3317
3685
|
}
|
|
3318
3686
|
}
|
|
3319
3687
|
const maxContextLength = definition.maxContextLength ?? globalMaxContextLength;
|
|
3688
|
+
const sharedConfig = {
|
|
3689
|
+
model,
|
|
3690
|
+
tools: agentTools,
|
|
3691
|
+
maxSteps: definition.maxSteps,
|
|
3692
|
+
customInstructions: definition.customInstructions,
|
|
3693
|
+
persistent: definition.persistent,
|
|
3694
|
+
maxContextLength,
|
|
3695
|
+
memoryStrategy: definition.memoryStrategy,
|
|
3696
|
+
maxTokens: definition.maxTokens,
|
|
3697
|
+
temperature: definition.temperature,
|
|
3698
|
+
name
|
|
3699
|
+
};
|
|
3320
3700
|
if (definition.type === "CodeAgent") {
|
|
3321
|
-
return new CodeAgent(
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
maxSteps: definition.maxSteps,
|
|
3325
|
-
customInstructions: definition.customInstructions,
|
|
3326
|
-
persistent: definition.persistent,
|
|
3327
|
-
maxContextLength,
|
|
3328
|
-
memoryStrategy: definition.memoryStrategy,
|
|
3329
|
-
maxTokens: definition.maxTokens,
|
|
3330
|
-
temperature: definition.temperature,
|
|
3331
|
-
name
|
|
3332
|
-
});
|
|
3701
|
+
return new CodeAgent(sharedConfig);
|
|
3702
|
+
} else if (definition.type === "TerminalAgent") {
|
|
3703
|
+
return new TerminalAgent(sharedConfig);
|
|
3333
3704
|
} else {
|
|
3334
|
-
return new ToolUseAgent(
|
|
3335
|
-
model,
|
|
3336
|
-
tools: agentTools,
|
|
3337
|
-
maxSteps: definition.maxSteps,
|
|
3338
|
-
customInstructions: definition.customInstructions,
|
|
3339
|
-
persistent: definition.persistent,
|
|
3340
|
-
maxContextLength,
|
|
3341
|
-
memoryStrategy: definition.memoryStrategy,
|
|
3342
|
-
maxTokens: definition.maxTokens,
|
|
3343
|
-
temperature: definition.temperature,
|
|
3344
|
-
name
|
|
3345
|
-
});
|
|
3705
|
+
return new ToolUseAgent(sharedConfig);
|
|
3346
3706
|
}
|
|
3347
3707
|
}
|
|
3348
3708
|
};
|
|
@@ -3742,6 +4102,7 @@ var Orchestrator = class {
|
|
|
3742
4102
|
Orchestrator,
|
|
3743
4103
|
ProxyTool,
|
|
3744
4104
|
ReadFileTool,
|
|
4105
|
+
TerminalAgent,
|
|
3745
4106
|
Tool,
|
|
3746
4107
|
ToolUseAgent,
|
|
3747
4108
|
UserInputTool,
|