@samrahimi/smol-js 0.1.0
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/README.md +295 -0
- package/dist/index.d.mts +793 -0
- package/dist/index.d.ts +793 -0
- package/dist/index.js +1771 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1719 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1771 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Agent: () => Agent,
|
|
34
|
+
AgentLogger: () => AgentLogger,
|
|
35
|
+
AgentMemory: () => AgentMemory,
|
|
36
|
+
CodeAgent: () => CodeAgent,
|
|
37
|
+
FINAL_ANSWER_PROMPT: () => FINAL_ANSWER_PROMPT,
|
|
38
|
+
FinalAnswerTool: () => FinalAnswerTool,
|
|
39
|
+
LocalExecutor: () => LocalExecutor,
|
|
40
|
+
LogLevel: () => LogLevel,
|
|
41
|
+
Model: () => Model,
|
|
42
|
+
OpenAIModel: () => OpenAIModel,
|
|
43
|
+
Tool: () => Tool,
|
|
44
|
+
UserInputTool: () => UserInputTool,
|
|
45
|
+
createTool: () => createTool,
|
|
46
|
+
finalAnswerTool: () => finalAnswerTool,
|
|
47
|
+
generateSystemPrompt: () => generateSystemPrompt,
|
|
48
|
+
getErrorRecoveryPrompt: () => getErrorRecoveryPrompt
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(index_exports);
|
|
51
|
+
|
|
52
|
+
// src/types.ts
|
|
53
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
54
|
+
LogLevel2[LogLevel2["OFF"] = -1] = "OFF";
|
|
55
|
+
LogLevel2[LogLevel2["ERROR"] = 0] = "ERROR";
|
|
56
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
57
|
+
LogLevel2[LogLevel2["DEBUG"] = 2] = "DEBUG";
|
|
58
|
+
return LogLevel2;
|
|
59
|
+
})(LogLevel || {});
|
|
60
|
+
|
|
61
|
+
// src/memory/AgentMemory.ts
|
|
62
|
+
var AgentMemory = class {
|
|
63
|
+
/**
|
|
64
|
+
* System prompt step (always first)
|
|
65
|
+
*/
|
|
66
|
+
systemPrompt;
|
|
67
|
+
/**
|
|
68
|
+
* All execution steps
|
|
69
|
+
*/
|
|
70
|
+
steps = [];
|
|
71
|
+
constructor(systemPrompt) {
|
|
72
|
+
this.systemPrompt = {
|
|
73
|
+
type: "system",
|
|
74
|
+
content: systemPrompt,
|
|
75
|
+
timestamp: Date.now()
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Reset memory, keeping only the system prompt.
|
|
80
|
+
*/
|
|
81
|
+
reset() {
|
|
82
|
+
this.steps = [];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Add a task step.
|
|
86
|
+
*/
|
|
87
|
+
addTask(task) {
|
|
88
|
+
const step = {
|
|
89
|
+
type: "task",
|
|
90
|
+
task,
|
|
91
|
+
timestamp: Date.now()
|
|
92
|
+
};
|
|
93
|
+
this.steps.push(step);
|
|
94
|
+
return step;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create a new action step.
|
|
98
|
+
*/
|
|
99
|
+
createActionStep(stepNumber) {
|
|
100
|
+
const step = {
|
|
101
|
+
type: "action",
|
|
102
|
+
stepNumber,
|
|
103
|
+
timing: {
|
|
104
|
+
startTime: Date.now()
|
|
105
|
+
},
|
|
106
|
+
modelInputMessages: [],
|
|
107
|
+
timestamp: Date.now()
|
|
108
|
+
};
|
|
109
|
+
this.steps.push(step);
|
|
110
|
+
return step;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Add a final answer step.
|
|
114
|
+
*/
|
|
115
|
+
addFinalAnswer(answer) {
|
|
116
|
+
const step = {
|
|
117
|
+
type: "final",
|
|
118
|
+
answer,
|
|
119
|
+
timestamp: Date.now()
|
|
120
|
+
};
|
|
121
|
+
this.steps.push(step);
|
|
122
|
+
return step;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the last step.
|
|
126
|
+
*/
|
|
127
|
+
getLastStep() {
|
|
128
|
+
return this.steps[this.steps.length - 1];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get all action steps.
|
|
132
|
+
*/
|
|
133
|
+
getActionSteps() {
|
|
134
|
+
return this.steps.filter((s) => s.type === "action");
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Convert memory to messages for LLM context.
|
|
138
|
+
*/
|
|
139
|
+
toMessages() {
|
|
140
|
+
const messages = [];
|
|
141
|
+
messages.push({
|
|
142
|
+
role: "system",
|
|
143
|
+
content: this.systemPrompt.content
|
|
144
|
+
});
|
|
145
|
+
for (const step of this.steps) {
|
|
146
|
+
switch (step.type) {
|
|
147
|
+
case "task":
|
|
148
|
+
messages.push({
|
|
149
|
+
role: "user",
|
|
150
|
+
content: `Task: ${step.task}`
|
|
151
|
+
});
|
|
152
|
+
break;
|
|
153
|
+
case "action":
|
|
154
|
+
if (step.modelOutputMessage) {
|
|
155
|
+
messages.push({
|
|
156
|
+
role: "assistant",
|
|
157
|
+
content: step.modelOutputMessage.content
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (step.observation) {
|
|
161
|
+
messages.push({
|
|
162
|
+
role: "user",
|
|
163
|
+
content: step.observation
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (step.error) {
|
|
167
|
+
messages.push({
|
|
168
|
+
role: "user",
|
|
169
|
+
content: `Error: ${step.error.message}`
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case "final":
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return messages;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get total token usage across all steps.
|
|
181
|
+
*/
|
|
182
|
+
getTotalTokenUsage() {
|
|
183
|
+
let inputTokens = 0;
|
|
184
|
+
let outputTokens = 0;
|
|
185
|
+
for (const step of this.steps) {
|
|
186
|
+
if (step.type === "action" && step.tokenUsage) {
|
|
187
|
+
inputTokens += step.tokenUsage.inputTokens;
|
|
188
|
+
outputTokens += step.tokenUsage.outputTokens;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
inputTokens,
|
|
193
|
+
outputTokens,
|
|
194
|
+
totalTokens: inputTokens + outputTokens
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get a summary of the memory for logging.
|
|
199
|
+
*/
|
|
200
|
+
getSummary() {
|
|
201
|
+
const actionSteps = this.getActionSteps();
|
|
202
|
+
const lines = [
|
|
203
|
+
`System Prompt: ${this.systemPrompt.content.slice(0, 100)}...`,
|
|
204
|
+
`Total Steps: ${this.steps.length}`,
|
|
205
|
+
`Action Steps: ${actionSteps.length}`
|
|
206
|
+
];
|
|
207
|
+
const tokenUsage = this.getTotalTokenUsage();
|
|
208
|
+
if (tokenUsage.totalTokens > 0) {
|
|
209
|
+
lines.push(`Total Tokens: ${tokenUsage.totalTokens}`);
|
|
210
|
+
}
|
|
211
|
+
return lines.join("\n");
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Serialize memory to JSON.
|
|
215
|
+
*/
|
|
216
|
+
toJSON() {
|
|
217
|
+
return {
|
|
218
|
+
systemPrompt: this.systemPrompt,
|
|
219
|
+
steps: this.steps
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/logging/AgentLogger.ts
|
|
225
|
+
var import_chalk = __toESM(require("chalk"));
|
|
226
|
+
var fs = __toESM(require("fs"));
|
|
227
|
+
var path = __toESM(require("path"));
|
|
228
|
+
var os = __toESM(require("os"));
|
|
229
|
+
var LOG_DIR = path.join(os.homedir(), ".smol-js");
|
|
230
|
+
var AgentLogger = class {
|
|
231
|
+
level;
|
|
232
|
+
logFile;
|
|
233
|
+
sessionId;
|
|
234
|
+
constructor(level = 1 /* INFO */) {
|
|
235
|
+
this.level = level;
|
|
236
|
+
this.sessionId = this.generateSessionId();
|
|
237
|
+
if (level > -1 /* OFF */) {
|
|
238
|
+
this.initLogFile();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Generate a unique session ID.
|
|
243
|
+
*/
|
|
244
|
+
generateSessionId() {
|
|
245
|
+
const now = /* @__PURE__ */ new Date();
|
|
246
|
+
const timestamp = now.toISOString().replace(/[:.]/g, "-");
|
|
247
|
+
return `session-${timestamp}`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Initialize the log file.
|
|
251
|
+
*/
|
|
252
|
+
initLogFile() {
|
|
253
|
+
try {
|
|
254
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
255
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
256
|
+
}
|
|
257
|
+
const logPath = path.join(LOG_DIR, `${this.sessionId}.log`);
|
|
258
|
+
this.logFile = fs.createWriteStream(logPath, { flags: "a" });
|
|
259
|
+
this.writeToFile(`=== Session Started: ${(/* @__PURE__ */ new Date()).toISOString()} ===
|
|
260
|
+
|
|
261
|
+
`);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.warn("Could not create log file:", error.message);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Write to the log file.
|
|
268
|
+
*/
|
|
269
|
+
writeToFile(content) {
|
|
270
|
+
if (this.logFile) {
|
|
271
|
+
const cleanContent = content.replace(/\x1b\[[0-9;]*m/g, "");
|
|
272
|
+
this.logFile.write(cleanContent);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Set the log level.
|
|
277
|
+
*/
|
|
278
|
+
setLevel(level) {
|
|
279
|
+
this.level = level;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Log a header (task start, step start, etc.)
|
|
283
|
+
*/
|
|
284
|
+
header(message, level = 1 /* INFO */) {
|
|
285
|
+
if (this.level < level) return;
|
|
286
|
+
const line = "\u2550".repeat(60);
|
|
287
|
+
const output = `
|
|
288
|
+
${import_chalk.default.cyan(line)}
|
|
289
|
+
${import_chalk.default.cyan.bold(message)}
|
|
290
|
+
${import_chalk.default.cyan(line)}
|
|
291
|
+
`;
|
|
292
|
+
console.log(output);
|
|
293
|
+
this.writeToFile(`
|
|
294
|
+
${"\u2550".repeat(60)}
|
|
295
|
+
${message}
|
|
296
|
+
${"\u2550".repeat(60)}
|
|
297
|
+
`);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Log a subheader.
|
|
301
|
+
*/
|
|
302
|
+
subheader(message, level = 1 /* INFO */) {
|
|
303
|
+
if (this.level < level) return;
|
|
304
|
+
const output = `
|
|
305
|
+
${import_chalk.default.cyan("\u2500".repeat(40))}
|
|
306
|
+
${import_chalk.default.cyan(message)}
|
|
307
|
+
`;
|
|
308
|
+
console.log(output);
|
|
309
|
+
this.writeToFile(`
|
|
310
|
+
${"\u2500".repeat(40)}
|
|
311
|
+
${message}
|
|
312
|
+
`);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Log reasoning/thought from the agent.
|
|
316
|
+
*/
|
|
317
|
+
reasoning(content, level = 1 /* INFO */) {
|
|
318
|
+
if (this.level < level) return;
|
|
319
|
+
const output = `${import_chalk.default.yellow.bold("\u{1F4AD} Reasoning:")}
|
|
320
|
+
${import_chalk.default.yellow(content)}
|
|
321
|
+
`;
|
|
322
|
+
console.log(output);
|
|
323
|
+
this.writeToFile(`
|
|
324
|
+
\u{1F4AD} Reasoning:
|
|
325
|
+
${content}
|
|
326
|
+
`);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Log code block.
|
|
330
|
+
*/
|
|
331
|
+
code(content, language = "javascript", level = 1 /* INFO */) {
|
|
332
|
+
if (this.level < level) return;
|
|
333
|
+
const output = `${import_chalk.default.green.bold("\u{1F4DD} Code:")}
|
|
334
|
+
${import_chalk.default.green("```" + language)}
|
|
335
|
+
${import_chalk.default.green(content)}
|
|
336
|
+
${import_chalk.default.green("```")}
|
|
337
|
+
`;
|
|
338
|
+
console.log(output);
|
|
339
|
+
this.writeToFile(`
|
|
340
|
+
\u{1F4DD} Code:
|
|
341
|
+
\`\`\`${language}
|
|
342
|
+
${content}
|
|
343
|
+
\`\`\`
|
|
344
|
+
`);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Log execution output.
|
|
348
|
+
*/
|
|
349
|
+
output(content, level = 1 /* INFO */) {
|
|
350
|
+
if (this.level < level) return;
|
|
351
|
+
const output = `${import_chalk.default.blue.bold("\u{1F4E4} Output:")}
|
|
352
|
+
${import_chalk.default.blue(content)}
|
|
353
|
+
`;
|
|
354
|
+
console.log(output);
|
|
355
|
+
this.writeToFile(`
|
|
356
|
+
\u{1F4E4} Output:
|
|
357
|
+
${content}
|
|
358
|
+
`);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Log execution logs (print statements).
|
|
362
|
+
*/
|
|
363
|
+
logs(content, level = 1 /* INFO */) {
|
|
364
|
+
if (this.level < level || !content.trim()) return;
|
|
365
|
+
const output = `${import_chalk.default.gray.bold("\u{1F4CB} Logs:")}
|
|
366
|
+
${import_chalk.default.gray(content)}
|
|
367
|
+
`;
|
|
368
|
+
console.log(output);
|
|
369
|
+
this.writeToFile(`
|
|
370
|
+
\u{1F4CB} Logs:
|
|
371
|
+
${content}
|
|
372
|
+
`);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Log an error.
|
|
376
|
+
*/
|
|
377
|
+
error(message, error, level = 0 /* ERROR */) {
|
|
378
|
+
if (this.level < level) return;
|
|
379
|
+
const errorMessage = error ? `${message}: ${error.message}` : message;
|
|
380
|
+
const output = `${import_chalk.default.red.bold("\u274C Error:")}
|
|
381
|
+
${import_chalk.default.red(errorMessage)}
|
|
382
|
+
`;
|
|
383
|
+
console.error(output);
|
|
384
|
+
this.writeToFile(`
|
|
385
|
+
\u274C Error:
|
|
386
|
+
${errorMessage}
|
|
387
|
+
`);
|
|
388
|
+
if (error?.stack && this.level >= 2 /* DEBUG */) {
|
|
389
|
+
console.error(import_chalk.default.red.dim(error.stack));
|
|
390
|
+
this.writeToFile(`Stack: ${error.stack}
|
|
391
|
+
`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Log a warning.
|
|
396
|
+
*/
|
|
397
|
+
warn(message, level = 1 /* INFO */) {
|
|
398
|
+
if (this.level < level) return;
|
|
399
|
+
const output = `${import_chalk.default.yellow.bold("\u26A0\uFE0F Warning:")} ${import_chalk.default.yellow(message)}
|
|
400
|
+
`;
|
|
401
|
+
console.warn(output);
|
|
402
|
+
this.writeToFile(`
|
|
403
|
+
\u26A0\uFE0F Warning: ${message}
|
|
404
|
+
`);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Log general info.
|
|
408
|
+
*/
|
|
409
|
+
info(message, level = 1 /* INFO */) {
|
|
410
|
+
if (this.level < level) return;
|
|
411
|
+
const output = `${import_chalk.default.white(message)}`;
|
|
412
|
+
console.log(output);
|
|
413
|
+
this.writeToFile(`${message}
|
|
414
|
+
`);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Log debug info.
|
|
418
|
+
*/
|
|
419
|
+
debug(message) {
|
|
420
|
+
if (this.level < 2 /* DEBUG */) return;
|
|
421
|
+
const output = `${import_chalk.default.dim("[DEBUG]")} ${import_chalk.default.dim(message)}`;
|
|
422
|
+
console.log(output);
|
|
423
|
+
this.writeToFile(`[DEBUG] ${message}
|
|
424
|
+
`);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Log final answer.
|
|
428
|
+
*/
|
|
429
|
+
finalAnswer(answer, level = 1 /* INFO */) {
|
|
430
|
+
if (this.level < level) return;
|
|
431
|
+
const line = "\u2550".repeat(60);
|
|
432
|
+
const answerStr = typeof answer === "string" ? answer : JSON.stringify(answer, null, 2);
|
|
433
|
+
const output = `
|
|
434
|
+
${import_chalk.default.magenta(line)}
|
|
435
|
+
${import_chalk.default.magenta.bold("\u2705 Final Answer:")}
|
|
436
|
+
${import_chalk.default.magenta(answerStr)}
|
|
437
|
+
${import_chalk.default.magenta(line)}
|
|
438
|
+
`;
|
|
439
|
+
console.log(output);
|
|
440
|
+
this.writeToFile(`
|
|
441
|
+
${"\u2550".repeat(60)}
|
|
442
|
+
\u2705 Final Answer:
|
|
443
|
+
${answerStr}
|
|
444
|
+
${"\u2550".repeat(60)}
|
|
445
|
+
`);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Log step progress.
|
|
449
|
+
*/
|
|
450
|
+
stepProgress(current, max, level = 1 /* INFO */) {
|
|
451
|
+
if (this.level < level) return;
|
|
452
|
+
const output = `${import_chalk.default.cyan.bold(`
|
|
453
|
+
\u{1F504} Step ${current}/${max}`)}
|
|
454
|
+
`;
|
|
455
|
+
console.log(output);
|
|
456
|
+
this.writeToFile(`
|
|
457
|
+
\u{1F504} Step ${current}/${max}
|
|
458
|
+
`);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Log waiting message for code execution delay.
|
|
462
|
+
*/
|
|
463
|
+
waiting(seconds, level = 1 /* INFO */) {
|
|
464
|
+
if (this.level < level) return;
|
|
465
|
+
const output = `${import_chalk.default.yellow(`\u23F3 Waiting ${seconds}s before code execution (Ctrl+C to abort)...`)}`;
|
|
466
|
+
console.log(output);
|
|
467
|
+
this.writeToFile(`\u23F3 Waiting ${seconds}s before code execution...
|
|
468
|
+
`);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Stream content character by character.
|
|
472
|
+
*/
|
|
473
|
+
streamChar(char) {
|
|
474
|
+
if (this.level < 1 /* INFO */) return;
|
|
475
|
+
process.stdout.write(import_chalk.default.yellow(char));
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* End streaming (add newline).
|
|
479
|
+
*/
|
|
480
|
+
streamEnd() {
|
|
481
|
+
if (this.level < 1 /* INFO */) return;
|
|
482
|
+
console.log();
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Close the log file.
|
|
486
|
+
*/
|
|
487
|
+
close() {
|
|
488
|
+
if (this.logFile) {
|
|
489
|
+
this.writeToFile(`
|
|
490
|
+
=== Session Ended: ${(/* @__PURE__ */ new Date()).toISOString()} ===
|
|
491
|
+
`);
|
|
492
|
+
this.logFile.end();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Get the log file path.
|
|
497
|
+
*/
|
|
498
|
+
getLogPath() {
|
|
499
|
+
return this.logFile ? path.join(LOG_DIR, `${this.sessionId}.log`) : void 0;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/agents/Agent.ts
|
|
504
|
+
var Agent = class {
|
|
505
|
+
/**
|
|
506
|
+
* The LLM model for generation
|
|
507
|
+
*/
|
|
508
|
+
model;
|
|
509
|
+
/**
|
|
510
|
+
* Available tools mapped by name
|
|
511
|
+
*/
|
|
512
|
+
tools = /* @__PURE__ */ new Map();
|
|
513
|
+
/**
|
|
514
|
+
* Agent memory tracking all steps
|
|
515
|
+
*/
|
|
516
|
+
memory;
|
|
517
|
+
/**
|
|
518
|
+
* Logger for formatted output
|
|
519
|
+
*/
|
|
520
|
+
logger;
|
|
521
|
+
/**
|
|
522
|
+
* Configuration options
|
|
523
|
+
*/
|
|
524
|
+
config;
|
|
525
|
+
/**
|
|
526
|
+
* Current step number
|
|
527
|
+
*/
|
|
528
|
+
currentStep = 0;
|
|
529
|
+
/**
|
|
530
|
+
* Whether the agent is currently running
|
|
531
|
+
*/
|
|
532
|
+
isRunning = false;
|
|
533
|
+
constructor(config) {
|
|
534
|
+
this.model = config.model;
|
|
535
|
+
this.logger = new AgentLogger(config.verboseLevel ?? 1 /* INFO */);
|
|
536
|
+
this.config = {
|
|
537
|
+
maxSteps: config.maxSteps ?? 20,
|
|
538
|
+
codeExecutionDelay: config.codeExecutionDelay ?? 5e3,
|
|
539
|
+
customInstructions: config.customInstructions ?? "",
|
|
540
|
+
verboseLevel: config.verboseLevel ?? 1 /* INFO */,
|
|
541
|
+
streamOutputs: config.streamOutputs ?? true
|
|
542
|
+
};
|
|
543
|
+
if (config.tools) {
|
|
544
|
+
for (const tool of config.tools) {
|
|
545
|
+
this.tools.set(tool.name, tool);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Run the agent on a task.
|
|
551
|
+
*
|
|
552
|
+
* @param task - The task description
|
|
553
|
+
* @param reset - Whether to reset memory before running
|
|
554
|
+
* @returns The final result
|
|
555
|
+
*/
|
|
556
|
+
async run(task, reset = true) {
|
|
557
|
+
const startTime = Date.now();
|
|
558
|
+
if (reset || !this.memory) {
|
|
559
|
+
const systemPrompt = this.initializeSystemPrompt();
|
|
560
|
+
this.memory = new AgentMemory(systemPrompt);
|
|
561
|
+
this.currentStep = 0;
|
|
562
|
+
}
|
|
563
|
+
this.memory.addTask(task);
|
|
564
|
+
this.isRunning = true;
|
|
565
|
+
this.logger.header(`\u{1F680} Starting Agent: ${task.slice(0, 50)}${task.length > 50 ? "..." : ""}`);
|
|
566
|
+
let finalOutput = null;
|
|
567
|
+
let isFinalAnswer = false;
|
|
568
|
+
try {
|
|
569
|
+
while (this.currentStep < this.config.maxSteps && this.isRunning) {
|
|
570
|
+
this.currentStep++;
|
|
571
|
+
this.logger.stepProgress(this.currentStep, this.config.maxSteps);
|
|
572
|
+
const memoryStep = this.memory.createActionStep(this.currentStep);
|
|
573
|
+
try {
|
|
574
|
+
const actionOutput = await this.executeStep(memoryStep);
|
|
575
|
+
memoryStep.timing.endTime = Date.now();
|
|
576
|
+
memoryStep.timing.duration = memoryStep.timing.endTime - memoryStep.timing.startTime;
|
|
577
|
+
memoryStep.actionOutput = actionOutput;
|
|
578
|
+
memoryStep.isFinalAnswer = actionOutput.isFinalAnswer;
|
|
579
|
+
if (actionOutput.isFinalAnswer) {
|
|
580
|
+
finalOutput = actionOutput.output;
|
|
581
|
+
isFinalAnswer = true;
|
|
582
|
+
this.logger.finalAnswer(finalOutput);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
} catch (error) {
|
|
586
|
+
memoryStep.error = error;
|
|
587
|
+
memoryStep.timing.endTime = Date.now();
|
|
588
|
+
memoryStep.timing.duration = memoryStep.timing.endTime - memoryStep.timing.startTime;
|
|
589
|
+
this.logger.error("Step execution failed", error);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (!isFinalAnswer && this.currentStep >= this.config.maxSteps) {
|
|
593
|
+
this.logger.warn(`Max steps (${this.config.maxSteps}) reached without final answer`);
|
|
594
|
+
finalOutput = await this.provideFinalAnswer(task);
|
|
595
|
+
}
|
|
596
|
+
} finally {
|
|
597
|
+
this.isRunning = false;
|
|
598
|
+
}
|
|
599
|
+
const duration = Date.now() - startTime;
|
|
600
|
+
const tokenUsage = this.memory.getTotalTokenUsage();
|
|
601
|
+
this.memory.addFinalAnswer(finalOutput);
|
|
602
|
+
this.logger.info(`
|
|
603
|
+
\u23F1\uFE0F Total time: ${(duration / 1e3).toFixed(2)}s`);
|
|
604
|
+
this.logger.info(`\u{1F4CA} Total tokens: ${tokenUsage.totalTokens}`);
|
|
605
|
+
const logPath = this.logger.getLogPath();
|
|
606
|
+
if (logPath) {
|
|
607
|
+
this.logger.info(`\u{1F4C1} Log file: ${logPath}`);
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
output: finalOutput,
|
|
611
|
+
steps: this.memory.steps,
|
|
612
|
+
tokenUsage,
|
|
613
|
+
duration
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Generate a final answer when max steps is reached.
|
|
618
|
+
*/
|
|
619
|
+
async provideFinalAnswer(task) {
|
|
620
|
+
this.logger.subheader("Generating final answer from accumulated context");
|
|
621
|
+
const messages = this.memory.toMessages();
|
|
622
|
+
messages.push({
|
|
623
|
+
role: "user",
|
|
624
|
+
content: `You have reached the maximum number of steps. Based on your work so far, provide the best answer you can for the original task: "${task}"
|
|
625
|
+
|
|
626
|
+
Summarize what you accomplished and provide a final answer. Call final_answer() with your response.`
|
|
627
|
+
});
|
|
628
|
+
const response = await this.model.generate(messages);
|
|
629
|
+
return response.content;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Stop the agent.
|
|
633
|
+
*/
|
|
634
|
+
stop() {
|
|
635
|
+
this.isRunning = false;
|
|
636
|
+
this.logger.info("Agent stopped by user");
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Get the current memory.
|
|
640
|
+
*/
|
|
641
|
+
getMemory() {
|
|
642
|
+
return this.memory;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Get registered tools.
|
|
646
|
+
*/
|
|
647
|
+
getTools() {
|
|
648
|
+
return this.tools;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Add a tool to the agent.
|
|
652
|
+
*/
|
|
653
|
+
addTool(tool) {
|
|
654
|
+
this.tools.set(tool.name, tool);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Remove a tool from the agent.
|
|
658
|
+
*/
|
|
659
|
+
removeTool(name) {
|
|
660
|
+
return this.tools.delete(name);
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Sleep for a specified duration.
|
|
664
|
+
*/
|
|
665
|
+
sleep(ms) {
|
|
666
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// src/executor/LocalExecutor.ts
|
|
671
|
+
var vm = __toESM(require("vm"));
|
|
672
|
+
var fs2 = __toESM(require("fs"));
|
|
673
|
+
var path2 = __toESM(require("path"));
|
|
674
|
+
var os2 = __toESM(require("os"));
|
|
675
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
676
|
+
var MAX_OUTPUT_LENGTH = 5e4;
|
|
677
|
+
var PACKAGE_CACHE_DIR = path2.join(os2.homedir(), ".smol-js", "packages");
|
|
678
|
+
var LocalExecutor = class {
|
|
679
|
+
context;
|
|
680
|
+
state = {};
|
|
681
|
+
tools = /* @__PURE__ */ new Map();
|
|
682
|
+
config;
|
|
683
|
+
capturedLogs = [];
|
|
684
|
+
constructor(config = {}) {
|
|
685
|
+
this.config = {
|
|
686
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
687
|
+
allowFs: true,
|
|
688
|
+
workingDirectory: process.cwd(),
|
|
689
|
+
...config
|
|
690
|
+
};
|
|
691
|
+
this.context = this.createContext();
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Create the VM context with available globals.
|
|
695
|
+
*/
|
|
696
|
+
createContext() {
|
|
697
|
+
const consoleProxy = {
|
|
698
|
+
log: (...args) => {
|
|
699
|
+
const output = args.map((arg) => this.stringify(arg)).join(" ");
|
|
700
|
+
this.capturedLogs.push(output);
|
|
701
|
+
},
|
|
702
|
+
error: (...args) => {
|
|
703
|
+
const output = args.map((arg) => this.stringify(arg)).join(" ");
|
|
704
|
+
this.capturedLogs.push(`[ERROR] ${output}`);
|
|
705
|
+
},
|
|
706
|
+
warn: (...args) => {
|
|
707
|
+
const output = args.map((arg) => this.stringify(arg)).join(" ");
|
|
708
|
+
this.capturedLogs.push(`[WARN] ${output}`);
|
|
709
|
+
},
|
|
710
|
+
info: (...args) => {
|
|
711
|
+
const output = args.map((arg) => this.stringify(arg)).join(" ");
|
|
712
|
+
this.capturedLogs.push(output);
|
|
713
|
+
},
|
|
714
|
+
debug: (...args) => {
|
|
715
|
+
const output = args.map((arg) => this.stringify(arg)).join(" ");
|
|
716
|
+
this.capturedLogs.push(`[DEBUG] ${output}`);
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
const print = (...args) => consoleProxy.log(...args);
|
|
720
|
+
const dynamicImport = async (packageName) => {
|
|
721
|
+
const authorized = this.config.authorizedImports ?? [];
|
|
722
|
+
const basePackage = packageName.split("/")[0];
|
|
723
|
+
if (!authorized.includes(basePackage) && !authorized.includes(packageName)) {
|
|
724
|
+
throw new Error(
|
|
725
|
+
`Import not authorized: ${packageName}. Add it to authorizedImports to allow.`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
try {
|
|
729
|
+
if (!fs2.existsSync(PACKAGE_CACHE_DIR)) {
|
|
730
|
+
fs2.mkdirSync(PACKAGE_CACHE_DIR, { recursive: true });
|
|
731
|
+
}
|
|
732
|
+
const safeFileName = packageName.replace(/[/@]/g, "_") + ".mjs";
|
|
733
|
+
const cachedPath = path2.join(PACKAGE_CACHE_DIR, safeFileName);
|
|
734
|
+
let needsFetch = !fs2.existsSync(cachedPath);
|
|
735
|
+
if (!needsFetch) {
|
|
736
|
+
const content = fs2.readFileSync(cachedPath, "utf-8");
|
|
737
|
+
if (content.includes('export * from "/') || content.includes("export * from '/")) {
|
|
738
|
+
needsFetch = true;
|
|
739
|
+
fs2.unlinkSync(cachedPath);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (needsFetch) {
|
|
743
|
+
this.capturedLogs.push(`[import] Fetching ${packageName}...`);
|
|
744
|
+
const jsdelivrUrl = `https://cdn.jsdelivr.net/npm/${packageName}/+esm`;
|
|
745
|
+
const response = await fetch(jsdelivrUrl);
|
|
746
|
+
if (!response.ok) {
|
|
747
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
748
|
+
}
|
|
749
|
+
let code = await response.text();
|
|
750
|
+
const importMatches = code.matchAll(/from\s+["'](https:\/\/cdn\.jsdelivr\.net\/[^"']+)["']/g);
|
|
751
|
+
for (const match of importMatches) {
|
|
752
|
+
const depUrl = match[1];
|
|
753
|
+
const depName = depUrl.split("/npm/")[1]?.split("/")[0] || "dep";
|
|
754
|
+
const depFileName = depName.replace(/[/@]/g, "_") + "_dep.mjs";
|
|
755
|
+
const depCachedPath = path2.join(PACKAGE_CACHE_DIR, depFileName);
|
|
756
|
+
if (!fs2.existsSync(depCachedPath)) {
|
|
757
|
+
this.capturedLogs.push(`[import] Fetching dependency from ${depUrl}...`);
|
|
758
|
+
const depResponse = await fetch(depUrl);
|
|
759
|
+
if (depResponse.ok) {
|
|
760
|
+
const depCode = await depResponse.text();
|
|
761
|
+
fs2.writeFileSync(depCachedPath, depCode, "utf-8");
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
code = code.replace(depUrl, `file://${depCachedPath}`);
|
|
765
|
+
}
|
|
766
|
+
fs2.writeFileSync(cachedPath, code, "utf-8");
|
|
767
|
+
this.capturedLogs.push(`[import] Cached ${packageName} to ${cachedPath}`);
|
|
768
|
+
} else {
|
|
769
|
+
this.capturedLogs.push(`[import] Using cached ${packageName}`);
|
|
770
|
+
}
|
|
771
|
+
const fileUrl = `file://${cachedPath}`;
|
|
772
|
+
const module2 = await import(fileUrl);
|
|
773
|
+
return module2.default ?? module2;
|
|
774
|
+
} catch (error) {
|
|
775
|
+
throw new Error(`Failed to import ${packageName}: ${error.message}`);
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
const contextObj = {
|
|
779
|
+
// Console and print
|
|
780
|
+
console: consoleProxy,
|
|
781
|
+
print,
|
|
782
|
+
// Built-in objects
|
|
783
|
+
Object,
|
|
784
|
+
Array,
|
|
785
|
+
String,
|
|
786
|
+
Number,
|
|
787
|
+
Boolean,
|
|
788
|
+
Date,
|
|
789
|
+
Math,
|
|
790
|
+
JSON,
|
|
791
|
+
RegExp,
|
|
792
|
+
Error,
|
|
793
|
+
Map,
|
|
794
|
+
Set,
|
|
795
|
+
WeakMap,
|
|
796
|
+
WeakSet,
|
|
797
|
+
Promise,
|
|
798
|
+
Symbol,
|
|
799
|
+
Proxy,
|
|
800
|
+
Reflect,
|
|
801
|
+
// Type checking
|
|
802
|
+
parseInt,
|
|
803
|
+
parseFloat,
|
|
804
|
+
isNaN,
|
|
805
|
+
isFinite,
|
|
806
|
+
typeof: (v) => typeof v,
|
|
807
|
+
// Timers (promisified for async support)
|
|
808
|
+
setTimeout: global.setTimeout,
|
|
809
|
+
clearTimeout: global.clearTimeout,
|
|
810
|
+
setInterval: global.setInterval,
|
|
811
|
+
clearInterval: global.clearInterval,
|
|
812
|
+
// Async utilities
|
|
813
|
+
fetch: global.fetch,
|
|
814
|
+
// Dynamic import for npm packages
|
|
815
|
+
importPackage: dynamicImport,
|
|
816
|
+
// URL handling
|
|
817
|
+
URL,
|
|
818
|
+
URLSearchParams,
|
|
819
|
+
// Text encoding
|
|
820
|
+
TextEncoder,
|
|
821
|
+
TextDecoder,
|
|
822
|
+
// Buffer (useful for many operations)
|
|
823
|
+
Buffer,
|
|
824
|
+
// State reference (variables persist here)
|
|
825
|
+
__state__: this.state,
|
|
826
|
+
// Final answer marker
|
|
827
|
+
__final_answer__: null,
|
|
828
|
+
__is_final_answer__: false
|
|
829
|
+
};
|
|
830
|
+
if (this.config.allowFs) {
|
|
831
|
+
contextObj.fs = {
|
|
832
|
+
readFileSync: (filePath, encoding) => {
|
|
833
|
+
const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
|
|
834
|
+
return fs2.readFileSync(resolvedPath, encoding ?? "utf-8");
|
|
835
|
+
},
|
|
836
|
+
writeFileSync: (filePath, data) => {
|
|
837
|
+
const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
|
|
838
|
+
return fs2.writeFileSync(resolvedPath, data);
|
|
839
|
+
},
|
|
840
|
+
existsSync: (filePath) => {
|
|
841
|
+
const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
|
|
842
|
+
return fs2.existsSync(resolvedPath);
|
|
843
|
+
},
|
|
844
|
+
readdirSync: (dirPath) => {
|
|
845
|
+
const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), dirPath);
|
|
846
|
+
return fs2.readdirSync(resolvedPath);
|
|
847
|
+
},
|
|
848
|
+
mkdirSync: (dirPath, options) => {
|
|
849
|
+
const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), dirPath);
|
|
850
|
+
return fs2.mkdirSync(resolvedPath, options);
|
|
851
|
+
},
|
|
852
|
+
unlinkSync: (filePath) => {
|
|
853
|
+
const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
|
|
854
|
+
return fs2.unlinkSync(resolvedPath);
|
|
855
|
+
},
|
|
856
|
+
statSync: (filePath) => {
|
|
857
|
+
const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
|
|
858
|
+
return fs2.statSync(resolvedPath);
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
contextObj.path = {
|
|
862
|
+
join: path2.join,
|
|
863
|
+
resolve: (...paths) => path2.resolve(this.config.workingDirectory ?? process.cwd(), ...paths),
|
|
864
|
+
dirname: path2.dirname,
|
|
865
|
+
basename: path2.basename,
|
|
866
|
+
extname: path2.extname
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
return vm.createContext(contextObj);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Add tools to the executor context.
|
|
873
|
+
*/
|
|
874
|
+
sendTools(tools) {
|
|
875
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
876
|
+
this.tools.set(name, tool);
|
|
877
|
+
this.context[name] = async (...args) => {
|
|
878
|
+
let callArgs;
|
|
879
|
+
if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
|
|
880
|
+
callArgs = args[0];
|
|
881
|
+
} else {
|
|
882
|
+
const inputNames = Object.keys(tool.inputs);
|
|
883
|
+
callArgs = {};
|
|
884
|
+
args.forEach((arg, i) => {
|
|
885
|
+
if (i < inputNames.length) {
|
|
886
|
+
callArgs[inputNames[i]] = arg;
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
return tool.call(callArgs);
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Send variables to the executor state.
|
|
896
|
+
*/
|
|
897
|
+
sendVariables(variables) {
|
|
898
|
+
Object.assign(this.state, variables);
|
|
899
|
+
Object.assign(this.context, variables);
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Execute JavaScript code and return the result.
|
|
903
|
+
*/
|
|
904
|
+
async execute(code) {
|
|
905
|
+
this.capturedLogs = [];
|
|
906
|
+
this.context.__is_final_answer__ = false;
|
|
907
|
+
this.context.__final_answer__ = null;
|
|
908
|
+
Object.assign(this.context, this.state);
|
|
909
|
+
const wrappedCode = this.wrapCode(code);
|
|
910
|
+
try {
|
|
911
|
+
const script = new vm.Script(wrappedCode, {
|
|
912
|
+
filename: "agent-code.js"
|
|
913
|
+
});
|
|
914
|
+
const result = await script.runInContext(this.context, {
|
|
915
|
+
timeout: this.config.timeout,
|
|
916
|
+
displayErrors: true
|
|
917
|
+
});
|
|
918
|
+
const output = result instanceof Promise ? await result : result;
|
|
919
|
+
this.updateStateFromContext();
|
|
920
|
+
const isFinalAnswer = this.context.__is_final_answer__;
|
|
921
|
+
const finalOutput = isFinalAnswer ? this.context.__final_answer__ : output;
|
|
922
|
+
const logs = this.capturedLogs.join("\n").slice(0, MAX_OUTPUT_LENGTH);
|
|
923
|
+
return {
|
|
924
|
+
output: finalOutput,
|
|
925
|
+
logs,
|
|
926
|
+
isFinalAnswer
|
|
927
|
+
};
|
|
928
|
+
} catch (error) {
|
|
929
|
+
const logs = this.capturedLogs.join("\n").slice(0, MAX_OUTPUT_LENGTH);
|
|
930
|
+
return {
|
|
931
|
+
output: null,
|
|
932
|
+
logs,
|
|
933
|
+
isFinalAnswer: false,
|
|
934
|
+
error
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Wrap code to handle async execution and final_answer calls.
|
|
940
|
+
*/
|
|
941
|
+
wrapCode(code) {
|
|
942
|
+
const finalAnswerFunc = `
|
|
943
|
+
function final_answer(answer) {
|
|
944
|
+
__is_final_answer__ = true;
|
|
945
|
+
__final_answer__ = answer;
|
|
946
|
+
return answer;
|
|
947
|
+
}
|
|
948
|
+
`;
|
|
949
|
+
return `
|
|
950
|
+
${finalAnswerFunc}
|
|
951
|
+
(async () => {
|
|
952
|
+
let __last_result__;
|
|
953
|
+
${this.instrumentCode(code)}
|
|
954
|
+
return __last_result__;
|
|
955
|
+
})()
|
|
956
|
+
`;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Instrument code to capture the last expression value and convert
|
|
960
|
+
* let/const/var declarations to global assignments for state persistence.
|
|
961
|
+
*/
|
|
962
|
+
instrumentCode(code) {
|
|
963
|
+
const lines = code.trim().split("\n");
|
|
964
|
+
if (lines.length === 0) {
|
|
965
|
+
return code;
|
|
966
|
+
}
|
|
967
|
+
const processedLines = lines.map((line, index) => {
|
|
968
|
+
const trimmed = line.trim();
|
|
969
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.endsWith("*/")) {
|
|
970
|
+
return line;
|
|
971
|
+
}
|
|
972
|
+
let transformed = line;
|
|
973
|
+
let hasDeclaration = false;
|
|
974
|
+
transformed = transformed.replace(
|
|
975
|
+
/\b(let|const|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g,
|
|
976
|
+
(_match, _keyword, varName) => {
|
|
977
|
+
hasDeclaration = true;
|
|
978
|
+
return `${varName} =`;
|
|
979
|
+
}
|
|
980
|
+
);
|
|
981
|
+
transformed = transformed.replace(
|
|
982
|
+
/\b(let|const|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=[;,]|$)/g,
|
|
983
|
+
(_match, _keyword, varName) => {
|
|
984
|
+
hasDeclaration = true;
|
|
985
|
+
return `${varName} = undefined`;
|
|
986
|
+
}
|
|
987
|
+
);
|
|
988
|
+
if (hasDeclaration) {
|
|
989
|
+
return transformed;
|
|
990
|
+
}
|
|
991
|
+
if (trimmed.startsWith("if") || trimmed.startsWith("else") || trimmed.startsWith("for") || trimmed.startsWith("while") || trimmed.startsWith("do") || trimmed.startsWith("switch") || trimmed.startsWith("case") || trimmed.startsWith("default") || trimmed.startsWith("try") || trimmed.startsWith("catch") || trimmed.startsWith("finally") || trimmed.startsWith("return") || trimmed.startsWith("throw") || trimmed.startsWith("break") || trimmed.startsWith("continue") || trimmed.startsWith("function") || trimmed.startsWith("class") || trimmed.startsWith("import") || trimmed.startsWith("export") || trimmed === "{" || trimmed === "}" || trimmed.endsWith("{") || trimmed.endsWith("}")) {
|
|
992
|
+
return line;
|
|
993
|
+
}
|
|
994
|
+
if (index === lines.length - 1 || this.isLastMeaningfulLine(lines, index)) {
|
|
995
|
+
if (!trimmed.endsWith(";")) {
|
|
996
|
+
return `__last_result__ = ${line}`;
|
|
997
|
+
} else {
|
|
998
|
+
const withoutSemi = trimmed.slice(0, -1);
|
|
999
|
+
if (!withoutSemi.endsWith("}") && !withoutSemi.endsWith(")")) {
|
|
1000
|
+
return `__last_result__ = ${withoutSemi};`;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return line;
|
|
1005
|
+
});
|
|
1006
|
+
return processedLines.join("\n");
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Check if this is the last meaningful line of code.
|
|
1010
|
+
*/
|
|
1011
|
+
isLastMeaningfulLine(lines, currentIndex) {
|
|
1012
|
+
for (let i = currentIndex + 1; i < lines.length; i++) {
|
|
1013
|
+
const trimmed = lines[i].trim();
|
|
1014
|
+
if (trimmed && !trimmed.startsWith("//") && !trimmed.startsWith("/*")) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Update internal state from context after execution.
|
|
1022
|
+
*/
|
|
1023
|
+
updateStateFromContext() {
|
|
1024
|
+
const excludeKeys = /* @__PURE__ */ new Set([
|
|
1025
|
+
"console",
|
|
1026
|
+
"print",
|
|
1027
|
+
"Object",
|
|
1028
|
+
"Array",
|
|
1029
|
+
"String",
|
|
1030
|
+
"Number",
|
|
1031
|
+
"Boolean",
|
|
1032
|
+
"Date",
|
|
1033
|
+
"Math",
|
|
1034
|
+
"JSON",
|
|
1035
|
+
"RegExp",
|
|
1036
|
+
"Error",
|
|
1037
|
+
"Map",
|
|
1038
|
+
"Set",
|
|
1039
|
+
"WeakMap",
|
|
1040
|
+
"WeakSet",
|
|
1041
|
+
"Promise",
|
|
1042
|
+
"Symbol",
|
|
1043
|
+
"Proxy",
|
|
1044
|
+
"Reflect",
|
|
1045
|
+
"parseInt",
|
|
1046
|
+
"parseFloat",
|
|
1047
|
+
"isNaN",
|
|
1048
|
+
"isFinite",
|
|
1049
|
+
"typeof",
|
|
1050
|
+
"setTimeout",
|
|
1051
|
+
"clearTimeout",
|
|
1052
|
+
"setInterval",
|
|
1053
|
+
"clearInterval",
|
|
1054
|
+
"fetch",
|
|
1055
|
+
"importPackage",
|
|
1056
|
+
"URL",
|
|
1057
|
+
"URLSearchParams",
|
|
1058
|
+
"TextEncoder",
|
|
1059
|
+
"TextDecoder",
|
|
1060
|
+
"Buffer",
|
|
1061
|
+
"__state__",
|
|
1062
|
+
"__final_answer__",
|
|
1063
|
+
"__is_final_answer__",
|
|
1064
|
+
"fs",
|
|
1065
|
+
"path",
|
|
1066
|
+
"final_answer",
|
|
1067
|
+
// Exclude tools
|
|
1068
|
+
...this.tools.keys()
|
|
1069
|
+
]);
|
|
1070
|
+
for (const key of Object.keys(this.context)) {
|
|
1071
|
+
if (!excludeKeys.has(key)) {
|
|
1072
|
+
this.state[key] = this.context[key];
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Stringify a value for logging.
|
|
1078
|
+
*/
|
|
1079
|
+
stringify(value) {
|
|
1080
|
+
if (value === void 0) return "undefined";
|
|
1081
|
+
if (value === null) return "null";
|
|
1082
|
+
if (typeof value === "string") return value;
|
|
1083
|
+
if (typeof value === "function") return `[Function: ${value.name || "anonymous"}]`;
|
|
1084
|
+
try {
|
|
1085
|
+
return JSON.stringify(value, null, 2);
|
|
1086
|
+
} catch {
|
|
1087
|
+
return String(value);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Reset the executor state.
|
|
1092
|
+
*/
|
|
1093
|
+
reset() {
|
|
1094
|
+
this.state = {};
|
|
1095
|
+
this.capturedLogs = [];
|
|
1096
|
+
this.context = this.createContext();
|
|
1097
|
+
const tools = Object.fromEntries(this.tools);
|
|
1098
|
+
this.sendTools(tools);
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Get the current state.
|
|
1102
|
+
*/
|
|
1103
|
+
getState() {
|
|
1104
|
+
return { ...this.state };
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
// src/tools/Tool.ts
|
|
1109
|
+
var Tool = class {
|
|
1110
|
+
/**
|
|
1111
|
+
* Whether the tool has been set up
|
|
1112
|
+
*/
|
|
1113
|
+
isSetup = false;
|
|
1114
|
+
/**
|
|
1115
|
+
* Optional setup method called before first use.
|
|
1116
|
+
* Override this for expensive initialization (loading models, etc.)
|
|
1117
|
+
*/
|
|
1118
|
+
async setup() {
|
|
1119
|
+
this.isSetup = true;
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Call the tool, ensuring setup is complete and validating arguments.
|
|
1123
|
+
*/
|
|
1124
|
+
async call(args) {
|
|
1125
|
+
if (!this.isSetup) {
|
|
1126
|
+
await this.setup();
|
|
1127
|
+
}
|
|
1128
|
+
this.validateArguments(args);
|
|
1129
|
+
return this.execute(args);
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Validate that provided arguments match the input schema.
|
|
1133
|
+
*/
|
|
1134
|
+
validateArguments(args) {
|
|
1135
|
+
const providedKeys = new Set(Object.keys(args));
|
|
1136
|
+
for (const [key, input] of Object.entries(this.inputs)) {
|
|
1137
|
+
if (input.required !== false && !providedKeys.has(key)) {
|
|
1138
|
+
throw new Error(`Missing required argument: ${key}`);
|
|
1139
|
+
}
|
|
1140
|
+
if (providedKeys.has(key) && args[key] !== void 0 && args[key] !== null) {
|
|
1141
|
+
const value = args[key];
|
|
1142
|
+
if (!this.checkType(value, input.type)) {
|
|
1143
|
+
throw new Error(
|
|
1144
|
+
`Argument '${key}' has invalid type. Expected ${input.type}, got ${typeof value}`
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
providedKeys.delete(key);
|
|
1149
|
+
}
|
|
1150
|
+
if (providedKeys.size > 0) {
|
|
1151
|
+
console.warn(`Unknown arguments provided to ${this.name}: ${[...providedKeys].join(", ")}`);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Check if a value matches the expected type.
|
|
1156
|
+
*/
|
|
1157
|
+
checkType(value, expectedType) {
|
|
1158
|
+
switch (expectedType) {
|
|
1159
|
+
case "string":
|
|
1160
|
+
return typeof value === "string";
|
|
1161
|
+
case "number":
|
|
1162
|
+
return typeof value === "number";
|
|
1163
|
+
case "boolean":
|
|
1164
|
+
return typeof value === "boolean";
|
|
1165
|
+
case "array":
|
|
1166
|
+
return Array.isArray(value);
|
|
1167
|
+
case "object":
|
|
1168
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1169
|
+
case "any":
|
|
1170
|
+
return true;
|
|
1171
|
+
default:
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Generate a code-friendly prompt representation of this tool.
|
|
1177
|
+
* Used in the CodeAgent system prompt.
|
|
1178
|
+
*/
|
|
1179
|
+
toCodePrompt() {
|
|
1180
|
+
const argsSignature = Object.entries(this.inputs).map(([name, input]) => {
|
|
1181
|
+
const optional = input.required === false ? "?" : "";
|
|
1182
|
+
return `${name}${optional}: ${this.typeToJsType(input.type)}`;
|
|
1183
|
+
}).join(", ");
|
|
1184
|
+
const argsDoc = Object.entries(this.inputs).map(([name, input]) => ` * @param ${name} - ${input.description}`).join("\n");
|
|
1185
|
+
return `
|
|
1186
|
+
/**
|
|
1187
|
+
* ${this.description}
|
|
1188
|
+
*
|
|
1189
|
+
${argsDoc}
|
|
1190
|
+
* @returns ${this.outputType}
|
|
1191
|
+
*/
|
|
1192
|
+
async function ${this.name}(${argsSignature}): Promise<${this.typeToJsType(this.outputType)}> { ... }
|
|
1193
|
+
`.trim();
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Convert tool input type to JS/TS type string.
|
|
1197
|
+
*/
|
|
1198
|
+
typeToJsType(type) {
|
|
1199
|
+
switch (type) {
|
|
1200
|
+
case "string":
|
|
1201
|
+
return "string";
|
|
1202
|
+
case "number":
|
|
1203
|
+
return "number";
|
|
1204
|
+
case "boolean":
|
|
1205
|
+
return "boolean";
|
|
1206
|
+
case "array":
|
|
1207
|
+
return "any[]";
|
|
1208
|
+
case "object":
|
|
1209
|
+
return "Record<string, any>";
|
|
1210
|
+
case "any":
|
|
1211
|
+
return "any";
|
|
1212
|
+
default:
|
|
1213
|
+
return type;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Serialize the tool to a JSON-compatible object.
|
|
1218
|
+
*/
|
|
1219
|
+
toJSON() {
|
|
1220
|
+
return {
|
|
1221
|
+
name: this.name,
|
|
1222
|
+
description: this.description,
|
|
1223
|
+
inputs: this.inputs,
|
|
1224
|
+
outputType: this.outputType
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
function createTool(config) {
|
|
1229
|
+
return new class extends Tool {
|
|
1230
|
+
name = config.name;
|
|
1231
|
+
description = config.description;
|
|
1232
|
+
inputs = config.inputs;
|
|
1233
|
+
outputType = config.outputType;
|
|
1234
|
+
async execute(args) {
|
|
1235
|
+
return config.execute(args);
|
|
1236
|
+
}
|
|
1237
|
+
}();
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// src/tools/defaultTools.ts
|
|
1241
|
+
var FinalAnswerTool = class extends Tool {
|
|
1242
|
+
name = "final_answer";
|
|
1243
|
+
description = "Returns the final answer to the user query. Use this when you have completed the task and have the final result.";
|
|
1244
|
+
inputs = {
|
|
1245
|
+
answer: {
|
|
1246
|
+
type: "any",
|
|
1247
|
+
description: "The final answer to return. Can be any type (string, number, object, etc.)",
|
|
1248
|
+
required: true
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
outputType = "any";
|
|
1252
|
+
async execute(args) {
|
|
1253
|
+
return args.answer;
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
var UserInputTool = class extends Tool {
|
|
1257
|
+
name = "user_input";
|
|
1258
|
+
description = "Asks the user for additional input or clarification.";
|
|
1259
|
+
inputs = {
|
|
1260
|
+
question: {
|
|
1261
|
+
type: "string",
|
|
1262
|
+
description: "The question to ask the user",
|
|
1263
|
+
required: true
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
outputType = "string";
|
|
1267
|
+
inputHandler;
|
|
1268
|
+
constructor(inputHandler) {
|
|
1269
|
+
super();
|
|
1270
|
+
this.inputHandler = inputHandler;
|
|
1271
|
+
}
|
|
1272
|
+
async execute(args) {
|
|
1273
|
+
const question = args.question;
|
|
1274
|
+
if (this.inputHandler) {
|
|
1275
|
+
return this.inputHandler(question);
|
|
1276
|
+
}
|
|
1277
|
+
const readline = await import("readline");
|
|
1278
|
+
const rl = readline.createInterface({
|
|
1279
|
+
input: process.stdin,
|
|
1280
|
+
output: process.stdout
|
|
1281
|
+
});
|
|
1282
|
+
return new Promise((resolve2) => {
|
|
1283
|
+
rl.question(`
|
|
1284
|
+
[Agent asks]: ${question}
|
|
1285
|
+
Your response: `, (answer) => {
|
|
1286
|
+
rl.close();
|
|
1287
|
+
resolve2(answer);
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
var finalAnswerTool = new FinalAnswerTool();
|
|
1293
|
+
|
|
1294
|
+
// src/prompts/codeAgent.ts
|
|
1295
|
+
function generateSystemPrompt(variables) {
|
|
1296
|
+
const { tools, authorizedImports, customInstructions } = variables;
|
|
1297
|
+
return `You are an expert JavaScript developer and problem-solving agent. Your role is to solve tasks by writing and executing JavaScript code step by step.
|
|
1298
|
+
|
|
1299
|
+
## How You Work
|
|
1300
|
+
|
|
1301
|
+
You follow a ReAct (Reasoning + Acting) framework:
|
|
1302
|
+
1. **Thought**: Analyze the current situation and decide what to do next
|
|
1303
|
+
2. **Code**: Write JavaScript code to perform the action
|
|
1304
|
+
3. **Observation**: See the result of your code execution
|
|
1305
|
+
4. Repeat until you have the final answer
|
|
1306
|
+
|
|
1307
|
+
## Available Tools
|
|
1308
|
+
|
|
1309
|
+
You have access to the following tools as async functions:
|
|
1310
|
+
|
|
1311
|
+
${tools}
|
|
1312
|
+
|
|
1313
|
+
## Available Imports
|
|
1314
|
+
|
|
1315
|
+
You can dynamically import the following npm packages using \`await importPackage('package-name')\`:
|
|
1316
|
+
${authorizedImports || "(No additional packages authorized)"}
|
|
1317
|
+
|
|
1318
|
+
## Built-in Capabilities
|
|
1319
|
+
|
|
1320
|
+
The following are available in your execution environment:
|
|
1321
|
+
- \`console.log()\` / \`print()\` - Output text (captured in logs)
|
|
1322
|
+
- \`fs\` - File system operations (readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync)
|
|
1323
|
+
- \`path\` - Path utilities (join, resolve, dirname, basename)
|
|
1324
|
+
- \`fetch()\` - HTTP requests
|
|
1325
|
+
- \`Buffer\` - Binary data handling
|
|
1326
|
+
- \`JSON\` - JSON parsing/stringifying
|
|
1327
|
+
- Standard JavaScript globals (Math, Date, Array methods, etc.)
|
|
1328
|
+
|
|
1329
|
+
## Response Format
|
|
1330
|
+
|
|
1331
|
+
Always respond with your thought process followed by a code block:
|
|
1332
|
+
|
|
1333
|
+
Thought: [Your reasoning about what to do]
|
|
1334
|
+
|
|
1335
|
+
\`\`\`javascript
|
|
1336
|
+
// Your code here
|
|
1337
|
+
\`\`\`
|
|
1338
|
+
|
|
1339
|
+
## Rules
|
|
1340
|
+
|
|
1341
|
+
1. **Always use final_answer()**: When you have the complete answer, call \`final_answer(yourResult)\` to return it.
|
|
1342
|
+
|
|
1343
|
+
2. **One action per step**: Execute one logical action per code block. Don't try to do everything at once.
|
|
1344
|
+
|
|
1345
|
+
3. **Handle errors gracefully**: If something fails, explain what went wrong and try a different approach.
|
|
1346
|
+
|
|
1347
|
+
4. **Use async/await**: All tool calls and imports are async. Always use await.
|
|
1348
|
+
|
|
1349
|
+
5. **Variables persist**: Variables you define in one step are available in the next step.
|
|
1350
|
+
|
|
1351
|
+
6. **Be concise**: Write clean, minimal code. Don't over-engineer.
|
|
1352
|
+
|
|
1353
|
+
7. **Print for debugging**: Use console.log() to output intermediate results you want to see.
|
|
1354
|
+
|
|
1355
|
+
8. **No require()**: Use \`await importPackage('name')\` for npm packages instead of require().
|
|
1356
|
+
|
|
1357
|
+
## Examples
|
|
1358
|
+
|
|
1359
|
+
### Example 1: Simple calculation
|
|
1360
|
+
Thought: I need to calculate the sum of squares from 1 to 10.
|
|
1361
|
+
|
|
1362
|
+
\`\`\`javascript
|
|
1363
|
+
let sum = 0;
|
|
1364
|
+
for (let i = 1; i <= 10; i++) {
|
|
1365
|
+
sum += i * i;
|
|
1366
|
+
}
|
|
1367
|
+
console.log("Sum of squares:", sum);
|
|
1368
|
+
final_answer(sum);
|
|
1369
|
+
\`\`\`
|
|
1370
|
+
|
|
1371
|
+
### Example 2: Using a tool
|
|
1372
|
+
Thought: I need to search the web for current information.
|
|
1373
|
+
|
|
1374
|
+
\`\`\`javascript
|
|
1375
|
+
const results = await web_search({ query: "latest JavaScript features 2024" });
|
|
1376
|
+
console.log("Search results:", results);
|
|
1377
|
+
\`\`\`
|
|
1378
|
+
|
|
1379
|
+
### Example 3: Reading a file
|
|
1380
|
+
Thought: I need to read the contents of package.json.
|
|
1381
|
+
|
|
1382
|
+
\`\`\`javascript
|
|
1383
|
+
const content = fs.readFileSync('package.json', 'utf-8');
|
|
1384
|
+
const pkg = JSON.parse(content);
|
|
1385
|
+
console.log("Package name:", pkg.name);
|
|
1386
|
+
final_answer(pkg);
|
|
1387
|
+
\`\`\`
|
|
1388
|
+
|
|
1389
|
+
### Example 4: Using dynamic imports
|
|
1390
|
+
Thought: I need to use lodash for array manipulation.
|
|
1391
|
+
|
|
1392
|
+
\`\`\`javascript
|
|
1393
|
+
const _ = await importPackage('lodash');
|
|
1394
|
+
const numbers = [1, 2, 3, 4, 5];
|
|
1395
|
+
const chunked = _.chunk(numbers, 2);
|
|
1396
|
+
final_answer(chunked);
|
|
1397
|
+
\`\`\`
|
|
1398
|
+
|
|
1399
|
+
### Example 5: Multi-step task
|
|
1400
|
+
Thought: First, I'll fetch the data from the API.
|
|
1401
|
+
|
|
1402
|
+
\`\`\`javascript
|
|
1403
|
+
const response = await fetch('https://api.example.com/data');
|
|
1404
|
+
const data = await response.json();
|
|
1405
|
+
console.log("Fetched items:", data.length);
|
|
1406
|
+
\`\`\`
|
|
1407
|
+
|
|
1408
|
+
(Observation: Fetched items: 42)
|
|
1409
|
+
|
|
1410
|
+
Thought: Now I'll process the data and return the result.
|
|
1411
|
+
|
|
1412
|
+
\`\`\`javascript
|
|
1413
|
+
const processed = data.filter(item => item.active).map(item => item.name);
|
|
1414
|
+
final_answer(processed);
|
|
1415
|
+
\`\`\`
|
|
1416
|
+
|
|
1417
|
+
${customInstructions ? `
|
|
1418
|
+
## Additional Instructions
|
|
1419
|
+
|
|
1420
|
+
${customInstructions}` : ""}
|
|
1421
|
+
|
|
1422
|
+
Now, let's solve the task step by step. Remember to always call final_answer() when you have the complete answer.`;
|
|
1423
|
+
}
|
|
1424
|
+
var FINAL_ANSWER_PROMPT = `Based on the steps you've taken so far, provide the best answer you can to the original task.
|
|
1425
|
+
If you couldn't fully complete the task, explain what you accomplished and what remains to be done.
|
|
1426
|
+
Call final_answer() with your response.`;
|
|
1427
|
+
function getErrorRecoveryPrompt(error) {
|
|
1428
|
+
return `Your previous code encountered an error:
|
|
1429
|
+
|
|
1430
|
+
${error}
|
|
1431
|
+
|
|
1432
|
+
Please analyze the error and try a different approach. Fix the issue and continue working on the task.`;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// src/agents/CodeAgent.ts
|
|
1436
|
+
var CODE_BLOCK_REGEX = /```(?:javascript|js)?\n([\s\S]*?)```/;
|
|
1437
|
+
var THOUGHT_REGEX = /(?:Thought|Reasoning):\s*([\s\S]*?)(?=```|$)/i;
|
|
1438
|
+
var CodeAgent = class extends Agent {
|
|
1439
|
+
/**
|
|
1440
|
+
* The JavaScript code executor
|
|
1441
|
+
*/
|
|
1442
|
+
executor;
|
|
1443
|
+
/**
|
|
1444
|
+
* Authorized imports for dynamic npm package loading
|
|
1445
|
+
*/
|
|
1446
|
+
authorizedImports;
|
|
1447
|
+
constructor(config) {
|
|
1448
|
+
super(config);
|
|
1449
|
+
this.authorizedImports = config.additionalAuthorizedImports ?? [];
|
|
1450
|
+
this.executor = new LocalExecutor({
|
|
1451
|
+
...config.executorConfig,
|
|
1452
|
+
authorizedImports: this.authorizedImports,
|
|
1453
|
+
workingDirectory: config.workingDirectory
|
|
1454
|
+
});
|
|
1455
|
+
if (!this.tools.has("final_answer")) {
|
|
1456
|
+
this.tools.set("final_answer", new FinalAnswerTool());
|
|
1457
|
+
}
|
|
1458
|
+
this.executor.sendTools(Object.fromEntries(this.tools));
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Initialize the system prompt with tool definitions.
|
|
1462
|
+
*/
|
|
1463
|
+
initializeSystemPrompt() {
|
|
1464
|
+
const toolDocs = Array.from(this.tools.values()).filter((tool) => tool.name !== "final_answer").map((tool) => tool.toCodePrompt()).join("\n\n");
|
|
1465
|
+
const finalAnswerDoc = `
|
|
1466
|
+
/**
|
|
1467
|
+
* Returns the final answer to the user. Call this when you have completed the task.
|
|
1468
|
+
* @param answer - The final answer (can be any type)
|
|
1469
|
+
*/
|
|
1470
|
+
function final_answer(answer: any): void { ... }
|
|
1471
|
+
`.trim();
|
|
1472
|
+
const allTools = toolDocs ? `${toolDocs}
|
|
1473
|
+
|
|
1474
|
+
${finalAnswerDoc}` : finalAnswerDoc;
|
|
1475
|
+
const importsDoc = this.authorizedImports.length > 0 ? this.authorizedImports.map((pkg) => `- ${pkg}`).join("\n") : "None (use built-in capabilities only)";
|
|
1476
|
+
return generateSystemPrompt({
|
|
1477
|
+
tools: allTools,
|
|
1478
|
+
authorizedImports: importsDoc,
|
|
1479
|
+
customInstructions: this.config.customInstructions
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Execute a single step: get LLM response, extract code, execute it.
|
|
1484
|
+
*/
|
|
1485
|
+
async executeStep(memoryStep) {
|
|
1486
|
+
const messages = this.memory.toMessages();
|
|
1487
|
+
memoryStep.modelInputMessages = [...messages];
|
|
1488
|
+
const lastStep = this.memory.getActionSteps().slice(-2)[0];
|
|
1489
|
+
if (lastStep?.error) {
|
|
1490
|
+
messages.push({
|
|
1491
|
+
role: "user",
|
|
1492
|
+
content: getErrorRecoveryPrompt(lastStep.error.message)
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
const response = await this.generateResponse(messages);
|
|
1496
|
+
memoryStep.modelOutputMessage = response;
|
|
1497
|
+
memoryStep.tokenUsage = response.tokenUsage;
|
|
1498
|
+
const content = response.content ?? "";
|
|
1499
|
+
const thoughtMatch = content.match(THOUGHT_REGEX);
|
|
1500
|
+
if (thoughtMatch) {
|
|
1501
|
+
this.logger.reasoning(thoughtMatch[1].trim());
|
|
1502
|
+
}
|
|
1503
|
+
const codeMatch = content.match(CODE_BLOCK_REGEX);
|
|
1504
|
+
if (!codeMatch) {
|
|
1505
|
+
this.logger.warn("No code block found in response");
|
|
1506
|
+
memoryStep.observation = "No code block was found in your response. Please provide JavaScript code in a ```javascript code block.";
|
|
1507
|
+
return {
|
|
1508
|
+
output: null,
|
|
1509
|
+
isFinalAnswer: false
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
const code = codeMatch[1].trim();
|
|
1513
|
+
memoryStep.codeAction = code;
|
|
1514
|
+
this.logger.code(code);
|
|
1515
|
+
if (this.config.codeExecutionDelay > 0) {
|
|
1516
|
+
this.logger.waiting(this.config.codeExecutionDelay / 1e3);
|
|
1517
|
+
await this.sleep(this.config.codeExecutionDelay);
|
|
1518
|
+
}
|
|
1519
|
+
this.logger.subheader("Executing code...");
|
|
1520
|
+
const result = await this.executor.execute(code);
|
|
1521
|
+
if (result.logs) {
|
|
1522
|
+
this.logger.logs(result.logs);
|
|
1523
|
+
}
|
|
1524
|
+
if (result.error) {
|
|
1525
|
+
this.logger.error("Code execution error", result.error);
|
|
1526
|
+
memoryStep.error = result.error;
|
|
1527
|
+
memoryStep.observation = `Error during code execution:
|
|
1528
|
+
${result.error.message}`;
|
|
1529
|
+
return {
|
|
1530
|
+
output: null,
|
|
1531
|
+
isFinalAnswer: false
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
const outputStr = this.formatOutput(result.output);
|
|
1535
|
+
this.logger.output(outputStr);
|
|
1536
|
+
memoryStep.observation = this.formatObservation(result.logs, outputStr);
|
|
1537
|
+
return {
|
|
1538
|
+
output: result.output,
|
|
1539
|
+
isFinalAnswer: result.isFinalAnswer
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Generate response from the LLM, optionally streaming.
|
|
1544
|
+
*/
|
|
1545
|
+
async generateResponse(messages) {
|
|
1546
|
+
if (this.config.streamOutputs && this.model.supportsStreaming() && this.model.generateStream) {
|
|
1547
|
+
this.logger.subheader("Agent thinking...");
|
|
1548
|
+
let fullContent = "";
|
|
1549
|
+
const generator = this.model.generateStream(messages, {
|
|
1550
|
+
stopSequences: ["Observation:", "Observation:\n"]
|
|
1551
|
+
});
|
|
1552
|
+
for await (const chunk of generator) {
|
|
1553
|
+
this.logger.streamChar(chunk);
|
|
1554
|
+
fullContent += chunk;
|
|
1555
|
+
}
|
|
1556
|
+
this.logger.streamEnd();
|
|
1557
|
+
return {
|
|
1558
|
+
role: "assistant",
|
|
1559
|
+
content: fullContent
|
|
1560
|
+
};
|
|
1561
|
+
} else {
|
|
1562
|
+
this.logger.subheader("Agent thinking...");
|
|
1563
|
+
return this.model.generate(messages, {
|
|
1564
|
+
stopSequences: ["Observation:", "Observation:\n"]
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Format output for display.
|
|
1570
|
+
*/
|
|
1571
|
+
formatOutput(output) {
|
|
1572
|
+
if (output === void 0 || output === null) {
|
|
1573
|
+
return "(no output)";
|
|
1574
|
+
}
|
|
1575
|
+
if (typeof output === "string") {
|
|
1576
|
+
return output;
|
|
1577
|
+
}
|
|
1578
|
+
try {
|
|
1579
|
+
return JSON.stringify(output, null, 2);
|
|
1580
|
+
} catch {
|
|
1581
|
+
return String(output);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Format the observation to send back to the LLM.
|
|
1586
|
+
*/
|
|
1587
|
+
formatObservation(logs, output) {
|
|
1588
|
+
const parts = [];
|
|
1589
|
+
if (logs.trim()) {
|
|
1590
|
+
parts.push(`Execution logs:
|
|
1591
|
+
${logs}`);
|
|
1592
|
+
}
|
|
1593
|
+
parts.push(`Last output:
|
|
1594
|
+
${output}`);
|
|
1595
|
+
return `Observation:
|
|
1596
|
+
${parts.join("\n\n")}`;
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Reset the agent and executor state.
|
|
1600
|
+
*/
|
|
1601
|
+
reset() {
|
|
1602
|
+
this.executor.reset();
|
|
1603
|
+
this.currentStep = 0;
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Get the executor instance.
|
|
1607
|
+
*/
|
|
1608
|
+
getExecutor() {
|
|
1609
|
+
return this.executor;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Override addTool to also register with executor.
|
|
1613
|
+
*/
|
|
1614
|
+
addTool(tool) {
|
|
1615
|
+
super.addTool(tool);
|
|
1616
|
+
this.executor.sendTools({ [tool.name]: tool });
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
// src/models/Model.ts
|
|
1621
|
+
var Model = class {
|
|
1622
|
+
/**
|
|
1623
|
+
* Check if the model supports streaming.
|
|
1624
|
+
*/
|
|
1625
|
+
supportsStreaming() {
|
|
1626
|
+
return typeof this.generateStream === "function";
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Extract token usage from a response message.
|
|
1630
|
+
*/
|
|
1631
|
+
extractTokenUsage(_response) {
|
|
1632
|
+
return void 0;
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Convert messages to the format expected by the model's API.
|
|
1636
|
+
*/
|
|
1637
|
+
formatMessages(messages) {
|
|
1638
|
+
return messages.map((msg) => ({
|
|
1639
|
+
role: msg.role,
|
|
1640
|
+
content: msg.content,
|
|
1641
|
+
...msg.name && { name: msg.name },
|
|
1642
|
+
...msg.toolCallId && { tool_call_id: msg.toolCallId }
|
|
1643
|
+
}));
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
// src/models/OpenAIModel.ts
|
|
1648
|
+
var import_openai = __toESM(require("openai"));
|
|
1649
|
+
var DEFAULT_CONFIG = {
|
|
1650
|
+
modelId: "anthropic/claude-sonnet-4.5",
|
|
1651
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
1652
|
+
maxTokens: 4096,
|
|
1653
|
+
temperature: 0.7,
|
|
1654
|
+
timeout: 12e4
|
|
1655
|
+
};
|
|
1656
|
+
var OpenAIModel = class extends Model {
|
|
1657
|
+
modelId;
|
|
1658
|
+
client;
|
|
1659
|
+
config;
|
|
1660
|
+
constructor(config = {}) {
|
|
1661
|
+
super();
|
|
1662
|
+
this.config = {
|
|
1663
|
+
...DEFAULT_CONFIG,
|
|
1664
|
+
...config
|
|
1665
|
+
};
|
|
1666
|
+
this.modelId = this.config.modelId ?? DEFAULT_CONFIG.modelId;
|
|
1667
|
+
const apiKey = this.config.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY;
|
|
1668
|
+
if (!apiKey) {
|
|
1669
|
+
throw new Error(
|
|
1670
|
+
"API key is required. Set OPENAI_API_KEY or OPENROUTER_API_KEY environment variable, or pass apiKey in config."
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
this.client = new import_openai.default({
|
|
1674
|
+
apiKey,
|
|
1675
|
+
baseURL: this.config.baseUrl,
|
|
1676
|
+
timeout: this.config.timeout,
|
|
1677
|
+
defaultHeaders: this.config.defaultHeaders
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Generate a response from the model.
|
|
1682
|
+
*/
|
|
1683
|
+
async generate(messages, options = {}) {
|
|
1684
|
+
const formattedMessages = this.formatMessages(messages);
|
|
1685
|
+
const response = await this.client.chat.completions.create({
|
|
1686
|
+
model: this.modelId,
|
|
1687
|
+
messages: formattedMessages,
|
|
1688
|
+
max_tokens: options.maxTokens ?? this.config.maxTokens,
|
|
1689
|
+
temperature: options.temperature ?? this.config.temperature,
|
|
1690
|
+
...options.stopSequences && { stop: options.stopSequences }
|
|
1691
|
+
});
|
|
1692
|
+
const choice = response.choices[0];
|
|
1693
|
+
const message = choice?.message;
|
|
1694
|
+
if (!message) {
|
|
1695
|
+
throw new Error("No response from model");
|
|
1696
|
+
}
|
|
1697
|
+
const tokenUsage = response.usage ? {
|
|
1698
|
+
inputTokens: response.usage.prompt_tokens,
|
|
1699
|
+
outputTokens: response.usage.completion_tokens,
|
|
1700
|
+
totalTokens: response.usage.total_tokens
|
|
1701
|
+
} : void 0;
|
|
1702
|
+
return {
|
|
1703
|
+
role: "assistant",
|
|
1704
|
+
content: message.content ?? "",
|
|
1705
|
+
tokenUsage
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Generate a streaming response from the model.
|
|
1710
|
+
*/
|
|
1711
|
+
async *generateStream(messages, options = {}) {
|
|
1712
|
+
const formattedMessages = this.formatMessages(messages);
|
|
1713
|
+
const stream = await this.client.chat.completions.create({
|
|
1714
|
+
model: this.modelId,
|
|
1715
|
+
messages: formattedMessages,
|
|
1716
|
+
max_tokens: options.maxTokens ?? this.config.maxTokens,
|
|
1717
|
+
temperature: options.temperature ?? this.config.temperature,
|
|
1718
|
+
...options.stopSequences && { stop: options.stopSequences },
|
|
1719
|
+
stream: true
|
|
1720
|
+
});
|
|
1721
|
+
let fullContent = "";
|
|
1722
|
+
for await (const chunk of stream) {
|
|
1723
|
+
const delta = chunk.choices[0]?.delta;
|
|
1724
|
+
if (delta?.content) {
|
|
1725
|
+
fullContent += delta.content;
|
|
1726
|
+
yield delta.content;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return {
|
|
1730
|
+
role: "assistant",
|
|
1731
|
+
content: fullContent
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Format messages for the OpenAI API.
|
|
1736
|
+
*/
|
|
1737
|
+
formatMessages(messages) {
|
|
1738
|
+
return messages.map((msg) => {
|
|
1739
|
+
if (msg.role === "tool") {
|
|
1740
|
+
return {
|
|
1741
|
+
role: "user",
|
|
1742
|
+
content: msg.content ?? ""
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
role: msg.role,
|
|
1747
|
+
content: msg.content ?? ""
|
|
1748
|
+
};
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1753
|
+
0 && (module.exports = {
|
|
1754
|
+
Agent,
|
|
1755
|
+
AgentLogger,
|
|
1756
|
+
AgentMemory,
|
|
1757
|
+
CodeAgent,
|
|
1758
|
+
FINAL_ANSWER_PROMPT,
|
|
1759
|
+
FinalAnswerTool,
|
|
1760
|
+
LocalExecutor,
|
|
1761
|
+
LogLevel,
|
|
1762
|
+
Model,
|
|
1763
|
+
OpenAIModel,
|
|
1764
|
+
Tool,
|
|
1765
|
+
UserInputTool,
|
|
1766
|
+
createTool,
|
|
1767
|
+
finalAnswerTool,
|
|
1768
|
+
generateSystemPrompt,
|
|
1769
|
+
getErrorRecoveryPrompt
|
|
1770
|
+
});
|
|
1771
|
+
//# sourceMappingURL=index.js.map
|