@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.mjs
CHANGED
|
@@ -724,7 +724,7 @@ Total time: ${(duration / 1e3).toFixed(2)}s`);
|
|
|
724
724
|
}
|
|
725
725
|
/** Sleep for a specified duration */
|
|
726
726
|
sleep(ms) {
|
|
727
|
-
return new Promise((
|
|
727
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
728
728
|
}
|
|
729
729
|
};
|
|
730
730
|
|
|
@@ -1393,12 +1393,12 @@ var UserInputTool = class extends Tool {
|
|
|
1393
1393
|
input: process.stdin,
|
|
1394
1394
|
output: process.stdout
|
|
1395
1395
|
});
|
|
1396
|
-
return new Promise((
|
|
1396
|
+
return new Promise((resolve7) => {
|
|
1397
1397
|
rl.question(`
|
|
1398
1398
|
[Agent asks]: ${question}
|
|
1399
1399
|
Your response: `, (answer) => {
|
|
1400
1400
|
rl.close();
|
|
1401
|
-
|
|
1401
|
+
resolve7(answer);
|
|
1402
1402
|
});
|
|
1403
1403
|
});
|
|
1404
1404
|
}
|
|
@@ -2040,6 +2040,369 @@ Please try a different approach.`
|
|
|
2040
2040
|
}
|
|
2041
2041
|
};
|
|
2042
2042
|
|
|
2043
|
+
// src/agents/TerminalAgent.ts
|
|
2044
|
+
import { execSync } from "child_process";
|
|
2045
|
+
|
|
2046
|
+
// src/prompts/terminalAgent.ts
|
|
2047
|
+
function generateTerminalAgentSystemPrompt(variables) {
|
|
2048
|
+
const { customInstructions, hasSubAgents, subAgentDescriptions } = variables;
|
|
2049
|
+
let delegationSection = "";
|
|
2050
|
+
if (hasSubAgents) {
|
|
2051
|
+
delegationSection = `
|
|
2052
|
+
## Delegation to Sub-Agents
|
|
2053
|
+
|
|
2054
|
+
You can delegate tasks to specialized sub-agents when terminal commands alone
|
|
2055
|
+
are not sufficient. Available sub-agents:
|
|
2056
|
+
|
|
2057
|
+
${subAgentDescriptions}
|
|
2058
|
+
|
|
2059
|
+
To delegate, call the sub-agent tool with a clear task description. Wait for
|
|
2060
|
+
its result before continuing. Sub-agents handle their own tool calls internally.
|
|
2061
|
+
`;
|
|
2062
|
+
}
|
|
2063
|
+
return `You are a terminal operations agent running on macOS. You accomplish tasks
|
|
2064
|
+
by reasoning about what shell commands to run and executing them one or more at
|
|
2065
|
+
a time.
|
|
2066
|
+
|
|
2067
|
+
## How You Work
|
|
2068
|
+
|
|
2069
|
+
You follow a ReAct (Reasoning + Acting) loop:
|
|
2070
|
+
1. **Think**: Analyze the task and decide what commands to run next.
|
|
2071
|
+
2. **Act**: Emit one or more shell commands inside a fenced code block.
|
|
2072
|
+
3. **Observe**: Review the command output (stdout, stderr, exit code) that the
|
|
2073
|
+
framework feeds back to you.
|
|
2074
|
+
4. Repeat until the task is complete, then signal your final answer (see below).
|
|
2075
|
+
|
|
2076
|
+
## Emitting Commands
|
|
2077
|
+
|
|
2078
|
+
Place your shell commands inside a fenced sh code block. You may include
|
|
2079
|
+
multiple commands separated by newlines. Each command is executed
|
|
2080
|
+
sequentially in its own shell invocation on the user's macOS terminal:
|
|
2081
|
+
|
|
2082
|
+
\`\`\`sh
|
|
2083
|
+
echo "hello"
|
|
2084
|
+
\`\`\`
|
|
2085
|
+
|
|
2086
|
+
\`\`\`sh
|
|
2087
|
+
ls -la ~/Documents
|
|
2088
|
+
pwd
|
|
2089
|
+
\`\`\`
|
|
2090
|
+
|
|
2091
|
+
**Important**: Each code block is treated as a batch. Commands within a single
|
|
2092
|
+
block run sequentially. The output of the entire block is returned as one
|
|
2093
|
+
observation. If you need to inspect intermediate output before proceeding,
|
|
2094
|
+
use separate blocks across steps.
|
|
2095
|
+
|
|
2096
|
+
## Rules
|
|
2097
|
+
|
|
2098
|
+
1. **Safety first**: Before running anything destructive (rm, mv on important
|
|
2099
|
+
files, format commands, etc.), explain what you are about to do in your
|
|
2100
|
+
reasoning so the user can read it during the 5-second delay and abort with
|
|
2101
|
+
Ctrl+C if needed.
|
|
2102
|
+
|
|
2103
|
+
2. **Signalling completion**: When the task is done, do NOT emit any more
|
|
2104
|
+
\`\`\`sh blocks. Instead, write your summary on a line that starts with
|
|
2105
|
+
the exact marker:
|
|
2106
|
+
|
|
2107
|
+
FINAL_ANSWER: <your summary here>
|
|
2108
|
+
|
|
2109
|
+
Everything after "FINAL_ANSWER: " (including multiple lines) is captured
|
|
2110
|
+
as your final answer. This is how you tell the framework you are finished.
|
|
2111
|
+
|
|
2112
|
+
3. **Handle errors**: If a command fails (non-zero exit code or stderr output),
|
|
2113
|
+
analyze what went wrong and try a corrective approach. Do not repeat the
|
|
2114
|
+
exact same command.
|
|
2115
|
+
|
|
2116
|
+
4. **Be explicit about paths**: Use absolute paths or \`cd\` explicitly. Do not
|
|
2117
|
+
assume the working directory persists between steps (it does not).
|
|
2118
|
+
|
|
2119
|
+
5. **macOS conventions**: Use macOS / BSD variants of commands (e.g. \`gstat\`
|
|
2120
|
+
may not exist; use \`stat\` with the right flags). Prefer Homebrew paths
|
|
2121
|
+
(\`/opt/homebrew/bin\`) for installed tools. Use \`sw_vers\` for OS info.
|
|
2122
|
+
|
|
2123
|
+
6. **Prefer streaming-friendly commands**: Avoid commands that buffer all output
|
|
2124
|
+
until completion. Prefer tools that print as they go.
|
|
2125
|
+
|
|
2126
|
+
7. **No interactive prompts**: Do not run commands that wait for user input
|
|
2127
|
+
(e.g. \`ssh\` without key auth, interactive installers). If a command would
|
|
2128
|
+
prompt, pass flags to make it non-interactive or use \`yes |\` piping.
|
|
2129
|
+
${delegationSection}
|
|
2130
|
+
${customInstructions ? `## Additional Instructions
|
|
2131
|
+
|
|
2132
|
+
${customInstructions}` : ""}
|
|
2133
|
+
|
|
2134
|
+
Begin. Think about the task, then emit your first shell command(s).`;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// src/agents/TerminalAgent.ts
|
|
2138
|
+
var SH_BLOCK_REGEX = /```(?:sh|bash|shell|zsh)\n([\s\S]*?)```/g;
|
|
2139
|
+
var THOUGHT_REGEX2 = /(?:Thought|Reasoning|Think):\s*([\s\S]*?)(?=```|FINAL_ANSWER:|$)/i;
|
|
2140
|
+
var FINAL_ANSWER_MARKER = /^FINAL_ANSWER:\s*([\s\S]*)$/m;
|
|
2141
|
+
var NEWLINE = "\n";
|
|
2142
|
+
var TerminalAgent = class extends Agent {
|
|
2143
|
+
commandDelay;
|
|
2144
|
+
maxOutputLength;
|
|
2145
|
+
constructor(config) {
|
|
2146
|
+
super({ ...config, verboseLevel: 2 /* DEBUG */ });
|
|
2147
|
+
this.commandDelay = config.commandDelay ?? 5;
|
|
2148
|
+
this.maxOutputLength = config.maxOutputLength ?? 8e3;
|
|
2149
|
+
const keepTools = /* @__PURE__ */ new Map();
|
|
2150
|
+
for (const [name, tool] of this.tools) {
|
|
2151
|
+
if (tool.constructor.name === "AgentTool") {
|
|
2152
|
+
keepTools.set(name, tool);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
this.tools = keepTools;
|
|
2156
|
+
if (!this.tools.has("final_answer")) {
|
|
2157
|
+
this.tools.set("final_answer", new FinalAnswerTool());
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Build the system prompt. Includes delegation info if sub-agents are present.
|
|
2162
|
+
*/
|
|
2163
|
+
initializeSystemPrompt() {
|
|
2164
|
+
const agentTools = Array.from(this.tools.values()).filter(
|
|
2165
|
+
(t) => t.constructor.name === "AgentTool"
|
|
2166
|
+
);
|
|
2167
|
+
const hasSubAgents = agentTools.length > 0;
|
|
2168
|
+
const subAgentDescriptions = agentTools.map((t) => `- **${t.name}**: ${t.description}`).join("\n");
|
|
2169
|
+
return generateTerminalAgentSystemPrompt({
|
|
2170
|
+
customInstructions: this.config.customInstructions,
|
|
2171
|
+
hasSubAgents,
|
|
2172
|
+
subAgentDescriptions
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Execute one step of the ReAct loop:
|
|
2177
|
+
* 1. Send messages to LLM (with tool defs for final_answer / delegation)
|
|
2178
|
+
* 2. Extract reasoning and ```sh blocks from the response
|
|
2179
|
+
* 3. If tool calls present (final_answer or delegation), process them
|
|
2180
|
+
* 4. Otherwise execute shell commands with the pre-execution delay
|
|
2181
|
+
* 5. Feed stdout/stderr/exit-code back as observation
|
|
2182
|
+
*/
|
|
2183
|
+
async executeStep(memoryStep) {
|
|
2184
|
+
const messages = this.memory.toMessages();
|
|
2185
|
+
memoryStep.modelInputMessages = [...messages];
|
|
2186
|
+
const actionSteps = this.memory.getActionSteps();
|
|
2187
|
+
const prevStep = actionSteps.length >= 2 ? actionSteps[actionSteps.length - 2] : void 0;
|
|
2188
|
+
if (prevStep?.error) {
|
|
2189
|
+
messages.push({
|
|
2190
|
+
role: "user",
|
|
2191
|
+
content: `Your previous action encountered an error: ${prevStep.error.message}
|
|
2192
|
+
Please try a different approach.`
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
const delegationTools = Array.from(this.tools.values()).filter((t) => t.constructor.name === "AgentTool");
|
|
2196
|
+
const toolDefinitions = delegationTools.map((t) => t.toOpenAITool());
|
|
2197
|
+
this.logger.subheader("Agent thinking...");
|
|
2198
|
+
const response = await this.generateResponse(messages, toolDefinitions);
|
|
2199
|
+
memoryStep.modelOutputMessage = response;
|
|
2200
|
+
memoryStep.tokenUsage = response.tokenUsage;
|
|
2201
|
+
const content = response.content ?? "";
|
|
2202
|
+
const thoughtMatch = content.match(THOUGHT_REGEX2);
|
|
2203
|
+
if (thoughtMatch) {
|
|
2204
|
+
this.logger.reasoning(thoughtMatch[1].trim());
|
|
2205
|
+
this.emitEvent("agent_thinking", { step: this.currentStep, content: thoughtMatch[1].trim() });
|
|
2206
|
+
}
|
|
2207
|
+
const finalMatch = content.match(FINAL_ANSWER_MARKER);
|
|
2208
|
+
if (finalMatch) {
|
|
2209
|
+
const answer = finalMatch[1].trim();
|
|
2210
|
+
this.logger.finalAnswer(answer);
|
|
2211
|
+
return { output: answer, isFinalAnswer: true };
|
|
2212
|
+
}
|
|
2213
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
2214
|
+
memoryStep.toolCalls = response.toolCalls;
|
|
2215
|
+
const results = await this.processToolCalls(response.toolCalls);
|
|
2216
|
+
memoryStep.toolResults = results;
|
|
2217
|
+
for (const result of results) {
|
|
2218
|
+
if (result.error) {
|
|
2219
|
+
this.logger.error(`Tool ${result.toolName} failed: ${result.error}`);
|
|
2220
|
+
} else {
|
|
2221
|
+
const str = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
|
|
2222
|
+
this.logger.output(`[${result.toolName}]: ${str.slice(0, 500)}${str.length > 500 ? "..." : ""}`);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
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");
|
|
2226
|
+
memoryStep.observation = `Observation:
|
|
2227
|
+
${obs}`;
|
|
2228
|
+
return { output: null, isFinalAnswer: false };
|
|
2229
|
+
}
|
|
2230
|
+
const blocks = [];
|
|
2231
|
+
let match;
|
|
2232
|
+
const regex = new RegExp(SH_BLOCK_REGEX.source, "g");
|
|
2233
|
+
while ((match = regex.exec(content)) !== null) {
|
|
2234
|
+
blocks.push(match[1].trim());
|
|
2235
|
+
}
|
|
2236
|
+
if (blocks.length === 0) {
|
|
2237
|
+
this.logger.warn("No shell commands or tool calls in response.");
|
|
2238
|
+
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.";
|
|
2239
|
+
return { output: null, isFinalAnswer: false };
|
|
2240
|
+
}
|
|
2241
|
+
const allCommands = blocks.join("\n---\n");
|
|
2242
|
+
this.logger.code(allCommands, "sh");
|
|
2243
|
+
this.emitEvent("agent_observation", {
|
|
2244
|
+
step: this.currentStep,
|
|
2245
|
+
observation: `Pending commands:
|
|
2246
|
+
${allCommands}`
|
|
2247
|
+
});
|
|
2248
|
+
this.logger.waiting(this.commandDelay);
|
|
2249
|
+
await this.sleep(this.commandDelay * 1e3);
|
|
2250
|
+
const observations = [];
|
|
2251
|
+
let hitError = false;
|
|
2252
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
2253
|
+
const block = blocks[i];
|
|
2254
|
+
const commands = block.split(NEWLINE).filter((l) => l.trim() && !l.trim().startsWith("#"));
|
|
2255
|
+
this.logger.subheader(`Executing command block ${blocks.length > 1 ? `${i + 1}/${blocks.length}` : ""}...`);
|
|
2256
|
+
for (const cmd of commands) {
|
|
2257
|
+
this.logger.info(` $ ${cmd}`);
|
|
2258
|
+
const result = this.runCommand(cmd);
|
|
2259
|
+
if (result.stdout) {
|
|
2260
|
+
this.logger.logs(result.stdout);
|
|
2261
|
+
}
|
|
2262
|
+
if (result.stderr) {
|
|
2263
|
+
this.logger.error(`stderr: ${result.stderr}`);
|
|
2264
|
+
}
|
|
2265
|
+
observations.push(
|
|
2266
|
+
`$ ${cmd}
|
|
2267
|
+
` + (result.stdout ? `stdout:
|
|
2268
|
+
${result.stdout}` : "") + (result.stderr ? `stderr:
|
|
2269
|
+
${result.stderr}` : "") + `exit code: ${result.exitCode}`
|
|
2270
|
+
);
|
|
2271
|
+
if (result.exitCode !== 0) {
|
|
2272
|
+
hitError = true;
|
|
2273
|
+
break;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
if (hitError) break;
|
|
2277
|
+
}
|
|
2278
|
+
const observation = observations.join("\n\n");
|
|
2279
|
+
memoryStep.observation = `Observation:
|
|
2280
|
+
${observation}`;
|
|
2281
|
+
if (hitError) {
|
|
2282
|
+
memoryStep.error = new Error("Command exited with non-zero status. See observation for details.");
|
|
2283
|
+
}
|
|
2284
|
+
return { output: observation, isFinalAnswer: false };
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Run a single shell command, capture stdout/stderr, return structured result.
|
|
2288
|
+
*/
|
|
2289
|
+
runCommand(cmd) {
|
|
2290
|
+
try {
|
|
2291
|
+
const stdout = execSync(cmd, {
|
|
2292
|
+
encoding: "utf8",
|
|
2293
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2294
|
+
shell: "/bin/zsh",
|
|
2295
|
+
timeout: 12e4,
|
|
2296
|
+
// 2-minute per-command timeout
|
|
2297
|
+
maxBuffer: 50 * 1024 * 1024
|
|
2298
|
+
// 50 MB
|
|
2299
|
+
});
|
|
2300
|
+
return { stdout: this.truncateOutput(stdout), stderr: "", exitCode: 0 };
|
|
2301
|
+
} catch (err) {
|
|
2302
|
+
const e = err;
|
|
2303
|
+
return {
|
|
2304
|
+
stdout: this.truncateOutput(e.stdout ?? ""),
|
|
2305
|
+
stderr: this.truncateOutput(e.stderr ?? e.message),
|
|
2306
|
+
exitCode: e.status ?? 1
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Truncate long output, preserving head and tail so context stays useful.
|
|
2312
|
+
*/
|
|
2313
|
+
truncateOutput(output) {
|
|
2314
|
+
if (output.length <= this.maxOutputLength) return output;
|
|
2315
|
+
const half = Math.floor(this.maxOutputLength / 2);
|
|
2316
|
+
const head = output.slice(0, half);
|
|
2317
|
+
const tail = output.slice(output.length - half);
|
|
2318
|
+
const omitted = output.length - this.maxOutputLength;
|
|
2319
|
+
return `${head}
|
|
2320
|
+
|
|
2321
|
+
... [${omitted} characters omitted] ...
|
|
2322
|
+
|
|
2323
|
+
${tail}`;
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Generate response, with streaming if available.
|
|
2327
|
+
*/
|
|
2328
|
+
async generateResponse(messages, toolDefinitions) {
|
|
2329
|
+
if (this.config.streamOutputs && this.model.supportsStreaming() && this.model.generateStream) {
|
|
2330
|
+
let fullContent = "";
|
|
2331
|
+
const generator = this.model.generateStream(messages, {
|
|
2332
|
+
toolDefinitions,
|
|
2333
|
+
maxTokens: this.config.maxTokens,
|
|
2334
|
+
temperature: this.config.temperature
|
|
2335
|
+
});
|
|
2336
|
+
for await (const chunk of generator) {
|
|
2337
|
+
this.logger.streamChar(chunk);
|
|
2338
|
+
fullContent += chunk;
|
|
2339
|
+
}
|
|
2340
|
+
this.logger.streamEnd();
|
|
2341
|
+
return { role: "assistant", content: fullContent };
|
|
2342
|
+
}
|
|
2343
|
+
return this.model.generate(messages, {
|
|
2344
|
+
toolDefinitions,
|
|
2345
|
+
maxTokens: this.config.maxTokens,
|
|
2346
|
+
temperature: this.config.temperature
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Process tool calls (final_answer or AgentTool delegation).
|
|
2351
|
+
*/
|
|
2352
|
+
async processToolCalls(toolCalls) {
|
|
2353
|
+
const results = [];
|
|
2354
|
+
for (const tc of toolCalls) {
|
|
2355
|
+
const toolName = tc.function.name;
|
|
2356
|
+
const tool = this.tools.get(toolName);
|
|
2357
|
+
if (!tool) {
|
|
2358
|
+
results.push({ toolCallId: tc.id, toolName, result: null, error: `Unknown tool: ${toolName}` });
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
let args;
|
|
2362
|
+
try {
|
|
2363
|
+
args = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
|
|
2364
|
+
} catch {
|
|
2365
|
+
results.push({ toolCallId: tc.id, toolName, result: null, error: "Failed to parse tool arguments" });
|
|
2366
|
+
continue;
|
|
2367
|
+
}
|
|
2368
|
+
this.logger.info(` Calling tool: ${toolName}(${JSON.stringify(args).slice(0, 100)}...)`);
|
|
2369
|
+
this.emitEvent("agent_tool_call", { step: this.currentStep, toolCallId: tc.id, toolName, arguments: args });
|
|
2370
|
+
try {
|
|
2371
|
+
const result = await tool.call(args);
|
|
2372
|
+
this.emitEvent("agent_tool_result", { step: this.currentStep, toolCallId: tc.id, toolName, result, duration: 0 });
|
|
2373
|
+
results.push({ toolCallId: tc.id, toolName, result });
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
const msg = `Tool execution error: ${error.message}`;
|
|
2376
|
+
this.emitEvent("agent_tool_result", { step: this.currentStep, toolCallId: tc.id, toolName, result: null, error: msg, duration: 0 });
|
|
2377
|
+
results.push({ toolCallId: tc.id, toolName, result: null, error: msg });
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return results;
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Override: force final answer via tool call format when max steps hit.
|
|
2384
|
+
*/
|
|
2385
|
+
async provideFinalAnswer(task) {
|
|
2386
|
+
this.logger.subheader("Generating final answer from accumulated context");
|
|
2387
|
+
const messages = this.memory.toMessages();
|
|
2388
|
+
messages.push({
|
|
2389
|
+
role: "user",
|
|
2390
|
+
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.`
|
|
2391
|
+
});
|
|
2392
|
+
const toolDefinitions = [new FinalAnswerTool().toOpenAITool()];
|
|
2393
|
+
const response = await this.model.generate(messages, { toolDefinitions, maxTokens: this.config.maxTokens, temperature: this.config.temperature });
|
|
2394
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
2395
|
+
try {
|
|
2396
|
+
const args = typeof response.toolCalls[0].function.arguments === "string" ? JSON.parse(response.toolCalls[0].function.arguments) : response.toolCalls[0].function.arguments;
|
|
2397
|
+
return args.answer;
|
|
2398
|
+
} catch {
|
|
2399
|
+
return response.content;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
return response.content;
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2043
2406
|
// src/models/Model.ts
|
|
2044
2407
|
var Model = class {
|
|
2045
2408
|
/**
|
|
@@ -2727,7 +3090,7 @@ var ExaResearchTool = class extends Tool {
|
|
|
2727
3090
|
while (Date.now() - startTime < this.maxPollTime) {
|
|
2728
3091
|
attempts++;
|
|
2729
3092
|
if (attempts > 1) {
|
|
2730
|
-
await new Promise((
|
|
3093
|
+
await new Promise((resolve7) => setTimeout(resolve7, this.pollInterval));
|
|
2731
3094
|
}
|
|
2732
3095
|
const statusResponse = await fetch(`https://api.exa.ai/research/v1/${researchId}`, {
|
|
2733
3096
|
method: "GET",
|
|
@@ -2780,9 +3143,10 @@ var ExaResearchTool = class extends Tool {
|
|
|
2780
3143
|
|
|
2781
3144
|
// src/tools/ProxyTool.ts
|
|
2782
3145
|
import { spawn } from "child_process";
|
|
3146
|
+
import * as path6 from "path";
|
|
2783
3147
|
|
|
2784
3148
|
// src/utils/bunInstaller.ts
|
|
2785
|
-
import { execSync } from "child_process";
|
|
3149
|
+
import { execSync as execSync2 } from "child_process";
|
|
2786
3150
|
import * as path5 from "path";
|
|
2787
3151
|
import * as fs5 from "fs";
|
|
2788
3152
|
import * as os3 from "os";
|
|
@@ -2803,7 +3167,7 @@ async function ensureBunAvailable() {
|
|
|
2803
3167
|
"\n[smol-js] Bun is required to run custom tools but was not found. Installing Bun automatically...\n"
|
|
2804
3168
|
);
|
|
2805
3169
|
try {
|
|
2806
|
-
|
|
3170
|
+
execSync2("curl --proto =https --tlsv1.2 -sSf https://bun.sh | bash", {
|
|
2807
3171
|
stdio: "inherit",
|
|
2808
3172
|
shell: "/bin/bash",
|
|
2809
3173
|
env: { ...process.env, HOME: os3.homedir() }
|
|
@@ -2828,7 +3192,7 @@ Details: ${err.message}`
|
|
|
2828
3192
|
function whichBun() {
|
|
2829
3193
|
try {
|
|
2830
3194
|
const cmd = process.platform === "win32" ? "where bun" : "which bun";
|
|
2831
|
-
const result =
|
|
3195
|
+
const result = execSync2(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
|
|
2832
3196
|
const first = result.split("\n")[0]?.trim();
|
|
2833
3197
|
if (first && fs5.existsSync(first)) return first;
|
|
2834
3198
|
return null;
|
|
@@ -2838,10 +3202,12 @@ function whichBun() {
|
|
|
2838
3202
|
}
|
|
2839
3203
|
|
|
2840
3204
|
// src/tools/ProxyTool.ts
|
|
2841
|
-
var TOOL_OUTPUT_PREFIX = "[TOOL_OUTPUT]";
|
|
2842
3205
|
var TOOL_RESULT_PREFIX = "[TOOL_RESULT]";
|
|
2843
3206
|
var TOOL_ERROR_PREFIX = "[TOOL_ERROR]";
|
|
2844
3207
|
var DEFAULT_TOOL_TIMEOUT_MS = 6e4;
|
|
3208
|
+
function resolveHarnessPath() {
|
|
3209
|
+
return path6.resolve(__dirname, "..", "toolHarness.ts");
|
|
3210
|
+
}
|
|
2845
3211
|
var ProxyTool = class extends Tool {
|
|
2846
3212
|
name;
|
|
2847
3213
|
description;
|
|
@@ -2850,6 +3216,7 @@ var ProxyTool = class extends Tool {
|
|
|
2850
3216
|
toolPath;
|
|
2851
3217
|
timeout;
|
|
2852
3218
|
bunPath = null;
|
|
3219
|
+
harnessPath = null;
|
|
2853
3220
|
constructor(config) {
|
|
2854
3221
|
super();
|
|
2855
3222
|
this.name = config.name;
|
|
@@ -2860,23 +3227,25 @@ var ProxyTool = class extends Tool {
|
|
|
2860
3227
|
this.timeout = config.timeout ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
2861
3228
|
}
|
|
2862
3229
|
/**
|
|
2863
|
-
* Ensure Bun is available before first invocation.
|
|
3230
|
+
* Ensure Bun is available and locate the harness before first invocation.
|
|
2864
3231
|
*/
|
|
2865
3232
|
async setup() {
|
|
2866
3233
|
this.bunPath = await ensureBunAvailable();
|
|
3234
|
+
this.harnessPath = resolveHarnessPath();
|
|
2867
3235
|
this.isSetup = true;
|
|
2868
3236
|
}
|
|
2869
3237
|
/**
|
|
2870
|
-
* Spawn the
|
|
2871
|
-
*
|
|
3238
|
+
* Spawn the harness in a Bun child process. The harness imports the tool,
|
|
3239
|
+
* calls execute(args), and writes the protocol lines. Any console.log from
|
|
3240
|
+
* the tool flows through stdout as plain lines.
|
|
2872
3241
|
*/
|
|
2873
3242
|
async execute(args) {
|
|
2874
|
-
if (!this.bunPath) {
|
|
3243
|
+
if (!this.bunPath || !this.harnessPath) {
|
|
2875
3244
|
await this.setup();
|
|
2876
3245
|
}
|
|
2877
3246
|
const serializedArgs = JSON.stringify(args);
|
|
2878
|
-
return new Promise((
|
|
2879
|
-
const child = spawn(this.bunPath, ["run", this.toolPath, serializedArgs], {
|
|
3247
|
+
return new Promise((resolve7, reject) => {
|
|
3248
|
+
const child = spawn(this.bunPath, ["run", this.harnessPath, this.toolPath, serializedArgs], {
|
|
2880
3249
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2881
3250
|
env: { ...process.env }
|
|
2882
3251
|
});
|
|
@@ -2941,12 +3310,12 @@ ${logBuffer.join("\n")}
|
|
|
2941
3310
|
[Tool result]
|
|
2942
3311
|
`;
|
|
2943
3312
|
if (typeof result === "string") {
|
|
2944
|
-
|
|
3313
|
+
resolve7(logPrefix + result);
|
|
2945
3314
|
} else {
|
|
2946
|
-
|
|
3315
|
+
resolve7({ logs: logBuffer.join("\n"), result });
|
|
2947
3316
|
}
|
|
2948
3317
|
} else {
|
|
2949
|
-
|
|
3318
|
+
resolve7(result);
|
|
2950
3319
|
}
|
|
2951
3320
|
return;
|
|
2952
3321
|
}
|
|
@@ -2956,7 +3325,7 @@ ${logBuffer.join("\n")}
|
|
|
2956
3325
|
`Custom tool "${this.name}" exited with code ${code}. Output: ${combined || "(none)"}`
|
|
2957
3326
|
));
|
|
2958
3327
|
} else {
|
|
2959
|
-
|
|
3328
|
+
resolve7(combined || `Tool "${this.name}" produced no output.`);
|
|
2960
3329
|
}
|
|
2961
3330
|
});
|
|
2962
3331
|
child.on("error", (err) => {
|
|
@@ -2967,7 +3336,7 @@ ${logBuffer.join("\n")}
|
|
|
2967
3336
|
});
|
|
2968
3337
|
});
|
|
2969
3338
|
}
|
|
2970
|
-
// ---
|
|
3339
|
+
// --- line parser: protocol is spoken by harness, interpreted here ---
|
|
2971
3340
|
processLine(line, handlers) {
|
|
2972
3341
|
const trimmed = line.trimEnd();
|
|
2973
3342
|
if (!trimmed) return;
|
|
@@ -2980,8 +3349,6 @@ ${logBuffer.join("\n")}
|
|
|
2980
3349
|
}
|
|
2981
3350
|
} else if (trimmed.startsWith(TOOL_ERROR_PREFIX)) {
|
|
2982
3351
|
handlers.onError(trimmed.slice(TOOL_ERROR_PREFIX.length).trim());
|
|
2983
|
-
} else if (trimmed.startsWith(TOOL_OUTPUT_PREFIX)) {
|
|
2984
|
-
handlers.onOutput(trimmed.slice(TOOL_OUTPUT_PREFIX.length).trim());
|
|
2985
3352
|
} else {
|
|
2986
3353
|
handlers.onOutput(trimmed);
|
|
2987
3354
|
}
|
|
@@ -2990,7 +3357,7 @@ ${logBuffer.join("\n")}
|
|
|
2990
3357
|
|
|
2991
3358
|
// src/tools/CustomToolScanner.ts
|
|
2992
3359
|
import * as fs6 from "fs";
|
|
2993
|
-
import * as
|
|
3360
|
+
import * as path7 from "path";
|
|
2994
3361
|
var METADATA_REGEX = /export\s+const\s+TOOL_METADATA\s*=\s*(\{[\s\S]*?\});\s*$/m;
|
|
2995
3362
|
function scanCustomTools(folderPath) {
|
|
2996
3363
|
if (!fs6.existsSync(folderPath)) {
|
|
@@ -3002,10 +3369,10 @@ function scanCustomTools(folderPath) {
|
|
|
3002
3369
|
const discovered = [];
|
|
3003
3370
|
for (const entry of entries) {
|
|
3004
3371
|
if (entry.isDirectory()) continue;
|
|
3005
|
-
const ext =
|
|
3372
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
3006
3373
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
3007
|
-
const filePath =
|
|
3008
|
-
const baseName =
|
|
3374
|
+
const filePath = path7.resolve(folderPath, entry.name);
|
|
3375
|
+
const baseName = path7.basename(entry.name, ext);
|
|
3009
3376
|
let metadata;
|
|
3010
3377
|
try {
|
|
3011
3378
|
metadata = extractMetadata(filePath);
|
|
@@ -3073,7 +3440,7 @@ function loadCustomTools(folderPath) {
|
|
|
3073
3440
|
|
|
3074
3441
|
// src/orchestrator/YAMLLoader.ts
|
|
3075
3442
|
import * as fs7 from "fs";
|
|
3076
|
-
import * as
|
|
3443
|
+
import * as path8 from "path";
|
|
3077
3444
|
import YAML from "yaml";
|
|
3078
3445
|
var TOOL_REGISTRY = {
|
|
3079
3446
|
read_file: ReadFileTool,
|
|
@@ -3105,7 +3472,7 @@ var YAMLLoader = class {
|
|
|
3105
3472
|
* Load a workflow from a YAML file path.
|
|
3106
3473
|
*/
|
|
3107
3474
|
loadFromFile(filePath) {
|
|
3108
|
-
const absolutePath =
|
|
3475
|
+
const absolutePath = path8.isAbsolute(filePath) ? filePath : path8.resolve(process.cwd(), filePath);
|
|
3109
3476
|
if (!fs7.existsSync(absolutePath)) {
|
|
3110
3477
|
throw new Error(`Workflow file not found: ${absolutePath}`);
|
|
3111
3478
|
}
|
|
@@ -3249,32 +3616,24 @@ var YAMLLoader = class {
|
|
|
3249
3616
|
}
|
|
3250
3617
|
}
|
|
3251
3618
|
const maxContextLength = definition.maxContextLength ?? globalMaxContextLength;
|
|
3619
|
+
const sharedConfig = {
|
|
3620
|
+
model,
|
|
3621
|
+
tools: agentTools,
|
|
3622
|
+
maxSteps: definition.maxSteps,
|
|
3623
|
+
customInstructions: definition.customInstructions,
|
|
3624
|
+
persistent: definition.persistent,
|
|
3625
|
+
maxContextLength,
|
|
3626
|
+
memoryStrategy: definition.memoryStrategy,
|
|
3627
|
+
maxTokens: definition.maxTokens,
|
|
3628
|
+
temperature: definition.temperature,
|
|
3629
|
+
name
|
|
3630
|
+
};
|
|
3252
3631
|
if (definition.type === "CodeAgent") {
|
|
3253
|
-
return new CodeAgent(
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
maxSteps: definition.maxSteps,
|
|
3257
|
-
customInstructions: definition.customInstructions,
|
|
3258
|
-
persistent: definition.persistent,
|
|
3259
|
-
maxContextLength,
|
|
3260
|
-
memoryStrategy: definition.memoryStrategy,
|
|
3261
|
-
maxTokens: definition.maxTokens,
|
|
3262
|
-
temperature: definition.temperature,
|
|
3263
|
-
name
|
|
3264
|
-
});
|
|
3632
|
+
return new CodeAgent(sharedConfig);
|
|
3633
|
+
} else if (definition.type === "TerminalAgent") {
|
|
3634
|
+
return new TerminalAgent(sharedConfig);
|
|
3265
3635
|
} else {
|
|
3266
|
-
return new ToolUseAgent(
|
|
3267
|
-
model,
|
|
3268
|
-
tools: agentTools,
|
|
3269
|
-
maxSteps: definition.maxSteps,
|
|
3270
|
-
customInstructions: definition.customInstructions,
|
|
3271
|
-
persistent: definition.persistent,
|
|
3272
|
-
maxContextLength,
|
|
3273
|
-
memoryStrategy: definition.memoryStrategy,
|
|
3274
|
-
maxTokens: definition.maxTokens,
|
|
3275
|
-
temperature: definition.temperature,
|
|
3276
|
-
name
|
|
3277
|
-
});
|
|
3636
|
+
return new ToolUseAgent(sharedConfig);
|
|
3278
3637
|
}
|
|
3279
3638
|
}
|
|
3280
3639
|
};
|
|
@@ -3673,6 +4032,7 @@ export {
|
|
|
3673
4032
|
Orchestrator,
|
|
3674
4033
|
ProxyTool,
|
|
3675
4034
|
ReadFileTool,
|
|
4035
|
+
TerminalAgent,
|
|
3676
4036
|
Tool,
|
|
3677
4037
|
ToolUseAgent,
|
|
3678
4038
|
UserInputTool,
|