@eko-ai/eko 1.0.8 → 1.0.9

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/index.cjs.js CHANGED
@@ -2,14 +2,219 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ /**
6
+ * Manages logging for action execution, providing a cleaner view of the execution
7
+ * flow while maintaining important context and history.
8
+ */
9
+ class ExecutionLogger {
10
+ constructor(options = {}) {
11
+ var _a;
12
+ this.history = [];
13
+ this.maxHistoryLength = options.maxHistoryLength || 10;
14
+ this.logLevel = options.logLevel || 'info';
15
+ this.includeTimestamp = (_a = options.includeTimestamp) !== null && _a !== void 0 ? _a : true;
16
+ this.debugImagePath = options.debugImagePath;
17
+ this.imageSaver = options.imageSaver;
18
+ // Check if running in Node.js environment
19
+ this.isNode =
20
+ typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
21
+ }
22
+ /**
23
+ * Logs a message with execution context
24
+ */
25
+ log(level, message, context) {
26
+ if (this.shouldLog(level)) {
27
+ const timestamp = this.includeTimestamp ? new Date().toISOString() : '';
28
+ const contextSummary = this.summarizeContext(context);
29
+ console.log(`${timestamp} [${level.toUpperCase()}] ${message}${contextSummary}`);
30
+ }
31
+ }
32
+ /**
33
+ * Updates conversation history while maintaining size limit
34
+ */
35
+ updateHistory(messages) {
36
+ // Keep system messages and last N messages
37
+ const systemMessages = messages.filter((m) => m.role === 'system');
38
+ const nonSystemMessages = messages.filter((m) => m.role !== 'system');
39
+ const recentMessages = nonSystemMessages.slice(-this.maxHistoryLength);
40
+ this.history = [...systemMessages, ...recentMessages];
41
+ }
42
+ /**
43
+ * Gets current conversation history
44
+ */
45
+ getHistory() {
46
+ return this.history;
47
+ }
48
+ /**
49
+ * Summarizes the execution context for logging
50
+ */
51
+ summarizeContext(context) {
52
+ if (!context)
53
+ return '';
54
+ const summary = {
55
+ variables: Object.fromEntries(context.variables),
56
+ tools: context.tools ? Array.from(context.tools.keys()) : [],
57
+ };
58
+ return `\nContext: ${JSON.stringify(summary, null, 2)}`;
59
+ }
60
+ /**
61
+ * Checks if message should be logged based on log level
62
+ */
63
+ shouldLog(level) {
64
+ const levels = {
65
+ error: 0,
66
+ warn: 1,
67
+ info: 2,
68
+ debug: 3,
69
+ };
70
+ return levels[level] <= levels[this.logLevel];
71
+ }
72
+ /**
73
+ * Logs the start of an action execution
74
+ */
75
+ logActionStart(actionName, input, context) {
76
+ this.log('info', `Starting action: ${actionName}`, context);
77
+ this.log('info', `Input: ${JSON.stringify(input, null, 2)}`);
78
+ }
79
+ /**
80
+ * Logs the completion of an action execution
81
+ */
82
+ logActionComplete(actionName, result, context) {
83
+ this.log('info', `Completed action: ${actionName}`, context);
84
+ this.log('info', `Result: ${JSON.stringify(result, null, 2)}`);
85
+ }
86
+ /**
87
+ * Logs a tool execution
88
+ */
89
+ logToolExecution(toolName, input, context) {
90
+ this.log('info', `Executing tool: ${toolName}`);
91
+ this.log('info', `Tool input: ${JSON.stringify(input, null, 2)}`);
92
+ }
93
+ /**
94
+ * Logs an error that occurred during execution
95
+ */
96
+ logError(error, context) {
97
+ this.log('error', `Error occurred: ${error.message}`, context);
98
+ if (error.stack) {
99
+ this.log('debug', `Stack trace: ${error.stack}`);
100
+ }
101
+ }
102
+ extractFromDataUrl(dataUrl) {
103
+ const matches = dataUrl.match(/^data:image\/([a-zA-Z0-9]+);base64,(.+)$/);
104
+ if (!matches) {
105
+ throw new Error('Invalid data URL format');
106
+ }
107
+ return {
108
+ extension: matches[1],
109
+ base64Data: matches[2],
110
+ };
111
+ }
112
+ async saveDebugImage(imageData, toolName) {
113
+ try {
114
+ let extension;
115
+ let base64Data;
116
+ // Handle both data URL strings and ImageData objects
117
+ if (typeof imageData === 'string' && imageData.startsWith('data:')) {
118
+ const extracted = this.extractFromDataUrl(imageData);
119
+ extension = extracted.extension;
120
+ base64Data = extracted.base64Data;
121
+ }
122
+ else if (typeof imageData === 'object' && 'type' in imageData) {
123
+ extension = imageData.media_type.split('/')[1] || 'png';
124
+ base64Data = imageData.data;
125
+ }
126
+ else {
127
+ return '[image]';
128
+ }
129
+ // If custom image saver is provided, use it
130
+ if (this.imageSaver) {
131
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
132
+ const filename = `${toolName}_${timestamp}.${extension}`;
133
+ return await this.imageSaver({ type: 'base64', media_type: `image/${extension}`, data: base64Data }, filename);
134
+ }
135
+ // If in Node.js environment and debugImagePath is set
136
+ if (this.isNode && this.debugImagePath) {
137
+ // Dynamically import Node.js modules only when needed
138
+ const { promises: fs } = await import('fs');
139
+ const { join } = await import('path');
140
+ await fs.mkdir(this.debugImagePath, { recursive: true });
141
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
142
+ const filename = `${toolName}_${timestamp}.${extension}`;
143
+ const filepath = join(this.debugImagePath, filename);
144
+ const buffer = Buffer.from(base64Data, 'base64');
145
+ await fs.writeFile(filepath, buffer);
146
+ return `[image saved to: ${filepath}]`;
147
+ }
148
+ // Default case - just return placeholder
149
+ return '[image]';
150
+ }
151
+ catch (error) {
152
+ console.warn('Failed to save debug image:', error);
153
+ return '[image]';
154
+ }
155
+ }
156
+ async formatToolResult(result) {
157
+ // Handle null/undefined
158
+ if (result == null) {
159
+ return 'null';
160
+ }
161
+ // Handle direct image result
162
+ if (result.image) {
163
+ const imagePlaceholder = await this.saveDebugImage(result.image, 'tool');
164
+ const modifiedResult = { ...result, image: imagePlaceholder };
165
+ return JSON.stringify(modifiedResult);
166
+ }
167
+ // Handle nested images in result object
168
+ if (typeof result === 'object') {
169
+ const formatted = { ...result };
170
+ for (const [key, value] of Object.entries(formatted)) {
171
+ if (value && typeof value === 'string' && value.startsWith('data:image/')) {
172
+ formatted[key] = await this.saveDebugImage(value, key);
173
+ }
174
+ else if (value &&
175
+ typeof value === 'object' &&
176
+ 'type' in value &&
177
+ value.type === 'base64') {
178
+ formatted[key] = await this.saveDebugImage(value, key);
179
+ }
180
+ }
181
+ return JSON.stringify(formatted);
182
+ }
183
+ // Handle primitive values
184
+ return String(result);
185
+ }
186
+ async logToolResult(toolName, result, context) {
187
+ if (this.shouldLog('info')) {
188
+ const timestamp = this.includeTimestamp ? new Date().toISOString() : '';
189
+ const contextSummary = this.summarizeContext(context);
190
+ const formattedResult = await this.formatToolResult(result);
191
+ console.log(`${timestamp} [INFO] Tool executed: ${toolName}\n` +
192
+ `${timestamp} [INFO] Tool result: ${formattedResult}${contextSummary}`);
193
+ }
194
+ }
195
+ }
196
+
5
197
  class WorkflowImpl {
6
- constructor(id, name, description, nodes = [], variables = new Map(), llmProvider) {
198
+ constructor(id, name, description, nodes = [], variables = new Map(), llmProvider, loggerOptions) {
7
199
  this.id = id;
8
200
  this.name = name;
9
201
  this.description = description;
10
202
  this.nodes = nodes;
11
203
  this.variables = variables;
12
204
  this.llmProvider = llmProvider;
205
+ this.abortControllers = new Map();
206
+ if (loggerOptions) {
207
+ this.logger = new ExecutionLogger(loggerOptions);
208
+ }
209
+ }
210
+ setLogger(logger) {
211
+ this.logger = logger;
212
+ }
213
+ async cancel() {
214
+ this.abort = true;
215
+ for (const controller of this.abortControllers.values()) {
216
+ controller.abort("Workflow cancelled");
217
+ }
13
218
  }
14
219
  async execute(callback) {
15
220
  var _a, _b, _c, _d;
@@ -32,37 +237,49 @@ class WorkflowImpl {
32
237
  throw new Error(`Circular dependency detected at node: ${nodeId}`);
33
238
  }
34
239
  const node = this.getNode(nodeId);
240
+ const abortController = new AbortController();
241
+ this.abortControllers.set(nodeId, abortController);
35
242
  // Execute the node's action
36
243
  const context = {
37
244
  __skip: false,
38
245
  __abort: false,
246
+ workflow: this,
39
247
  variables: this.variables,
40
248
  llmProvider: this.llmProvider,
41
249
  tools: new Map(node.action.tools.map(tool => [tool.name, tool])),
42
250
  callback,
251
+ logger: this.logger,
43
252
  next: () => context.__skip = true,
44
- abortAll: () => this.abort = context.__abort = true,
253
+ abortAll: () => {
254
+ this.abort = context.__abort = true;
255
+ // Abort all running tasks
256
+ for (const controller of this.abortControllers.values()) {
257
+ controller.abort("Workflow cancelled");
258
+ }
259
+ },
260
+ signal: abortController.signal
45
261
  };
46
- callback && await ((_b = (_a = callback.hooks).beforeSubtask) === null || _b === void 0 ? void 0 : _b.call(_a, node, context));
47
- if (context.__abort) {
48
- throw new Error("Abort");
49
- }
50
- else if (context.__skip) {
51
- return;
52
- }
53
262
  executing.add(nodeId);
54
263
  // Execute dependencies first
55
264
  for (const depId of node.dependencies) {
56
265
  await executeNode(depId);
57
266
  }
58
267
  // Prepare input by gathering outputs from dependencies
59
- const input = {};
268
+ const input = { items: [] };
60
269
  for (const depId of node.dependencies) {
61
270
  const depNode = this.getNode(depId);
62
- input[depId] = depNode.output.value;
271
+ input.items.push(depNode.output);
63
272
  }
64
- node.input.value = input;
65
- node.output.value = await node.action.execute(node.input.value, context);
273
+ node.input = input;
274
+ // Run pre-execution hooks and execute action
275
+ callback && await ((_b = (_a = callback.hooks).beforeSubtask) === null || _b === void 0 ? void 0 : _b.call(_a, node, context));
276
+ if (context.__abort) {
277
+ throw new Error("Abort");
278
+ }
279
+ else if (context.__skip) {
280
+ return;
281
+ }
282
+ node.output.value = await node.action.execute(node.input, node.output, context);
66
283
  executing.delete(nodeId);
67
284
  executed.add(nodeId);
68
285
  callback && await ((_d = (_c = callback.hooks).afterSubtask) === null || _d === void 0 ? void 0 : _d.call(_c, node, context, (_e = node.output) === null || _e === void 0 ? void 0 : _e.value));
@@ -71,6 +288,7 @@ class WorkflowImpl {
71
288
  const terminalNodes = this.nodes.filter(node => !this.nodes.some(n => n.dependencies.includes(node.id)));
72
289
  await Promise.all(terminalNodes.map(node => executeNode(node.id)));
73
290
  callback && await ((_d = (_c = callback.hooks).afterWorkflow) === null || _d === void 0 ? void 0 : _d.call(_c, this, this.variables));
291
+ return terminalNodes.map(node => node.output);
74
292
  }
75
293
  addNode(node) {
76
294
  if (this.nodes.some(n => n.id === node.id)) {
@@ -129,7 +347,7 @@ class WorkflowImpl {
129
347
  class WriteContextTool {
130
348
  constructor() {
131
349
  this.name = 'write_context';
132
- this.description = 'Write a value to the workflow context. Use this to store intermediate results or outputs.';
350
+ this.description = 'Write a value to the global workflow context. Use this to store important intermediate results, but only when a piece of information is essential for future reference but missing from the final output specification of the current action.';
133
351
  this.input_schema = {
134
352
  type: 'object',
135
353
  properties: {
@@ -159,25 +377,32 @@ class WriteContextTool {
159
377
  return { success: true, key, value };
160
378
  }
161
379
  }
162
- function createReturnTool(outputSchema) {
380
+ function createReturnTool(actionName, outputDescription, outputSchema) {
163
381
  return {
164
382
  name: 'return_output',
165
- description: 'Return the final output of this action. Use this to return a value matching the required output schema.',
383
+ description: `Return the final output of this action. Use this to return a value matching the required output schema (if specified) and the following description:
384
+ ${outputDescription}
385
+
386
+ You can either set 'use_tool_result=true' to return the result of a previous tool call, or explicitly specify 'value' with 'use_tool_result=false' to return a value according to your own understanding. Whenever possible, reuse tool results to avoid redundancy.
387
+ `,
166
388
  input_schema: {
167
389
  type: 'object',
168
390
  properties: {
391
+ use_tool_result: {
392
+ type: ['boolean'],
393
+ description: `Whether to use the latest tool result as output. When set to true, the 'value' parameter is ignored.`,
394
+ },
169
395
  value: outputSchema || {
170
396
  // Default to accepting any JSON value
171
397
  type: ['string', 'number', 'boolean', 'object', 'null'],
172
- description: 'The output value',
398
+ description: 'The output value. Only provide a value if the previous tool result is not suitable for the output description. Otherwise, leave this as null.',
173
399
  },
174
400
  },
175
- required: ['value'],
401
+ required: ['use_tool_result', 'value'],
176
402
  },
177
403
  async execute(context, params) {
178
- const { value } = params;
179
- context.variables.set('__action_output', value);
180
- return { returned: value };
404
+ context.variables.set(`__action_${actionName}_output`, params);
405
+ return { success: true };
181
406
  },
182
407
  };
183
408
  }
@@ -191,6 +416,8 @@ class ActionImpl {
191
416
  this.llmProvider = llmProvider;
192
417
  this.llmConfig = llmConfig;
193
418
  this.maxRounds = 10; // Default max rounds
419
+ this.toolResults = new Map();
420
+ this.logger = new ExecutionLogger();
194
421
  this.writeContextTool = new WriteContextTool();
195
422
  this.tools = [...tools, this.writeContextTool];
196
423
  if (config === null || config === void 0 ? void 0 : config.maxRounds) {
@@ -198,6 +425,7 @@ class ActionImpl {
198
425
  }
199
426
  }
200
427
  async executeSingleRound(messages, params, toolMap, context) {
428
+ this.logger = context.logger;
201
429
  const roundMessages = [];
202
430
  let hasToolUse = false;
203
431
  let response = null;
@@ -207,6 +435,12 @@ class ActionImpl {
207
435
  let toolResultMessage = null;
208
436
  // Track tool execution promise
209
437
  let toolExecutionPromise = null;
438
+ // Listen for abort signal
439
+ if (context.signal) {
440
+ context.signal.addEventListener('abort', () => {
441
+ context.__abort = true;
442
+ });
443
+ }
210
444
  const handler = {
211
445
  onContent: (content) => {
212
446
  if (content.trim()) {
@@ -214,7 +448,8 @@ class ActionImpl {
214
448
  }
215
449
  },
216
450
  onToolUse: async (toolCall) => {
217
- console.log('Tool Call:', toolCall.name, toolCall.input);
451
+ this.logger.log('info', `Assistant: ${assistantTextMessage}`);
452
+ this.logger.logToolExecution(toolCall.name, toolCall.input, context);
218
453
  hasToolUse = true;
219
454
  const tool = toolMap.get(toolCall.name);
220
455
  if (!tool) {
@@ -233,6 +468,7 @@ class ActionImpl {
233
468
  };
234
469
  // Store the promise of tool execution
235
470
  toolExecutionPromise = (async () => {
471
+ var _a;
236
472
  try {
237
473
  // beforeToolUse
238
474
  context.__skip = false;
@@ -242,7 +478,7 @@ class ActionImpl {
242
478
  toolCall.input = modified_input;
243
479
  }
244
480
  }
245
- if (context.__skip || context.__abort) {
481
+ if (context.__skip || context.__abort || ((_a = context.signal) === null || _a === void 0 ? void 0 : _a.aborted)) {
246
482
  toolResultMessage = {
247
483
  role: 'user',
248
484
  content: [
@@ -264,31 +500,42 @@ class ActionImpl {
264
500
  result = modified_result;
265
501
  }
266
502
  }
503
+ const result_has_image = result && "image" in result;
504
+ const resultContent = result_has_image
505
+ ? {
506
+ type: 'tool_result',
507
+ tool_use_id: toolCall.id,
508
+ content: result.text
509
+ ? [
510
+ { type: 'image', source: result.image },
511
+ { type: 'text', text: result.text },
512
+ ]
513
+ : [{ type: 'image', source: result.image }],
514
+ }
515
+ : {
516
+ type: 'tool_result',
517
+ tool_use_id: toolCall.id,
518
+ content: [{ type: 'text', text: JSON.stringify(result) }],
519
+ };
520
+ const resultContentText = result_has_image
521
+ ? result.text
522
+ ? result.text + ' [Image]'
523
+ : '[Image]'
524
+ : JSON.stringify(result);
267
525
  const resultMessage = {
268
526
  role: 'user',
269
- content: [
270
- result.image && result.image.type
271
- ? {
272
- type: 'tool_result',
273
- tool_use_id: toolCall.id,
274
- content: result.text
275
- ? [
276
- { type: 'image', source: result.image },
277
- { type: 'text', text: result.text },
278
- ]
279
- : [{ type: 'image', source: result.image }],
280
- }
281
- : {
282
- type: 'tool_result',
283
- tool_use_id: toolCall.id,
284
- content: [{ type: 'text', text: JSON.stringify(result) }],
285
- },
286
- ],
527
+ content: [resultContent],
287
528
  };
288
529
  toolResultMessage = resultMessage;
289
- console.log('Tool Result:', result);
530
+ this.logger.logToolResult(tool.name, result, context);
531
+ // Store tool results except for the return_output tool
532
+ if (tool.name !== 'return_output') {
533
+ this.toolResults.set(toolCall.id, resultContentText);
534
+ }
290
535
  }
291
536
  catch (err) {
537
+ console.log("An error occurred when calling tool:");
538
+ console.log(err);
292
539
  const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
293
540
  const errorResult = {
294
541
  role: 'user',
@@ -302,7 +549,7 @@ class ActionImpl {
302
549
  ],
303
550
  };
304
551
  toolResultMessage = errorResult;
305
- console.error('Tool Error:', err);
552
+ this.logger.logError(err, context);
306
553
  }
307
554
  })();
308
555
  },
@@ -311,10 +558,14 @@ class ActionImpl {
311
558
  },
312
559
  onError: (error) => {
313
560
  console.error('Stream Error:', error);
561
+ console.log('Last message array sent to LLM:', JSON.stringify(messages, null, 2));
314
562
  },
315
563
  };
316
564
  this.handleHistoryImageMessages(messages);
317
565
  // Wait for stream to complete
566
+ if (!this.llmProvider) {
567
+ throw new Error('LLM provider not set');
568
+ }
318
569
  await this.llmProvider.generateStream(messages, params, handler);
319
570
  // Wait for tool execution to complete if it was started
320
571
  if (toolExecutionPromise) {
@@ -336,40 +587,58 @@ class ActionImpl {
336
587
  return { response, hasToolUse, roundMessages };
337
588
  }
338
589
  handleHistoryImageMessages(messages) {
339
- // Remove all images of the historical tool call results, except for the last one.
340
- let last_user = true;
590
+ // Remove all images from historical tool results except the most recent user message
591
+ const initialImageCount = this.countImages(messages);
592
+ let foundFirstUser = false;
341
593
  for (let i = messages.length - 1; i >= 0; i--) {
342
594
  const message = messages[i];
343
595
  if (message.role === 'user') {
344
- if (last_user) {
345
- last_user = false;
596
+ if (!foundFirstUser) {
597
+ foundFirstUser = true;
346
598
  continue;
347
599
  }
348
- if (message.content instanceof Array) {
349
- let content = message.content;
350
- for (let j = 0; j < content.length; j++) {
351
- if (content[j].type === 'tool_result' && content[j].content instanceof Array) {
352
- let tool_content = content[j].content;
353
- if (tool_content.length > 0) {
354
- for (let k = tool_content.length - 1; k >= 0; k--) {
355
- if (tool_content[k].type === 'image') {
356
- tool_content.splice(k, 1);
357
- }
600
+ if (Array.isArray(message.content)) {
601
+ // Directly modify the message content array
602
+ message.content = message.content.map((item) => {
603
+ if (item.type === 'tool_result' && Array.isArray(item.content)) {
604
+ // Create a new content array without images
605
+ if (item.content.length > 0) {
606
+ item.content = item.content.filter((c) => c.type !== 'image');
607
+ // If all content was images and got filtered out, replace with ok message
608
+ if (item.content.length === 0) {
609
+ item.content = [{ type: 'text', text: 'ok' }];
358
610
  }
359
611
  }
360
- else if (tool_content[0].type === 'image') {
361
- tool_content = [{ type: 'text', text: 'ok' }];
362
- }
363
612
  }
364
- }
613
+ return item;
614
+ });
365
615
  }
366
616
  }
367
617
  }
618
+ const finalImageCount = this.countImages(messages);
619
+ if (initialImageCount !== finalImageCount) {
620
+ this.logger.log("info", `Removed ${initialImageCount - finalImageCount} images from history`);
621
+ }
368
622
  }
369
- async execute(input, context, outputSchema) {
370
- var _a;
623
+ countImages(messages) {
624
+ let count = 0;
625
+ messages.forEach(msg => {
626
+ if (Array.isArray(msg.content)) {
627
+ msg.content.forEach((item) => {
628
+ if (item.type === 'tool_result' && Array.isArray(item.content)) {
629
+ count += item.content.filter((c) => c.type === 'image').length;
630
+ }
631
+ });
632
+ }
633
+ });
634
+ return count;
635
+ }
636
+ async execute(input, output, context, outputSchema) {
637
+ var _a, _b, _c, _d, _e;
638
+ this.logger = context.logger;
639
+ console.log(`Executing action started: ${this.name}`);
371
640
  // Create return tool with output schema
372
- const returnTool = createReturnTool(outputSchema);
641
+ const returnTool = createReturnTool(this.name, output.description, outputSchema);
373
642
  // Create tool map combining context tools, action tools, and return tool
374
643
  const toolMap = new Map();
375
644
  this.tools.forEach((tool) => toolMap.set(tool.name, tool));
@@ -380,9 +649,7 @@ class ActionImpl {
380
649
  { role: 'system', content: this.formatSystemPrompt() },
381
650
  { role: 'user', content: this.formatUserPrompt(context, input) },
382
651
  ];
383
- console.log('Starting LLM conversation...');
384
- console.log('Initial messages:', messages);
385
- console.log('Output schema:', outputSchema);
652
+ this.logger.logActionStart(this.name, input, context);
386
653
  // Configure tool parameters
387
654
  const params = {
388
655
  ...this.llmConfig,
@@ -394,17 +661,24 @@ class ActionImpl {
394
661
  };
395
662
  let roundCount = 0;
396
663
  while (roundCount < this.maxRounds) {
664
+ // Check for abort signal
665
+ if ((_b = context.signal) === null || _b === void 0 ? void 0 : _b.aborted) {
666
+ throw new Error('Workflow cancelled');
667
+ }
397
668
  roundCount++;
398
- console.log(`Starting round ${roundCount} of ${this.maxRounds}`);
399
- console.log('Current conversation status:', JSON.stringify(messages, null, 2));
669
+ this.logger.log('info', `Starting round ${roundCount} of ${this.maxRounds}`, context);
400
670
  const { response, hasToolUse, roundMessages } = await this.executeSingleRound(messages, params, toolMap, context);
671
+ if (response === null || response === void 0 ? void 0 : response.textContent) {
672
+ (_e = (_d = (_c = context.callback) === null || _c === void 0 ? void 0 : _c.hooks) === null || _d === void 0 ? void 0 : _d.onLlmMessage) === null || _e === void 0 ? void 0 : _e.call(_d, response.textContent);
673
+ }
401
674
  // Add round messages to conversation history
402
675
  messages.push(...roundMessages);
676
+ this.logger.log('debug', `Round ${roundCount} messages: ${JSON.stringify(roundMessages)}`, context);
403
677
  // Check termination conditions
404
678
  if (!hasToolUse && response) {
405
679
  // LLM sent a message without using tools - request explicit return
406
- console.log('No tool use detected, requesting explicit return');
407
- console.log('Response:', response);
680
+ this.logger.log('info', `Assistant: ${response.textContent}`);
681
+ this.logger.log('warn', 'LLM sent a message without using tools; requesting explicit return');
408
682
  const returnOnlyParams = {
409
683
  ...params,
410
684
  tools: [
@@ -424,12 +698,11 @@ class ActionImpl {
424
698
  break;
425
699
  }
426
700
  if (response === null || response === void 0 ? void 0 : response.toolCalls.some((call) => call.name === 'return_output')) {
427
- console.log('Task completed with return_output tool');
428
701
  break;
429
702
  }
430
703
  // If this is the last round, force an explicit return
431
704
  if (roundCount === this.maxRounds) {
432
- console.log('Max rounds reached, requesting explicit return');
705
+ this.logger.log('warn', 'Max rounds reached, requesting explicit return');
433
706
  const returnOnlyParams = {
434
707
  ...params,
435
708
  tools: [
@@ -449,33 +722,50 @@ class ActionImpl {
449
722
  }
450
723
  }
451
724
  // Get and clean up output value
452
- const output = context.variables.get('__action_output');
453
- context.variables.delete('__action_output');
454
- if (output === undefined) {
725
+ const outputKey = `__action_${this.name}_output`;
726
+ const outputParams = context.variables.get(outputKey);
727
+ context.variables.delete(outputKey);
728
+ // Get output value, first checking for use_tool_result
729
+ const outputValue = outputParams.use_tool_result
730
+ ? Array.from(this.toolResults.values()).pop()
731
+ : outputParams === null || outputParams === void 0 ? void 0 : outputParams.value;
732
+ if (outputValue === undefined) {
455
733
  console.warn('Action completed without returning a value');
456
734
  return {};
457
735
  }
458
- return output;
736
+ return outputValue;
459
737
  }
460
738
  formatSystemPrompt() {
461
- return `You are a task executor. You need to complete the task specified by the user, using the tools provided. When you need to store results or outputs, use the write_context tool. When you are ready to return the final output, use the return_output tool.
739
+ return `You are a subtask executor. You need to complete the subtask specified by the user, which is a consisting part of the overall task. Help the user by calling the tools provided.
462
740
 
463
741
  Remember to:
464
742
  1. Use tools when needed to accomplish the task
465
- 2. Store important results using write_context, including intermediate and final results
466
- 3. Think step by step about what needs to be done`;
743
+ 2. Think step by step about what needs to be done
744
+ 3. Return the output of the subtask using the 'return_output' tool when you are done; prefer using the 'tool_use_id' parameter to refer to the output of a tool call over providing a long text as the value
745
+ 4. Use the context to store important information for later reference, but use it sparingly: most of the time, the output of the subtask should be sufficient for the next steps
746
+ 5. If there are any unclear points during the task execution, please use the human-related tool to inquire with the user
747
+ 6. If user intervention is required during the task execution, please use the human-related tool to transfer the operation rights to the user
748
+ `;
467
749
  }
468
750
  formatUserPrompt(context, input) {
469
- // Create a description of the current context
470
- const contextDescription = Array.from(context.variables.entries())
751
+ var _a;
752
+ const workflowDescription = ((_a = context.workflow) === null || _a === void 0 ? void 0 : _a.description) || null;
753
+ const actionDescription = `${this.name} -- ${this.description}`;
754
+ const inputDescription = JSON.stringify(input, null, 2) || null;
755
+ const contextVariables = Array.from(context.variables.entries())
471
756
  .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
472
757
  .join('\n');
473
- return `You are executing the action "${this.name}". The specific instructions are: "${this.description}". You have access to the following context:
758
+ return `You are executing a subtask in the workflow. The workflow description is as follows:
759
+ ${workflowDescription}
474
760
 
475
- ${contextDescription || 'No context variables set'}
761
+ The subtask description is as follows:
762
+ ${actionDescription}
476
763
 
477
- You have been provided with the following input:
478
- ${(typeof input === 'string' ? input : JSON.stringify(input, null, 2)) || 'No additional input provided'}
764
+ The input to the subtask is as follows:
765
+ ${inputDescription}
766
+
767
+ There are some variables stored in the context that you can use for reference:
768
+ ${contextVariables}
479
769
  `;
480
770
  }
481
771
  // Static factory method
@@ -505,7 +795,8 @@ Generate a complete workflow that:
505
795
  2. Properly sequences tool usage based on dependencies
506
796
  3. Ensures each action has appropriate input/output schemas, and that the "tools" field in each action is populated with the sufficient subset of all available tools needed to complete the action
507
797
  4. Creates a clear, logical flow to accomplish the user's goal
508
- 5. Includes detailed descriptions for each action, ensuring that the actions, when combined, is a complete solution to the user's problem`;
798
+ 5. Includes detailed descriptions for each action, ensuring that the actions, when combined, is a complete solution to the user's problem
799
+ 6. You should always add a SubTask at the end of the workflow to summarize it, and this SubTask should always call the "summary_workflow" tool. It's dependencies should be all of the SubTasks`;
509
800
  },
510
801
  formatUserPrompt: (requirement) => `Create a workflow for the following requirement: ${requirement}`,
511
802
  modifyUserPrompt: (prompt) => `Modify workflow: ${prompt}`,
@@ -646,6 +937,19 @@ class WorkflowGenerator {
646
937
  ],
647
938
  });
648
939
  const workflowData = response.toolCalls[0].input.workflow;
940
+ // Forcibly add special tools
941
+ const specialTools = [
942
+ "cancel_workflow",
943
+ "human_input_text",
944
+ "human_operate",
945
+ ];
946
+ for (const node of workflowData.nodes) {
947
+ for (const tool of specialTools) {
948
+ if (!node.action.tools.includes(tool)) {
949
+ node.action.tools.push(tool);
950
+ }
951
+ }
952
+ }
649
953
  // Validate all tools exist
650
954
  for (const node of workflowData.nodes) {
651
955
  if (!this.toolRegistry.hasTools(node.action.tools)) {
@@ -656,15 +960,22 @@ class WorkflowGenerator {
656
960
  if (!workflowData.id) {
657
961
  workflowData.id = v4();
658
962
  }
963
+ // debug
964
+ console.log("Debug the workflow...");
965
+ console.log(workflowData);
966
+ console.log("Debug the workflow...Done");
659
967
  return this.createWorkflowFromData(workflowData);
660
968
  }
661
969
  createWorkflowFromData(data) {
662
- const workflow = new WorkflowImpl(data.id, data.name, data.description || '', [], new Map(Object.entries(data.variables || {})), this.llmProvider);
970
+ const workflow = new WorkflowImpl(data.id, data.name, data.description || '', [], new Map(Object.entries(data.variables || {})), this.llmProvider, {
971
+ logLevel: 'info',
972
+ includeTimestamp: true,
973
+ });
663
974
  // Add nodes to workflow
664
975
  if (Array.isArray(data.nodes)) {
665
976
  data.nodes.forEach((nodeData) => {
666
977
  const tools = nodeData.action.tools.map((toolName) => this.toolRegistry.getTool(toolName));
667
- const action = ActionImpl.createPromptAction(nodeData.action.name, nodeData.action.description, tools, this.llmProvider, { maxTokens: 1000 });
978
+ const action = ActionImpl.createPromptAction(nodeData.action.name, nodeData.action.description, tools, this.llmProvider, { maxTokens: 8192 });
668
979
  const node = {
669
980
  id: nodeData.id,
670
981
  name: nodeData.name || nodeData.id,
@@ -3408,8 +3719,13 @@ class ClaudeProvider {
3408
3719
  ...options,
3409
3720
  });
3410
3721
  }
3722
+ else if (param.messages && param.completions) {
3723
+ this.client = param;
3724
+ }
3411
3725
  else {
3412
- this.client = new Anthropic(param);
3726
+ let options = param;
3727
+ options.dangerouslyAllowBrowser = true;
3728
+ this.client = new Anthropic(options);
3413
3729
  }
3414
3730
  }
3415
3731
  processResponse(response) {
@@ -8828,8 +9144,13 @@ class OpenaiProvider {
8828
9144
  ...options,
8829
9145
  });
8830
9146
  }
9147
+ else if (param.chat && param.chat.completions) {
9148
+ this.client = param;
9149
+ }
8831
9150
  else {
8832
- this.client = new OpenAI(param);
9151
+ let options = param;
9152
+ options.dangerouslyAllowBrowser = true;
9153
+ this.client = new OpenAI(options);
8833
9154
  }
8834
9155
  }
8835
9156
  buildParams(messages, params, stream) {
@@ -9140,18 +9461,11 @@ const workflowSchema = {
9140
9461
  type: "array",
9141
9462
  items: { type: "string" },
9142
9463
  },
9143
- input: {
9144
- type: "object",
9145
- properties: {
9146
- type: { type: "string" },
9147
- schema: { type: "object" },
9148
- },
9149
- },
9150
9464
  output: {
9151
9465
  type: "object",
9152
9466
  properties: {
9153
- type: { type: "string" },
9154
- schema: { type: "object" },
9467
+ name: { type: "string" },
9468
+ description: { type: "string" },
9155
9469
  },
9156
9470
  },
9157
9471
  action: {
@@ -9280,8 +9594,27 @@ class Eko {
9280
9594
  return workflow;
9281
9595
  }
9282
9596
  async execute(workflow, callback) {
9597
+ // Inject LLM provider at workflow level
9598
+ workflow.llmProvider = this.llmProvider;
9599
+ // Process each node's action
9600
+ for (const node of workflow.nodes) {
9601
+ if (node.action.type === 'prompt') {
9602
+ // Inject LLM provider
9603
+ node.action.llmProvider = this.llmProvider;
9604
+ // Resolve tools
9605
+ node.action.tools = node.action.tools.map(tool => {
9606
+ if (typeof tool === 'string') {
9607
+ return this.toolRegistry.getTool(tool);
9608
+ }
9609
+ return tool;
9610
+ });
9611
+ }
9612
+ }
9283
9613
  return await workflow.execute(callback);
9284
9614
  }
9615
+ async cancel(workflow) {
9616
+ return await workflow.cancel();
9617
+ }
9285
9618
  async modify(workflow, prompt) {
9286
9619
  const generator = this.workflowGeneratorMap.get(workflow);
9287
9620
  workflow = await generator.modifyWorkflow(prompt);
@@ -9465,31 +9798,30 @@ class WorkflowParser {
9465
9798
  errors,
9466
9799
  };
9467
9800
  }
9468
- /**
9469
- * Convert parsed JSON to runtime Workflow object
9470
- */
9471
9801
  static toRuntime(json) {
9472
9802
  const variables = new Map(Object.entries(json.variables || {}));
9473
- const workflow = new WorkflowImpl(json.id, json.name, json.description, [], variables);
9803
+ const workflow = new WorkflowImpl(json.id, json.name, json.description, [], variables, undefined, {
9804
+ logLevel: 'info',
9805
+ includeTimestamp: true,
9806
+ });
9474
9807
  // Convert nodes
9475
9808
  json.nodes.forEach((nodeJson) => {
9809
+ const action = ActionImpl.createPromptAction(nodeJson.action.name, nodeJson.action.description,
9810
+ // Pass tool names as strings, they'll be resolved at execution time
9811
+ nodeJson.action.tools || [], undefined, // LLM provider will be injected at execution time
9812
+ { maxTokens: 1000 });
9476
9813
  const node = {
9477
9814
  id: nodeJson.id,
9478
9815
  name: nodeJson.name || nodeJson.id,
9479
9816
  description: nodeJson.description,
9480
9817
  dependencies: nodeJson.dependencies || [],
9481
- input: this.convertIO(nodeJson.input),
9482
- output: this.convertIO(nodeJson.output),
9483
- action: {
9484
- type: nodeJson.action.type,
9485
- name: nodeJson.action.name,
9486
- description: nodeJson.action.description,
9487
- tools: nodeJson.action.tools || [],
9488
- execute: async (input, context) => {
9489
- // Default implementation - should be overridden by specific action types
9490
- return input;
9491
- },
9818
+ input: { items: [] },
9819
+ output: nodeJson.output || {
9820
+ name: `${nodeJson.name || nodeJson.id}_output`,
9821
+ description: `Output of node ${nodeJson.name || nodeJson.id}`,
9822
+ value: null,
9492
9823
  },
9824
+ action: action,
9493
9825
  };
9494
9826
  workflow.addNode(node);
9495
9827
  });
@@ -9509,32 +9841,24 @@ class WorkflowParser {
9509
9841
  name: node.name,
9510
9842
  description: node.description,
9511
9843
  dependencies: node.dependencies,
9512
- input: node.input,
9513
9844
  output: node.output,
9514
9845
  action: {
9515
9846
  type: node.action.type,
9516
9847
  name: node.action.name,
9517
9848
  description: node.action.description,
9518
- tools: node.action.tools,
9849
+ tools: node.action.tools
9850
+ .map((tool) => (typeof tool === 'string' ? tool : tool.name))
9851
+ .filter((tool) => tool !== 'write_context'),
9519
9852
  },
9520
9853
  })),
9521
9854
  variables: Object.fromEntries(workflow.variables),
9522
9855
  };
9523
9856
  }
9524
- /**
9525
- * Helper to convert IO definitions
9526
- */
9527
- static convertIO(io) {
9528
- return {
9529
- type: (io === null || io === void 0 ? void 0 : io.type) || 'object',
9530
- schema: (io === null || io === void 0 ? void 0 : io.schema) || {},
9531
- value: null,
9532
- };
9533
- }
9534
9857
  }
9535
9858
 
9536
9859
  exports.ClaudeProvider = ClaudeProvider;
9537
9860
  exports.Eko = Eko;
9861
+ exports.ExecutionLogger = ExecutionLogger;
9538
9862
  exports.OpenaiProvider = OpenaiProvider;
9539
9863
  exports.ToolRegistry = ToolRegistry;
9540
9864
  exports.WorkflowGenerator = WorkflowGenerator;