@eko-ai/eko 1.0.7 → 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.
Files changed (38) hide show
  1. package/README.md +72 -21
  2. package/dist/core/eko.d.ts +3 -2
  3. package/dist/extension/content/index.d.ts +1 -0
  4. package/dist/extension/tools/browser.d.ts +2 -1
  5. package/dist/extension/tools/get_all_tabs.d.ts +9 -0
  6. package/dist/extension/tools/index.d.ts +4 -1
  7. package/dist/extension/tools/request_login.d.ts +10 -0
  8. package/dist/extension/tools/tab_management.d.ts +1 -1
  9. package/dist/extension/utils.d.ts +2 -1
  10. package/dist/extension.cjs.js +797 -209
  11. package/dist/extension.esm.js +797 -209
  12. package/dist/extension_content_script.js +129 -2
  13. package/dist/index.cjs.js +518 -114
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.esm.js +518 -115
  16. package/dist/models/action.d.ts +9 -4
  17. package/dist/models/workflow.d.ts +8 -3
  18. package/dist/nodejs/script/build_dom_tree.d.ts +1 -0
  19. package/dist/nodejs/tools/browser_use.d.ts +28 -0
  20. package/dist/nodejs/tools/index.d.ts +2 -0
  21. package/dist/nodejs.cjs.js +71638 -12
  22. package/dist/nodejs.esm.js +71632 -6
  23. package/dist/schemas/workflow.schema.d.ts +2 -13
  24. package/dist/services/llm/claude-provider.d.ts +2 -1
  25. package/dist/services/llm/openai-provider.d.ts +2 -1
  26. package/dist/services/parser/workflow-parser.d.ts +0 -7
  27. package/dist/types/action.types.d.ts +8 -3
  28. package/dist/types/tools.types.d.ts +44 -1
  29. package/dist/types/workflow.types.d.ts +22 -9
  30. package/dist/universal_tools/cancel_workflow.d.ts +9 -0
  31. package/dist/universal_tools/human.d.ts +30 -0
  32. package/dist/universal_tools/index.d.ts +4 -0
  33. package/dist/universal_tools/summary_workflow.d.ts +9 -0
  34. package/dist/utils/execution-logger.d.ts +69 -0
  35. package/dist/web/tools/browser.d.ts +2 -1
  36. package/dist/web.cjs.js +29 -17
  37. package/dist/web.esm.js +29 -17
  38. package/package.json +6 -9
package/dist/index.esm.js CHANGED
@@ -1,11 +1,216 @@
1
+ /**
2
+ * Manages logging for action execution, providing a cleaner view of the execution
3
+ * flow while maintaining important context and history.
4
+ */
5
+ class ExecutionLogger {
6
+ constructor(options = {}) {
7
+ var _a;
8
+ this.history = [];
9
+ this.maxHistoryLength = options.maxHistoryLength || 10;
10
+ this.logLevel = options.logLevel || 'info';
11
+ this.includeTimestamp = (_a = options.includeTimestamp) !== null && _a !== void 0 ? _a : true;
12
+ this.debugImagePath = options.debugImagePath;
13
+ this.imageSaver = options.imageSaver;
14
+ // Check if running in Node.js environment
15
+ this.isNode =
16
+ typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
17
+ }
18
+ /**
19
+ * Logs a message with execution context
20
+ */
21
+ log(level, message, context) {
22
+ if (this.shouldLog(level)) {
23
+ const timestamp = this.includeTimestamp ? new Date().toISOString() : '';
24
+ const contextSummary = this.summarizeContext(context);
25
+ console.log(`${timestamp} [${level.toUpperCase()}] ${message}${contextSummary}`);
26
+ }
27
+ }
28
+ /**
29
+ * Updates conversation history while maintaining size limit
30
+ */
31
+ updateHistory(messages) {
32
+ // Keep system messages and last N messages
33
+ const systemMessages = messages.filter((m) => m.role === 'system');
34
+ const nonSystemMessages = messages.filter((m) => m.role !== 'system');
35
+ const recentMessages = nonSystemMessages.slice(-this.maxHistoryLength);
36
+ this.history = [...systemMessages, ...recentMessages];
37
+ }
38
+ /**
39
+ * Gets current conversation history
40
+ */
41
+ getHistory() {
42
+ return this.history;
43
+ }
44
+ /**
45
+ * Summarizes the execution context for logging
46
+ */
47
+ summarizeContext(context) {
48
+ if (!context)
49
+ return '';
50
+ const summary = {
51
+ variables: Object.fromEntries(context.variables),
52
+ tools: context.tools ? Array.from(context.tools.keys()) : [],
53
+ };
54
+ return `\nContext: ${JSON.stringify(summary, null, 2)}`;
55
+ }
56
+ /**
57
+ * Checks if message should be logged based on log level
58
+ */
59
+ shouldLog(level) {
60
+ const levels = {
61
+ error: 0,
62
+ warn: 1,
63
+ info: 2,
64
+ debug: 3,
65
+ };
66
+ return levels[level] <= levels[this.logLevel];
67
+ }
68
+ /**
69
+ * Logs the start of an action execution
70
+ */
71
+ logActionStart(actionName, input, context) {
72
+ this.log('info', `Starting action: ${actionName}`, context);
73
+ this.log('info', `Input: ${JSON.stringify(input, null, 2)}`);
74
+ }
75
+ /**
76
+ * Logs the completion of an action execution
77
+ */
78
+ logActionComplete(actionName, result, context) {
79
+ this.log('info', `Completed action: ${actionName}`, context);
80
+ this.log('info', `Result: ${JSON.stringify(result, null, 2)}`);
81
+ }
82
+ /**
83
+ * Logs a tool execution
84
+ */
85
+ logToolExecution(toolName, input, context) {
86
+ this.log('info', `Executing tool: ${toolName}`);
87
+ this.log('info', `Tool input: ${JSON.stringify(input, null, 2)}`);
88
+ }
89
+ /**
90
+ * Logs an error that occurred during execution
91
+ */
92
+ logError(error, context) {
93
+ this.log('error', `Error occurred: ${error.message}`, context);
94
+ if (error.stack) {
95
+ this.log('debug', `Stack trace: ${error.stack}`);
96
+ }
97
+ }
98
+ extractFromDataUrl(dataUrl) {
99
+ const matches = dataUrl.match(/^data:image\/([a-zA-Z0-9]+);base64,(.+)$/);
100
+ if (!matches) {
101
+ throw new Error('Invalid data URL format');
102
+ }
103
+ return {
104
+ extension: matches[1],
105
+ base64Data: matches[2],
106
+ };
107
+ }
108
+ async saveDebugImage(imageData, toolName) {
109
+ try {
110
+ let extension;
111
+ let base64Data;
112
+ // Handle both data URL strings and ImageData objects
113
+ if (typeof imageData === 'string' && imageData.startsWith('data:')) {
114
+ const extracted = this.extractFromDataUrl(imageData);
115
+ extension = extracted.extension;
116
+ base64Data = extracted.base64Data;
117
+ }
118
+ else if (typeof imageData === 'object' && 'type' in imageData) {
119
+ extension = imageData.media_type.split('/')[1] || 'png';
120
+ base64Data = imageData.data;
121
+ }
122
+ else {
123
+ return '[image]';
124
+ }
125
+ // If custom image saver is provided, use it
126
+ if (this.imageSaver) {
127
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
128
+ const filename = `${toolName}_${timestamp}.${extension}`;
129
+ return await this.imageSaver({ type: 'base64', media_type: `image/${extension}`, data: base64Data }, filename);
130
+ }
131
+ // If in Node.js environment and debugImagePath is set
132
+ if (this.isNode && this.debugImagePath) {
133
+ // Dynamically import Node.js modules only when needed
134
+ const { promises: fs } = await import('fs');
135
+ const { join } = await import('path');
136
+ await fs.mkdir(this.debugImagePath, { recursive: true });
137
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
138
+ const filename = `${toolName}_${timestamp}.${extension}`;
139
+ const filepath = join(this.debugImagePath, filename);
140
+ const buffer = Buffer.from(base64Data, 'base64');
141
+ await fs.writeFile(filepath, buffer);
142
+ return `[image saved to: ${filepath}]`;
143
+ }
144
+ // Default case - just return placeholder
145
+ return '[image]';
146
+ }
147
+ catch (error) {
148
+ console.warn('Failed to save debug image:', error);
149
+ return '[image]';
150
+ }
151
+ }
152
+ async formatToolResult(result) {
153
+ // Handle null/undefined
154
+ if (result == null) {
155
+ return 'null';
156
+ }
157
+ // Handle direct image result
158
+ if (result.image) {
159
+ const imagePlaceholder = await this.saveDebugImage(result.image, 'tool');
160
+ const modifiedResult = { ...result, image: imagePlaceholder };
161
+ return JSON.stringify(modifiedResult);
162
+ }
163
+ // Handle nested images in result object
164
+ if (typeof result === 'object') {
165
+ const formatted = { ...result };
166
+ for (const [key, value] of Object.entries(formatted)) {
167
+ if (value && typeof value === 'string' && value.startsWith('data:image/')) {
168
+ formatted[key] = await this.saveDebugImage(value, key);
169
+ }
170
+ else if (value &&
171
+ typeof value === 'object' &&
172
+ 'type' in value &&
173
+ value.type === 'base64') {
174
+ formatted[key] = await this.saveDebugImage(value, key);
175
+ }
176
+ }
177
+ return JSON.stringify(formatted);
178
+ }
179
+ // Handle primitive values
180
+ return String(result);
181
+ }
182
+ async logToolResult(toolName, result, context) {
183
+ if (this.shouldLog('info')) {
184
+ const timestamp = this.includeTimestamp ? new Date().toISOString() : '';
185
+ const contextSummary = this.summarizeContext(context);
186
+ const formattedResult = await this.formatToolResult(result);
187
+ console.log(`${timestamp} [INFO] Tool executed: ${toolName}\n` +
188
+ `${timestamp} [INFO] Tool result: ${formattedResult}${contextSummary}`);
189
+ }
190
+ }
191
+ }
192
+
1
193
  class WorkflowImpl {
2
- constructor(id, name, description, nodes = [], variables = new Map(), llmProvider) {
194
+ constructor(id, name, description, nodes = [], variables = new Map(), llmProvider, loggerOptions) {
3
195
  this.id = id;
4
196
  this.name = name;
5
197
  this.description = description;
6
198
  this.nodes = nodes;
7
199
  this.variables = variables;
8
200
  this.llmProvider = llmProvider;
201
+ this.abortControllers = new Map();
202
+ if (loggerOptions) {
203
+ this.logger = new ExecutionLogger(loggerOptions);
204
+ }
205
+ }
206
+ setLogger(logger) {
207
+ this.logger = logger;
208
+ }
209
+ async cancel() {
210
+ this.abort = true;
211
+ for (const controller of this.abortControllers.values()) {
212
+ controller.abort("Workflow cancelled");
213
+ }
9
214
  }
10
215
  async execute(callback) {
11
216
  var _a, _b, _c, _d;
@@ -28,37 +233,49 @@ class WorkflowImpl {
28
233
  throw new Error(`Circular dependency detected at node: ${nodeId}`);
29
234
  }
30
235
  const node = this.getNode(nodeId);
236
+ const abortController = new AbortController();
237
+ this.abortControllers.set(nodeId, abortController);
31
238
  // Execute the node's action
32
239
  const context = {
33
240
  __skip: false,
34
241
  __abort: false,
242
+ workflow: this,
35
243
  variables: this.variables,
36
244
  llmProvider: this.llmProvider,
37
245
  tools: new Map(node.action.tools.map(tool => [tool.name, tool])),
38
246
  callback,
247
+ logger: this.logger,
39
248
  next: () => context.__skip = true,
40
- abortAll: () => this.abort = context.__abort = true,
249
+ abortAll: () => {
250
+ this.abort = context.__abort = true;
251
+ // Abort all running tasks
252
+ for (const controller of this.abortControllers.values()) {
253
+ controller.abort("Workflow cancelled");
254
+ }
255
+ },
256
+ signal: abortController.signal
41
257
  };
42
- callback && await ((_b = (_a = callback.hooks).beforeSubtask) === null || _b === void 0 ? void 0 : _b.call(_a, node, context));
43
- if (context.__abort) {
44
- throw new Error("Abort");
45
- }
46
- else if (context.__skip) {
47
- return;
48
- }
49
258
  executing.add(nodeId);
50
259
  // Execute dependencies first
51
260
  for (const depId of node.dependencies) {
52
261
  await executeNode(depId);
53
262
  }
54
263
  // Prepare input by gathering outputs from dependencies
55
- const input = {};
264
+ const input = { items: [] };
56
265
  for (const depId of node.dependencies) {
57
266
  const depNode = this.getNode(depId);
58
- input[depId] = depNode.output.value;
267
+ input.items.push(depNode.output);
268
+ }
269
+ node.input = input;
270
+ // Run pre-execution hooks and execute action
271
+ callback && await ((_b = (_a = callback.hooks).beforeSubtask) === null || _b === void 0 ? void 0 : _b.call(_a, node, context));
272
+ if (context.__abort) {
273
+ throw new Error("Abort");
59
274
  }
60
- node.input.value = input;
61
- node.output.value = await node.action.execute(node.input.value, context);
275
+ else if (context.__skip) {
276
+ return;
277
+ }
278
+ node.output.value = await node.action.execute(node.input, node.output, context);
62
279
  executing.delete(nodeId);
63
280
  executed.add(nodeId);
64
281
  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));
@@ -67,6 +284,7 @@ class WorkflowImpl {
67
284
  const terminalNodes = this.nodes.filter(node => !this.nodes.some(n => n.dependencies.includes(node.id)));
68
285
  await Promise.all(terminalNodes.map(node => executeNode(node.id)));
69
286
  callback && await ((_d = (_c = callback.hooks).afterWorkflow) === null || _d === void 0 ? void 0 : _d.call(_c, this, this.variables));
287
+ return terminalNodes.map(node => node.output);
70
288
  }
71
289
  addNode(node) {
72
290
  if (this.nodes.some(n => n.id === node.id)) {
@@ -125,7 +343,7 @@ class WorkflowImpl {
125
343
  class WriteContextTool {
126
344
  constructor() {
127
345
  this.name = 'write_context';
128
- this.description = 'Write a value to the workflow context. Use this to store intermediate results or outputs.';
346
+ 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.';
129
347
  this.input_schema = {
130
348
  type: 'object',
131
349
  properties: {
@@ -155,25 +373,32 @@ class WriteContextTool {
155
373
  return { success: true, key, value };
156
374
  }
157
375
  }
158
- function createReturnTool(outputSchema) {
376
+ function createReturnTool(actionName, outputDescription, outputSchema) {
159
377
  return {
160
378
  name: 'return_output',
161
- description: 'Return the final output of this action. Use this to return a value matching the required output schema.',
379
+ 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:
380
+ ${outputDescription}
381
+
382
+ 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.
383
+ `,
162
384
  input_schema: {
163
385
  type: 'object',
164
386
  properties: {
387
+ use_tool_result: {
388
+ type: ['boolean'],
389
+ description: `Whether to use the latest tool result as output. When set to true, the 'value' parameter is ignored.`,
390
+ },
165
391
  value: outputSchema || {
166
392
  // Default to accepting any JSON value
167
393
  type: ['string', 'number', 'boolean', 'object', 'null'],
168
- description: 'The output value',
394
+ 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.',
169
395
  },
170
396
  },
171
- required: ['value'],
397
+ required: ['use_tool_result', 'value'],
172
398
  },
173
399
  async execute(context, params) {
174
- const { value } = params;
175
- context.variables.set('__action_output', value);
176
- return { returned: value };
400
+ context.variables.set(`__action_${actionName}_output`, params);
401
+ return { success: true };
177
402
  },
178
403
  };
179
404
  }
@@ -187,6 +412,8 @@ class ActionImpl {
187
412
  this.llmProvider = llmProvider;
188
413
  this.llmConfig = llmConfig;
189
414
  this.maxRounds = 10; // Default max rounds
415
+ this.toolResults = new Map();
416
+ this.logger = new ExecutionLogger();
190
417
  this.writeContextTool = new WriteContextTool();
191
418
  this.tools = [...tools, this.writeContextTool];
192
419
  if (config === null || config === void 0 ? void 0 : config.maxRounds) {
@@ -194,6 +421,7 @@ class ActionImpl {
194
421
  }
195
422
  }
196
423
  async executeSingleRound(messages, params, toolMap, context) {
424
+ this.logger = context.logger;
197
425
  const roundMessages = [];
198
426
  let hasToolUse = false;
199
427
  let response = null;
@@ -203,6 +431,12 @@ class ActionImpl {
203
431
  let toolResultMessage = null;
204
432
  // Track tool execution promise
205
433
  let toolExecutionPromise = null;
434
+ // Listen for abort signal
435
+ if (context.signal) {
436
+ context.signal.addEventListener('abort', () => {
437
+ context.__abort = true;
438
+ });
439
+ }
206
440
  const handler = {
207
441
  onContent: (content) => {
208
442
  if (content.trim()) {
@@ -210,7 +444,8 @@ class ActionImpl {
210
444
  }
211
445
  },
212
446
  onToolUse: async (toolCall) => {
213
- console.log('Tool Call:', toolCall.name, toolCall.input);
447
+ this.logger.log('info', `Assistant: ${assistantTextMessage}`);
448
+ this.logger.logToolExecution(toolCall.name, toolCall.input, context);
214
449
  hasToolUse = true;
215
450
  const tool = toolMap.get(toolCall.name);
216
451
  if (!tool) {
@@ -229,6 +464,7 @@ class ActionImpl {
229
464
  };
230
465
  // Store the promise of tool execution
231
466
  toolExecutionPromise = (async () => {
467
+ var _a;
232
468
  try {
233
469
  // beforeToolUse
234
470
  context.__skip = false;
@@ -238,7 +474,7 @@ class ActionImpl {
238
474
  toolCall.input = modified_input;
239
475
  }
240
476
  }
241
- if (context.__skip || context.__abort) {
477
+ if (context.__skip || context.__abort || ((_a = context.signal) === null || _a === void 0 ? void 0 : _a.aborted)) {
242
478
  toolResultMessage = {
243
479
  role: 'user',
244
480
  content: [
@@ -260,31 +496,42 @@ class ActionImpl {
260
496
  result = modified_result;
261
497
  }
262
498
  }
499
+ const result_has_image = result && "image" in result;
500
+ const resultContent = result_has_image
501
+ ? {
502
+ type: 'tool_result',
503
+ tool_use_id: toolCall.id,
504
+ content: result.text
505
+ ? [
506
+ { type: 'image', source: result.image },
507
+ { type: 'text', text: result.text },
508
+ ]
509
+ : [{ type: 'image', source: result.image }],
510
+ }
511
+ : {
512
+ type: 'tool_result',
513
+ tool_use_id: toolCall.id,
514
+ content: [{ type: 'text', text: JSON.stringify(result) }],
515
+ };
516
+ const resultContentText = result_has_image
517
+ ? result.text
518
+ ? result.text + ' [Image]'
519
+ : '[Image]'
520
+ : JSON.stringify(result);
263
521
  const resultMessage = {
264
522
  role: 'user',
265
- content: [
266
- result.image && result.image.type
267
- ? {
268
- type: 'tool_result',
269
- tool_use_id: toolCall.id,
270
- content: result.text
271
- ? [
272
- { type: 'image', source: result.image },
273
- { type: 'text', text: result.text },
274
- ]
275
- : [{ type: 'image', source: result.image }],
276
- }
277
- : {
278
- type: 'tool_result',
279
- tool_use_id: toolCall.id,
280
- content: [{ type: 'text', text: JSON.stringify(result) }],
281
- },
282
- ],
523
+ content: [resultContent],
283
524
  };
284
525
  toolResultMessage = resultMessage;
285
- console.log('Tool Result:', result);
526
+ this.logger.logToolResult(tool.name, result, context);
527
+ // Store tool results except for the return_output tool
528
+ if (tool.name !== 'return_output') {
529
+ this.toolResults.set(toolCall.id, resultContentText);
530
+ }
286
531
  }
287
532
  catch (err) {
533
+ console.log("An error occurred when calling tool:");
534
+ console.log(err);
288
535
  const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
289
536
  const errorResult = {
290
537
  role: 'user',
@@ -298,7 +545,7 @@ class ActionImpl {
298
545
  ],
299
546
  };
300
547
  toolResultMessage = errorResult;
301
- console.error('Tool Error:', err);
548
+ this.logger.logError(err, context);
302
549
  }
303
550
  })();
304
551
  },
@@ -307,16 +554,21 @@ class ActionImpl {
307
554
  },
308
555
  onError: (error) => {
309
556
  console.error('Stream Error:', error);
557
+ console.log('Last message array sent to LLM:', JSON.stringify(messages, null, 2));
310
558
  },
311
559
  };
560
+ this.handleHistoryImageMessages(messages);
312
561
  // Wait for stream to complete
562
+ if (!this.llmProvider) {
563
+ throw new Error('LLM provider not set');
564
+ }
313
565
  await this.llmProvider.generateStream(messages, params, handler);
314
566
  // Wait for tool execution to complete if it was started
315
567
  if (toolExecutionPromise) {
316
568
  await toolExecutionPromise;
317
569
  }
318
570
  if (context.__abort) {
319
- throw new Error("Abort");
571
+ throw new Error('Abort');
320
572
  }
321
573
  // Add messages in the correct order after everything is complete
322
574
  if (assistantTextMessage) {
@@ -330,10 +582,59 @@ class ActionImpl {
330
582
  }
331
583
  return { response, hasToolUse, roundMessages };
332
584
  }
333
- async execute(input, context, outputSchema) {
334
- var _a;
585
+ handleHistoryImageMessages(messages) {
586
+ // Remove all images from historical tool results except the most recent user message
587
+ const initialImageCount = this.countImages(messages);
588
+ let foundFirstUser = false;
589
+ for (let i = messages.length - 1; i >= 0; i--) {
590
+ const message = messages[i];
591
+ if (message.role === 'user') {
592
+ if (!foundFirstUser) {
593
+ foundFirstUser = true;
594
+ continue;
595
+ }
596
+ if (Array.isArray(message.content)) {
597
+ // Directly modify the message content array
598
+ message.content = message.content.map((item) => {
599
+ if (item.type === 'tool_result' && Array.isArray(item.content)) {
600
+ // Create a new content array without images
601
+ if (item.content.length > 0) {
602
+ item.content = item.content.filter((c) => c.type !== 'image');
603
+ // If all content was images and got filtered out, replace with ok message
604
+ if (item.content.length === 0) {
605
+ item.content = [{ type: 'text', text: 'ok' }];
606
+ }
607
+ }
608
+ }
609
+ return item;
610
+ });
611
+ }
612
+ }
613
+ }
614
+ const finalImageCount = this.countImages(messages);
615
+ if (initialImageCount !== finalImageCount) {
616
+ this.logger.log("info", `Removed ${initialImageCount - finalImageCount} images from history`);
617
+ }
618
+ }
619
+ countImages(messages) {
620
+ let count = 0;
621
+ messages.forEach(msg => {
622
+ if (Array.isArray(msg.content)) {
623
+ msg.content.forEach((item) => {
624
+ if (item.type === 'tool_result' && Array.isArray(item.content)) {
625
+ count += item.content.filter((c) => c.type === 'image').length;
626
+ }
627
+ });
628
+ }
629
+ });
630
+ return count;
631
+ }
632
+ async execute(input, output, context, outputSchema) {
633
+ var _a, _b, _c, _d, _e;
634
+ this.logger = context.logger;
635
+ console.log(`Executing action started: ${this.name}`);
335
636
  // Create return tool with output schema
336
- const returnTool = createReturnTool(outputSchema);
637
+ const returnTool = createReturnTool(this.name, output.description, outputSchema);
337
638
  // Create tool map combining context tools, action tools, and return tool
338
639
  const toolMap = new Map();
339
640
  this.tools.forEach((tool) => toolMap.set(tool.name, tool));
@@ -344,9 +645,7 @@ class ActionImpl {
344
645
  { role: 'system', content: this.formatSystemPrompt() },
345
646
  { role: 'user', content: this.formatUserPrompt(context, input) },
346
647
  ];
347
- console.log('Starting LLM conversation...');
348
- console.log('Initial messages:', messages);
349
- console.log('Output schema:', outputSchema);
648
+ this.logger.logActionStart(this.name, input, context);
350
649
  // Configure tool parameters
351
650
  const params = {
352
651
  ...this.llmConfig,
@@ -358,17 +657,24 @@ class ActionImpl {
358
657
  };
359
658
  let roundCount = 0;
360
659
  while (roundCount < this.maxRounds) {
660
+ // Check for abort signal
661
+ if ((_b = context.signal) === null || _b === void 0 ? void 0 : _b.aborted) {
662
+ throw new Error('Workflow cancelled');
663
+ }
361
664
  roundCount++;
362
- console.log(`Starting round ${roundCount} of ${this.maxRounds}`);
363
- console.log('Current conversation status:', JSON.stringify(messages, null, 2));
665
+ this.logger.log('info', `Starting round ${roundCount} of ${this.maxRounds}`, context);
364
666
  const { response, hasToolUse, roundMessages } = await this.executeSingleRound(messages, params, toolMap, context);
667
+ if (response === null || response === void 0 ? void 0 : response.textContent) {
668
+ (_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);
669
+ }
365
670
  // Add round messages to conversation history
366
671
  messages.push(...roundMessages);
672
+ this.logger.log('debug', `Round ${roundCount} messages: ${JSON.stringify(roundMessages)}`, context);
367
673
  // Check termination conditions
368
674
  if (!hasToolUse && response) {
369
675
  // LLM sent a message without using tools - request explicit return
370
- console.log('No tool use detected, requesting explicit return');
371
- console.log('Response:', response);
676
+ this.logger.log('info', `Assistant: ${response.textContent}`);
677
+ this.logger.log('warn', 'LLM sent a message without using tools; requesting explicit return');
372
678
  const returnOnlyParams = {
373
679
  ...params,
374
680
  tools: [
@@ -388,12 +694,11 @@ class ActionImpl {
388
694
  break;
389
695
  }
390
696
  if (response === null || response === void 0 ? void 0 : response.toolCalls.some((call) => call.name === 'return_output')) {
391
- console.log('Task completed with return_output tool');
392
697
  break;
393
698
  }
394
699
  // If this is the last round, force an explicit return
395
700
  if (roundCount === this.maxRounds) {
396
- console.log('Max rounds reached, requesting explicit return');
701
+ this.logger.log('warn', 'Max rounds reached, requesting explicit return');
397
702
  const returnOnlyParams = {
398
703
  ...params,
399
704
  tools: [
@@ -413,33 +718,50 @@ class ActionImpl {
413
718
  }
414
719
  }
415
720
  // Get and clean up output value
416
- const output = context.variables.get('__action_output');
417
- context.variables.delete('__action_output');
418
- if (output === undefined) {
721
+ const outputKey = `__action_${this.name}_output`;
722
+ const outputParams = context.variables.get(outputKey);
723
+ context.variables.delete(outputKey);
724
+ // Get output value, first checking for use_tool_result
725
+ const outputValue = outputParams.use_tool_result
726
+ ? Array.from(this.toolResults.values()).pop()
727
+ : outputParams === null || outputParams === void 0 ? void 0 : outputParams.value;
728
+ if (outputValue === undefined) {
419
729
  console.warn('Action completed without returning a value');
420
730
  return {};
421
731
  }
422
- return output;
732
+ return outputValue;
423
733
  }
424
734
  formatSystemPrompt() {
425
- 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.
735
+ 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.
426
736
 
427
737
  Remember to:
428
738
  1. Use tools when needed to accomplish the task
429
- 2. Store important results using write_context, including intermediate and final results
430
- 3. Think step by step about what needs to be done`;
739
+ 2. Think step by step about what needs to be done
740
+ 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
741
+ 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
742
+ 5. If there are any unclear points during the task execution, please use the human-related tool to inquire with the user
743
+ 6. If user intervention is required during the task execution, please use the human-related tool to transfer the operation rights to the user
744
+ `;
431
745
  }
432
746
  formatUserPrompt(context, input) {
433
- // Create a description of the current context
434
- const contextDescription = Array.from(context.variables.entries())
747
+ var _a;
748
+ const workflowDescription = ((_a = context.workflow) === null || _a === void 0 ? void 0 : _a.description) || null;
749
+ const actionDescription = `${this.name} -- ${this.description}`;
750
+ const inputDescription = JSON.stringify(input, null, 2) || null;
751
+ const contextVariables = Array.from(context.variables.entries())
435
752
  .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
436
753
  .join('\n');
437
- return `You are executing the action "${this.name}". The specific instructions are: "${this.description}". You have access to the following context:
754
+ return `You are executing a subtask in the workflow. The workflow description is as follows:
755
+ ${workflowDescription}
438
756
 
439
- ${contextDescription || 'No context variables set'}
757
+ The subtask description is as follows:
758
+ ${actionDescription}
440
759
 
441
- You have been provided with the following input:
442
- ${(typeof input === 'string' ? input : JSON.stringify(input, null, 2)) || 'No additional input provided'}
760
+ The input to the subtask is as follows:
761
+ ${inputDescription}
762
+
763
+ There are some variables stored in the context that you can use for reference:
764
+ ${contextVariables}
443
765
  `;
444
766
  }
445
767
  // Static factory method
@@ -469,7 +791,8 @@ Generate a complete workflow that:
469
791
  2. Properly sequences tool usage based on dependencies
470
792
  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
471
793
  4. Creates a clear, logical flow to accomplish the user's goal
472
- 5. Includes detailed descriptions for each action, ensuring that the actions, when combined, is a complete solution to the user's problem`;
794
+ 5. Includes detailed descriptions for each action, ensuring that the actions, when combined, is a complete solution to the user's problem
795
+ 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`;
473
796
  },
474
797
  formatUserPrompt: (requirement) => `Create a workflow for the following requirement: ${requirement}`,
475
798
  modifyUserPrompt: (prompt) => `Modify workflow: ${prompt}`,
@@ -610,6 +933,19 @@ class WorkflowGenerator {
610
933
  ],
611
934
  });
612
935
  const workflowData = response.toolCalls[0].input.workflow;
936
+ // Forcibly add special tools
937
+ const specialTools = [
938
+ "cancel_workflow",
939
+ "human_input_text",
940
+ "human_operate",
941
+ ];
942
+ for (const node of workflowData.nodes) {
943
+ for (const tool of specialTools) {
944
+ if (!node.action.tools.includes(tool)) {
945
+ node.action.tools.push(tool);
946
+ }
947
+ }
948
+ }
613
949
  // Validate all tools exist
614
950
  for (const node of workflowData.nodes) {
615
951
  if (!this.toolRegistry.hasTools(node.action.tools)) {
@@ -620,15 +956,22 @@ class WorkflowGenerator {
620
956
  if (!workflowData.id) {
621
957
  workflowData.id = v4();
622
958
  }
959
+ // debug
960
+ console.log("Debug the workflow...");
961
+ console.log(workflowData);
962
+ console.log("Debug the workflow...Done");
623
963
  return this.createWorkflowFromData(workflowData);
624
964
  }
625
965
  createWorkflowFromData(data) {
626
- const workflow = new WorkflowImpl(data.id, data.name, data.description || '', [], new Map(Object.entries(data.variables || {})), this.llmProvider);
966
+ const workflow = new WorkflowImpl(data.id, data.name, data.description || '', [], new Map(Object.entries(data.variables || {})), this.llmProvider, {
967
+ logLevel: 'info',
968
+ includeTimestamp: true,
969
+ });
627
970
  // Add nodes to workflow
628
971
  if (Array.isArray(data.nodes)) {
629
972
  data.nodes.forEach((nodeData) => {
630
973
  const tools = nodeData.action.tools.map((toolName) => this.toolRegistry.getTool(toolName));
631
- const action = ActionImpl.createPromptAction(nodeData.action.name, nodeData.action.description, tools, this.llmProvider, { maxTokens: 1000 });
974
+ const action = ActionImpl.createPromptAction(nodeData.action.name, nodeData.action.description, tools, this.llmProvider, { maxTokens: 8192 });
632
975
  const node = {
633
976
  id: nodeData.id,
634
977
  name: nodeData.name || nodeData.id,
@@ -3372,8 +3715,13 @@ class ClaudeProvider {
3372
3715
  ...options,
3373
3716
  });
3374
3717
  }
3718
+ else if (param.messages && param.completions) {
3719
+ this.client = param;
3720
+ }
3375
3721
  else {
3376
- this.client = new Anthropic(param);
3722
+ let options = param;
3723
+ options.dangerouslyAllowBrowser = true;
3724
+ this.client = new Anthropic(options);
3377
3725
  }
3378
3726
  }
3379
3727
  processResponse(response) {
@@ -8792,8 +9140,13 @@ class OpenaiProvider {
8792
9140
  ...options,
8793
9141
  });
8794
9142
  }
9143
+ else if (param.chat && param.chat.completions) {
9144
+ this.client = param;
9145
+ }
8795
9146
  else {
8796
- this.client = new OpenAI(param);
9147
+ let options = param;
9148
+ options.dangerouslyAllowBrowser = true;
9149
+ this.client = new OpenAI(options);
8797
9150
  }
8798
9151
  }
8799
9152
  buildParams(messages, params, stream) {
@@ -8872,13 +9225,61 @@ class OpenaiProvider {
8872
9225
  content: content.text,
8873
9226
  });
8874
9227
  }
8875
- else if (content.type == 'tool_result') {
9228
+ else if (content.type == 'image') {
8876
9229
  _messages.push({
8877
- role: 'tool',
8878
- content: content.content,
8879
- tool_call_id: content.tool_call_id || content.tool_use_id,
9230
+ role: 'user',
9231
+ content: [
9232
+ {
9233
+ type: 'image_url',
9234
+ image_url: {
9235
+ url: `data:${content.source.media_type};base64,${content.source.data}`,
9236
+ },
9237
+ },
9238
+ ],
8880
9239
  });
8881
9240
  }
9241
+ else if (content.type == 'tool_result') {
9242
+ let _content = [];
9243
+ if (content.content == 'string') {
9244
+ _content.push({ type: 'text', text: content.content });
9245
+ }
9246
+ else {
9247
+ for (let k = 0; k < content.content.length; k++) {
9248
+ let item = content.content[k];
9249
+ if (item.type == 'text') {
9250
+ _content.push({ ...item });
9251
+ }
9252
+ else if (item.type == 'image') {
9253
+ _content.push({
9254
+ type: 'image_url',
9255
+ image_url: {
9256
+ url: `data:${item.source.media_type};base64,${item.source.data}`,
9257
+ },
9258
+ });
9259
+ }
9260
+ }
9261
+ }
9262
+ let hasImage = _content.filter((s) => s.type == 'image_url').length > 0;
9263
+ if (hasImage) {
9264
+ // OpenAI does not support images returned by the tool.
9265
+ _messages.push({
9266
+ role: 'tool',
9267
+ content: 'ok',
9268
+ tool_call_id: content.tool_call_id || content.tool_use_id,
9269
+ });
9270
+ _messages.push({
9271
+ role: 'user',
9272
+ content: _content,
9273
+ });
9274
+ }
9275
+ else {
9276
+ _messages.push({
9277
+ role: 'tool',
9278
+ content: _content,
9279
+ tool_call_id: content.tool_call_id || content.tool_use_id,
9280
+ });
9281
+ }
9282
+ }
8882
9283
  }
8883
9284
  }
8884
9285
  else {
@@ -9056,18 +9457,11 @@ const workflowSchema = {
9056
9457
  type: "array",
9057
9458
  items: { type: "string" },
9058
9459
  },
9059
- input: {
9060
- type: "object",
9061
- properties: {
9062
- type: { type: "string" },
9063
- schema: { type: "object" },
9064
- },
9065
- },
9066
9460
  output: {
9067
9461
  type: "object",
9068
9462
  properties: {
9069
- type: { type: "string" },
9070
- schema: { type: "object" },
9463
+ name: { type: "string" },
9464
+ description: { type: "string" },
9071
9465
  },
9072
9466
  },
9073
9467
  action: {
@@ -9196,8 +9590,27 @@ class Eko {
9196
9590
  return workflow;
9197
9591
  }
9198
9592
  async execute(workflow, callback) {
9593
+ // Inject LLM provider at workflow level
9594
+ workflow.llmProvider = this.llmProvider;
9595
+ // Process each node's action
9596
+ for (const node of workflow.nodes) {
9597
+ if (node.action.type === 'prompt') {
9598
+ // Inject LLM provider
9599
+ node.action.llmProvider = this.llmProvider;
9600
+ // Resolve tools
9601
+ node.action.tools = node.action.tools.map(tool => {
9602
+ if (typeof tool === 'string') {
9603
+ return this.toolRegistry.getTool(tool);
9604
+ }
9605
+ return tool;
9606
+ });
9607
+ }
9608
+ }
9199
9609
  return await workflow.execute(callback);
9200
9610
  }
9611
+ async cancel(workflow) {
9612
+ return await workflow.cancel();
9613
+ }
9201
9614
  async modify(workflow, prompt) {
9202
9615
  const generator = this.workflowGeneratorMap.get(workflow);
9203
9616
  workflow = await generator.modifyWorkflow(prompt);
@@ -9381,31 +9794,30 @@ class WorkflowParser {
9381
9794
  errors,
9382
9795
  };
9383
9796
  }
9384
- /**
9385
- * Convert parsed JSON to runtime Workflow object
9386
- */
9387
9797
  static toRuntime(json) {
9388
9798
  const variables = new Map(Object.entries(json.variables || {}));
9389
- const workflow = new WorkflowImpl(json.id, json.name, json.description, [], variables);
9799
+ const workflow = new WorkflowImpl(json.id, json.name, json.description, [], variables, undefined, {
9800
+ logLevel: 'info',
9801
+ includeTimestamp: true,
9802
+ });
9390
9803
  // Convert nodes
9391
9804
  json.nodes.forEach((nodeJson) => {
9805
+ const action = ActionImpl.createPromptAction(nodeJson.action.name, nodeJson.action.description,
9806
+ // Pass tool names as strings, they'll be resolved at execution time
9807
+ nodeJson.action.tools || [], undefined, // LLM provider will be injected at execution time
9808
+ { maxTokens: 1000 });
9392
9809
  const node = {
9393
9810
  id: nodeJson.id,
9394
9811
  name: nodeJson.name || nodeJson.id,
9395
9812
  description: nodeJson.description,
9396
9813
  dependencies: nodeJson.dependencies || [],
9397
- input: this.convertIO(nodeJson.input),
9398
- output: this.convertIO(nodeJson.output),
9399
- action: {
9400
- type: nodeJson.action.type,
9401
- name: nodeJson.action.name,
9402
- description: nodeJson.action.description,
9403
- tools: nodeJson.action.tools || [],
9404
- execute: async (input, context) => {
9405
- // Default implementation - should be overridden by specific action types
9406
- return input;
9407
- },
9814
+ input: { items: [] },
9815
+ output: nodeJson.output || {
9816
+ name: `${nodeJson.name || nodeJson.id}_output`,
9817
+ description: `Output of node ${nodeJson.name || nodeJson.id}`,
9818
+ value: null,
9408
9819
  },
9820
+ action: action,
9409
9821
  };
9410
9822
  workflow.addNode(node);
9411
9823
  });
@@ -9425,28 +9837,19 @@ class WorkflowParser {
9425
9837
  name: node.name,
9426
9838
  description: node.description,
9427
9839
  dependencies: node.dependencies,
9428
- input: node.input,
9429
9840
  output: node.output,
9430
9841
  action: {
9431
9842
  type: node.action.type,
9432
9843
  name: node.action.name,
9433
9844
  description: node.action.description,
9434
- tools: node.action.tools,
9845
+ tools: node.action.tools
9846
+ .map((tool) => (typeof tool === 'string' ? tool : tool.name))
9847
+ .filter((tool) => tool !== 'write_context'),
9435
9848
  },
9436
9849
  })),
9437
9850
  variables: Object.fromEntries(workflow.variables),
9438
9851
  };
9439
9852
  }
9440
- /**
9441
- * Helper to convert IO definitions
9442
- */
9443
- static convertIO(io) {
9444
- return {
9445
- type: (io === null || io === void 0 ? void 0 : io.type) || 'object',
9446
- schema: (io === null || io === void 0 ? void 0 : io.schema) || {},
9447
- value: null,
9448
- };
9449
- }
9450
9853
  }
9451
9854
 
9452
- export { ClaudeProvider, Eko, OpenaiProvider, ToolRegistry, WorkflowGenerator, WorkflowParser, Eko as default };
9855
+ export { ClaudeProvider, Eko, ExecutionLogger, OpenaiProvider, ToolRegistry, WorkflowGenerator, WorkflowParser, Eko as default };