@samrahimi/smol-js 0.4.0 → 0.6.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/dist/cli.js ADDED
@@ -0,0 +1,3159 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ "use strict";
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // src/cli.ts
28
+ var fs6 = __toESM(require("fs"));
29
+ var path6 = __toESM(require("path"));
30
+ var readline = __toESM(require("readline"));
31
+ var import_chalk3 = __toESM(require("chalk"));
32
+ var import_dotenv = __toESM(require("dotenv"));
33
+
34
+ // src/orchestrator/Orchestrator.ts
35
+ var import_chalk2 = __toESM(require("chalk"));
36
+
37
+ // src/orchestrator/YAMLLoader.ts
38
+ var fs5 = __toESM(require("fs"));
39
+ var path5 = __toESM(require("path"));
40
+ var import_yaml = __toESM(require("yaml"));
41
+
42
+ // src/memory/AgentMemory.ts
43
+ function estimateTokens(text) {
44
+ return Math.ceil(text.length / 4);
45
+ }
46
+ function estimateMessagesTokens(messages) {
47
+ let total = 0;
48
+ for (const msg of messages) {
49
+ total += estimateTokens(msg.content ?? "");
50
+ if (msg.toolCalls) {
51
+ total += estimateTokens(JSON.stringify(msg.toolCalls));
52
+ }
53
+ total += 4;
54
+ }
55
+ return total;
56
+ }
57
+ var AgentMemory = class {
58
+ /** System prompt step (always first) */
59
+ systemPrompt;
60
+ /** All execution steps */
61
+ steps = [];
62
+ maxContextLength;
63
+ memoryStrategy;
64
+ model;
65
+ constructor(systemPrompt, config) {
66
+ this.systemPrompt = {
67
+ type: "system",
68
+ content: systemPrompt,
69
+ timestamp: Date.now()
70
+ };
71
+ this.maxContextLength = config?.maxContextLength ?? 1e5;
72
+ this.memoryStrategy = config?.memoryStrategy ?? "truncate";
73
+ this.model = config?.model;
74
+ }
75
+ /** Reset memory, keeping only the system prompt */
76
+ reset() {
77
+ this.steps = [];
78
+ }
79
+ /** Add a task step */
80
+ addTask(task) {
81
+ const step = {
82
+ type: "task",
83
+ task,
84
+ timestamp: Date.now()
85
+ };
86
+ this.steps.push(step);
87
+ return step;
88
+ }
89
+ /** Create a new action step */
90
+ createActionStep(stepNumber) {
91
+ const step = {
92
+ type: "action",
93
+ stepNumber,
94
+ timing: { startTime: Date.now() },
95
+ modelInputMessages: [],
96
+ timestamp: Date.now()
97
+ };
98
+ this.steps.push(step);
99
+ return step;
100
+ }
101
+ /** Add a final answer step */
102
+ addFinalAnswer(answer) {
103
+ const step = {
104
+ type: "final",
105
+ answer,
106
+ timestamp: Date.now()
107
+ };
108
+ this.steps.push(step);
109
+ return step;
110
+ }
111
+ /** Get the last step */
112
+ getLastStep() {
113
+ return this.steps[this.steps.length - 1];
114
+ }
115
+ /** Get all action steps */
116
+ getActionSteps() {
117
+ return this.steps.filter((s) => s.type === "action");
118
+ }
119
+ /**
120
+ * Convert memory to messages for LLM context.
121
+ * Handles both CodeAgent (observation-based) and ToolUseAgent (tool_call-based) patterns.
122
+ */
123
+ toMessages() {
124
+ const messages = [];
125
+ messages.push({
126
+ role: "system",
127
+ content: this.systemPrompt.content
128
+ });
129
+ for (const step of this.steps) {
130
+ switch (step.type) {
131
+ case "task":
132
+ messages.push({
133
+ role: "user",
134
+ content: `Task: ${step.task}`
135
+ });
136
+ break;
137
+ case "action":
138
+ if (step.toolCalls && step.toolCalls.length > 0) {
139
+ messages.push({
140
+ role: "assistant",
141
+ content: step.modelOutputMessage?.content ?? null,
142
+ toolCalls: step.toolCalls
143
+ });
144
+ if (step.toolResults) {
145
+ for (const result of step.toolResults) {
146
+ messages.push({
147
+ role: "tool",
148
+ content: result.error ? `Error: ${result.error}` : typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2),
149
+ toolCallId: result.toolCallId
150
+ });
151
+ }
152
+ }
153
+ } else {
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 && !step.observation) {
167
+ messages.push({
168
+ role: "user",
169
+ content: `Error: ${step.error.message}`
170
+ });
171
+ }
172
+ }
173
+ break;
174
+ case "final":
175
+ break;
176
+ }
177
+ }
178
+ return messages;
179
+ }
180
+ /**
181
+ * Manage context length - truncate or compact if exceeded.
182
+ */
183
+ async manageContext() {
184
+ const messages = this.toMessages();
185
+ const tokenCount = estimateMessagesTokens(messages);
186
+ if (tokenCount <= this.maxContextLength) {
187
+ return;
188
+ }
189
+ if (this.memoryStrategy === "truncate") {
190
+ this.truncateOlderMessages();
191
+ } else if (this.memoryStrategy === "compact") {
192
+ await this.compactMessages();
193
+ }
194
+ }
195
+ /**
196
+ * Truncate older action steps to fit within context.
197
+ */
198
+ truncateOlderMessages() {
199
+ const actionSteps = this.getActionSteps();
200
+ if (actionSteps.length <= 2) return;
201
+ const targetTokens = this.maxContextLength * 0.75;
202
+ let currentTokens = estimateMessagesTokens(this.toMessages());
203
+ while (currentTokens > targetTokens && this.steps.length > 2) {
204
+ const idx = this.steps.findIndex((s) => s.type === "action");
205
+ if (idx === -1) break;
206
+ this.steps.splice(idx, 1);
207
+ currentTokens = estimateMessagesTokens(this.toMessages());
208
+ }
209
+ }
210
+ /**
211
+ * Compact older messages into a summary.
212
+ */
213
+ async compactMessages() {
214
+ if (!this.model) {
215
+ this.truncateOlderMessages();
216
+ return;
217
+ }
218
+ const actionSteps = this.getActionSteps();
219
+ if (actionSteps.length <= 2) return;
220
+ const stepsToSummarize = actionSteps.slice(0, -2);
221
+ const summaryContent = stepsToSummarize.map((step) => {
222
+ const parts = [];
223
+ if (step.modelOutputMessage?.content) {
224
+ parts.push(`Action: ${step.modelOutputMessage.content.slice(0, 200)}`);
225
+ }
226
+ if (step.observation) {
227
+ parts.push(`Observation: ${step.observation.slice(0, 200)}`);
228
+ }
229
+ if (step.toolResults) {
230
+ for (const r of step.toolResults) {
231
+ const resultStr = typeof r.result === "string" ? r.result : JSON.stringify(r.result);
232
+ parts.push(`Tool ${r.toolName}: ${resultStr.slice(0, 200)}`);
233
+ }
234
+ }
235
+ return parts.join("\n");
236
+ }).join("\n---\n");
237
+ try {
238
+ const summaryResponse = await this.model.generate([
239
+ {
240
+ role: "system",
241
+ content: "Summarize the following agent execution history concisely, preserving key findings and results. Be brief but complete."
242
+ },
243
+ {
244
+ role: "user",
245
+ content: summaryContent
246
+ }
247
+ ]);
248
+ const recentSteps = this.steps.filter(
249
+ (s) => s.type === "task" || s.type === "final" || s.type === "action" && actionSteps.indexOf(s) >= actionSteps.length - 2
250
+ );
251
+ this.steps = [
252
+ {
253
+ type: "task",
254
+ task: `[Context Summary from previous steps]
255
+ ${summaryResponse.content}`,
256
+ timestamp: Date.now()
257
+ },
258
+ ...recentSteps
259
+ ];
260
+ } catch {
261
+ this.truncateOlderMessages();
262
+ }
263
+ }
264
+ /** Get total token usage across all steps */
265
+ getTotalTokenUsage() {
266
+ let inputTokens = 0;
267
+ let outputTokens = 0;
268
+ for (const step of this.steps) {
269
+ if (step.type === "action" && step.tokenUsage) {
270
+ inputTokens += step.tokenUsage.inputTokens;
271
+ outputTokens += step.tokenUsage.outputTokens;
272
+ }
273
+ }
274
+ return {
275
+ inputTokens,
276
+ outputTokens,
277
+ totalTokens: inputTokens + outputTokens
278
+ };
279
+ }
280
+ /** Get current estimated token count */
281
+ getEstimatedTokenCount() {
282
+ return estimateMessagesTokens(this.toMessages());
283
+ }
284
+ /** Get a summary of the memory for logging */
285
+ getSummary() {
286
+ const actionSteps = this.getActionSteps();
287
+ const lines = [
288
+ `System Prompt: ${this.systemPrompt.content.slice(0, 100)}...`,
289
+ `Total Steps: ${this.steps.length}`,
290
+ `Action Steps: ${actionSteps.length}`
291
+ ];
292
+ const tokenUsage = this.getTotalTokenUsage();
293
+ if (tokenUsage.totalTokens > 0) {
294
+ lines.push(`Total Tokens: ${tokenUsage.totalTokens}`);
295
+ }
296
+ return lines.join("\n");
297
+ }
298
+ /** Serialize memory to JSON */
299
+ toJSON() {
300
+ return {
301
+ systemPrompt: this.systemPrompt,
302
+ steps: this.steps
303
+ };
304
+ }
305
+ };
306
+
307
+ // src/logging/AgentLogger.ts
308
+ var import_chalk = __toESM(require("chalk"));
309
+ var fs = __toESM(require("fs"));
310
+ var path = __toESM(require("path"));
311
+ var os = __toESM(require("os"));
312
+ var LOG_DIR = path.join(os.homedir(), ".smol-js/logs");
313
+ var AgentLogger = class {
314
+ level;
315
+ logFile;
316
+ sessionId;
317
+ constructor(level = 1 /* INFO */) {
318
+ this.level = level;
319
+ this.sessionId = this.generateSessionId();
320
+ if (level > -1 /* OFF */) {
321
+ this.initLogFile();
322
+ }
323
+ }
324
+ /**
325
+ * Generate a unique session ID.
326
+ */
327
+ generateSessionId() {
328
+ const now = /* @__PURE__ */ new Date();
329
+ const timestamp = now.toISOString().replace(/[:.]/g, "-");
330
+ return `session-${timestamp}`;
331
+ }
332
+ /**
333
+ * Initialize the log file.
334
+ */
335
+ initLogFile() {
336
+ try {
337
+ if (!fs.existsSync(LOG_DIR)) {
338
+ fs.mkdirSync(LOG_DIR, { recursive: true });
339
+ }
340
+ const logPath = path.join(LOG_DIR, `${this.sessionId}.log`);
341
+ this.logFile = fs.createWriteStream(logPath, { flags: "a" });
342
+ this.writeToFile(`=== Session Started: ${(/* @__PURE__ */ new Date()).toISOString()} ===
343
+
344
+ `);
345
+ } catch (error) {
346
+ console.warn("Could not create log file:", error.message);
347
+ }
348
+ }
349
+ /**
350
+ * Write to the log file.
351
+ */
352
+ writeToFile(content) {
353
+ if (this.logFile) {
354
+ const cleanContent = content.replace(/\x1b\[[0-9;]*m/g, "");
355
+ this.logFile.write(cleanContent);
356
+ }
357
+ }
358
+ /**
359
+ * Set the log level.
360
+ */
361
+ setLevel(level) {
362
+ this.level = level;
363
+ }
364
+ /**
365
+ * Log a header (task start, step start, etc.)
366
+ */
367
+ header(message, level = 1 /* INFO */) {
368
+ if (this.level < level) return;
369
+ const line = "\u2550".repeat(60);
370
+ const output = `
371
+ ${import_chalk.default.cyan(line)}
372
+ ${import_chalk.default.cyan.bold(message)}
373
+ ${import_chalk.default.cyan(line)}
374
+ `;
375
+ console.log(output);
376
+ this.writeToFile(`
377
+ ${"\u2550".repeat(60)}
378
+ ${message}
379
+ ${"\u2550".repeat(60)}
380
+ `);
381
+ }
382
+ /**
383
+ * Log a subheader.
384
+ */
385
+ subheader(message, level = 1 /* INFO */) {
386
+ if (this.level < level) return;
387
+ const output = `
388
+ ${import_chalk.default.cyan("\u2500".repeat(40))}
389
+ ${import_chalk.default.cyan(message)}
390
+ `;
391
+ console.log(output);
392
+ this.writeToFile(`
393
+ ${"\u2500".repeat(40)}
394
+ ${message}
395
+ `);
396
+ }
397
+ /**
398
+ * Log reasoning/thought from the agent.
399
+ */
400
+ reasoning(content, level = 1 /* INFO */) {
401
+ if (this.level < level) return;
402
+ const output = `${import_chalk.default.yellow.bold("\u{1F4AD} Reasoning:")}
403
+ ${import_chalk.default.yellow(content)}
404
+ `;
405
+ console.log(output);
406
+ this.writeToFile(`
407
+ \u{1F4AD} Reasoning:
408
+ ${content}
409
+ `);
410
+ }
411
+ /**
412
+ * Log code block.
413
+ */
414
+ code(content, language = "javascript", level = 1 /* INFO */) {
415
+ if (this.level < level) return;
416
+ const output = `${import_chalk.default.green.bold("\u{1F4DD} Code:")}
417
+ ${import_chalk.default.green("```" + language)}
418
+ ${import_chalk.default.green(content)}
419
+ ${import_chalk.default.green("```")}
420
+ `;
421
+ console.log(output);
422
+ this.writeToFile(`
423
+ \u{1F4DD} Code:
424
+ \`\`\`${language}
425
+ ${content}
426
+ \`\`\`
427
+ `);
428
+ }
429
+ /**
430
+ * Log execution output.
431
+ */
432
+ output(content, level = 1 /* INFO */) {
433
+ if (this.level < level) return;
434
+ const output = `${import_chalk.default.blue.bold("\u{1F4E4} Output:")}
435
+ ${import_chalk.default.blue(content)}
436
+ `;
437
+ console.log(output);
438
+ this.writeToFile(`
439
+ \u{1F4E4} Output:
440
+ ${content}
441
+ `);
442
+ }
443
+ /**
444
+ * Log execution logs (print statements).
445
+ */
446
+ logs(content, level = 1 /* INFO */) {
447
+ if (this.level < level || !content.trim()) return;
448
+ const output = `${import_chalk.default.gray.bold("\u{1F4CB} Logs:")}
449
+ ${import_chalk.default.gray(content)}
450
+ `;
451
+ console.log(output);
452
+ this.writeToFile(`
453
+ \u{1F4CB} Logs:
454
+ ${content}
455
+ `);
456
+ }
457
+ /**
458
+ * Log an error.
459
+ */
460
+ error(message, error, level = 0 /* ERROR */) {
461
+ if (this.level < level) return;
462
+ const errorMessage = error ? `${message}: ${error.message}` : message;
463
+ const output = `${import_chalk.default.red.bold("\u274C Error:")}
464
+ ${import_chalk.default.red(errorMessage)}
465
+ `;
466
+ console.error(output);
467
+ this.writeToFile(`
468
+ \u274C Error:
469
+ ${errorMessage}
470
+ `);
471
+ if (error?.stack && this.level >= 2 /* DEBUG */) {
472
+ console.error(import_chalk.default.red.dim(error.stack));
473
+ this.writeToFile(`Stack: ${error.stack}
474
+ `);
475
+ }
476
+ }
477
+ /**
478
+ * Log a warning.
479
+ */
480
+ warn(message, level = 1 /* INFO */) {
481
+ if (this.level < level) return;
482
+ const output = `${import_chalk.default.yellow.bold("\u26A0\uFE0F Warning:")} ${import_chalk.default.yellow(message)}
483
+ `;
484
+ console.warn(output);
485
+ this.writeToFile(`
486
+ \u26A0\uFE0F Warning: ${message}
487
+ `);
488
+ }
489
+ /**
490
+ * Log general info.
491
+ */
492
+ info(message, level = 1 /* INFO */) {
493
+ if (this.level < level) return;
494
+ const output = `${import_chalk.default.white(message)}`;
495
+ console.log(output);
496
+ this.writeToFile(`${message}
497
+ `);
498
+ }
499
+ /**
500
+ * Log debug info.
501
+ */
502
+ debug(message) {
503
+ if (this.level < 2 /* DEBUG */) return;
504
+ const output = `${import_chalk.default.dim("[DEBUG]")} ${import_chalk.default.dim(message)}`;
505
+ console.log(output);
506
+ this.writeToFile(`[DEBUG] ${message}
507
+ `);
508
+ }
509
+ /**
510
+ * Log final answer.
511
+ */
512
+ finalAnswer(answer, level = 1 /* INFO */) {
513
+ if (this.level < level) return;
514
+ const line = "\u2550".repeat(60);
515
+ const answerStr = typeof answer === "string" ? answer : JSON.stringify(answer, null, 2);
516
+ const output = `
517
+ ${import_chalk.default.magenta(line)}
518
+ ${import_chalk.default.magenta.bold("\u2705 Final Answer:")}
519
+ ${import_chalk.default.magenta(answerStr)}
520
+ ${import_chalk.default.magenta(line)}
521
+ `;
522
+ console.log(output);
523
+ this.writeToFile(`
524
+ ${"\u2550".repeat(60)}
525
+ \u2705 Final Answer:
526
+ ${answerStr}
527
+ ${"\u2550".repeat(60)}
528
+ `);
529
+ }
530
+ /**
531
+ * Log step progress.
532
+ */
533
+ stepProgress(current, max, level = 1 /* INFO */) {
534
+ if (this.level < level) return;
535
+ const output = `${import_chalk.default.cyan.bold(`
536
+ \u{1F504} Step ${current}/${max}`)}
537
+ `;
538
+ console.log(output);
539
+ this.writeToFile(`
540
+ \u{1F504} Step ${current}/${max}
541
+ `);
542
+ }
543
+ /**
544
+ * Log waiting message for code execution delay.
545
+ */
546
+ waiting(seconds, level = 1 /* INFO */) {
547
+ if (this.level < level) return;
548
+ const output = `${import_chalk.default.yellow(`\u23F3 Waiting ${seconds}s before code execution (Ctrl+C to abort)...`)}`;
549
+ console.log(output);
550
+ this.writeToFile(`\u23F3 Waiting ${seconds}s before code execution...
551
+ `);
552
+ }
553
+ /**
554
+ * Stream content character by character.
555
+ */
556
+ streamChar(char) {
557
+ if (this.level < 1 /* INFO */) return;
558
+ process.stdout.write(import_chalk.default.yellow(char));
559
+ }
560
+ /**
561
+ * End streaming (add newline).
562
+ */
563
+ streamEnd() {
564
+ if (this.level < 1 /* INFO */) return;
565
+ console.log();
566
+ }
567
+ /**
568
+ * Close the log file.
569
+ */
570
+ close() {
571
+ if (this.logFile) {
572
+ this.writeToFile(`
573
+ === Session Ended: ${(/* @__PURE__ */ new Date()).toISOString()} ===
574
+ `);
575
+ this.logFile.end();
576
+ }
577
+ }
578
+ /**
579
+ * Get the log file path.
580
+ */
581
+ getLogPath() {
582
+ return this.logFile ? path.join(LOG_DIR, `${this.sessionId}.log`) : void 0;
583
+ }
584
+ };
585
+
586
+ // src/agents/Agent.ts
587
+ var DEFAULT_MAX_CONTEXT_LENGTH = 1e5;
588
+ var Agent = class {
589
+ /** The LLM model for generation */
590
+ model;
591
+ /** Available tools mapped by name */
592
+ tools = /* @__PURE__ */ new Map();
593
+ /** Agent memory tracking all steps */
594
+ memory;
595
+ /** Logger for formatted output */
596
+ logger;
597
+ /** Configuration options */
598
+ config;
599
+ /** Current step number */
600
+ currentStep = 0;
601
+ /** Whether the agent is currently running */
602
+ isRunning = false;
603
+ /** Whether the agent has been initialized at least once */
604
+ initialized = false;
605
+ constructor(config) {
606
+ this.model = config.model;
607
+ this.logger = new AgentLogger(config.verboseLevel ?? 1 /* INFO */);
608
+ this.config = {
609
+ maxSteps: config.maxSteps ?? 20,
610
+ codeExecutionDelay: config.codeExecutionDelay ?? 5e3,
611
+ customInstructions: config.customInstructions ?? "",
612
+ verboseLevel: config.verboseLevel ?? 1 /* INFO */,
613
+ streamOutputs: config.streamOutputs ?? true,
614
+ persistent: config.persistent ?? false,
615
+ maxContextLength: config.maxContextLength ?? DEFAULT_MAX_CONTEXT_LENGTH,
616
+ memoryStrategy: config.memoryStrategy ?? "truncate",
617
+ maxTokens: config.maxTokens,
618
+ temperature: config.temperature,
619
+ name: config.name ?? "Agent",
620
+ onEvent: config.onEvent
621
+ };
622
+ if (config.tools) {
623
+ for (const tool of config.tools) {
624
+ this.tools.set(tool.name, tool);
625
+ }
626
+ }
627
+ }
628
+ /**
629
+ * Run the agent on a task.
630
+ */
631
+ async run(task, reset = true) {
632
+ const startTime = Date.now();
633
+ const shouldReset = !this.config.persistent ? reset || !this.memory : !this.initialized;
634
+ if (shouldReset) {
635
+ const systemPrompt = this.initializeSystemPrompt();
636
+ this.memory = new AgentMemory(systemPrompt, {
637
+ maxContextLength: this.config.maxContextLength,
638
+ memoryStrategy: this.config.memoryStrategy,
639
+ model: this.model
640
+ });
641
+ this.currentStep = 0;
642
+ this.initialized = true;
643
+ }
644
+ this.memory.addTask(task);
645
+ this.isRunning = true;
646
+ this.emitEvent("agent_start", { task, name: this.config.name });
647
+ this.logger.header(`Starting ${this.config.name}: ${task.slice(0, 80)}${task.length > 80 ? "..." : ""}`);
648
+ let finalOutput = null;
649
+ let isFinalAnswer = false;
650
+ try {
651
+ while (this.currentStep < this.config.maxSteps && this.isRunning) {
652
+ this.currentStep++;
653
+ this.logger.stepProgress(this.currentStep, this.config.maxSteps);
654
+ this.emitEvent("agent_step", { step: this.currentStep, maxSteps: this.config.maxSteps });
655
+ const memoryStep = this.memory.createActionStep(this.currentStep);
656
+ try {
657
+ const actionOutput = await this.executeStep(memoryStep);
658
+ memoryStep.timing.endTime = Date.now();
659
+ memoryStep.timing.duration = memoryStep.timing.endTime - memoryStep.timing.startTime;
660
+ memoryStep.actionOutput = actionOutput;
661
+ memoryStep.isFinalAnswer = actionOutput.isFinalAnswer;
662
+ if (actionOutput.isFinalAnswer) {
663
+ finalOutput = actionOutput.output;
664
+ isFinalAnswer = true;
665
+ this.logger.finalAnswer(finalOutput);
666
+ break;
667
+ }
668
+ } catch (error) {
669
+ memoryStep.error = error;
670
+ memoryStep.timing.endTime = Date.now();
671
+ memoryStep.timing.duration = memoryStep.timing.endTime - memoryStep.timing.startTime;
672
+ this.logger.error("Step execution failed", error);
673
+ this.emitEvent("agent_error", { error: error.message, step: this.currentStep });
674
+ }
675
+ await this.memory.manageContext();
676
+ }
677
+ if (!isFinalAnswer && this.currentStep >= this.config.maxSteps) {
678
+ this.logger.warn(`Max steps (${this.config.maxSteps}) reached without final answer`);
679
+ finalOutput = await this.provideFinalAnswer(task);
680
+ }
681
+ } finally {
682
+ this.isRunning = false;
683
+ }
684
+ const duration = Date.now() - startTime;
685
+ const tokenUsage = this.memory.getTotalTokenUsage();
686
+ this.memory.addFinalAnswer(finalOutput);
687
+ this.emitEvent("agent_end", { output: finalOutput, duration, tokenUsage });
688
+ this.logger.info(`
689
+ Total time: ${(duration / 1e3).toFixed(2)}s`);
690
+ this.logger.info(`Total tokens: ${tokenUsage.totalTokens}`);
691
+ return {
692
+ output: finalOutput,
693
+ steps: this.memory.steps,
694
+ tokenUsage,
695
+ duration
696
+ };
697
+ }
698
+ /**
699
+ * Generate a final answer when max steps is reached.
700
+ */
701
+ async provideFinalAnswer(task) {
702
+ this.logger.subheader("Generating final answer from accumulated context");
703
+ const messages = this.memory.toMessages();
704
+ messages.push({
705
+ role: "user",
706
+ 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}". Summarize what you accomplished and provide a final answer.`
707
+ });
708
+ const response = await this.model.generate(messages, {
709
+ maxTokens: this.config.maxTokens,
710
+ temperature: this.config.temperature
711
+ });
712
+ return response.content;
713
+ }
714
+ /** Emit an orchestration event */
715
+ emitEvent(type, data) {
716
+ if (this.config.onEvent) {
717
+ this.config.onEvent({ type, data });
718
+ }
719
+ }
720
+ /** Stop the agent */
721
+ stop() {
722
+ this.isRunning = false;
723
+ this.logger.info("Agent stopped by user");
724
+ }
725
+ /** Get the current memory */
726
+ getMemory() {
727
+ return this.memory;
728
+ }
729
+ /** Get registered tools */
730
+ getTools() {
731
+ return this.tools;
732
+ }
733
+ /** Add a tool to the agent */
734
+ addTool(tool) {
735
+ this.tools.set(tool.name, tool);
736
+ }
737
+ /** Remove a tool from the agent */
738
+ removeTool(name) {
739
+ return this.tools.delete(name);
740
+ }
741
+ /** Get agent name */
742
+ getName() {
743
+ return this.config.name;
744
+ }
745
+ /** Sleep for a specified duration */
746
+ sleep(ms) {
747
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
748
+ }
749
+ };
750
+
751
+ // src/executor/LocalExecutor.ts
752
+ var vm = __toESM(require("vm"));
753
+ var fs2 = __toESM(require("fs"));
754
+ var path2 = __toESM(require("path"));
755
+ var os2 = __toESM(require("os"));
756
+ var DEFAULT_TIMEOUT_MS = 3e4;
757
+ var MAX_OUTPUT_LENGTH = 5e4;
758
+ var PACKAGE_CACHE_DIR = path2.join(os2.homedir(), ".smol-js", "packages");
759
+ var LocalExecutor = class {
760
+ context;
761
+ state = {};
762
+ tools = /* @__PURE__ */ new Map();
763
+ config;
764
+ capturedLogs = [];
765
+ constructor(config = {}) {
766
+ this.config = {
767
+ timeout: DEFAULT_TIMEOUT_MS,
768
+ allowFs: true,
769
+ workingDirectory: process.cwd(),
770
+ ...config
771
+ };
772
+ this.context = this.createContext();
773
+ }
774
+ /**
775
+ * Create the VM context with available globals.
776
+ */
777
+ createContext() {
778
+ const consoleProxy = {
779
+ log: (...args) => {
780
+ const output = args.map((arg) => this.stringify(arg)).join(" ");
781
+ this.capturedLogs.push(output);
782
+ },
783
+ error: (...args) => {
784
+ const output = args.map((arg) => this.stringify(arg)).join(" ");
785
+ this.capturedLogs.push(`[ERROR] ${output}`);
786
+ },
787
+ warn: (...args) => {
788
+ const output = args.map((arg) => this.stringify(arg)).join(" ");
789
+ this.capturedLogs.push(`[WARN] ${output}`);
790
+ },
791
+ info: (...args) => {
792
+ const output = args.map((arg) => this.stringify(arg)).join(" ");
793
+ this.capturedLogs.push(output);
794
+ },
795
+ debug: (...args) => {
796
+ const output = args.map((arg) => this.stringify(arg)).join(" ");
797
+ this.capturedLogs.push(`[DEBUG] ${output}`);
798
+ }
799
+ };
800
+ const print = (...args) => consoleProxy.log(...args);
801
+ const dynamicImport = async (packageName) => {
802
+ const authorized = this.config.authorizedImports ?? [];
803
+ const basePackage = packageName.split("/")[0];
804
+ if (!authorized.includes(basePackage) && !authorized.includes(packageName)) {
805
+ throw new Error(
806
+ `Import not authorized: ${packageName}. Add it to authorizedImports to allow.`
807
+ );
808
+ }
809
+ try {
810
+ if (!fs2.existsSync(PACKAGE_CACHE_DIR)) {
811
+ fs2.mkdirSync(PACKAGE_CACHE_DIR, { recursive: true });
812
+ }
813
+ const safeFileName = packageName.replace(/[/@]/g, "_") + ".mjs";
814
+ const cachedPath = path2.join(PACKAGE_CACHE_DIR, safeFileName);
815
+ let needsFetch = !fs2.existsSync(cachedPath);
816
+ if (!needsFetch) {
817
+ const content = fs2.readFileSync(cachedPath, "utf-8");
818
+ if (content.includes('export * from "/') || content.includes("export * from '/")) {
819
+ needsFetch = true;
820
+ fs2.unlinkSync(cachedPath);
821
+ }
822
+ }
823
+ if (needsFetch) {
824
+ this.capturedLogs.push(`[import] Fetching ${packageName}...`);
825
+ const jsdelivrUrl = `https://cdn.jsdelivr.net/npm/${packageName}/+esm`;
826
+ const response = await fetch(jsdelivrUrl);
827
+ if (!response.ok) {
828
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
829
+ }
830
+ let code = await response.text();
831
+ const importMatches = code.matchAll(/from\s+["'](https:\/\/cdn\.jsdelivr\.net\/[^"']+)["']/g);
832
+ for (const match of importMatches) {
833
+ const depUrl = match[1];
834
+ const depName = depUrl.split("/npm/")[1]?.split("/")[0] || "dep";
835
+ const depFileName = depName.replace(/[/@]/g, "_") + "_dep.mjs";
836
+ const depCachedPath = path2.join(PACKAGE_CACHE_DIR, depFileName);
837
+ if (!fs2.existsSync(depCachedPath)) {
838
+ this.capturedLogs.push(`[import] Fetching dependency from ${depUrl}...`);
839
+ const depResponse = await fetch(depUrl);
840
+ if (depResponse.ok) {
841
+ const depCode = await depResponse.text();
842
+ fs2.writeFileSync(depCachedPath, depCode, "utf-8");
843
+ }
844
+ }
845
+ code = code.replace(depUrl, `file://${depCachedPath}`);
846
+ }
847
+ fs2.writeFileSync(cachedPath, code, "utf-8");
848
+ this.capturedLogs.push(`[import] Cached ${packageName} to ${cachedPath}`);
849
+ } else {
850
+ this.capturedLogs.push(`[import] Using cached ${packageName}`);
851
+ }
852
+ const fileUrl = `file://${cachedPath}`;
853
+ const module2 = await import(fileUrl);
854
+ return module2.default ?? module2;
855
+ } catch (error) {
856
+ throw new Error(`Failed to import ${packageName}: ${error.message}`);
857
+ }
858
+ };
859
+ const contextObj = {
860
+ // Console and print
861
+ console: consoleProxy,
862
+ print,
863
+ // Built-in objects
864
+ Object,
865
+ Array,
866
+ String,
867
+ Number,
868
+ Boolean,
869
+ Date,
870
+ Math,
871
+ JSON,
872
+ RegExp,
873
+ Error,
874
+ Map,
875
+ Set,
876
+ WeakMap,
877
+ WeakSet,
878
+ Promise,
879
+ Symbol,
880
+ Proxy,
881
+ Reflect,
882
+ // Type checking
883
+ parseInt,
884
+ parseFloat,
885
+ isNaN,
886
+ isFinite,
887
+ typeof: (v) => typeof v,
888
+ // Timers (promisified for async support)
889
+ setTimeout: global.setTimeout,
890
+ clearTimeout: global.clearTimeout,
891
+ setInterval: global.setInterval,
892
+ clearInterval: global.clearInterval,
893
+ // Async utilities
894
+ fetch: global.fetch,
895
+ // Dynamic import for npm packages
896
+ importPackage: dynamicImport,
897
+ // URL handling
898
+ URL,
899
+ URLSearchParams,
900
+ // Text encoding
901
+ TextEncoder,
902
+ TextDecoder,
903
+ // Buffer (useful for many operations)
904
+ Buffer,
905
+ // State reference (variables persist here)
906
+ __state__: this.state,
907
+ // Final answer marker
908
+ __final_answer__: null,
909
+ __is_final_answer__: false
910
+ };
911
+ if (this.config.allowFs) {
912
+ contextObj.fs = {
913
+ readFileSync: (filePath, encoding) => {
914
+ const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
915
+ return fs2.readFileSync(resolvedPath, encoding ?? "utf-8");
916
+ },
917
+ writeFileSync: (filePath, data) => {
918
+ const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
919
+ return fs2.writeFileSync(resolvedPath, data);
920
+ },
921
+ existsSync: (filePath) => {
922
+ const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
923
+ return fs2.existsSync(resolvedPath);
924
+ },
925
+ readdirSync: (dirPath) => {
926
+ const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), dirPath);
927
+ return fs2.readdirSync(resolvedPath);
928
+ },
929
+ mkdirSync: (dirPath, options) => {
930
+ const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), dirPath);
931
+ return fs2.mkdirSync(resolvedPath, options);
932
+ },
933
+ unlinkSync: (filePath) => {
934
+ const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
935
+ return fs2.unlinkSync(resolvedPath);
936
+ },
937
+ statSync: (filePath) => {
938
+ const resolvedPath = path2.resolve(this.config.workingDirectory ?? process.cwd(), filePath);
939
+ return fs2.statSync(resolvedPath);
940
+ }
941
+ };
942
+ contextObj.path = {
943
+ join: path2.join,
944
+ resolve: (...paths) => path2.resolve(this.config.workingDirectory ?? process.cwd(), ...paths),
945
+ dirname: path2.dirname,
946
+ basename: path2.basename,
947
+ extname: path2.extname
948
+ };
949
+ }
950
+ return vm.createContext(contextObj);
951
+ }
952
+ /**
953
+ * Add tools to the executor context.
954
+ */
955
+ sendTools(tools) {
956
+ for (const [name, tool] of Object.entries(tools)) {
957
+ this.tools.set(name, tool);
958
+ this.context[name] = async (...args) => {
959
+ let callArgs;
960
+ if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
961
+ callArgs = args[0];
962
+ } else {
963
+ const inputNames = Object.keys(tool.inputs);
964
+ callArgs = {};
965
+ args.forEach((arg, i) => {
966
+ if (i < inputNames.length) {
967
+ callArgs[inputNames[i]] = arg;
968
+ }
969
+ });
970
+ }
971
+ return tool.call(callArgs);
972
+ };
973
+ }
974
+ }
975
+ /**
976
+ * Send variables to the executor state.
977
+ */
978
+ sendVariables(variables) {
979
+ Object.assign(this.state, variables);
980
+ Object.assign(this.context, variables);
981
+ }
982
+ /**
983
+ * Execute JavaScript code and return the result.
984
+ */
985
+ async execute(code) {
986
+ this.capturedLogs = [];
987
+ this.context.__is_final_answer__ = false;
988
+ this.context.__final_answer__ = null;
989
+ Object.assign(this.context, this.state);
990
+ const wrappedCode = this.wrapCode(code);
991
+ try {
992
+ const script = new vm.Script(wrappedCode, {
993
+ filename: "agent-code.js"
994
+ });
995
+ const result = await script.runInContext(this.context, {
996
+ timeout: this.config.timeout,
997
+ displayErrors: true
998
+ });
999
+ const output = result instanceof Promise ? await result : result;
1000
+ this.updateStateFromContext();
1001
+ const isFinalAnswer = this.context.__is_final_answer__;
1002
+ const finalOutput = isFinalAnswer ? this.context.__final_answer__ : output;
1003
+ const logs = this.capturedLogs.join("\n").slice(0, MAX_OUTPUT_LENGTH);
1004
+ return {
1005
+ output: finalOutput,
1006
+ logs,
1007
+ isFinalAnswer
1008
+ };
1009
+ } catch (error) {
1010
+ const logs = this.capturedLogs.join("\n").slice(0, MAX_OUTPUT_LENGTH);
1011
+ return {
1012
+ output: null,
1013
+ logs,
1014
+ isFinalAnswer: false,
1015
+ error
1016
+ };
1017
+ }
1018
+ }
1019
+ /**
1020
+ * Wrap code to handle async execution and final_answer calls.
1021
+ */
1022
+ wrapCode(code) {
1023
+ const finalAnswerFunc = `
1024
+ function final_answer(answer) {
1025
+ __is_final_answer__ = true;
1026
+ __final_answer__ = answer;
1027
+ return answer;
1028
+ }
1029
+ `;
1030
+ return `
1031
+ ${finalAnswerFunc}
1032
+ (async () => {
1033
+ let __last_result__;
1034
+ ${this.instrumentCode(code)}
1035
+ return __last_result__;
1036
+ })()
1037
+ `;
1038
+ }
1039
+ /**
1040
+ * Instrument code to capture the last expression value and convert
1041
+ * let/const/var declarations to global assignments for state persistence.
1042
+ */
1043
+ instrumentCode(code) {
1044
+ const lines = code.trim().split("\n");
1045
+ if (lines.length === 0) {
1046
+ return code;
1047
+ }
1048
+ const processedLines = lines.map((line, index) => {
1049
+ const trimmed = line.trim();
1050
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.endsWith("*/")) {
1051
+ return line;
1052
+ }
1053
+ let transformed = line;
1054
+ let hasDeclaration = false;
1055
+ transformed = transformed.replace(
1056
+ /\b(let|const|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g,
1057
+ (_match, _keyword, varName) => {
1058
+ hasDeclaration = true;
1059
+ return `${varName} =`;
1060
+ }
1061
+ );
1062
+ transformed = transformed.replace(
1063
+ /\b(let|const|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=[;,]|$)/g,
1064
+ (_match, _keyword, varName) => {
1065
+ hasDeclaration = true;
1066
+ return `${varName} = undefined`;
1067
+ }
1068
+ );
1069
+ if (hasDeclaration) {
1070
+ return transformed;
1071
+ }
1072
+ 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("}")) {
1073
+ return line;
1074
+ }
1075
+ if (index === lines.length - 1 || this.isLastMeaningfulLine(lines, index)) {
1076
+ if (!trimmed.endsWith(";")) {
1077
+ return `__last_result__ = ${line}`;
1078
+ } else {
1079
+ const withoutSemi = trimmed.slice(0, -1);
1080
+ if (!withoutSemi.endsWith("}") && !withoutSemi.endsWith(")")) {
1081
+ return `__last_result__ = ${withoutSemi};`;
1082
+ }
1083
+ }
1084
+ }
1085
+ return line;
1086
+ });
1087
+ return processedLines.join("\n");
1088
+ }
1089
+ /**
1090
+ * Check if this is the last meaningful line of code.
1091
+ */
1092
+ isLastMeaningfulLine(lines, currentIndex) {
1093
+ for (let i = currentIndex + 1; i < lines.length; i++) {
1094
+ const trimmed = lines[i].trim();
1095
+ if (trimmed && !trimmed.startsWith("//") && !trimmed.startsWith("/*")) {
1096
+ return false;
1097
+ }
1098
+ }
1099
+ return true;
1100
+ }
1101
+ /**
1102
+ * Update internal state from context after execution.
1103
+ */
1104
+ updateStateFromContext() {
1105
+ const excludeKeys = /* @__PURE__ */ new Set([
1106
+ "console",
1107
+ "print",
1108
+ "Object",
1109
+ "Array",
1110
+ "String",
1111
+ "Number",
1112
+ "Boolean",
1113
+ "Date",
1114
+ "Math",
1115
+ "JSON",
1116
+ "RegExp",
1117
+ "Error",
1118
+ "Map",
1119
+ "Set",
1120
+ "WeakMap",
1121
+ "WeakSet",
1122
+ "Promise",
1123
+ "Symbol",
1124
+ "Proxy",
1125
+ "Reflect",
1126
+ "parseInt",
1127
+ "parseFloat",
1128
+ "isNaN",
1129
+ "isFinite",
1130
+ "typeof",
1131
+ "setTimeout",
1132
+ "clearTimeout",
1133
+ "setInterval",
1134
+ "clearInterval",
1135
+ "fetch",
1136
+ "importPackage",
1137
+ "URL",
1138
+ "URLSearchParams",
1139
+ "TextEncoder",
1140
+ "TextDecoder",
1141
+ "Buffer",
1142
+ "__state__",
1143
+ "__final_answer__",
1144
+ "__is_final_answer__",
1145
+ "fs",
1146
+ "path",
1147
+ "final_answer",
1148
+ // Exclude tools
1149
+ ...this.tools.keys()
1150
+ ]);
1151
+ for (const key of Object.keys(this.context)) {
1152
+ if (!excludeKeys.has(key)) {
1153
+ this.state[key] = this.context[key];
1154
+ }
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Stringify a value for logging.
1159
+ */
1160
+ stringify(value) {
1161
+ if (value === void 0) return "undefined";
1162
+ if (value === null) return "null";
1163
+ if (typeof value === "string") return value;
1164
+ if (typeof value === "function") return `[Function: ${value.name || "anonymous"}]`;
1165
+ try {
1166
+ return JSON.stringify(value, null, 2);
1167
+ } catch {
1168
+ return String(value);
1169
+ }
1170
+ }
1171
+ /**
1172
+ * Reset the executor state.
1173
+ */
1174
+ reset() {
1175
+ this.state = {};
1176
+ this.capturedLogs = [];
1177
+ this.context = this.createContext();
1178
+ const tools = Object.fromEntries(this.tools);
1179
+ this.sendTools(tools);
1180
+ }
1181
+ /**
1182
+ * Get the current state.
1183
+ */
1184
+ getState() {
1185
+ return { ...this.state };
1186
+ }
1187
+ };
1188
+
1189
+ // src/tools/Tool.ts
1190
+ var Tool = class {
1191
+ /**
1192
+ * Whether the tool has been set up
1193
+ */
1194
+ isSetup = false;
1195
+ /**
1196
+ * Optional setup method called before first use.
1197
+ * Override this for expensive initialization (loading models, etc.)
1198
+ */
1199
+ async setup() {
1200
+ this.isSetup = true;
1201
+ }
1202
+ /**
1203
+ * Call the tool, ensuring setup is complete and validating arguments.
1204
+ */
1205
+ async call(args) {
1206
+ if (!this.isSetup) {
1207
+ await this.setup();
1208
+ }
1209
+ this.validateArguments(args);
1210
+ return this.execute(args);
1211
+ }
1212
+ /**
1213
+ * Validate that provided arguments match the input schema.
1214
+ */
1215
+ validateArguments(args) {
1216
+ const providedKeys = new Set(Object.keys(args));
1217
+ for (const [key, input] of Object.entries(this.inputs)) {
1218
+ if (input.required !== false && !providedKeys.has(key)) {
1219
+ throw new Error(`Missing required argument: ${key}`);
1220
+ }
1221
+ if (providedKeys.has(key) && args[key] !== void 0 && args[key] !== null) {
1222
+ const value = args[key];
1223
+ if (!this.checkType(value, input.type)) {
1224
+ throw new Error(
1225
+ `Argument '${key}' has invalid type. Expected ${input.type}, got ${typeof value}`
1226
+ );
1227
+ }
1228
+ }
1229
+ providedKeys.delete(key);
1230
+ }
1231
+ }
1232
+ /**
1233
+ * Check if a value matches the expected type.
1234
+ */
1235
+ checkType(value, expectedType) {
1236
+ switch (expectedType) {
1237
+ case "string":
1238
+ return typeof value === "string";
1239
+ case "number":
1240
+ return typeof value === "number";
1241
+ case "boolean":
1242
+ return typeof value === "boolean";
1243
+ case "array":
1244
+ return Array.isArray(value);
1245
+ case "object":
1246
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1247
+ case "any":
1248
+ return true;
1249
+ default:
1250
+ return false;
1251
+ }
1252
+ }
1253
+ /**
1254
+ * Generate a code-friendly prompt representation of this tool.
1255
+ * Used in the CodeAgent system prompt.
1256
+ */
1257
+ toCodePrompt() {
1258
+ const argsSignature = Object.entries(this.inputs).map(([name, input]) => {
1259
+ const optional = input.required === false ? "?" : "";
1260
+ return `${name}${optional}: ${this.typeToJsType(input.type)}`;
1261
+ }).join(", ");
1262
+ const argsDoc = Object.entries(this.inputs).map(([name, input]) => ` * @param ${name} - ${input.description}`).join("\n");
1263
+ return `
1264
+ /**
1265
+ * ${this.description}
1266
+ *
1267
+ ${argsDoc}
1268
+ * @returns ${this.outputType}
1269
+ */
1270
+ async function ${this.name}(${argsSignature}): Promise<${this.typeToJsType(this.outputType)}> { ... }
1271
+ `.trim();
1272
+ }
1273
+ /**
1274
+ * Generate an OpenAI-compatible tool definition for function calling.
1275
+ */
1276
+ toOpenAITool() {
1277
+ const properties = {};
1278
+ const required = [];
1279
+ for (const [key, input] of Object.entries(this.inputs)) {
1280
+ const prop = {
1281
+ type: this.typeToJsonSchemaType(input.type),
1282
+ description: input.description
1283
+ };
1284
+ if (input.enum) {
1285
+ prop.enum = input.enum;
1286
+ }
1287
+ if (input.default !== void 0) {
1288
+ prop.default = input.default;
1289
+ }
1290
+ properties[key] = prop;
1291
+ if (input.required !== false) {
1292
+ required.push(key);
1293
+ }
1294
+ }
1295
+ return {
1296
+ type: "function",
1297
+ function: {
1298
+ name: this.name,
1299
+ description: this.description,
1300
+ parameters: {
1301
+ type: "object",
1302
+ properties,
1303
+ ...required.length > 0 && { required }
1304
+ }
1305
+ }
1306
+ };
1307
+ }
1308
+ /**
1309
+ * Convert tool input type to JSON Schema type.
1310
+ */
1311
+ typeToJsonSchemaType(type) {
1312
+ switch (type) {
1313
+ case "string":
1314
+ return "string";
1315
+ case "number":
1316
+ return "number";
1317
+ case "boolean":
1318
+ return "boolean";
1319
+ case "array":
1320
+ return "array";
1321
+ case "object":
1322
+ return "object";
1323
+ case "any":
1324
+ return "string";
1325
+ default:
1326
+ return "string";
1327
+ }
1328
+ }
1329
+ /**
1330
+ * Convert tool input type to JS/TS type string.
1331
+ */
1332
+ typeToJsType(type) {
1333
+ switch (type) {
1334
+ case "string":
1335
+ return "string";
1336
+ case "number":
1337
+ return "number";
1338
+ case "boolean":
1339
+ return "boolean";
1340
+ case "array":
1341
+ return "any[]";
1342
+ case "object":
1343
+ return "Record<string, any>";
1344
+ case "any":
1345
+ return "any";
1346
+ default:
1347
+ return type;
1348
+ }
1349
+ }
1350
+ /**
1351
+ * Serialize the tool to a JSON-compatible object.
1352
+ */
1353
+ toJSON() {
1354
+ return {
1355
+ name: this.name,
1356
+ description: this.description,
1357
+ inputs: this.inputs,
1358
+ outputType: this.outputType
1359
+ };
1360
+ }
1361
+ };
1362
+
1363
+ // src/tools/defaultTools.ts
1364
+ var FinalAnswerTool = class extends Tool {
1365
+ name = "final_answer";
1366
+ description = "Returns the final answer to the user query. Use this when you have completed the task and have the final result.";
1367
+ inputs = {
1368
+ answer: {
1369
+ type: "any",
1370
+ description: "The final answer to return. Can be any type (string, number, object, etc.)",
1371
+ required: true
1372
+ }
1373
+ };
1374
+ outputType = "any";
1375
+ async execute(args) {
1376
+ return args.answer;
1377
+ }
1378
+ };
1379
+ var finalAnswerTool = new FinalAnswerTool();
1380
+
1381
+ // src/prompts/codeAgent.ts
1382
+ function generateSystemPrompt(variables) {
1383
+ const { tools, authorizedImports, customInstructions } = variables;
1384
+ 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.
1385
+
1386
+ ## How You Work
1387
+
1388
+ You follow a ReAct (Reasoning + Acting) framework:
1389
+ 1. **Thought**: Analyze the current situation and decide what to do next
1390
+ 2. **Code**: Write JavaScript code to perform the action
1391
+ 3. **Observation**: See the result of your code execution
1392
+ 4. Repeat until you have the final answer
1393
+
1394
+ ## Available Tools
1395
+
1396
+ You have access to the following tools as async functions:
1397
+
1398
+ ${tools}
1399
+
1400
+ ## Available Imports
1401
+
1402
+ You can dynamically import the following npm packages using \`await importPackage('package-name')\`:
1403
+ ${authorizedImports || "(No additional packages authorized)"}
1404
+
1405
+ ## Built-in Capabilities
1406
+
1407
+ The following are available in your execution environment:
1408
+ - \`console.log()\` / \`print()\` - Output text (captured in logs)
1409
+ - \`fs\` - File system operations (readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync)
1410
+ - \`path\` - Path utilities (join, resolve, dirname, basename)
1411
+ - \`fetch()\` - HTTP requests
1412
+ - \`Buffer\` - Binary data handling
1413
+ - \`JSON\` - JSON parsing/stringifying
1414
+ - Standard JavaScript globals (Math, Date, Array methods, etc.)
1415
+
1416
+ ## Response Format
1417
+
1418
+ Always respond with your thought process followed by a code block:
1419
+
1420
+ Thought: [Your reasoning about what to do]
1421
+
1422
+ \`\`\`javascript
1423
+ // Your code here
1424
+ \`\`\`
1425
+
1426
+ ## Rules
1427
+
1428
+ 1. **Always use final_answer()**: When you have the complete answer, call \`final_answer(yourResult)\` to return it.
1429
+
1430
+ 2. **One action per step**: Execute one logical action per code block and one code block per inference step. You will be given additional steps to complete your work if it cannot be done safely in one step. Don't try to do everything at once because you need to make sure that your tools returned valid, useful data before going on to make use of that data. In particular, if you are a Manager agent who is invoking Sub-Agents as tools, do not script the entire workflow in one step, make sure that you get to review the work of your subagents at critical points before going on to the next step.
1431
+
1432
+ 3. **Handle errors gracefully**: If something fails, explain what went wrong and try a different approach.
1433
+
1434
+ 4. **Use async/await**: All tool calls and imports are async. Always use await.
1435
+
1436
+ 5. **Variables persist**: Variables you define in one step are available in the next step.
1437
+
1438
+ 6. **Be concise**: Write clean, minimal code. Don't over-engineer.
1439
+
1440
+ 7. **Print for debugging**: Use console.log() to output intermediate results you want to see.
1441
+
1442
+ 8. **No require()**: Use \`await importPackage('name')\` for npm packages instead of require().
1443
+
1444
+ 9. **Internet Access**: You can use fetch() to get data from the web as needed. However, if you have access to specialized tools for browsing / searching / retrieving information, use those first and fall back to fetch if they don't work
1445
+
1446
+ ## Examples
1447
+
1448
+ ### Example 1: Simple calculation
1449
+ Thought: I need to calculate the sum of squares from 1 to 10.
1450
+
1451
+ \`\`\`javascript
1452
+ let sum = 0;
1453
+ for (let i = 1; i <= 10; i++) {
1454
+ sum += i * i;
1455
+ }
1456
+ console.log("Sum of squares:", sum);
1457
+ final_answer(sum);
1458
+ \`\`\`
1459
+
1460
+ ### Example 2: Using a tool
1461
+ Thought: I need to search the web for current information.
1462
+
1463
+ \`\`\`javascript
1464
+ const results = await web_search({ query: "latest JavaScript features 2024" });
1465
+ console.log("Search results:", results);
1466
+ \`\`\`
1467
+
1468
+ ### Example 3: Reading a file
1469
+ Thought: I need to read the contents of package.json.
1470
+
1471
+ \`\`\`javascript
1472
+ const content = fs.readFileSync('package.json', 'utf-8');
1473
+ const pkg = JSON.parse(content);
1474
+ console.log("Package name:", pkg.name);
1475
+ final_answer(pkg);
1476
+ \`\`\`
1477
+
1478
+ ### Example 4: Using dynamic imports
1479
+ Thought: I need to use lodash for array manipulation.
1480
+
1481
+ \`\`\`javascript
1482
+ const _ = await importPackage('lodash');
1483
+ const numbers = [1, 2, 3, 4, 5];
1484
+ const chunked = _.chunk(numbers, 2);
1485
+ final_answer(chunked);
1486
+ \`\`\`
1487
+
1488
+ ### Example 5: Multi-step task
1489
+ Thought: First, I'll fetch the data from the API.
1490
+
1491
+ \`\`\`javascript
1492
+ const response = await fetch('https://api.example.com/data');
1493
+ const data = await response.json();
1494
+ console.log("Fetched items:", data.length);
1495
+ \`\`\`
1496
+
1497
+ (Observation: Fetched items: 42)
1498
+
1499
+ Thought: Now I'll process the data and return the result.
1500
+
1501
+ \`\`\`javascript
1502
+ const processed = data.filter(item => item.active).map(item => item.name);
1503
+ final_answer(processed);
1504
+ \`\`\`
1505
+
1506
+ ${customInstructions ? `
1507
+ ## Additional Instructions
1508
+
1509
+ ${customInstructions}` : ""}
1510
+
1511
+ Now, let's solve the task step by step. Remember to always call final_answer() when you have the complete answer.`;
1512
+ }
1513
+ function getErrorRecoveryPrompt(error) {
1514
+ return `Your previous code encountered an error:
1515
+
1516
+ ${error}
1517
+
1518
+ Please analyze the error and try a different approach. Fix the issue and continue working on the task.`;
1519
+ }
1520
+
1521
+ // src/agents/CodeAgent.ts
1522
+ var CODE_BLOCK_REGEX = /```(?:javascript|js)?\n([\s\S]*?)```/;
1523
+ var THOUGHT_REGEX = /(?:Thought|Reasoning):\s*([\s\S]*?)(?=```|$)/i;
1524
+ var CodeAgent = class extends Agent {
1525
+ /**
1526
+ * The JavaScript code executor
1527
+ */
1528
+ executor;
1529
+ /**
1530
+ * Authorized imports for dynamic npm package loading
1531
+ */
1532
+ authorizedImports;
1533
+ constructor(config) {
1534
+ super(config);
1535
+ this.authorizedImports = config.additionalAuthorizedImports ?? [];
1536
+ this.executor = new LocalExecutor({
1537
+ ...config.executorConfig,
1538
+ authorizedImports: this.authorizedImports,
1539
+ workingDirectory: config.workingDirectory
1540
+ });
1541
+ if (!this.tools.has("final_answer")) {
1542
+ this.tools.set("final_answer", new FinalAnswerTool());
1543
+ }
1544
+ this.executor.sendTools(Object.fromEntries(this.tools));
1545
+ }
1546
+ /**
1547
+ * Initialize the system prompt with tool definitions.
1548
+ */
1549
+ initializeSystemPrompt() {
1550
+ const toolDocs = Array.from(this.tools.values()).filter((tool) => tool.name !== "final_answer").map((tool) => tool.toCodePrompt()).join("\n\n");
1551
+ const finalAnswerDoc = `
1552
+ /**
1553
+ * Returns the final answer to the user. Call this when you have completed the task.
1554
+ * @param answer - The final answer (can be any type)
1555
+ */
1556
+ function final_answer(answer: any): void { ... }
1557
+ `.trim();
1558
+ const allTools = toolDocs ? `${toolDocs}
1559
+
1560
+ ${finalAnswerDoc}` : finalAnswerDoc;
1561
+ const importsDoc = this.authorizedImports.length > 0 ? this.authorizedImports.map((pkg) => `- ${pkg}`).join("\n") : "None (use built-in capabilities only)";
1562
+ return generateSystemPrompt({
1563
+ tools: allTools,
1564
+ authorizedImports: importsDoc,
1565
+ customInstructions: this.config.customInstructions
1566
+ });
1567
+ }
1568
+ /**
1569
+ * Execute a single step: get LLM response, extract code, execute it.
1570
+ */
1571
+ async executeStep(memoryStep) {
1572
+ const messages = this.memory.toMessages();
1573
+ memoryStep.modelInputMessages = [...messages];
1574
+ const lastStep = this.memory.getActionSteps().slice(-2)[0];
1575
+ if (lastStep?.error) {
1576
+ messages.push({
1577
+ role: "user",
1578
+ content: getErrorRecoveryPrompt(lastStep.error.message)
1579
+ });
1580
+ }
1581
+ const response = await this.generateResponse(messages);
1582
+ memoryStep.modelOutputMessage = response;
1583
+ memoryStep.tokenUsage = response.tokenUsage;
1584
+ const content = response.content ?? "";
1585
+ const thoughtMatch = content.match(THOUGHT_REGEX);
1586
+ if (thoughtMatch) {
1587
+ this.logger.reasoning(thoughtMatch[1].trim());
1588
+ }
1589
+ const codeMatch = content.match(CODE_BLOCK_REGEX);
1590
+ if (!codeMatch) {
1591
+ this.logger.warn("No code block found in response");
1592
+ memoryStep.observation = "No code block was found in your response. Please provide JavaScript code in a ```javascript code block.";
1593
+ return {
1594
+ output: null,
1595
+ isFinalAnswer: false
1596
+ };
1597
+ }
1598
+ const code = codeMatch[1].trim();
1599
+ memoryStep.codeAction = code;
1600
+ this.logger.code(code);
1601
+ if (this.config.codeExecutionDelay > 0) {
1602
+ this.logger.waiting(this.config.codeExecutionDelay / 1e3);
1603
+ await this.sleep(this.config.codeExecutionDelay);
1604
+ }
1605
+ this.logger.subheader("Executing code...");
1606
+ const result = await this.executor.execute(code);
1607
+ if (result.logs) {
1608
+ this.logger.logs(result.logs);
1609
+ }
1610
+ if (result.error) {
1611
+ this.logger.error("Code execution error", result.error);
1612
+ memoryStep.error = result.error;
1613
+ memoryStep.observation = `Error during code execution:
1614
+ ${result.error.message}`;
1615
+ return {
1616
+ output: null,
1617
+ isFinalAnswer: false
1618
+ };
1619
+ }
1620
+ const outputStr = this.formatOutput(result.output);
1621
+ this.logger.output(outputStr);
1622
+ memoryStep.observation = this.formatObservation(result.logs, outputStr);
1623
+ return {
1624
+ output: result.output,
1625
+ isFinalAnswer: result.isFinalAnswer
1626
+ };
1627
+ }
1628
+ /**
1629
+ * Generate response from the LLM, optionally streaming.
1630
+ */
1631
+ async generateResponse(messages) {
1632
+ if (this.config.streamOutputs && this.model.supportsStreaming() && this.model.generateStream) {
1633
+ this.logger.subheader("Agent thinking...");
1634
+ let fullContent = "";
1635
+ const generator = this.model.generateStream(messages, {
1636
+ stopSequences: ["Observation:", "Observation:\n"],
1637
+ maxTokens: this.config.maxTokens,
1638
+ temperature: this.config.temperature
1639
+ });
1640
+ for await (const chunk of generator) {
1641
+ this.logger.streamChar(chunk);
1642
+ fullContent += chunk;
1643
+ }
1644
+ this.logger.streamEnd();
1645
+ return {
1646
+ role: "assistant",
1647
+ content: fullContent
1648
+ };
1649
+ } else {
1650
+ this.logger.subheader("Agent thinking...");
1651
+ return this.model.generate(messages, {
1652
+ stopSequences: ["Observation:", "Observation:\n"],
1653
+ maxTokens: this.config.maxTokens,
1654
+ temperature: this.config.temperature
1655
+ });
1656
+ }
1657
+ }
1658
+ /**
1659
+ * Format output for display.
1660
+ */
1661
+ formatOutput(output) {
1662
+ if (output === void 0 || output === null) {
1663
+ return "(no output)";
1664
+ }
1665
+ if (typeof output === "string") {
1666
+ return output;
1667
+ }
1668
+ try {
1669
+ return JSON.stringify(output, null, 2);
1670
+ } catch {
1671
+ return String(output);
1672
+ }
1673
+ }
1674
+ /**
1675
+ * Format the observation to send back to the LLM.
1676
+ */
1677
+ formatObservation(logs, output) {
1678
+ const parts = [];
1679
+ if (logs.trim()) {
1680
+ parts.push(`Execution logs:
1681
+ ${logs}`);
1682
+ }
1683
+ parts.push(`Last output:
1684
+ ${output}`);
1685
+ return `Observation:
1686
+ ${parts.join("\n\n")}`;
1687
+ }
1688
+ /**
1689
+ * Reset the agent and executor state.
1690
+ */
1691
+ reset() {
1692
+ this.executor.reset();
1693
+ this.currentStep = 0;
1694
+ }
1695
+ /**
1696
+ * Get the executor instance.
1697
+ */
1698
+ getExecutor() {
1699
+ return this.executor;
1700
+ }
1701
+ /**
1702
+ * Override addTool to also register with executor.
1703
+ */
1704
+ addTool(tool) {
1705
+ super.addTool(tool);
1706
+ this.executor.sendTools({ [tool.name]: tool });
1707
+ }
1708
+ };
1709
+
1710
+ // src/prompts/toolUseAgent.ts
1711
+ function generateToolUseSystemPrompt(variables) {
1712
+ const { tools, customInstructions, hasSubAgents, hasFileTools } = variables;
1713
+ let contentGuidelines = "";
1714
+ if (hasFileTools) {
1715
+ contentGuidelines += `
1716
+ ## Content Output Guidelines
1717
+
1718
+ When you produce long-form content (reports, articles, analyses, or any output longer than a few paragraphs):
1719
+ 1. **Save to file**: Use \`write_file\` to save the full content to a descriptively-named file (e.g., \`research_report.md\`, \`analysis_results.md\`).
1720
+ 2. **Return summary + filename**: In your \`final_answer\`, provide a brief summary of what you produced AND the filename where the full content is saved. Example: "Completed the research report covering X, Y, Z. Full report saved to: research_report.md"
1721
+
1722
+ This ensures managing agents can access your full output via \`read_file\` without it being truncated in message passing.
1723
+ `;
1724
+ }
1725
+ if (hasSubAgents) {
1726
+ contentGuidelines += `
1727
+ ## Working with Sub-Agents
1728
+
1729
+ When you delegate tasks to sub-agents:
1730
+ - Sub-agents return a **summary and filename** rather than the full content of long-form outputs.
1731
+ - To access the full content a sub-agent produced, use \`read_file\` with the filename they provide.
1732
+ - **Do NOT re-invoke a sub-agent to retrieve content it already created.** Instead, read the file directly.
1733
+ - When composing your own final output from sub-agent results, read their files as needed and synthesize.
1734
+ `;
1735
+ }
1736
+ if (hasSubAgents && hasFileTools) {
1737
+ contentGuidelines += `
1738
+ ## When You Are Both a Manager and a Sub-Agent
1739
+
1740
+ If you manage sub-agents AND are yourself delegated tasks by a parent agent:
1741
+ - Follow the sub-agent content guidelines: save your own long-form output to a file, return summary + filename.
1742
+ - Follow the manager guidelines: read sub-agent output files rather than re-calling them.
1743
+ `;
1744
+ }
1745
+ return `You are an expert assistant and problem-solving agent. You solve tasks by reasoning step by step and using the tools available to you.
1746
+
1747
+ ## How You Work
1748
+
1749
+ You follow a ReAct (Reasoning + Acting) framework:
1750
+ 1. **Think**: Analyze the current situation and decide what to do next
1751
+ 2. **Act**: Call one or more tools to perform actions
1752
+ 3. **Observe**: Review the results of your tool calls
1753
+ 4. Repeat until you have the final answer
1754
+
1755
+ ## Available Tools
1756
+
1757
+ ${tools}
1758
+
1759
+ ## Rules
1760
+
1761
+ 1. **Always use final_answer**: When you have the complete answer, call the \`final_answer\` tool to return it. This is mandatory - you must always end by calling final_answer.
1762
+
1763
+ 2. **Think before acting**: Provide your reasoning in the content of your response before making tool calls. This helps track your thought process.
1764
+
1765
+ 3. **One logical action per step**: Focus on one logical action per step. You may call multiple tools in a single step if they are independent, but avoid chaining dependent operations without reviewing intermediate results.
1766
+
1767
+ 4. **Handle errors gracefully**: If a tool call fails, analyze the error and try a different approach.
1768
+
1769
+ 5. **Be concise**: Keep your reasoning brief and focused. Don't over-explain.
1770
+
1771
+ 6. **Use the right tool**: Choose the most appropriate tool for each action. Don't try to accomplish something a tool can do through other means.
1772
+ ${contentGuidelines}
1773
+ ${customInstructions ? `## Additional Instructions
1774
+
1775
+ ${customInstructions}` : ""}
1776
+
1777
+ Now, let's solve the task step by step. Think carefully about what tools to use and in what order.`;
1778
+ }
1779
+ function formatToolDescriptions(tools) {
1780
+ return tools.map((tool) => {
1781
+ const params = Object.entries(tool.inputs).map(([name, input]) => {
1782
+ const req = input.required !== false ? " (required)" : " (optional)";
1783
+ return ` - ${name}${req}: ${input.description}`;
1784
+ }).join("\n");
1785
+ return `### ${tool.name}
1786
+ ${tool.description}
1787
+ Parameters:
1788
+ ${params}`;
1789
+ }).join("\n\n");
1790
+ }
1791
+
1792
+ // src/agents/ToolUseAgent.ts
1793
+ var ToolUseAgent = class extends Agent {
1794
+ parallelToolCalls;
1795
+ constructor(config) {
1796
+ super(config);
1797
+ this.parallelToolCalls = config.parallelToolCalls ?? true;
1798
+ if (!this.tools.has("final_answer")) {
1799
+ this.tools.set("final_answer", new FinalAnswerTool());
1800
+ }
1801
+ }
1802
+ /**
1803
+ * Initialize the system prompt with tool descriptions.
1804
+ */
1805
+ initializeSystemPrompt() {
1806
+ const toolList = Array.from(this.tools.values());
1807
+ const toolDescriptions = formatToolDescriptions(
1808
+ toolList.map((t) => ({
1809
+ name: t.name,
1810
+ description: t.description,
1811
+ inputs: t.inputs
1812
+ }))
1813
+ );
1814
+ const toolNames = new Set(Array.from(this.tools.keys()));
1815
+ const hasSubAgents = toolList.some((t) => t.constructor.name === "AgentTool");
1816
+ const hasFileTools = toolNames.has("read_file") || toolNames.has("write_file") || toolNames.has("read") || toolNames.has("write");
1817
+ return generateToolUseSystemPrompt({
1818
+ tools: toolDescriptions,
1819
+ customInstructions: this.config.customInstructions,
1820
+ hasSubAgents,
1821
+ hasFileTools
1822
+ });
1823
+ }
1824
+ /**
1825
+ * Execute a single step: send messages with tool definitions, process tool calls.
1826
+ */
1827
+ async executeStep(memoryStep) {
1828
+ const messages = this.memory.toMessages();
1829
+ memoryStep.modelInputMessages = [...messages];
1830
+ const actionSteps = this.memory.getActionSteps();
1831
+ const prevStep = actionSteps.length >= 2 ? actionSteps[actionSteps.length - 2] : void 0;
1832
+ if (prevStep?.error) {
1833
+ messages.push({
1834
+ role: "user",
1835
+ content: `Your previous action encountered an error: ${prevStep.error.message}
1836
+ Please try a different approach.`
1837
+ });
1838
+ }
1839
+ const toolDefinitions = Array.from(this.tools.values()).map((t) => t.toOpenAITool());
1840
+ this.logger.subheader("Agent thinking...");
1841
+ const response = await this.model.generate(messages, {
1842
+ toolDefinitions,
1843
+ maxTokens: this.config.maxTokens,
1844
+ temperature: this.config.temperature
1845
+ });
1846
+ memoryStep.modelOutputMessage = response;
1847
+ memoryStep.tokenUsage = response.tokenUsage;
1848
+ if (response.content && response.content.trim()) {
1849
+ this.logger.reasoning(response.content.trim());
1850
+ }
1851
+ if (!response.toolCalls || response.toolCalls.length === 0) {
1852
+ this.logger.warn("No tool calls in response. Prompting model to use tools.");
1853
+ memoryStep.observation = "You must use tools to complete the task. Please call the appropriate tool(s). When you have the final answer, call the final_answer tool.";
1854
+ return { output: null, isFinalAnswer: false };
1855
+ }
1856
+ memoryStep.toolCalls = response.toolCalls;
1857
+ const toolResults = await this.processToolCalls(response.toolCalls);
1858
+ memoryStep.toolResults = toolResults;
1859
+ for (const result of toolResults) {
1860
+ if (result.toolName === "final_answer") {
1861
+ return { output: result.result, isFinalAnswer: true };
1862
+ }
1863
+ }
1864
+ for (const result of toolResults) {
1865
+ if (result.error) {
1866
+ this.logger.error(`Tool ${result.toolName} failed: ${result.error}`);
1867
+ this.emitEvent("agent_error", { tool: result.toolName, error: result.error });
1868
+ } else {
1869
+ const resultStr = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
1870
+ this.logger.output(`[${result.toolName}]: ${resultStr.slice(0, 500)}${resultStr.length > 500 ? "..." : ""}`);
1871
+ this.emitEvent("agent_observation", { tool: result.toolName, result: resultStr.slice(0, 500) });
1872
+ }
1873
+ }
1874
+ return { output: null, isFinalAnswer: false };
1875
+ }
1876
+ /**
1877
+ * Process tool calls from the model response.
1878
+ */
1879
+ async processToolCalls(toolCalls) {
1880
+ const results = [];
1881
+ const executeTool = async (tc) => {
1882
+ const toolName = tc.function.name;
1883
+ const tool = this.tools.get(toolName);
1884
+ if (!tool) {
1885
+ return {
1886
+ toolCallId: tc.id,
1887
+ toolName,
1888
+ result: null,
1889
+ error: `Unknown tool: ${toolName}. Available tools: ${Array.from(this.tools.keys()).join(", ")}`
1890
+ };
1891
+ }
1892
+ let args;
1893
+ try {
1894
+ args = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
1895
+ } catch {
1896
+ return {
1897
+ toolCallId: tc.id,
1898
+ toolName,
1899
+ result: null,
1900
+ error: `Failed to parse tool arguments: ${tc.function.arguments}`
1901
+ };
1902
+ }
1903
+ this.logger.info(` Calling tool: ${toolName}(${JSON.stringify(args).slice(0, 100)}...)`);
1904
+ this.emitEvent("agent_tool_call", { tool: toolName, args });
1905
+ try {
1906
+ const result = await tool.call(args);
1907
+ return {
1908
+ toolCallId: tc.id,
1909
+ toolName,
1910
+ result
1911
+ };
1912
+ } catch (error) {
1913
+ return {
1914
+ toolCallId: tc.id,
1915
+ toolName,
1916
+ result: null,
1917
+ error: `Tool execution error: ${error.message}`
1918
+ };
1919
+ }
1920
+ };
1921
+ if (this.parallelToolCalls) {
1922
+ const promises = toolCalls.map((tc) => executeTool(tc));
1923
+ const resolvedResults = await Promise.all(promises);
1924
+ results.push(...resolvedResults);
1925
+ } else {
1926
+ for (const tc of toolCalls) {
1927
+ results.push(await executeTool(tc));
1928
+ }
1929
+ }
1930
+ return results;
1931
+ }
1932
+ /**
1933
+ * Override provideFinalAnswer to use tool calling format.
1934
+ */
1935
+ async provideFinalAnswer(task) {
1936
+ this.logger.subheader("Generating final answer from accumulated context");
1937
+ const messages = this.memory.toMessages();
1938
+ messages.push({
1939
+ role: "user",
1940
+ 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.`
1941
+ });
1942
+ const toolDefinitions = [new FinalAnswerTool().toOpenAITool()];
1943
+ const response = await this.model.generate(messages, {
1944
+ toolDefinitions,
1945
+ maxTokens: this.config.maxTokens,
1946
+ temperature: this.config.temperature
1947
+ });
1948
+ if (response.toolCalls && response.toolCalls.length > 0) {
1949
+ const tc = response.toolCalls[0];
1950
+ try {
1951
+ const args = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
1952
+ return args.answer;
1953
+ } catch {
1954
+ return response.content;
1955
+ }
1956
+ }
1957
+ return response.content;
1958
+ }
1959
+ /**
1960
+ * Add a tool, which can also be an Agent instance (auto-wraps with AgentTool).
1961
+ */
1962
+ addTool(tool) {
1963
+ super.addTool(tool);
1964
+ }
1965
+ };
1966
+
1967
+ // src/tools/AgentTool.ts
1968
+ var AgentTool = class extends Tool {
1969
+ name;
1970
+ description;
1971
+ inputs = {
1972
+ task: {
1973
+ type: "string",
1974
+ description: "The task or question to delegate to this agent",
1975
+ required: true
1976
+ }
1977
+ };
1978
+ outputType = "string";
1979
+ agent;
1980
+ additionalContext;
1981
+ returnFullResult;
1982
+ constructor(config) {
1983
+ super();
1984
+ this.agent = config.agent;
1985
+ this.name = config.name ?? "managed_agent";
1986
+ this.additionalContext = config.additionalContext;
1987
+ this.returnFullResult = config.returnFullResult ?? false;
1988
+ this.description = config.description ?? this.generateDescription();
1989
+ }
1990
+ /**
1991
+ * Generate a default description based on the agent's configuration
1992
+ */
1993
+ generateDescription() {
1994
+ return `Delegates a task to a specialized agent.
1995
+ This agent can help with complex sub-tasks that require multiple steps.
1996
+ Pass a clear, specific task description and the agent will work autonomously to solve it.
1997
+ Returns the agent's final answer as a string.`;
1998
+ }
1999
+ /**
2000
+ * Execute the agent with the given task
2001
+ */
2002
+ async execute(args) {
2003
+ let task = args.task;
2004
+ if (this.additionalContext) {
2005
+ task = `${this.additionalContext}
2006
+
2007
+ Task: ${task}`;
2008
+ }
2009
+ const result = await this.agent.run(task, true);
2010
+ if (this.returnFullResult) {
2011
+ return {
2012
+ output: result.output,
2013
+ steps: result.steps.length,
2014
+ duration: result.duration
2015
+ };
2016
+ }
2017
+ const output = result.output;
2018
+ if (typeof output === "string") {
2019
+ return output;
2020
+ }
2021
+ return JSON.stringify(output, null, 2);
2022
+ }
2023
+ /**
2024
+ * Override toCodePrompt to provide a cleaner signature for nested agents
2025
+ */
2026
+ toCodePrompt() {
2027
+ return `
2028
+ /**
2029
+ * ${this.description}
2030
+ *
2031
+ * @param task - The task or question to delegate to this specialized agent
2032
+ * @returns The agent's answer as a string
2033
+ */
2034
+ async function ${this.name}(task: string): Promise<string> { ... }
2035
+ `.trim();
2036
+ }
2037
+ };
2038
+
2039
+ // src/models/OpenAIModel.ts
2040
+ var import_openai = __toESM(require("openai"));
2041
+
2042
+ // src/models/Model.ts
2043
+ var Model = class {
2044
+ /**
2045
+ * Check if the model supports streaming.
2046
+ */
2047
+ supportsStreaming() {
2048
+ return typeof this.generateStream === "function";
2049
+ }
2050
+ /**
2051
+ * Extract token usage from a response message.
2052
+ */
2053
+ extractTokenUsage(_response) {
2054
+ return void 0;
2055
+ }
2056
+ /**
2057
+ * Convert messages to the format expected by the model's API.
2058
+ */
2059
+ formatMessages(messages) {
2060
+ return messages.map((msg) => ({
2061
+ role: msg.role,
2062
+ content: msg.content,
2063
+ ...msg.name && { name: msg.name },
2064
+ ...msg.toolCallId && { tool_call_id: msg.toolCallId }
2065
+ }));
2066
+ }
2067
+ };
2068
+
2069
+ // src/models/OpenAIModel.ts
2070
+ var DEFAULT_MODEL_ID = "anthropic/claude-sonnet-4.5";
2071
+ var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
2072
+ var DEFAULT_TIMEOUT = 12e4;
2073
+ var OpenAIModel = class extends Model {
2074
+ modelId;
2075
+ client;
2076
+ config;
2077
+ constructor(config = {}) {
2078
+ super();
2079
+ this.config = config;
2080
+ this.modelId = config.modelId ?? DEFAULT_MODEL_ID;
2081
+ const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY;
2082
+ if (!apiKey) {
2083
+ throw new Error(
2084
+ "API key is required. Set OPENAI_API_KEY or OPENROUTER_API_KEY environment variable, or pass apiKey in config."
2085
+ );
2086
+ }
2087
+ this.client = new import_openai.default({
2088
+ apiKey,
2089
+ baseURL: config.baseUrl ?? DEFAULT_BASE_URL,
2090
+ timeout: config.timeout ?? DEFAULT_TIMEOUT,
2091
+ defaultHeaders: config.defaultHeaders
2092
+ });
2093
+ }
2094
+ /**
2095
+ * Generate a response from the model (supports tool calling).
2096
+ */
2097
+ async generate(messages, options = {}) {
2098
+ const formattedMessages = this.formatMessages(messages);
2099
+ const requestParams = {
2100
+ model: this.modelId,
2101
+ messages: formattedMessages
2102
+ };
2103
+ const maxTokens = options.maxTokens ?? this.config.maxTokens;
2104
+ if (maxTokens !== void 0) {
2105
+ requestParams.max_tokens = maxTokens;
2106
+ }
2107
+ const temperature = options.temperature ?? this.config.temperature;
2108
+ if (temperature !== void 0) {
2109
+ requestParams.temperature = temperature;
2110
+ }
2111
+ if (options.stopSequences) {
2112
+ requestParams.stop = options.stopSequences;
2113
+ }
2114
+ if (options.toolDefinitions && options.toolDefinitions.length > 0) {
2115
+ requestParams.tools = options.toolDefinitions;
2116
+ } else if (options.tools && options.tools.length > 0) {
2117
+ requestParams.tools = options.tools.map((t) => t.toOpenAITool());
2118
+ }
2119
+ const response = await this.client.chat.completions.create({
2120
+ ...requestParams,
2121
+ model: this.modelId,
2122
+ messages: formattedMessages
2123
+ });
2124
+ const choice = response.choices[0];
2125
+ const message = choice?.message;
2126
+ if (!message) {
2127
+ throw new Error("No response from model");
2128
+ }
2129
+ const tokenUsage = response.usage ? {
2130
+ inputTokens: response.usage.prompt_tokens,
2131
+ outputTokens: response.usage.completion_tokens ?? 0,
2132
+ totalTokens: response.usage.total_tokens
2133
+ } : void 0;
2134
+ const toolCalls = message.tool_calls?.map((tc) => ({
2135
+ id: tc.id,
2136
+ type: "function",
2137
+ function: {
2138
+ name: tc.function.name,
2139
+ arguments: tc.function.arguments
2140
+ }
2141
+ }));
2142
+ return {
2143
+ role: "assistant",
2144
+ content: message.content ?? "",
2145
+ toolCalls,
2146
+ tokenUsage
2147
+ };
2148
+ }
2149
+ /**
2150
+ * Generate a streaming response from the model.
2151
+ */
2152
+ async *generateStream(messages, options = {}) {
2153
+ const formattedMessages = this.formatMessages(messages);
2154
+ const requestParams = {
2155
+ model: this.modelId,
2156
+ messages: formattedMessages,
2157
+ stream: true
2158
+ };
2159
+ const maxTokens = options.maxTokens ?? this.config.maxTokens;
2160
+ if (maxTokens !== void 0) {
2161
+ requestParams.max_tokens = maxTokens;
2162
+ }
2163
+ const temperature = options.temperature ?? this.config.temperature;
2164
+ if (temperature !== void 0) {
2165
+ requestParams.temperature = temperature;
2166
+ }
2167
+ if (options.stopSequences) {
2168
+ requestParams.stop = options.stopSequences;
2169
+ }
2170
+ const stream = await this.client.chat.completions.create({
2171
+ ...requestParams,
2172
+ model: this.modelId,
2173
+ messages: formattedMessages,
2174
+ stream: true
2175
+ });
2176
+ let fullContent = "";
2177
+ for await (const chunk of stream) {
2178
+ const delta = chunk.choices[0]?.delta;
2179
+ if (delta?.content) {
2180
+ fullContent += delta.content;
2181
+ yield delta.content;
2182
+ }
2183
+ }
2184
+ return {
2185
+ role: "assistant",
2186
+ content: fullContent
2187
+ };
2188
+ }
2189
+ /**
2190
+ * Format messages for the OpenAI API, including tool call/response messages.
2191
+ */
2192
+ formatMessages(messages) {
2193
+ return messages.map((msg) => {
2194
+ if (msg.role === "tool" && msg.toolCallId) {
2195
+ return {
2196
+ role: "tool",
2197
+ content: msg.content ?? "",
2198
+ tool_call_id: msg.toolCallId
2199
+ };
2200
+ }
2201
+ if (msg.role === "tool") {
2202
+ return {
2203
+ role: "user",
2204
+ content: msg.content ?? ""
2205
+ };
2206
+ }
2207
+ if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
2208
+ return {
2209
+ role: "assistant",
2210
+ content: msg.content || null,
2211
+ tool_calls: msg.toolCalls.map((tc) => ({
2212
+ id: tc.id,
2213
+ type: "function",
2214
+ function: {
2215
+ name: tc.function.name,
2216
+ arguments: typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2217
+ }
2218
+ }))
2219
+ };
2220
+ }
2221
+ return {
2222
+ role: msg.role,
2223
+ content: msg.content ?? ""
2224
+ };
2225
+ });
2226
+ }
2227
+ };
2228
+
2229
+ // src/tools/ReadFileTool.ts
2230
+ var fs3 = __toESM(require("fs"));
2231
+ var path3 = __toESM(require("path"));
2232
+ var ReadFileTool = class extends Tool {
2233
+ name = "read_file";
2234
+ description = "Read the contents of a file at the specified path. Returns the file content as a string.";
2235
+ inputs = {
2236
+ path: {
2237
+ type: "string",
2238
+ description: "The file path to read (absolute or relative to working directory)",
2239
+ required: true
2240
+ },
2241
+ encoding: {
2242
+ type: "string",
2243
+ description: "File encoding (default: utf-8)",
2244
+ required: false,
2245
+ default: "utf-8"
2246
+ }
2247
+ };
2248
+ outputType = "string";
2249
+ workingDirectory;
2250
+ constructor(config) {
2251
+ super();
2252
+ this.workingDirectory = config?.workingDirectory ?? process.cwd();
2253
+ }
2254
+ async execute(args) {
2255
+ const filePath = args.path;
2256
+ const encoding = args.encoding ?? "utf-8";
2257
+ const resolvedPath = path3.isAbsolute(filePath) ? filePath : path3.resolve(this.workingDirectory, filePath);
2258
+ if (!fs3.existsSync(resolvedPath)) {
2259
+ throw new Error(`File not found: ${resolvedPath}`);
2260
+ }
2261
+ const stat = fs3.statSync(resolvedPath);
2262
+ if (stat.isDirectory()) {
2263
+ throw new Error(`Path is a directory, not a file: ${resolvedPath}`);
2264
+ }
2265
+ const content = fs3.readFileSync(resolvedPath, encoding);
2266
+ const maxLength = 1e5;
2267
+ if (content.length > maxLength) {
2268
+ return content.slice(0, maxLength) + `
2269
+
2270
+ [... truncated, file is ${content.length} characters total]`;
2271
+ }
2272
+ return content;
2273
+ }
2274
+ };
2275
+
2276
+ // src/tools/WriteFileTool.ts
2277
+ var fs4 = __toESM(require("fs"));
2278
+ var path4 = __toESM(require("path"));
2279
+ var WriteFileTool = class extends Tool {
2280
+ name = "write_file";
2281
+ description = "Write content to a file at the specified path. Creates the file if it does not exist, and creates parent directories as needed. Overwrites existing content by default.";
2282
+ inputs = {
2283
+ path: {
2284
+ type: "string",
2285
+ description: "The file path to write to (absolute or relative to working directory)",
2286
+ required: true
2287
+ },
2288
+ content: {
2289
+ type: "string",
2290
+ description: "The content to write to the file",
2291
+ required: true
2292
+ },
2293
+ append: {
2294
+ type: "boolean",
2295
+ description: "If true, append to the file instead of overwriting (default: false)",
2296
+ required: false,
2297
+ default: false
2298
+ }
2299
+ };
2300
+ outputType = "string";
2301
+ workingDirectory;
2302
+ constructor(config) {
2303
+ super();
2304
+ this.workingDirectory = config?.workingDirectory ?? process.cwd();
2305
+ }
2306
+ async execute(args) {
2307
+ const filePath = args.path;
2308
+ const content = args.content;
2309
+ const append = args.append ?? false;
2310
+ const resolvedPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(this.workingDirectory, filePath);
2311
+ const dir = path4.dirname(resolvedPath);
2312
+ if (!fs4.existsSync(dir)) {
2313
+ fs4.mkdirSync(dir, { recursive: true });
2314
+ }
2315
+ if (append) {
2316
+ fs4.appendFileSync(resolvedPath, content, "utf-8");
2317
+ return `Successfully appended ${content.length} characters to ${resolvedPath}`;
2318
+ } else {
2319
+ fs4.writeFileSync(resolvedPath, content, "utf-8");
2320
+ return `Successfully wrote ${content.length} characters to ${resolvedPath}`;
2321
+ }
2322
+ }
2323
+ };
2324
+
2325
+ // src/tools/CurlTool.ts
2326
+ var CurlTool = class extends Tool {
2327
+ name = "curl";
2328
+ description = "Make HTTP requests to any URL. Supports GET and POST methods with custom headers and body. Returns the response body as text.";
2329
+ inputs = {
2330
+ url: {
2331
+ type: "string",
2332
+ description: "The URL to request",
2333
+ required: true
2334
+ },
2335
+ method: {
2336
+ type: "string",
2337
+ description: "HTTP method: GET or POST (default: GET)",
2338
+ required: false,
2339
+ default: "GET",
2340
+ enum: ["GET", "POST"]
2341
+ },
2342
+ headers: {
2343
+ type: "object",
2344
+ description: 'Optional HTTP headers as key-value pairs (e.g., {"Content-Type": "application/json"})',
2345
+ required: false
2346
+ },
2347
+ body: {
2348
+ type: "string",
2349
+ description: "Request body for POST requests (typically JSON string)",
2350
+ required: false
2351
+ }
2352
+ };
2353
+ outputType = "string";
2354
+ timeout;
2355
+ constructor(config) {
2356
+ super();
2357
+ this.timeout = config?.timeout ?? 3e4;
2358
+ }
2359
+ async execute(args) {
2360
+ const url = args.url;
2361
+ const method = (args.method ?? "GET").toUpperCase();
2362
+ const headers = args.headers ?? {};
2363
+ const body = args.body;
2364
+ const controller = new AbortController();
2365
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2366
+ try {
2367
+ const fetchOptions = {
2368
+ method,
2369
+ headers,
2370
+ signal: controller.signal
2371
+ };
2372
+ if (method === "POST" && body) {
2373
+ fetchOptions.body = body;
2374
+ if (!headers["Content-Type"] && !headers["content-type"]) {
2375
+ fetchOptions.headers["Content-Type"] = "application/json";
2376
+ }
2377
+ }
2378
+ const response = await fetch(url, fetchOptions);
2379
+ const responseText = await response.text();
2380
+ const statusLine = `HTTP ${response.status} ${response.statusText}`;
2381
+ const maxLength = 5e4;
2382
+ const truncatedBody = responseText.length > maxLength ? responseText.slice(0, maxLength) + `
2383
+
2384
+ [... truncated, response is ${responseText.length} characters total]` : responseText;
2385
+ return `${statusLine}
2386
+
2387
+ ${truncatedBody}`;
2388
+ } catch (error) {
2389
+ if (error.name === "AbortError") {
2390
+ throw new Error(`Request timed out after ${this.timeout}ms: ${url}`);
2391
+ }
2392
+ throw new Error(`HTTP request failed: ${error.message}`);
2393
+ } finally {
2394
+ clearTimeout(timeoutId);
2395
+ }
2396
+ }
2397
+ };
2398
+
2399
+ // src/tools/ExaSearchTool.ts
2400
+ var ExaSearchTool = class extends Tool {
2401
+ name = "exa_search";
2402
+ description = "Search the web using Exa.ai semantic search. Returns relevant web pages with titles, URLs, and optionally content snippets. Use this for finding information, research, and fact-checking.";
2403
+ inputs = {
2404
+ query: {
2405
+ type: "string",
2406
+ description: "The search query. Be specific and descriptive for best results.",
2407
+ required: true
2408
+ },
2409
+ numResults: {
2410
+ type: "number",
2411
+ description: "Number of results to return (default: 10, max: 30)",
2412
+ required: false,
2413
+ default: 10
2414
+ },
2415
+ type: {
2416
+ type: "string",
2417
+ description: 'Search type: "auto" (default), "neural" (embeddings-based), or "keyword"',
2418
+ required: false,
2419
+ default: "auto",
2420
+ enum: ["auto", "neural", "keyword"]
2421
+ },
2422
+ category: {
2423
+ type: "string",
2424
+ description: 'Optional category filter: "research paper", "news", "pdf", "github", "tweet", "company", "blog"',
2425
+ required: false
2426
+ },
2427
+ includeDomains: {
2428
+ type: "array",
2429
+ description: 'Only include results from these domains (e.g., ["arxiv.org", "github.com"])',
2430
+ required: false
2431
+ },
2432
+ excludeDomains: {
2433
+ type: "array",
2434
+ description: "Exclude results from these domains",
2435
+ required: false
2436
+ },
2437
+ startPublishedDate: {
2438
+ type: "string",
2439
+ description: 'Filter results published after this ISO 8601 date (e.g., "2024-01-01")',
2440
+ required: false
2441
+ }
2442
+ };
2443
+ outputType = "string";
2444
+ apiKey;
2445
+ constructor(config) {
2446
+ super();
2447
+ this.apiKey = config?.apiKey ?? process.env.EXA_API_KEY ?? "";
2448
+ }
2449
+ async setup() {
2450
+ if (!this.apiKey) {
2451
+ throw new Error("EXA_API_KEY is required. Set it as an environment variable or pass it in the config.");
2452
+ }
2453
+ this.isSetup = true;
2454
+ }
2455
+ async execute(args) {
2456
+ const query = args.query;
2457
+ const numResults = Math.min(args.numResults ?? 10, 30);
2458
+ const type = args.type ?? "auto";
2459
+ const requestBody = {
2460
+ query,
2461
+ numResults,
2462
+ type,
2463
+ text: { maxCharacters: 1e3 }
2464
+ };
2465
+ if (args.category) requestBody.category = args.category;
2466
+ if (args.includeDomains) requestBody.includeDomains = args.includeDomains;
2467
+ if (args.excludeDomains) requestBody.excludeDomains = args.excludeDomains;
2468
+ if (args.startPublishedDate) requestBody.startPublishedDate = args.startPublishedDate;
2469
+ const response = await fetch("https://api.exa.ai/search", {
2470
+ method: "POST",
2471
+ headers: {
2472
+ "x-api-key": this.apiKey,
2473
+ "Content-Type": "application/json"
2474
+ },
2475
+ body: JSON.stringify(requestBody)
2476
+ });
2477
+ if (!response.ok) {
2478
+ const errorText = await response.text();
2479
+ throw new Error(`Exa search failed (${response.status}): ${errorText}`);
2480
+ }
2481
+ const data = await response.json();
2482
+ if (!data.results || data.results.length === 0) {
2483
+ return "No results found for the query.";
2484
+ }
2485
+ const formattedResults = data.results.map((result, i) => {
2486
+ const parts = [`[${i + 1}] ${result.title ?? "Untitled"}`];
2487
+ parts.push(` URL: ${result.url}`);
2488
+ if (result.publishedDate) parts.push(` Date: ${result.publishedDate}`);
2489
+ if (result.author) parts.push(` Author: ${result.author}`);
2490
+ if (result.text) {
2491
+ const snippet = result.text.slice(0, 300).trim();
2492
+ parts.push(` Snippet: ${snippet}${result.text.length > 300 ? "..." : ""}`);
2493
+ }
2494
+ return parts.join("\n");
2495
+ }).join("\n\n");
2496
+ return `Search results for "${query}" (${data.results.length} results):
2497
+
2498
+ ${formattedResults}`;
2499
+ }
2500
+ };
2501
+
2502
+ // src/tools/ExaGetContentsTool.ts
2503
+ var ExaGetContentsTool = class extends Tool {
2504
+ name = "exa_get_contents";
2505
+ description = "Get the full text content of one or more web pages using Exa.ai. Returns cleaned, readable text extracted from the HTML. Use this to read articles, documentation, or any web page content.";
2506
+ inputs = {
2507
+ urls: {
2508
+ type: "array",
2509
+ description: "Array of URLs to fetch content from (max 10)",
2510
+ required: true
2511
+ },
2512
+ maxCharacters: {
2513
+ type: "number",
2514
+ description: "Maximum characters of content to return per page (default: 10000)",
2515
+ required: false,
2516
+ default: 1e4
2517
+ },
2518
+ livecrawl: {
2519
+ type: "string",
2520
+ description: 'Crawl strategy: "fallback" (use cache, fetch live if unavailable), "always" (always fetch live), "never" (cache only). Default: "fallback"',
2521
+ required: false,
2522
+ default: "fallback",
2523
+ enum: ["fallback", "always", "never"]
2524
+ }
2525
+ };
2526
+ outputType = "string";
2527
+ apiKey;
2528
+ constructor(config) {
2529
+ super();
2530
+ this.apiKey = config?.apiKey ?? process.env.EXA_API_KEY ?? "";
2531
+ }
2532
+ async setup() {
2533
+ if (!this.apiKey) {
2534
+ throw new Error("EXA_API_KEY is required. Set it as an environment variable or pass it in the config.");
2535
+ }
2536
+ this.isSetup = true;
2537
+ }
2538
+ async execute(args) {
2539
+ const urls = args.urls.slice(0, 10);
2540
+ const maxCharacters = args.maxCharacters ?? 1e4;
2541
+ const livecrawl = args.livecrawl ?? "fallback";
2542
+ const requestBody = {
2543
+ urls,
2544
+ text: { maxCharacters },
2545
+ livecrawl
2546
+ };
2547
+ const response = await fetch("https://api.exa.ai/contents", {
2548
+ method: "POST",
2549
+ headers: {
2550
+ "x-api-key": this.apiKey,
2551
+ "Content-Type": "application/json"
2552
+ },
2553
+ body: JSON.stringify(requestBody)
2554
+ });
2555
+ if (!response.ok) {
2556
+ const errorText = await response.text();
2557
+ throw new Error(`Exa get contents failed (${response.status}): ${errorText}`);
2558
+ }
2559
+ const data = await response.json();
2560
+ if (!data.results || data.results.length === 0) {
2561
+ return "No content could be retrieved from the provided URLs.";
2562
+ }
2563
+ const formattedResults = data.results.map((result) => {
2564
+ const parts = [`## ${result.title ?? result.url}`];
2565
+ parts.push(`URL: ${result.url}`);
2566
+ if (result.author) parts.push(`Author: ${result.author}`);
2567
+ if (result.publishedDate) parts.push(`Date: ${result.publishedDate}`);
2568
+ parts.push("");
2569
+ if (result.text) {
2570
+ parts.push(result.text);
2571
+ } else {
2572
+ parts.push("[No text content available]");
2573
+ }
2574
+ return parts.join("\n");
2575
+ }).join("\n\n---\n\n");
2576
+ return formattedResults;
2577
+ }
2578
+ };
2579
+
2580
+ // src/tools/ExaResearchTool.ts
2581
+ var ExaResearchTool = class extends Tool {
2582
+ name = "exa_research";
2583
+ description = "Perform deep research on a single topic using Exa.ai. Searches for relevant sources, retrieves their content, and finds similar pages for comprehensive coverage. Returns a structured research summary with sources. Use this for thorough research on any topic.";
2584
+ inputs = {
2585
+ topic: {
2586
+ type: "string",
2587
+ description: "The research topic or question to investigate",
2588
+ required: true
2589
+ },
2590
+ numSources: {
2591
+ type: "number",
2592
+ description: "Number of primary sources to retrieve (default: 5, max: 10)",
2593
+ required: false,
2594
+ default: 5
2595
+ },
2596
+ category: {
2597
+ type: "string",
2598
+ description: 'Optional category: "research paper", "news", "blog", "company"',
2599
+ required: false
2600
+ },
2601
+ includeDomains: {
2602
+ type: "array",
2603
+ description: "Only include results from these domains",
2604
+ required: false
2605
+ },
2606
+ startPublishedDate: {
2607
+ type: "string",
2608
+ description: "Only include results published after this date (ISO 8601)",
2609
+ required: false
2610
+ }
2611
+ };
2612
+ outputType = "string";
2613
+ apiKey;
2614
+ constructor(config) {
2615
+ super();
2616
+ this.apiKey = config?.apiKey ?? process.env.EXA_API_KEY ?? "";
2617
+ }
2618
+ async setup() {
2619
+ if (!this.apiKey) {
2620
+ throw new Error("EXA_API_KEY is required. Set it as an environment variable or pass it in the config.");
2621
+ }
2622
+ this.isSetup = true;
2623
+ }
2624
+ async execute(args) {
2625
+ const topic = args.topic;
2626
+ const numSources = Math.min(args.numSources ?? 5, 10);
2627
+ const searchBody = {
2628
+ query: topic,
2629
+ numResults: numSources,
2630
+ type: "auto",
2631
+ text: { maxCharacters: 3e3 }
2632
+ };
2633
+ if (args.category) searchBody.category = args.category;
2634
+ if (args.includeDomains) searchBody.includeDomains = args.includeDomains;
2635
+ if (args.startPublishedDate) searchBody.startPublishedDate = args.startPublishedDate;
2636
+ const searchResponse = await fetch("https://api.exa.ai/search", {
2637
+ method: "POST",
2638
+ headers: {
2639
+ "x-api-key": this.apiKey,
2640
+ "Content-Type": "application/json"
2641
+ },
2642
+ body: JSON.stringify(searchBody)
2643
+ });
2644
+ if (!searchResponse.ok) {
2645
+ const errorText = await searchResponse.text();
2646
+ throw new Error(`Exa research search failed (${searchResponse.status}): ${errorText}`);
2647
+ }
2648
+ const searchData = await searchResponse.json();
2649
+ if (!searchData.results || searchData.results.length === 0) {
2650
+ return `No research sources found for topic: "${topic}"`;
2651
+ }
2652
+ let similarResults = [];
2653
+ if (searchData.results.length > 0) {
2654
+ try {
2655
+ const similarBody = {
2656
+ url: searchData.results[0].url,
2657
+ numResults: 3,
2658
+ text: { maxCharacters: 2e3 }
2659
+ };
2660
+ const similarResponse = await fetch("https://api.exa.ai/findSimilar", {
2661
+ method: "POST",
2662
+ headers: {
2663
+ "x-api-key": this.apiKey,
2664
+ "Content-Type": "application/json"
2665
+ },
2666
+ body: JSON.stringify(similarBody)
2667
+ });
2668
+ if (similarResponse.ok) {
2669
+ const similarData = await similarResponse.json();
2670
+ similarResults = similarData.results ?? [];
2671
+ }
2672
+ } catch {
2673
+ }
2674
+ }
2675
+ const allSources = [...searchData.results, ...similarResults];
2676
+ const seenUrls = /* @__PURE__ */ new Set();
2677
+ const uniqueSources = allSources.filter((s) => {
2678
+ if (seenUrls.has(s.url)) return false;
2679
+ seenUrls.add(s.url);
2680
+ return true;
2681
+ });
2682
+ const sections = [];
2683
+ sections.push(`# Research: ${topic}
2684
+ `);
2685
+ sections.push(`Found ${uniqueSources.length} sources.
2686
+ `);
2687
+ sections.push("## Key Sources\n");
2688
+ for (let i = 0; i < uniqueSources.length; i++) {
2689
+ const source = uniqueSources[i];
2690
+ sections.push(`### ${i + 1}. ${source.title ?? "Untitled"}`);
2691
+ sections.push(`URL: ${source.url}`);
2692
+ if ("publishedDate" in source && source.publishedDate) {
2693
+ sections.push(`Date: ${source.publishedDate}`);
2694
+ }
2695
+ if ("author" in source && source.author) {
2696
+ sections.push(`Author: ${source.author}`);
2697
+ }
2698
+ if (source.text) {
2699
+ sections.push(`
2700
+ Content:
2701
+ ${source.text.slice(0, 2e3)}`);
2702
+ }
2703
+ sections.push("");
2704
+ }
2705
+ sections.push("## Source URLs\n");
2706
+ uniqueSources.forEach((s, i) => {
2707
+ sections.push(`${i + 1}. ${s.url}`);
2708
+ });
2709
+ return sections.join("\n");
2710
+ }
2711
+ };
2712
+
2713
+ // src/orchestrator/YAMLLoader.ts
2714
+ var TOOL_REGISTRY = {
2715
+ read_file: ReadFileTool,
2716
+ write_file: WriteFileTool,
2717
+ curl: CurlTool,
2718
+ exa_search: ExaSearchTool,
2719
+ exa_get_contents: ExaGetContentsTool,
2720
+ exa_research: ExaResearchTool,
2721
+ final_answer: FinalAnswerTool
2722
+ };
2723
+ var YAMLLoader = class {
2724
+ customTools = /* @__PURE__ */ new Map();
2725
+ /**
2726
+ * Register a custom tool type for use in YAML definitions.
2727
+ */
2728
+ registerToolType(typeName, toolClass) {
2729
+ this.customTools.set(typeName, toolClass);
2730
+ }
2731
+ /**
2732
+ * Load a workflow from a YAML file path.
2733
+ */
2734
+ loadFromFile(filePath) {
2735
+ const absolutePath = path5.isAbsolute(filePath) ? filePath : path5.resolve(process.cwd(), filePath);
2736
+ if (!fs5.existsSync(absolutePath)) {
2737
+ throw new Error(`Workflow file not found: ${absolutePath}`);
2738
+ }
2739
+ const content = fs5.readFileSync(absolutePath, "utf-8");
2740
+ return this.loadFromString(content);
2741
+ }
2742
+ /**
2743
+ * Load a workflow from a YAML string.
2744
+ */
2745
+ loadFromString(yamlContent) {
2746
+ const definition = import_yaml.default.parse(yamlContent);
2747
+ return this.buildWorkflow(definition);
2748
+ }
2749
+ /**
2750
+ * Build a runnable workflow from a parsed definition.
2751
+ */
2752
+ buildWorkflow(definition) {
2753
+ if (!definition.name) {
2754
+ throw new Error("Workflow must have a name");
2755
+ }
2756
+ if (!definition.entrypoint) {
2757
+ throw new Error("Workflow must have an entrypoint agent");
2758
+ }
2759
+ if (!definition.agents) {
2760
+ throw new Error("Workflow must define at least one agent");
2761
+ }
2762
+ const tools = /* @__PURE__ */ new Map();
2763
+ if (definition.tools) {
2764
+ for (const [name, toolDef] of Object.entries(definition.tools)) {
2765
+ const tool = this.buildTool(name, toolDef.type, toolDef.config);
2766
+ tools.set(name, tool);
2767
+ }
2768
+ }
2769
+ const agents = /* @__PURE__ */ new Map();
2770
+ const agentDefs = definition.agents;
2771
+ const resolved = /* @__PURE__ */ new Set();
2772
+ const maxIterations = Object.keys(agentDefs).length * 2;
2773
+ let iterations = 0;
2774
+ while (resolved.size < Object.keys(agentDefs).length && iterations < maxIterations) {
2775
+ iterations++;
2776
+ for (const [agentName, agentDef] of Object.entries(agentDefs)) {
2777
+ if (resolved.has(agentName)) continue;
2778
+ const agentDeps = agentDef.agents ?? [];
2779
+ const allDepsResolved = agentDeps.every((dep) => resolved.has(dep));
2780
+ if (allDepsResolved) {
2781
+ const agent = this.buildAgent(
2782
+ agentName,
2783
+ agentDef,
2784
+ definition.model,
2785
+ tools,
2786
+ agents,
2787
+ definition.globalMaxContextLength
2788
+ );
2789
+ agents.set(agentName, agent);
2790
+ resolved.add(agentName);
2791
+ }
2792
+ }
2793
+ }
2794
+ if (resolved.size < Object.keys(agentDefs).length) {
2795
+ const unresolved = Object.keys(agentDefs).filter((n) => !resolved.has(n));
2796
+ throw new Error(`Circular or unresolvable agent dependencies: ${unresolved.join(", ")}`);
2797
+ }
2798
+ const entrypointAgent = agents.get(definition.entrypoint);
2799
+ if (!entrypointAgent) {
2800
+ throw new Error(`Entrypoint agent "${definition.entrypoint}" not found in agents`);
2801
+ }
2802
+ return {
2803
+ name: definition.name,
2804
+ description: definition.description,
2805
+ entrypointAgent,
2806
+ agents,
2807
+ tools
2808
+ };
2809
+ }
2810
+ /**
2811
+ * Build a tool instance from a type name and config.
2812
+ */
2813
+ buildTool(name, type, config) {
2814
+ const ToolClass = TOOL_REGISTRY[type] ?? this.customTools.get(type);
2815
+ if (!ToolClass) {
2816
+ throw new Error(`Unknown tool type: ${type}. Available types: ${[...Object.keys(TOOL_REGISTRY), ...this.customTools.keys()].join(", ")}`);
2817
+ }
2818
+ const tool = new ToolClass(config);
2819
+ if (name !== type && name !== tool.name) {
2820
+ Object.defineProperty(tool, "name", { value: name, writable: false });
2821
+ }
2822
+ return tool;
2823
+ }
2824
+ /**
2825
+ * Build an agent instance from a YAML definition.
2826
+ */
2827
+ buildAgent(name, definition, globalModel, availableTools, resolvedAgents, globalMaxContextLength) {
2828
+ const modelConfig = definition.model ?? globalModel;
2829
+ const model = new OpenAIModel({
2830
+ modelId: modelConfig?.modelId,
2831
+ apiKey: modelConfig?.apiKey,
2832
+ baseUrl: modelConfig?.baseUrl,
2833
+ maxTokens: definition.maxTokens ?? modelConfig?.maxTokens,
2834
+ temperature: definition.temperature ?? modelConfig?.temperature,
2835
+ timeout: modelConfig?.timeout
2836
+ });
2837
+ const agentTools = [];
2838
+ if (definition.tools && availableTools) {
2839
+ for (const toolName of definition.tools) {
2840
+ const tool = availableTools.get(toolName);
2841
+ if (tool) {
2842
+ agentTools.push(tool);
2843
+ } else {
2844
+ const ToolClass = TOOL_REGISTRY[toolName] ?? this.customTools.get(toolName);
2845
+ if (ToolClass) {
2846
+ agentTools.push(new ToolClass());
2847
+ } else {
2848
+ throw new Error(`Tool "${toolName}" not found for agent "${name}"`);
2849
+ }
2850
+ }
2851
+ }
2852
+ }
2853
+ if (definition.agents && resolvedAgents) {
2854
+ for (const subAgentName of definition.agents) {
2855
+ const subAgent = resolvedAgents.get(subAgentName);
2856
+ if (!subAgent) {
2857
+ throw new Error(`Sub-agent "${subAgentName}" not found for agent "${name}"`);
2858
+ }
2859
+ agentTools.push(new AgentTool({
2860
+ agent: subAgent,
2861
+ name: subAgentName,
2862
+ description: definition.description ? `Sub-agent: ${subAgentName}` : `Delegate tasks to the ${subAgentName} agent`
2863
+ }));
2864
+ }
2865
+ }
2866
+ const maxContextLength = definition.maxContextLength ?? globalMaxContextLength;
2867
+ if (definition.type === "CodeAgent") {
2868
+ return new CodeAgent({
2869
+ model,
2870
+ tools: agentTools,
2871
+ maxSteps: definition.maxSteps,
2872
+ customInstructions: definition.customInstructions,
2873
+ persistent: definition.persistent,
2874
+ maxContextLength,
2875
+ memoryStrategy: definition.memoryStrategy,
2876
+ maxTokens: definition.maxTokens,
2877
+ temperature: definition.temperature,
2878
+ name
2879
+ });
2880
+ } else {
2881
+ return new ToolUseAgent({
2882
+ model,
2883
+ tools: agentTools,
2884
+ maxSteps: definition.maxSteps,
2885
+ customInstructions: definition.customInstructions,
2886
+ persistent: definition.persistent,
2887
+ maxContextLength,
2888
+ memoryStrategy: definition.memoryStrategy,
2889
+ maxTokens: definition.maxTokens,
2890
+ temperature: definition.temperature,
2891
+ name
2892
+ });
2893
+ }
2894
+ }
2895
+ };
2896
+
2897
+ // src/orchestrator/Orchestrator.ts
2898
+ var Orchestrator = class {
2899
+ loader;
2900
+ config;
2901
+ activeAgents = /* @__PURE__ */ new Map();
2902
+ eventLog = [];
2903
+ constructor(config = {}) {
2904
+ this.loader = new YAMLLoader();
2905
+ this.config = {
2906
+ verbose: config.verbose ?? true,
2907
+ onEvent: config.onEvent
2908
+ };
2909
+ }
2910
+ /**
2911
+ * Load a workflow from a YAML file.
2912
+ */
2913
+ loadWorkflow(filePath) {
2914
+ const workflow = this.loader.loadFromFile(filePath);
2915
+ this.displayWorkflowInfo(workflow);
2916
+ return workflow;
2917
+ }
2918
+ /**
2919
+ * Load a workflow from YAML string.
2920
+ */
2921
+ loadWorkflowFromString(yamlContent) {
2922
+ const workflow = this.loader.loadFromString(yamlContent);
2923
+ this.displayWorkflowInfo(workflow);
2924
+ return workflow;
2925
+ }
2926
+ /**
2927
+ * Run a loaded workflow with a task.
2928
+ */
2929
+ async runWorkflow(workflow, task) {
2930
+ this.displayRunStart(workflow.name, task);
2931
+ this.instrumentAgent(workflow.entrypointAgent, workflow.entrypointAgent.getName(), 0);
2932
+ for (const [name, agent] of workflow.agents) {
2933
+ if (agent !== workflow.entrypointAgent) {
2934
+ this.instrumentAgent(agent, name, 1);
2935
+ }
2936
+ }
2937
+ try {
2938
+ const result = await workflow.entrypointAgent.run(task);
2939
+ this.displayRunEnd(result);
2940
+ return result;
2941
+ } catch (error) {
2942
+ this.displayError(error);
2943
+ throw error;
2944
+ }
2945
+ }
2946
+ /**
2947
+ * Run a standalone agent with a task.
2948
+ */
2949
+ async runAgent(agent, task) {
2950
+ this.instrumentAgent(agent, agent.getName(), 0);
2951
+ const result = await agent.run(task);
2952
+ return result;
2953
+ }
2954
+ /**
2955
+ * Instrument an agent with orchestrator event tracking.
2956
+ */
2957
+ instrumentAgent(agent, name, depth) {
2958
+ this.activeAgents.set(name, { agent, depth });
2959
+ }
2960
+ /**
2961
+ * Display workflow info at startup.
2962
+ */
2963
+ displayWorkflowInfo(workflow) {
2964
+ if (!this.config.verbose) return;
2965
+ const line = "\u2550".repeat(70);
2966
+ console.log(import_chalk2.default.cyan(line));
2967
+ console.log(import_chalk2.default.cyan.bold(` Workflow: ${workflow.name}`));
2968
+ if (workflow.description) {
2969
+ console.log(import_chalk2.default.cyan(` ${workflow.description}`));
2970
+ }
2971
+ console.log(import_chalk2.default.cyan(` Agents: ${Array.from(workflow.agents.keys()).join(", ")}`));
2972
+ console.log(import_chalk2.default.cyan(` Tools: ${Array.from(workflow.tools.keys()).join(", ") || "(none defined at workflow level)"}`));
2973
+ console.log(import_chalk2.default.cyan(` Entrypoint: ${workflow.entrypointAgent.getName()}`));
2974
+ console.log(import_chalk2.default.cyan(line));
2975
+ console.log();
2976
+ }
2977
+ /**
2978
+ * Display run start info.
2979
+ */
2980
+ displayRunStart(workflowName, task) {
2981
+ if (!this.config.verbose) return;
2982
+ console.log(import_chalk2.default.green.bold(`
2983
+ \u25B6 Running workflow "${workflowName}"`));
2984
+ console.log(import_chalk2.default.green(` Task: ${task}`));
2985
+ console.log(import_chalk2.default.gray("\u2500".repeat(70)));
2986
+ }
2987
+ /**
2988
+ * Display run completion info.
2989
+ */
2990
+ displayRunEnd(result) {
2991
+ if (!this.config.verbose) return;
2992
+ console.log(import_chalk2.default.gray("\n" + "\u2500".repeat(70)));
2993
+ console.log(import_chalk2.default.green.bold(`
2994
+ \u2705 Workflow complete`));
2995
+ console.log(import_chalk2.default.green(` Duration: ${(result.duration / 1e3).toFixed(2)}s`));
2996
+ console.log(import_chalk2.default.green(` Tokens: ${result.tokenUsage.totalTokens}`));
2997
+ console.log(import_chalk2.default.green(` Steps: ${result.steps.length}`));
2998
+ const outputStr = typeof result.output === "string" ? result.output : JSON.stringify(result.output, null, 2);
2999
+ console.log(import_chalk2.default.magenta.bold("\n Final Output:"));
3000
+ const indentedOutput = outputStr.split("\n").map((line) => ` ${line}`).join("\n");
3001
+ console.log(import_chalk2.default.magenta(indentedOutput));
3002
+ console.log();
3003
+ }
3004
+ /**
3005
+ * Display an error.
3006
+ */
3007
+ displayError(error) {
3008
+ if (!this.config.verbose) return;
3009
+ console.error(import_chalk2.default.red.bold(`
3010
+ \u274C Workflow failed: ${error.message}`));
3011
+ if (error.stack) {
3012
+ console.error(import_chalk2.default.red.dim(error.stack));
3013
+ }
3014
+ }
3015
+ /**
3016
+ * Log an orchestration event.
3017
+ */
3018
+ logEvent(event) {
3019
+ this.eventLog.push(event);
3020
+ if (this.config.onEvent) {
3021
+ this.config.onEvent(event);
3022
+ }
3023
+ }
3024
+ /**
3025
+ * Get the event log.
3026
+ */
3027
+ getEventLog() {
3028
+ return [...this.eventLog];
3029
+ }
3030
+ /**
3031
+ * Get the YAML loader for registering custom tools.
3032
+ */
3033
+ getLoader() {
3034
+ return this.loader;
3035
+ }
3036
+ };
3037
+
3038
+ // src/cli.ts
3039
+ import_dotenv.default.config();
3040
+ async function main() {
3041
+ const args = process.argv.slice(2);
3042
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
3043
+ printUsage();
3044
+ process.exit(0);
3045
+ }
3046
+ const command = args[0];
3047
+ if (command === "run") {
3048
+ await runCommand(args.slice(1));
3049
+ } else if (command === "validate") {
3050
+ await validateCommand(args.slice(1));
3051
+ } else {
3052
+ console.error(import_chalk3.default.red(`Unknown command: ${command}`));
3053
+ printUsage();
3054
+ process.exit(1);
3055
+ }
3056
+ }
3057
+ function printUsage() {
3058
+ console.log(import_chalk3.default.cyan.bold("\nsmol-js CLI - YAML Agent Orchestrator\n"));
3059
+ console.log("Usage:");
3060
+ console.log(" smol-js run <workflow.yaml> [options] Run a workflow");
3061
+ console.log(" smol-js validate <workflow.yaml> Validate a workflow file");
3062
+ console.log("");
3063
+ console.log("Options:");
3064
+ console.log(" --task, -t <task> Task description (prompted if not provided)");
3065
+ console.log(" --quiet, -q Reduce output verbosity");
3066
+ console.log(" --help, -h Show this help message");
3067
+ console.log("");
3068
+ console.log("Examples:");
3069
+ console.log(' smol-js run workflow.yaml --task "Research AI safety"');
3070
+ console.log(' smol-js run research-agent.yaml -t "Write a summary of quantum computing"');
3071
+ console.log(" smol-js validate my-workflow.yaml");
3072
+ }
3073
+ async function runCommand(args) {
3074
+ if (args.length === 0) {
3075
+ console.error(import_chalk3.default.red("Error: workflow file path required"));
3076
+ process.exit(1);
3077
+ }
3078
+ const filePath = args[0];
3079
+ let task = "";
3080
+ let quiet = false;
3081
+ for (let i = 1; i < args.length; i++) {
3082
+ if (args[i] === "--task" || args[i] === "-t") {
3083
+ task = args[i + 1] ?? "";
3084
+ i++;
3085
+ } else if (args[i] === "--quiet" || args[i] === "-q") {
3086
+ quiet = true;
3087
+ }
3088
+ }
3089
+ const resolvedPath = path6.isAbsolute(filePath) ? filePath : path6.resolve(process.cwd(), filePath);
3090
+ if (!fs6.existsSync(resolvedPath)) {
3091
+ console.error(import_chalk3.default.red(`Error: file not found: ${resolvedPath}`));
3092
+ process.exit(1);
3093
+ }
3094
+ if (!task) {
3095
+ task = await promptUser("Enter your task: ");
3096
+ if (!task.trim()) {
3097
+ console.error(import_chalk3.default.red("Error: task cannot be empty"));
3098
+ process.exit(1);
3099
+ }
3100
+ }
3101
+ const orchestrator = new Orchestrator({ verbose: !quiet });
3102
+ try {
3103
+ console.log(import_chalk3.default.gray(`
3104
+ Loading workflow from: ${resolvedPath}
3105
+ `));
3106
+ const workflow = orchestrator.loadWorkflow(resolvedPath);
3107
+ await orchestrator.runWorkflow(workflow, task);
3108
+ process.exit(0);
3109
+ } catch (error) {
3110
+ console.error(import_chalk3.default.red(`
3111
+ Error: ${error.message}`));
3112
+ if (process.env.DEBUG) {
3113
+ console.error(error.stack);
3114
+ }
3115
+ process.exit(1);
3116
+ }
3117
+ }
3118
+ async function validateCommand(args) {
3119
+ if (args.length === 0) {
3120
+ console.error(import_chalk3.default.red("Error: workflow file path required"));
3121
+ process.exit(1);
3122
+ }
3123
+ const filePath = args[0];
3124
+ const resolvedPath = path6.isAbsolute(filePath) ? filePath : path6.resolve(process.cwd(), filePath);
3125
+ if (!fs6.existsSync(resolvedPath)) {
3126
+ console.error(import_chalk3.default.red(`Error: file not found: ${resolvedPath}`));
3127
+ process.exit(1);
3128
+ }
3129
+ const orchestrator = new Orchestrator({ verbose: false });
3130
+ try {
3131
+ const workflow = orchestrator.loadWorkflow(resolvedPath);
3132
+ console.log(import_chalk3.default.green.bold("\u2705 Workflow is valid"));
3133
+ console.log(import_chalk3.default.green(` Name: ${workflow.name}`));
3134
+ console.log(import_chalk3.default.green(` Agents: ${Array.from(workflow.agents.keys()).join(", ")}`));
3135
+ console.log(import_chalk3.default.green(` Tools: ${Array.from(workflow.tools.keys()).join(", ") || "(using defaults)"}`));
3136
+ console.log(import_chalk3.default.green(` Entrypoint: ${workflow.entrypointAgent.getName()}`));
3137
+ process.exit(0);
3138
+ } catch (error) {
3139
+ console.error(import_chalk3.default.red(`\u274C Validation failed: ${error.message}`));
3140
+ process.exit(1);
3141
+ }
3142
+ }
3143
+ function promptUser(question) {
3144
+ const rl = readline.createInterface({
3145
+ input: process.stdin,
3146
+ output: process.stdout
3147
+ });
3148
+ return new Promise((resolve6) => {
3149
+ rl.question(import_chalk3.default.cyan(question), (answer) => {
3150
+ rl.close();
3151
+ resolve6(answer);
3152
+ });
3153
+ });
3154
+ }
3155
+ main().catch((error) => {
3156
+ console.error(import_chalk3.default.red(`Fatal error: ${error.message}`));
3157
+ process.exit(1);
3158
+ });
3159
+ //# sourceMappingURL=cli.js.map