@eko-ai/eko 4.0.0 → 4.0.1

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.esm.js CHANGED
@@ -31458,7 +31458,7 @@ class Planner {
31458
31458
  }
31459
31459
  }
31460
31460
  if (this.callback) {
31461
- let workflow = parseWorkflow(this.taskId, streamText, false, thinkingText);
31461
+ const workflow = parseWorkflow(this.taskId, streamText, false, thinkingText);
31462
31462
  if (workflow) {
31463
31463
  await this.callback.onMessage({
31464
31464
  streamType: "agent",
@@ -31491,7 +31491,14 @@ class Planner {
31491
31491
  chain.planRequest = request;
31492
31492
  chain.planResult = streamText;
31493
31493
  }
31494
- let workflow = parseWorkflow(this.taskId, streamText, true, thinkingText);
31494
+ const workflow = parseWorkflow(this.taskId, streamText, true, thinkingText);
31495
+ if (workflow.taskPrompt) {
31496
+ workflow.taskPrompt += "\n" + taskPrompt;
31497
+ }
31498
+ else {
31499
+ workflow.taskPrompt = taskPrompt;
31500
+ }
31501
+ workflow.taskPrompt = workflow.taskPrompt.trim();
31495
31502
  if (this.callback) {
31496
31503
  await this.callback.onMessage({
31497
31504
  streamType: "agent",
@@ -31503,13 +31510,6 @@ class Planner {
31503
31510
  workflow: workflow,
31504
31511
  });
31505
31512
  }
31506
- if (workflow.taskPrompt) {
31507
- workflow.taskPrompt += "\n" + taskPrompt;
31508
- }
31509
- else {
31510
- workflow.taskPrompt = taskPrompt;
31511
- }
31512
- workflow.taskPrompt = workflow.taskPrompt.trim();
31513
31513
  return workflow;
31514
31514
  }
31515
31515
  }
@@ -35836,1487 +35836,1487 @@ class EkoMemory {
35836
35836
  }
35837
35837
  }
35838
35838
 
35839
- async function callChatLLM(messageId, chatContext, rlm, messages, tools, toolChoice, retryNum = 0, callback, signal) {
35840
- const streamCallback = callback?.chatCallback || {
35841
- onMessage: async () => { },
35842
- };
35843
- const request = {
35844
- tools: tools,
35845
- toolChoice,
35846
- messages: messages,
35847
- abortSignal: signal,
35848
- };
35849
- let streamText = "";
35850
- let thinkText = "";
35851
- let toolArgsText = "";
35852
- let textStreamId = uuidv4();
35853
- let thinkStreamId = uuidv4();
35854
- let textStreamDone = false;
35855
- const toolParts = [];
35856
- let reader = null;
35857
- try {
35858
- const result = await rlm.callStream(request);
35859
- reader = result.stream.getReader();
35860
- let toolPart = null;
35861
- while (true) {
35862
- const { done, value } = await reader.read();
35863
- if (done) {
35864
- break;
35865
- }
35866
- const chunk = value;
35867
- switch (chunk.type) {
35868
- case "text-start": {
35869
- textStreamId = uuidv4();
35870
- break;
35871
- }
35872
- case "text-delta": {
35873
- if (toolPart && !chunk.delta) {
35874
- continue;
35839
+ class SimpleSseMcpClient {
35840
+ constructor(sseServerUrl, clientName = "EkoMcpClient", headers = {}) {
35841
+ this.protocolVersion = "2024-11-05";
35842
+ this.sseUrl = sseServerUrl;
35843
+ this.clientName = clientName;
35844
+ this.headers = headers;
35845
+ this.requestMap = new Map();
35846
+ }
35847
+ async connect(signal) {
35848
+ Log.info("MCP Client, connecting...", this.sseUrl);
35849
+ if (this.sseHandler && this.sseHandler.readyState == 1) {
35850
+ this.sseHandler.close && this.sseHandler.close();
35851
+ this.sseHandler = undefined;
35852
+ }
35853
+ this.pingTimer && clearInterval(this.pingTimer);
35854
+ this.reconnectTimer && clearTimeout(this.reconnectTimer);
35855
+ await new Promise((resolve) => {
35856
+ const timer = setTimeout(resolve, 15000);
35857
+ this.sseHandler = {
35858
+ onopen: () => {
35859
+ Log.info("MCP Client, connection successful", this.sseUrl);
35860
+ clearTimeout(timer);
35861
+ setTimeout(resolve, 200);
35862
+ },
35863
+ onmessage: (data) => this.onmessage(data),
35864
+ onerror: (e) => {
35865
+ Log.error("MCP Client, error: ", e);
35866
+ clearTimeout(timer);
35867
+ if (this.sseHandler?.readyState === 2) {
35868
+ this.pingTimer && clearInterval(this.pingTimer);
35869
+ this.reconnectTimer = setTimeout(() => {
35870
+ this.connect();
35871
+ }, 500);
35875
35872
  }
35876
- streamText += chunk.delta || "";
35877
- await streamCallback.onMessage({
35878
- streamType: "chat",
35879
- chatId: chatContext.getChatId(),
35880
- messageId,
35881
- type: "text",
35882
- streamId: textStreamId,
35883
- streamDone: false,
35884
- text: streamText,
35873
+ resolve();
35874
+ },
35875
+ };
35876
+ connectSse(this.sseUrl, this.sseHandler, this.headers, signal);
35877
+ });
35878
+ this.pingTimer = setInterval(() => this.ping(), 10000);
35879
+ }
35880
+ onmessage(data) {
35881
+ Log.debug("MCP Client, onmessage", this.sseUrl, data);
35882
+ if (data.event == "endpoint") {
35883
+ let uri = data.data;
35884
+ let msgUrl;
35885
+ let idx = this.sseUrl.indexOf("/", 10);
35886
+ if (idx > -1) {
35887
+ msgUrl = this.sseUrl.substring(0, idx) + uri;
35888
+ }
35889
+ else {
35890
+ msgUrl = this.sseUrl + uri;
35891
+ }
35892
+ this.msgUrl = msgUrl;
35893
+ this.initialize();
35894
+ }
35895
+ else if (data.event == "message") {
35896
+ let message = JSON.parse(data.data);
35897
+ let _resolve = this.requestMap.get(message.id);
35898
+ _resolve && _resolve(message);
35899
+ }
35900
+ }
35901
+ async initialize() {
35902
+ await this.request("initialize", {
35903
+ protocolVersion: this.protocolVersion,
35904
+ capabilities: {
35905
+ tools: {
35906
+ listChanged: true,
35907
+ },
35908
+ sampling: {},
35909
+ },
35910
+ clientInfo: {
35911
+ name: this.clientName,
35912
+ version: "1.0.0",
35913
+ },
35914
+ });
35915
+ try {
35916
+ await this.request("notifications/initialized", {});
35917
+ }
35918
+ catch (ignored) { }
35919
+ }
35920
+ ping() {
35921
+ this.request("ping", {});
35922
+ }
35923
+ async listTools(param, signal) {
35924
+ const message = await this.request("tools/list", {
35925
+ ...param,
35926
+ }, signal);
35927
+ return message.result.tools || [];
35928
+ }
35929
+ async callTool(param, signal) {
35930
+ const message = await this.request("tools/call", {
35931
+ ...param,
35932
+ }, signal);
35933
+ return message.result;
35934
+ }
35935
+ async request(method, params, signal) {
35936
+ const id = method.startsWith("notifications/") ? undefined : uuidv4();
35937
+ try {
35938
+ const callback = new Promise((resolve, reject) => {
35939
+ if (signal) {
35940
+ signal.addEventListener("abort", () => {
35941
+ const error = new Error("Operation was interrupted");
35942
+ error.name = "AbortError";
35943
+ reject(error);
35885
35944
  });
35886
- if (toolPart) {
35887
- await streamCallback.onMessage({
35888
- streamType: "chat",
35889
- chatId: chatContext.getChatId(),
35890
- messageId,
35891
- type: "tool_use",
35892
- toolCallId: toolPart.toolCallId,
35893
- toolName: toolPart.toolName,
35894
- params: toolPart.input || {},
35895
- });
35896
- toolPart = null;
35897
- }
35898
- break;
35899
- }
35900
- case "text-end": {
35901
- textStreamDone = true;
35902
- if (streamText) {
35903
- await streamCallback.onMessage({
35904
- streamType: "chat",
35905
- chatId: chatContext.getChatId(),
35906
- messageId,
35907
- type: "text",
35908
- streamId: textStreamId,
35909
- streamDone: true,
35910
- text: streamText,
35911
- });
35912
- }
35913
- break;
35914
- }
35915
- case "reasoning-start": {
35916
- thinkStreamId = uuidv4();
35917
- break;
35918
35945
  }
35919
- case "reasoning-delta": {
35920
- thinkText += chunk.delta || "";
35921
- await streamCallback.onMessage({
35922
- streamType: "chat",
35923
- chatId: chatContext.getChatId(),
35924
- messageId,
35925
- type: "thinking",
35926
- streamId: thinkStreamId,
35927
- streamDone: false,
35928
- text: thinkText,
35929
- });
35930
- break;
35946
+ id && this.requestMap.set(id, resolve);
35947
+ });
35948
+ Log.debug(`MCP Client, ${method}`, id, params);
35949
+ const response = await fetch(this.msgUrl, {
35950
+ method: "POST",
35951
+ headers: {
35952
+ "Content-Type": "application/json",
35953
+ ...this.headers,
35954
+ },
35955
+ body: JSON.stringify({
35956
+ jsonrpc: "2.0",
35957
+ id: id,
35958
+ method: method,
35959
+ params: {
35960
+ ...params,
35961
+ },
35962
+ }),
35963
+ signal: signal,
35964
+ });
35965
+ const body = await response.text();
35966
+ if (body == "Accepted") {
35967
+ const message = await callback;
35968
+ if (message.error) {
35969
+ Log.error(`MCP ${method} error: ` + message.error);
35970
+ throw new Error(`MCP ${method} error: ` +
35971
+ (typeof message.error === "string"
35972
+ ? message.error
35973
+ : message.error.message));
35931
35974
  }
35932
- case "reasoning-end": {
35933
- if (thinkText) {
35934
- await streamCallback.onMessage({
35935
- streamType: "chat",
35936
- chatId: chatContext.getChatId(),
35937
- messageId,
35938
- type: "thinking",
35939
- streamId: thinkStreamId,
35940
- streamDone: true,
35941
- text: thinkText,
35942
- });
35975
+ if (message.result?.isError == true) {
35976
+ if (message.result.content) {
35977
+ throw new Error(`MCP ${method} error: ` +
35978
+ (typeof message.result.content === "string"
35979
+ ? message.result.content
35980
+ : message.result.content[0].text));
35981
+ }
35982
+ else {
35983
+ throw new Error(`MCP ${method} error: ` + JSON.stringify(message.result));
35943
35984
  }
35944
- break;
35945
35985
  }
35946
- case "tool-input-start": {
35947
- if (toolPart && toolPart.toolCallId == chunk.id) {
35948
- toolPart.toolName = chunk.toolName;
35949
- }
35950
- else {
35951
- toolPart = {
35952
- type: "tool-call",
35953
- toolCallId: chunk.id,
35954
- toolName: chunk.toolName,
35955
- input: {},
35956
- };
35957
- toolParts.push(toolPart);
35958
- }
35959
- break;
35960
- }
35961
- case "tool-input-delta": {
35962
- if (!textStreamDone) {
35963
- textStreamDone = true;
35964
- await streamCallback.onMessage({
35965
- streamType: "chat",
35966
- chatId: chatContext.getChatId(),
35967
- messageId,
35968
- type: "text",
35969
- streamId: textStreamId,
35970
- streamDone: true,
35971
- text: streamText,
35972
- });
35973
- }
35974
- toolArgsText += chunk.delta || "";
35975
- await streamCallback.onMessage({
35976
- streamType: "chat",
35977
- chatId: chatContext.getChatId(),
35978
- messageId,
35979
- type: "tool_streaming",
35980
- toolCallId: chunk.id,
35981
- toolName: toolPart?.toolName || "",
35982
- paramsText: toolArgsText,
35983
- });
35984
- break;
35985
- }
35986
- case "tool-call": {
35987
- toolArgsText = "";
35988
- const args = chunk.input ? JSON.parse(chunk.input) : {};
35989
- const message = {
35990
- streamType: "chat",
35991
- chatId: chatContext.getChatId(),
35992
- messageId,
35993
- type: "tool_use",
35994
- toolCallId: chunk.toolCallId,
35995
- toolName: chunk.toolName,
35996
- params: args,
35997
- };
35998
- await streamCallback.onMessage(message);
35999
- if (toolPart == null) {
36000
- toolParts.push({
36001
- type: "tool-call",
36002
- toolCallId: chunk.toolCallId,
36003
- toolName: chunk.toolName,
36004
- input: message.params || args,
36005
- });
36006
- }
36007
- else {
36008
- toolPart.input = message.params || args;
36009
- toolPart = null;
36010
- }
36011
- break;
36012
- }
36013
- case "error": {
36014
- Log.error(`chatLLM error: `, chunk);
36015
- await streamCallback.onMessage({
36016
- streamType: "chat",
36017
- chatId: chatContext.getChatId(),
36018
- messageId,
36019
- type: "error",
36020
- error: chunk.error,
36021
- });
36022
- throw new Error("LLM Error: " + chunk.error);
36023
- }
36024
- case "finish": {
36025
- if (!textStreamDone) {
36026
- textStreamDone = true;
36027
- await streamCallback.onMessage({
36028
- streamType: "chat",
36029
- chatId: chatContext.getChatId(),
36030
- messageId,
36031
- type: "text",
36032
- streamId: textStreamId,
36033
- streamDone: true,
36034
- text: streamText,
36035
- });
36036
- }
36037
- if (toolPart) {
36038
- await streamCallback.onMessage({
36039
- streamType: "chat",
36040
- chatId: chatContext.getChatId(),
36041
- messageId,
36042
- type: "tool_use",
36043
- toolCallId: toolPart.toolCallId,
36044
- toolName: toolPart.toolName,
36045
- params: toolPart.input || {},
36046
- });
36047
- toolPart = null;
36048
- }
36049
- await streamCallback.onMessage({
36050
- streamType: "chat",
36051
- chatId: chatContext.getChatId(),
36052
- messageId,
36053
- type: "finish",
36054
- finishReason: chunk.finishReason,
36055
- usage: {
36056
- promptTokens: chunk.usage.inputTokens || 0,
36057
- completionTokens: chunk.usage.outputTokens || 0,
36058
- totalTokens: chunk.usage.totalTokens ||
36059
- (chunk.usage.inputTokens || 0) +
36060
- (chunk.usage.outputTokens || 0),
36061
- },
36062
- });
36063
- break;
35986
+ return message;
35987
+ }
35988
+ else {
35989
+ throw new Error(`MCP ${method} error:` + body);
35990
+ }
35991
+ }
35992
+ finally {
35993
+ id && this.requestMap.delete(id);
35994
+ }
35995
+ }
35996
+ isConnected() {
35997
+ if (this.sseHandler && this.sseHandler.readyState == 1) {
35998
+ return true;
35999
+ }
36000
+ return false;
36001
+ }
36002
+ async close() {
36003
+ try {
36004
+ await this.request("notifications/cancelled", {
36005
+ requestId: uuidv4(),
36006
+ reason: "User requested cancellation",
36007
+ });
36008
+ }
36009
+ catch (ignored) { }
36010
+ this.pingTimer && clearInterval(this.pingTimer);
36011
+ this.reconnectTimer && clearTimeout(this.reconnectTimer);
36012
+ this.sseHandler && this.sseHandler.close && this.sseHandler.close();
36013
+ this.pingTimer = undefined;
36014
+ this.sseHandler = undefined;
36015
+ this.reconnectTimer = undefined;
36016
+ }
36017
+ }
36018
+ async function connectSse(sseUrl, hander, headers = {}, _signal) {
36019
+ try {
36020
+ hander.readyState = 0;
36021
+ const controller = new AbortController();
36022
+ const signal = _signal
36023
+ ? AbortSignal.any([controller.signal, _signal])
36024
+ : controller.signal;
36025
+ const response = await fetch(sseUrl, {
36026
+ method: "GET",
36027
+ headers: {
36028
+ "Content-Type": "text/event-stream",
36029
+ "Cache-Control": "no-cache",
36030
+ ...headers,
36031
+ },
36032
+ body: null,
36033
+ keepalive: true,
36034
+ signal: signal,
36035
+ });
36036
+ const reader = response.body?.getReader();
36037
+ hander.close = () => {
36038
+ controller.abort();
36039
+ hander.readyState = 2;
36040
+ Log.debug("McpClient close abort.", sseUrl);
36041
+ };
36042
+ let str = "";
36043
+ const decoder = new TextDecoder();
36044
+ hander.readyState = 1;
36045
+ hander.onopen();
36046
+ while (hander.readyState == 1) {
36047
+ const { value, done } = await reader?.read();
36048
+ if (done) {
36049
+ break;
36050
+ }
36051
+ const text = decoder.decode(value);
36052
+ str += text;
36053
+ if (str.indexOf("\n\n") > -1) {
36054
+ const chunks = str.split("\n\n");
36055
+ for (let i = 0; i < chunks.length - 1; i++) {
36056
+ const chunk = chunks[i];
36057
+ const chunkData = parseChunk(chunk);
36058
+ hander.onmessage(chunkData);
36064
36059
  }
36060
+ str = chunks[chunks.length - 1];
36065
36061
  }
36066
36062
  }
36067
36063
  }
36068
36064
  catch (e) {
36069
- if (retryNum < config$1.maxRetryNum) {
36070
- await sleep(200 * (retryNum + 1) * (retryNum + 1));
36071
- return callChatLLM(messageId, chatContext, rlm, messages, tools, toolChoice, ++retryNum, callback, signal);
36065
+ if (e?.name !== "AbortError") {
36066
+ Log.error("MCP Client, connectSse error:", e);
36067
+ hander.onerror(e);
36072
36068
  }
36073
- throw e;
36074
36069
  }
36075
36070
  finally {
36076
- reader && reader.releaseLock();
36071
+ hander.readyState = 2;
36077
36072
  }
36078
- return streamText
36079
- ? [
36080
- { type: "text", text: streamText },
36081
- ...toolParts,
36082
- ]
36083
- : toolParts;
36084
36073
  }
36085
- function convertAssistantToolResults(results) {
36086
- return results.map((part) => {
36087
- if (part.type == "text") {
36088
- return {
36089
- type: "text",
36090
- text: part.text,
36091
- };
36074
+ function parseChunk(chunk) {
36075
+ const lines = chunk.split("\n");
36076
+ const chunk_obj = {};
36077
+ for (let j = 0; j < lines.length; j++) {
36078
+ const line = lines[j];
36079
+ if (line.startsWith("id:")) {
36080
+ chunk_obj["id"] = line.substring(3).trim();
36092
36081
  }
36093
- else if (part.type == "tool-call") {
36094
- return {
36095
- type: "tool-call",
36096
- toolCallId: part.toolCallId,
36097
- toolName: part.toolName,
36098
- args: (part.input || {}),
36099
- };
36082
+ else if (line.startsWith("event:")) {
36083
+ chunk_obj["event"] = line.substring(6).trim();
36100
36084
  }
36101
- return part;
36102
- });
36103
- }
36104
- function convertToolResults(toolResults) {
36105
- return toolResults.map((part) => {
36106
- const output = part.output;
36107
- return {
36108
- type: "tool-result",
36109
- toolCallId: part.toolCallId,
36110
- toolName: part.toolName,
36111
- result: output.type == "text" || output.type == "error-text"
36112
- ? output.value
36113
- : output.type == "json" || output.type == "error-json"
36114
- ? output.value
36115
- : output.value
36116
- .map((s) => {
36117
- if (s.type == "text") {
36118
- return s.text;
36119
- }
36120
- else if (s.type == "media") {
36121
- return JSON.stringify({
36122
- data: s.data,
36123
- mimeType: s.mediaType,
36124
- });
36125
- }
36126
- })
36127
- .join("\n"),
36128
- };
36129
- });
36085
+ else if (line.startsWith("data:")) {
36086
+ chunk_obj["data"] = line.substring(5).trim();
36087
+ }
36088
+ else {
36089
+ const idx = line.indexOf(":");
36090
+ if (idx > -1) {
36091
+ chunk_obj[line.substring(0, idx)] = line.substring(idx + 1).trim();
36092
+ }
36093
+ }
36094
+ }
36095
+ return chunk_obj;
36130
36096
  }
36131
36097
 
36132
- class ChatContext {
36133
- constructor(chatId, config) {
36134
- this.chatId = chatId;
36135
- this.config = config;
36136
- this.ekoMap = new Map();
36137
- this.globalVariables = new Map();
36098
+ class SimpleHttpMcpClient {
36099
+ constructor(httpUrl, clientName = "EkoMcpClient", headers = {}) {
36100
+ this.protocolVersion = "2025-06-18";
36101
+ this.connected = false;
36102
+ this.httpUrl = httpUrl;
36103
+ this.clientName = clientName;
36104
+ this.headers = headers;
36138
36105
  }
36139
- getChatId() {
36140
- return this.chatId;
36106
+ async connect(signal) {
36107
+ Log.info("MCP Client, connecting...", this.httpUrl);
36108
+ this.mcpSessionId = null;
36109
+ await this.request("initialize", {
36110
+ protocolVersion: this.protocolVersion,
36111
+ capabilities: {
36112
+ tools: {
36113
+ listChanged: true,
36114
+ },
36115
+ sampling: {},
36116
+ },
36117
+ clientInfo: {
36118
+ name: this.clientName,
36119
+ version: "1.0.0",
36120
+ },
36121
+ }, signal);
36122
+ if (this.mcpSessionId) {
36123
+ try {
36124
+ await this.request("notifications/initialized", {});
36125
+ }
36126
+ catch (ignored) { }
36127
+ }
36128
+ this.connected = true;
36141
36129
  }
36142
- getConfig() {
36143
- return this.config;
36130
+ async listTools(param, signal) {
36131
+ const message = await this.request("tools/list", {
36132
+ ...param,
36133
+ }, signal);
36134
+ return message.result.tools || [];
36144
36135
  }
36145
- addEko(taskId, eko) {
36146
- this.ekoMap.set(taskId, eko);
36136
+ async callTool(param, signal) {
36137
+ const message = await this.request("tools/call", {
36138
+ ...param,
36139
+ }, signal);
36140
+ return message.result;
36147
36141
  }
36148
- getEko(taskId) {
36149
- return this.ekoMap.get(taskId);
36142
+ isConnected() {
36143
+ return this.connected;
36150
36144
  }
36151
- getGlobalVariables() {
36152
- return this.globalVariables;
36145
+ async close() {
36146
+ this.connected = false;
36147
+ if (this.mcpSessionId) {
36148
+ try {
36149
+ await this.request("notifications/cancelled", {
36150
+ requestId: uuidv4(),
36151
+ reason: "User requested cancellation",
36152
+ });
36153
+ }
36154
+ catch (ignored) { }
36155
+ this.mcpSessionId = null;
36156
+ }
36153
36157
  }
36154
- }
36155
-
36156
- const TOOL_NAME$3 = "webpageQa";
36157
- const WEBPAGE_QA_PROMPT = `
36158
- You are a helpful assistant that can answer questions based on the provided webpage context.
36159
-
36160
- # Webpage Context
36161
- <webpage_contexts>
36162
- {{contexts}}
36163
- </webpage_contexts>
36164
-
36165
- # User Question
36166
- <user_question>
36167
- {{userPrompt}}
36168
- </user_question>
36169
- <if language>
36170
- <language>{{language}}</language>
36171
- </if>
36172
-
36173
- Answer user's question based on the webpage context, the answer should be in the same language as the user's question.
36174
- `;
36175
- class WebpageQaTool {
36176
- constructor(chatContext, params) {
36177
- this.name = TOOL_NAME$3;
36178
- this.params = params;
36179
- this.chatContext = chatContext;
36180
- this.description = `This tool is designed only for handling simple web-related tasks, including summarizing webpage content, extracting data from web pages, translating webpage content, and converting webpage information into more easily understandable forms. It does not interact with or operate web pages. For more complex browser tasks, please use deepAction.It does not perform operations on the webpage itself, but only involves reading the page content. Users do not need to provide the web page content, as the tool can automatically extract the content of the web page based on the tabId to respond.`;
36181
- this.parameters = {
36182
- type: "object",
36183
- properties: {
36184
- language: {
36185
- type: "string",
36186
- description: "User language used, eg: English",
36187
- },
36188
- tabIds: {
36189
- type: "array",
36190
- description: "The browser tab ids to be used for the QA. When the user says 'left side' or 'current', it means current active tab.",
36191
- items: { type: "integer" },
36158
+ async request(method, params, signal) {
36159
+ try {
36160
+ const id = method.startsWith("notifications/") ? undefined : uuidv4();
36161
+ const extHeaders = {};
36162
+ if (this.mcpSessionId && method !== "initialize") {
36163
+ extHeaders["Mcp-Session-Id"] = this.mcpSessionId;
36164
+ }
36165
+ const response = await fetch(this.httpUrl, {
36166
+ method: "POST",
36167
+ headers: {
36168
+ "Cache-Control": "no-cache",
36169
+ "Content-Type": "application/json",
36170
+ Accept: "application/json, text/event-stream",
36171
+ "MCP-Protocol-Version": this.protocolVersion,
36172
+ ...extHeaders,
36173
+ ...this.headers,
36192
36174
  },
36193
- },
36194
- required: ["tabIds", "language"],
36195
- };
36196
- }
36197
- async execute(args, toolCall, messageId) {
36198
- if (!global.browserService) {
36199
- return {
36200
- content: [
36201
- {
36202
- type: "text",
36203
- text: "Error: not implemented",
36175
+ body: JSON.stringify({
36176
+ jsonrpc: "2.0",
36177
+ id: id,
36178
+ method: method,
36179
+ params: {
36180
+ ...params,
36204
36181
  },
36205
- ],
36206
- };
36207
- }
36208
- const tabIds = args.tabIds;
36209
- const language = args.language;
36210
- const tabs = await global.browserService.extractPageContents(this.chatContext.getChatId(), tabIds);
36211
- const chatConfig = this.chatContext.getConfig();
36212
- const rlm = new RetryLanguageModel(chatConfig.llms, chatConfig.chatLlms);
36213
- const prompt = PromptTemplate.render(global.prompts.get(GlobalPromptKey.webpage_qa_prompt) ||
36214
- WEBPAGE_QA_PROMPT, {
36215
- language: language,
36216
- userPrompt: this.params.user
36217
- .map((part) => (part.type == "text" ? part.text : ""))
36218
- .join("\n")
36219
- .trim(),
36220
- contexts: this.buildTabContents(tabs),
36221
- }).trim();
36222
- const result = await rlm.callStream({
36223
- temperature: 0.7,
36224
- maxOutputTokens: config$1.maxOutputTokens,
36225
- messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
36226
- });
36227
- const stream = result.stream;
36228
- const reader = stream.getReader();
36229
- const streamId = uuidv4();
36230
- const callback = this.params.callback.chatCallback;
36231
- let text = "";
36232
- try {
36233
- while (true) {
36234
- const { done, value } = await reader.read();
36235
- if (done) {
36236
- break;
36237
- }
36238
- const chunk = value;
36239
- if (chunk.type == "text-delta") {
36240
- text += chunk.delta;
36241
- await callback.onMessage({
36242
- streamType: "chat",
36243
- chatId: this.chatContext.getChatId(),
36244
- messageId: messageId,
36245
- type: "tool_running",
36246
- toolName: this.name,
36247
- toolCallId: toolCall.toolCallId,
36248
- text: text,
36249
- streamId: streamId,
36250
- streamDone: false,
36251
- });
36252
- }
36253
- else if (chunk.type == "error") {
36254
- throw new Error(chunk.error);
36255
- }
36256
- else if (chunk.type == "finish") {
36257
- break;
36182
+ }),
36183
+ keepalive: true,
36184
+ signal: signal,
36185
+ });
36186
+ if (method.startsWith("notifications/")) {
36187
+ return;
36188
+ }
36189
+ if (method == "initialize") {
36190
+ this.mcpSessionId =
36191
+ response.headers.get("Mcp-Session-Id") ||
36192
+ response.headers.get("mcp-session-id");
36193
+ }
36194
+ const contentType = response.headers.get("Content-Type") ||
36195
+ response.headers.get("content-type") ||
36196
+ "application/json";
36197
+ if (contentType?.includes("text/event-stream")) {
36198
+ // SSE
36199
+ const reader = response.body?.getReader();
36200
+ let str = "";
36201
+ let message;
36202
+ const decoder = new TextDecoder();
36203
+ while (true) {
36204
+ const { value, done } = await reader?.read();
36205
+ if (done) {
36206
+ break;
36207
+ }
36208
+ const text = decoder.decode(value);
36209
+ str += text;
36210
+ if (str.indexOf("\n\n") > -1) {
36211
+ const chunks = str.split("\n\n");
36212
+ for (let i = 0; i < chunks.length - 1; i++) {
36213
+ const chunk = chunks[i];
36214
+ const chunkData = this.parseChunk(chunk);
36215
+ if (chunkData.event == "message") {
36216
+ message = JSON.parse(chunkData.data);
36217
+ if (message.id == id) {
36218
+ return message;
36219
+ }
36220
+ }
36221
+ }
36222
+ str = chunks[chunks.length - 1];
36223
+ }
36258
36224
  }
36225
+ this.handleError(method, message);
36226
+ return message;
36259
36227
  }
36260
- }
36261
- finally {
36262
- reader.releaseLock();
36263
- await callback.onMessage({
36264
- streamType: "chat",
36265
- chatId: this.chatContext.getChatId(),
36266
- messageId: messageId,
36267
- type: "tool_running",
36268
- toolName: this.name,
36269
- toolCallId: toolCall.toolCallId,
36270
- text: text,
36271
- streamId: streamId,
36272
- streamDone: true,
36273
- });
36274
- }
36275
- return {
36276
- content: [
36277
- {
36278
- type: "text",
36279
- text: text,
36280
- },
36281
- ],
36282
- };
36283
- }
36284
- buildTabContents(tabs) {
36285
- return tabs
36286
- .map((tab) => {
36287
- return `<webpage>\nTabId: ${tab.tabId}\nTitle: ${tab.title}\nURL: ${tab.url}\nContent: ${sub(tab.content, 8000)}\n</webpage>`;
36288
- })
36289
- .join("\n");
36290
- }
36291
- }
36292
-
36293
- const TOOL_NAME$2 = "webSearch";
36294
- class WebSearchTool {
36295
- constructor(chatContext, params) {
36296
- this.name = TOOL_NAME$2;
36297
- this.params = params;
36298
- this.chatContext = chatContext;
36299
- this.description = `Search the web for information using search engine API. This tool can perform web searches to find current information, news, articles, and other web content related to the query. It returns search results with titles, descriptions, URLs, and other relevant metadata, use this tool when users need the latest data/information and have NOT specified a particular platform or website, use the search tool.`;
36300
- this.parameters = {
36301
- type: "object",
36302
- properties: {
36303
- query: {
36304
- type: "string",
36305
- description: "The search query to execute. Use specific keywords and phrases for better results.",
36306
- },
36307
- language: {
36308
- type: "string",
36309
- description: "Language code for search results (e.g., 'en', 'zh', 'ja'). If not specified, will be auto-detected from query.",
36310
- },
36311
- count: {
36312
- type: "integer",
36313
- description: "Number of search results to return (default: 10, max: 50)",
36314
- default: 10,
36315
- minimum: 1,
36316
- maximum: 50,
36317
- },
36318
- },
36319
- required: ["query", "keywords"],
36320
- };
36321
- }
36322
- async execute(args) {
36323
- if (!global.chatService) {
36324
- return {
36325
- content: [
36326
- {
36327
- type: "text",
36328
- text: "Error: not implemented",
36329
- },
36330
- ],
36331
- };
36332
- }
36333
- const query = args.query;
36334
- const language = args.language;
36335
- const count = args.count || 10;
36336
- const results = await global.chatService.websearch(this.chatContext.getChatId(), query, undefined, language, count);
36337
- return Promise.resolve({
36338
- content: [
36339
- {
36340
- type: "text",
36341
- text: JSON.stringify(results.map((result) => {
36342
- return {
36343
- title: result.title,
36344
- url: result.url,
36345
- content: sub(result.content || result.snippet || "", 6000),
36346
- };
36347
- })),
36348
- },
36349
- ],
36350
- });
36351
- }
36352
- }
36353
-
36354
- async function recursiveTextNode(node, callback) {
36355
- if (node.type === "normal") {
36356
- callback(node, node);
36357
- }
36358
- if (node.type === "forEach") {
36359
- node.nodes.map((item) => recursiveTextNode(item, callback));
36360
- }
36361
- if (node.type === "watch") {
36362
- node.triggerNodes.map((triggerNode) => recursiveTextNode(triggerNode, callback));
36363
- }
36364
- }
36365
-
36366
- const TOOL_NAME$1 = "deepAction";
36367
- const deep_action_description = "Delegate tasks to a Javis AI assistant for completion. This assistant can understand natural language instructions and has full control over both networked computers, browser agent, and multiple specialized agents ({agentNames}). The assistant can autonomously decide to use various software tools, browse the internet to query information, write code, and perform direct operations to complete tasks. He can deliver various digitized outputs (text reports, tables, images, music, videos, websites, deepSearch, programs, etc.) and handle design/analysis tasks. and execute operational tasks (such as batch following bloggers of specific topics on certain websites). For operational tasks, the focus is on completing the process actions rather than delivering final outputs, and the assistant can complete these types of tasks well. It should also be noted that users may actively mention deepsearch, which is also one of the capabilities of this tool. If users mention it, please explicitly tell the assistant to use deepsearch. Supports parallel execution of multiple tasks.";
36368
- const deep_action_param_task_description = "Task description, please output the user's original instructions without omitting any information from the user's instructions, and use the same language as the user's question.";
36369
- class DeepActionTool {
36370
- constructor(chatContext, params) {
36371
- this.name = TOOL_NAME$1;
36372
- this.chatContext = chatContext;
36373
- const agents = this.chatContext.getConfig().agents || [];
36374
- const agentNames = agents.map((agent) => agent.Name).join(", ");
36375
- const description = global.prompts.get(GlobalPromptKey.deep_action_description) ||
36376
- deep_action_description;
36377
- const paramTaskDescription = global.prompts.get(GlobalPromptKey.deep_action_param_task_description) ||
36378
- deep_action_param_task_description;
36379
- this.description = description.replace("{agentNames}", agentNames).trim();
36380
- this.parameters = {
36381
- type: "object",
36382
- properties: {
36383
- language: {
36384
- type: "string",
36385
- description: "User language used, eg: English",
36386
- },
36387
- taskDescription: {
36388
- type: "string",
36389
- description: paramTaskDescription.trim(),
36390
- },
36391
- tabIds: {
36392
- type: "array",
36393
- description: "Browser Tab IDs associated with this task, When user says 'left side' or 'current', it means current active tab",
36394
- items: { type: "integer" },
36395
- },
36396
- dependentVariables: {
36397
- type: "array",
36398
- description: "The current task relies on variable data from prerequisite execution outputs. Provide the name of the dependent variable.",
36399
- items: {
36400
- type: "string",
36401
- },
36402
- },
36403
- },
36404
- required: ["language", "taskDescription"],
36405
- };
36406
- this.params = params;
36407
- }
36408
- async execute(args, toolCall, messageId) {
36409
- const chatId = this.chatContext.getChatId();
36410
- const language = args.language;
36411
- const taskDescription = args.taskDescription;
36412
- const tabIds = args.tabIds;
36413
- const dependentVariables = args.dependentVariables;
36414
- const config = this.chatContext.getConfig();
36415
- const globalVariables = this.chatContext.getGlobalVariables();
36416
- const eko = new Eko({
36417
- ...config,
36418
- callback: this.params.callback?.taskCallback,
36419
- }, chatId);
36420
- this.chatContext.addEko(messageId, eko);
36421
- if (this.params.signal) {
36422
- if (this.params.signal.aborted) {
36423
- const error = new Error("Operation was interrupted");
36424
- error.name = "AbortError";
36425
- throw error;
36228
+ else {
36229
+ // JSON
36230
+ const message = await response.json();
36231
+ this.handleError(method, message);
36232
+ return message;
36426
36233
  }
36427
- this.params.signal.addEventListener("abort", () => {
36428
- eko.abortTask(messageId, "User aborted");
36429
- });
36430
- }
36431
- const attachments = this.params.user
36432
- .filter((part) => part.type === "file")
36433
- .filter((part) => part.data && part.data.length < 500)
36434
- .map((part) => {
36435
- return {
36436
- file_name: part.filename,
36437
- file_path: part.filePath,
36438
- file_url: part.data,
36439
- };
36440
- });
36441
- const taskWebsite = await this.gettaskWebsite(tabIds);
36442
- const workflow = await eko.generate(taskDescription, messageId, {
36443
- ...globalVariables,
36444
- tabIds: tabIds,
36445
- language: language,
36446
- attachments: attachments,
36447
- taskWebsite: taskWebsite,
36448
- dependentVariables: dependentVariables,
36449
- datetime: this.params.datetime || new Date().toLocaleString(),
36450
- });
36451
- const context = eko.getTask(messageId);
36452
- console.log("==> workflow", workflow);
36453
- const result = await eko.execute(messageId);
36454
- const variableNames = [];
36455
- if (context.variables && context.variables.size > 0) {
36456
- workflow.agents
36457
- .map((agent) => agent.nodes)
36458
- .flat()
36459
- .forEach((node) => {
36460
- recursiveTextNode(node, async (textNode) => {
36461
- if (textNode.output) {
36462
- variableNames.push(textNode.output);
36463
- globalVariables.set(textNode.output, context.variables.get(textNode.output));
36464
- }
36465
- });
36466
- });
36467
- }
36468
- return {
36469
- content: [
36470
- {
36471
- type: "text",
36472
- text: JSON.stringify({
36473
- taskPlan: workflow.xml,
36474
- subAgents: context.chain.agents.map((agent) => {
36475
- return {
36476
- agent: agent.agent.name,
36477
- subTask: agent.agent.task,
36478
- agentResult: sub(agent.agentResult || "", 800, true),
36479
- };
36480
- }),
36481
- variables: variableNames,
36482
- taskResult: result.result,
36483
- success: result.success,
36484
- }),
36485
- },
36486
- ],
36487
- };
36488
- }
36489
- async gettaskWebsite(tabIds) {
36490
- if (!global.browserService) {
36491
- return [];
36492
36234
  }
36493
- const tabs = await global.browserService.loadTabs(this.chatContext.getChatId(), tabIds);
36494
- return tabs.map((tab) => {
36495
- return {
36496
- tabId: tab.tabId,
36497
- title: tab.title,
36498
- url: sub(tab.url, 300),
36499
- };
36500
- });
36501
- }
36502
- }
36503
-
36504
- const TOOL_NAME = "taskVariableStorage";
36505
- class TaskVariableStorageTool {
36506
- constructor(chatContext, params) {
36507
- this.name = TOOL_NAME;
36508
- this.params = params;
36509
- this.chatContext = chatContext;
36510
- this.description = `Used for storing, reading, and retrieving variable data, and maintaining input/output variables in task nodes.`;
36511
- this.parameters = {
36512
- type: "object",
36513
- properties: {
36514
- operation: {
36515
- type: "string",
36516
- description: "variable storage operation type.",
36517
- enum: ["read_variable", "write_variable", "list_all_variable"],
36518
- },
36519
- name: {
36520
- type: "string",
36521
- description: "variable name, required when reading and writing variables, If reading variables, it supports reading multiple variables separated by commas.",
36522
- },
36523
- value: {
36524
- type: "string",
36525
- description: "variable value, required when writing variables",
36526
- },
36527
- },
36528
- required: ["operation"],
36529
- };
36530
- }
36531
- async execute(args) {
36532
- let operation = args.operation;
36533
- let resultText = "";
36534
- switch (operation) {
36535
- case "read_variable": {
36536
- if (!args.name) {
36537
- resultText = "Error: name is required";
36538
- }
36539
- else {
36540
- let result = {};
36541
- let name = args.name;
36542
- let keys = name.split(",");
36543
- for (let i = 0; i < keys.length; i++) {
36544
- let key = keys[i].trim();
36545
- let value = this.chatContext.getGlobalVariables().get(key);
36546
- result[key] = value;
36547
- }
36548
- resultText = JSON.stringify(result);
36549
- }
36550
- break;
36551
- }
36552
- case "write_variable": {
36553
- if (!args.name) {
36554
- resultText = "Error: name is required";
36555
- break;
36556
- }
36557
- if (args.value == undefined) {
36558
- resultText = "Error: value is required";
36559
- break;
36560
- }
36561
- let key = args.name;
36562
- this.chatContext.getGlobalVariables().set(key.trim(), args.value);
36563
- resultText = "success";
36564
- break;
36565
- }
36566
- case "list_all_variable": {
36567
- resultText = JSON.stringify([
36568
- ...this.chatContext.getGlobalVariables().keys(),
36569
- ]);
36570
- break;
36235
+ catch (e) {
36236
+ if (e?.name !== "AbortError") {
36237
+ Log.error("MCP Client, connectSse error:", e);
36571
36238
  }
36239
+ throw e;
36572
36240
  }
36573
- return {
36574
- content: [
36575
- {
36576
- type: "text",
36577
- text: resultText || "",
36578
- },
36579
- ],
36580
- };
36581
- }
36582
- }
36583
-
36584
- const CHAT_SYSTEM_TEMPLATE = `
36585
- You are {{name}}, it is an action-oriented assistant in the browser, a general-purpose intelligent agent running in the browser environment.
36586
-
36587
- <tool_instructions>
36588
- General Principles:
36589
- - Only one tool can be called at a time.
36590
- - Users may not be able to clearly describe their needs in a single conversation. When needs are ambiguous or lack details, assistant can appropriately initiate follow-up questions before making tool calls. Follow-up rounds should not exceed two rounds.
36591
- - Users may switch topics multiple times during ongoing conversations. When calling tools, assistant must focus ONLY on the current user question and ignore previous conversation topics unless they are directly related to the current request. Each question should be treated as independent unless explicitly building on previous context.
36592
-
36593
- For non-chat related tasks issued by users, the following tools need to be called to complete them:
36594
- <if ${TOOL_NAME$1}Tool>
36595
- - ${TOOL_NAME$1}: This tool is used to execute tasks, delegate to Javis AI assistant with full computer control.
36596
- </if>
36597
- <if ${TOOL_NAME$3}Tool>
36598
- - ${TOOL_NAME$3}: When a user's query involves finding content in a webpage within a browser tab, extracting webpage content, summarizing webpage content, translating webpage content, read PDF page content, or converting webpage content into a more understandable format, this tool should be used. If the task requires performing actions based on webpage content, deepAction should be used. only needs to provide the required invocation parameters according to the tool's needs; users do not need to manually provide the content of the browser tab.
36599
- </if>
36600
- <if ${TOOL_NAME$2}Tool>
36601
- - ${TOOL_NAME$2}: Search the web for information using search engine API. This tool can perform web searches to find current information, news, articles, and other web content related to the query. It returns search results with titles, descriptions, URLs, and other relevant metadata. Use this tool when you need to find current information from the internet that may not be available in your training data.
36602
- </if>
36603
- <if ${TOOL_NAME}Tool>
36604
- - ${TOOL_NAME}: This tool is used to read output variables from task nodes and write input variables to task nodes, mainly used to retrieve variable results after task execution is completed.
36605
- </if>
36606
- </tool_instructions>
36607
-
36608
- <if memory>
36609
- The assistant always focuses on the user's current question and will not allow previous conversation turns or irrelevant memory content to interfere with the response to the user's current question. Each question should be handled independently unless it explicitly builds upon prior context.
36610
- Before responding to user questions, the assistant intelligently analyzes the relevance of memories. When responding, the assistant first determines whether the user's current question is related to information in the retrieved memories, and only incorporates memory data when there is clear contextual relevance. If the user's question is unrelated to the retrieved memories, the assistant will directly respond to the current question without referencing memory content, ensuring the conversation flows naturally.
36611
- Avoid forcing the use of memories when they are irrelevant to the current context, prioritizing the accuracy and relevance of responses over the inclusion of memories.
36612
- <retrieved_memories>
36613
- {{memory}}
36614
- </retrieved_memories>
36615
- </if>
36616
-
36617
- <if tabs>
36618
- The information about the browser tabs currently open by the user is as follows:
36619
- <browser_tabs>
36620
- {{tabs}}
36621
- </browser_tabs>
36622
- </if>
36623
-
36624
- Current datetime: {{datetime}}
36625
- The output language should match the user's conversation language.
36626
- `;
36627
- function getChatSystemPrompt(tools, datetime, memory, tabs) {
36628
- const systemPrompt = global.prompts.get(GlobalPromptKey.chat_system) || CHAT_SYSTEM_TEMPLATE;
36629
- const toolVars = {};
36630
- for (let i = 0; i < tools.length; i++) {
36631
- toolVars[tools[i].name + "Tool"] = true;
36632
- }
36633
- return PromptTemplate.render(systemPrompt, {
36634
- name: config$1.name,
36635
- datetime: datetime,
36636
- memory: memory || "",
36637
- tabs: getTabsInfo(tabs),
36638
- ...toolVars,
36639
- }).trim();
36640
- }
36641
- function getTabsInfo(tabs) {
36642
- if (!tabs || tabs.length == 0) {
36643
- return "Empty";
36644
36241
  }
36645
- return JSON.stringify(tabs.slice(0, 10).map((tab) => {
36646
- return {
36647
- tabId: tab.tabId,
36648
- title: sub(tab.title, 50),
36649
- url: sub(tab.url, 300),
36650
- active: tab.active,
36651
- lastAccessed: tab.lastAccessed,
36652
- };
36653
- }), null, 2);
36654
- }
36655
-
36656
- class ChatAgent {
36657
- constructor(config, chatId = uuidv4(), memory, tools) {
36658
- this.tools = tools ?? [];
36659
- this.memory = memory ?? new EkoMemory();
36660
- this.chatContext = new ChatContext(chatId, config);
36661
- global.chatMap.set(chatId, this.chatContext);
36662
- }
36663
- async chat(params) {
36664
- return this.doChat(params, false);
36665
- }
36666
- async doChat(params, segmentedExecution) {
36667
- const runStartTime = Date.now();
36668
- let reactLoopNum = 0;
36669
- let errorInfo = null;
36670
- try {
36671
- if (params.callback?.chatCallback) {
36672
- await params.callback.chatCallback.onMessage({
36673
- streamType: "chat",
36674
- chatId: this.chatContext.getChatId(),
36675
- messageId: params.messageId,
36676
- type: "chat_start",
36677
- });
36678
- }
36679
- const chatTools = mergeTools(this.buildInnerTools(params), this.tools);
36680
- await this.buildSystemPrompt(params, chatTools);
36681
- await this.addUserMessage(params.messageId, params.user);
36682
- const config = this.chatContext.getConfig();
36683
- const rlm = new RetryLanguageModel(config.llms, config.chatLlms);
36684
- for (; reactLoopNum < 15; reactLoopNum++) {
36685
- const messages = this.memory.buildMessages();
36686
- const results = await callChatLLM(params.messageId, this.chatContext, rlm, messages, convertTools(chatTools), undefined, 0, params.callback, params.signal);
36687
- const finalResult = await this.handleCallResult(params.messageId, chatTools, results, params.callback);
36688
- if (finalResult) {
36689
- return finalResult;
36690
- }
36691
- if (params.signal?.aborted) {
36692
- const error = new Error("Operation was interrupted");
36693
- error.name = "AbortError";
36694
- throw error;
36695
- }
36696
- }
36697
- reactLoopNum--;
36698
- return "Unfinished";
36242
+ handleError(method, message) {
36243
+ if (!message) {
36244
+ throw new Error(`MCP ${method} error: no response`);
36699
36245
  }
36700
- catch (e) {
36701
- Log.error("chat error: ", e);
36702
- if (e instanceof Error) {
36703
- errorInfo = e.name + ": " + e.message;
36246
+ if (message?.error) {
36247
+ Log.error(`MCP ${method} error: ` + message.error);
36248
+ throw new Error(`MCP ${method} error: ` +
36249
+ (typeof message.error === "string"
36250
+ ? message.error
36251
+ : message.error.message));
36252
+ }
36253
+ if (message.result?.isError == true) {
36254
+ if (message.result.content) {
36255
+ throw new Error(`MCP ${method} error: ` +
36256
+ (typeof message.result.content === "string"
36257
+ ? message.result.content
36258
+ : message.result.content[0].text));
36704
36259
  }
36705
36260
  else {
36706
- errorInfo = String(e);
36707
- }
36708
- return errorInfo;
36709
- }
36710
- finally {
36711
- if (params.callback?.chatCallback) {
36712
- await params.callback.chatCallback.onMessage({
36713
- streamType: "chat",
36714
- chatId: this.chatContext.getChatId(),
36715
- messageId: params.messageId,
36716
- type: "chat_end",
36717
- error: errorInfo,
36718
- duration: Date.now() - runStartTime,
36719
- reactLoopNum: reactLoopNum + 1,
36720
- });
36261
+ throw new Error(`MCP ${method} error: ` + JSON.stringify(message.result));
36721
36262
  }
36722
36263
  }
36723
36264
  }
36724
- async initMessages() {
36725
- if (!global.chatService) {
36726
- return;
36727
- }
36728
- const messages = this.memory.getMessages();
36729
- if (messages.length == 0) {
36730
- const messages = await global.chatService.loadMessages(this.chatContext.getChatId());
36731
- if (messages && messages.length > 0) {
36732
- await this.memory.addMessages(messages);
36265
+ parseChunk(chunk) {
36266
+ const lines = chunk.split("\n");
36267
+ const chunk_obj = {};
36268
+ for (let j = 0; j < lines.length; j++) {
36269
+ const line = lines[j];
36270
+ if (line.startsWith("id:")) {
36271
+ chunk_obj["id"] = line.substring(3).trim();
36272
+ }
36273
+ else if (line.startsWith("event:")) {
36274
+ chunk_obj["event"] = line.substring(6).trim();
36275
+ }
36276
+ else if (line.startsWith("data:")) {
36277
+ chunk_obj["data"] = line.substring(5).trim();
36278
+ }
36279
+ else {
36280
+ const idx = line.indexOf(":");
36281
+ if (idx > -1) {
36282
+ chunk_obj[line.substring(0, idx)] = line.substring(idx + 1).trim();
36283
+ }
36733
36284
  }
36734
36285
  }
36286
+ return chunk_obj;
36735
36287
  }
36736
- async buildSystemPrompt(params, chatTools) {
36737
- let _memory = undefined;
36738
- if (global.chatService) {
36739
- try {
36740
- const userPrompt = params.user
36741
- .map((part) => (part.type == "text" ? part.text : ""))
36742
- .join("\n")
36743
- .trim();
36744
- if (userPrompt) {
36745
- _memory = await global.chatService.memoryRecall(this.chatContext.getChatId(), userPrompt);
36288
+ }
36289
+
36290
+ async function callChatLLM(messageId, chatContext, rlm, messages, tools, toolChoice, retryNum = 0, callback, signal) {
36291
+ const streamCallback = callback?.chatCallback || {
36292
+ onMessage: async () => { },
36293
+ };
36294
+ const request = {
36295
+ tools: tools,
36296
+ toolChoice,
36297
+ messages: messages,
36298
+ abortSignal: signal,
36299
+ };
36300
+ let streamText = "";
36301
+ let thinkText = "";
36302
+ let toolArgsText = "";
36303
+ let textStreamId = uuidv4();
36304
+ let thinkStreamId = uuidv4();
36305
+ let textStreamDone = false;
36306
+ const toolParts = [];
36307
+ let reader = null;
36308
+ try {
36309
+ const result = await rlm.callStream(request);
36310
+ reader = result.stream.getReader();
36311
+ let toolPart = null;
36312
+ while (true) {
36313
+ const { done, value } = await reader.read();
36314
+ if (done) {
36315
+ break;
36316
+ }
36317
+ const chunk = value;
36318
+ switch (chunk.type) {
36319
+ case "text-start": {
36320
+ textStreamId = uuidv4();
36321
+ break;
36322
+ }
36323
+ case "text-delta": {
36324
+ if (toolPart && !chunk.delta) {
36325
+ continue;
36326
+ }
36327
+ streamText += chunk.delta || "";
36328
+ await streamCallback.onMessage({
36329
+ streamType: "chat",
36330
+ chatId: chatContext.getChatId(),
36331
+ messageId,
36332
+ type: "text",
36333
+ streamId: textStreamId,
36334
+ streamDone: false,
36335
+ text: streamText,
36336
+ });
36337
+ if (toolPart) {
36338
+ await streamCallback.onMessage({
36339
+ streamType: "chat",
36340
+ chatId: chatContext.getChatId(),
36341
+ messageId,
36342
+ type: "tool_use",
36343
+ toolCallId: toolPart.toolCallId,
36344
+ toolName: toolPart.toolName,
36345
+ params: toolPart.input || {},
36346
+ });
36347
+ toolPart = null;
36348
+ }
36349
+ break;
36350
+ }
36351
+ case "text-end": {
36352
+ textStreamDone = true;
36353
+ if (streamText) {
36354
+ await streamCallback.onMessage({
36355
+ streamType: "chat",
36356
+ chatId: chatContext.getChatId(),
36357
+ messageId,
36358
+ type: "text",
36359
+ streamId: textStreamId,
36360
+ streamDone: true,
36361
+ text: streamText,
36362
+ });
36363
+ }
36364
+ break;
36365
+ }
36366
+ case "reasoning-start": {
36367
+ thinkStreamId = uuidv4();
36368
+ break;
36369
+ }
36370
+ case "reasoning-delta": {
36371
+ thinkText += chunk.delta || "";
36372
+ await streamCallback.onMessage({
36373
+ streamType: "chat",
36374
+ chatId: chatContext.getChatId(),
36375
+ messageId,
36376
+ type: "thinking",
36377
+ streamId: thinkStreamId,
36378
+ streamDone: false,
36379
+ text: thinkText,
36380
+ });
36381
+ break;
36382
+ }
36383
+ case "reasoning-end": {
36384
+ if (thinkText) {
36385
+ await streamCallback.onMessage({
36386
+ streamType: "chat",
36387
+ chatId: chatContext.getChatId(),
36388
+ messageId,
36389
+ type: "thinking",
36390
+ streamId: thinkStreamId,
36391
+ streamDone: true,
36392
+ text: thinkText,
36393
+ });
36394
+ }
36395
+ break;
36396
+ }
36397
+ case "tool-input-start": {
36398
+ if (toolPart && toolPart.toolCallId == chunk.id) {
36399
+ toolPart.toolName = chunk.toolName;
36400
+ }
36401
+ else {
36402
+ toolPart = {
36403
+ type: "tool-call",
36404
+ toolCallId: chunk.id,
36405
+ toolName: chunk.toolName,
36406
+ input: {},
36407
+ };
36408
+ toolParts.push(toolPart);
36409
+ }
36410
+ break;
36411
+ }
36412
+ case "tool-input-delta": {
36413
+ if (!textStreamDone) {
36414
+ textStreamDone = true;
36415
+ await streamCallback.onMessage({
36416
+ streamType: "chat",
36417
+ chatId: chatContext.getChatId(),
36418
+ messageId,
36419
+ type: "text",
36420
+ streamId: textStreamId,
36421
+ streamDone: true,
36422
+ text: streamText,
36423
+ });
36424
+ }
36425
+ toolArgsText += chunk.delta || "";
36426
+ await streamCallback.onMessage({
36427
+ streamType: "chat",
36428
+ chatId: chatContext.getChatId(),
36429
+ messageId,
36430
+ type: "tool_streaming",
36431
+ toolCallId: chunk.id,
36432
+ toolName: toolPart?.toolName || "",
36433
+ paramsText: toolArgsText,
36434
+ });
36435
+ break;
36436
+ }
36437
+ case "tool-call": {
36438
+ toolArgsText = "";
36439
+ const args = chunk.input ? JSON.parse(chunk.input) : {};
36440
+ const message = {
36441
+ streamType: "chat",
36442
+ chatId: chatContext.getChatId(),
36443
+ messageId,
36444
+ type: "tool_use",
36445
+ toolCallId: chunk.toolCallId,
36446
+ toolName: chunk.toolName,
36447
+ params: args,
36448
+ };
36449
+ await streamCallback.onMessage(message);
36450
+ if (toolPart == null) {
36451
+ toolParts.push({
36452
+ type: "tool-call",
36453
+ toolCallId: chunk.toolCallId,
36454
+ toolName: chunk.toolName,
36455
+ input: message.params || args,
36456
+ });
36457
+ }
36458
+ else {
36459
+ toolPart.input = message.params || args;
36460
+ toolPart = null;
36461
+ }
36462
+ break;
36463
+ }
36464
+ case "error": {
36465
+ Log.error(`chatLLM error: `, chunk);
36466
+ await streamCallback.onMessage({
36467
+ streamType: "chat",
36468
+ chatId: chatContext.getChatId(),
36469
+ messageId,
36470
+ type: "error",
36471
+ error: chunk.error,
36472
+ });
36473
+ throw new Error("LLM Error: " + chunk.error);
36474
+ }
36475
+ case "finish": {
36476
+ if (!textStreamDone) {
36477
+ textStreamDone = true;
36478
+ await streamCallback.onMessage({
36479
+ streamType: "chat",
36480
+ chatId: chatContext.getChatId(),
36481
+ messageId,
36482
+ type: "text",
36483
+ streamId: textStreamId,
36484
+ streamDone: true,
36485
+ text: streamText,
36486
+ });
36487
+ }
36488
+ if (toolPart) {
36489
+ await streamCallback.onMessage({
36490
+ streamType: "chat",
36491
+ chatId: chatContext.getChatId(),
36492
+ messageId,
36493
+ type: "tool_use",
36494
+ toolCallId: toolPart.toolCallId,
36495
+ toolName: toolPart.toolName,
36496
+ params: toolPart.input || {},
36497
+ });
36498
+ toolPart = null;
36499
+ }
36500
+ await streamCallback.onMessage({
36501
+ streamType: "chat",
36502
+ chatId: chatContext.getChatId(),
36503
+ messageId,
36504
+ type: "finish",
36505
+ finishReason: chunk.finishReason,
36506
+ usage: {
36507
+ promptTokens: chunk.usage.inputTokens || 0,
36508
+ completionTokens: chunk.usage.outputTokens || 0,
36509
+ totalTokens: chunk.usage.totalTokens ||
36510
+ (chunk.usage.inputTokens || 0) +
36511
+ (chunk.usage.outputTokens || 0),
36512
+ },
36513
+ });
36514
+ break;
36746
36515
  }
36747
36516
  }
36748
- catch (e) {
36749
- Log.error("chat service memory recall error: ", e);
36750
- }
36751
- }
36752
- let _tabs = undefined;
36753
- if (global.browserService) {
36754
- try {
36755
- _tabs = await global.browserService.loadTabs(this.chatContext.getChatId());
36756
- }
36757
- catch (e) {
36758
- Log.error("browser service load tabs error: ", e);
36759
- }
36760
- }
36761
- const datetime = params.datetime || new Date().toLocaleString();
36762
- const systemPrompt = getChatSystemPrompt(chatTools, datetime, _memory, _tabs);
36763
- this.memory.setSystemPrompt(systemPrompt);
36764
- }
36765
- async addUserMessage(messageId, user) {
36766
- const message = {
36767
- id: messageId,
36768
- role: "user",
36769
- timestamp: Date.now(),
36770
- content: user,
36771
- };
36772
- await this.addMessages([message]);
36773
- return message;
36774
- }
36775
- async addMessages(messages, storage = true) {
36776
- await this.memory.addMessages(messages);
36777
- if (storage && global.chatService) {
36778
- await global.chatService.addMessage(this.chatContext.getChatId(), messages);
36779
36517
  }
36780
36518
  }
36781
- buildInnerTools(params) {
36782
- const tools = [];
36783
- tools.push(new DeepActionTool(this.chatContext, params));
36784
- if (global.browserService) {
36785
- tools.push(new WebpageQaTool(this.chatContext, params));
36519
+ catch (e) {
36520
+ if (retryNum < config$1.maxRetryNum) {
36521
+ await sleep(200 * (retryNum + 1) * (retryNum + 1));
36522
+ return callChatLLM(messageId, chatContext, rlm, messages, tools, toolChoice, ++retryNum, callback, signal);
36786
36523
  }
36787
- tools.push(new WebSearchTool(this.chatContext, params));
36788
- tools.push(new TaskVariableStorageTool(this.chatContext, params));
36789
- return tools;
36524
+ throw e;
36790
36525
  }
36791
- getChatContext() {
36792
- return this.chatContext;
36526
+ finally {
36527
+ reader && reader.releaseLock();
36793
36528
  }
36794
- async handleCallResult(messageId, chatTools, results, chatStreamCallback) {
36795
- let text = null;
36796
- const toolResults = [];
36797
- if (results.length == 0) {
36798
- return null;
36799
- }
36800
- for (let i = 0; i < results.length; i++) {
36801
- const result = results[i];
36802
- if (result.type == "text") {
36803
- text = result.text;
36804
- continue;
36805
- }
36806
- let toolResult;
36807
- try {
36808
- const args = typeof result.input == "string"
36809
- ? JSON.parse(result.input || "{}")
36810
- : result.input || {};
36811
- const tool = getTool(chatTools, result.toolName);
36812
- if (!tool) {
36813
- throw new Error(result.toolName + " tool does not exist");
36814
- }
36815
- toolResult = await tool.execute(args, result, messageId);
36816
- }
36817
- catch (e) {
36818
- Log.error("tool call error: ", result.toolName, result.input, e);
36819
- toolResult = {
36820
- content: [
36821
- {
36822
- type: "text",
36823
- text: e + "",
36824
- },
36825
- ],
36826
- isError: true,
36827
- };
36828
- }
36829
- const callback = chatStreamCallback?.chatCallback;
36830
- if (callback) {
36831
- await callback.onMessage({
36832
- streamType: "chat",
36833
- chatId: this.chatContext.getChatId(),
36834
- messageId: messageId,
36835
- type: "tool_result",
36836
- toolCallId: result.toolCallId,
36837
- toolName: result.toolName,
36838
- params: result.input || {},
36839
- toolResult: toolResult,
36840
- });
36841
- }
36842
- const llmToolResult = convertToolResult(result, toolResult);
36843
- toolResults.push(llmToolResult);
36844
- }
36845
- await this.addMessages([
36846
- {
36847
- id: this.memory.genMessageId(),
36848
- role: "assistant",
36849
- timestamp: Date.now(),
36850
- content: convertAssistantToolResults(results),
36851
- },
36852
- ]);
36853
- if (toolResults.length > 0) {
36854
- await this.addMessages([
36855
- {
36856
- id: this.memory.genMessageId(),
36857
- role: "tool",
36858
- timestamp: Date.now(),
36859
- content: convertToolResults(toolResults),
36860
- },
36861
- ]);
36862
- return null;
36529
+ return streamText
36530
+ ? [
36531
+ { type: "text", text: streamText },
36532
+ ...toolParts,
36533
+ ]
36534
+ : toolParts;
36535
+ }
36536
+ function convertAssistantToolResults(results) {
36537
+ return results.map((part) => {
36538
+ if (part.type == "text") {
36539
+ return {
36540
+ type: "text",
36541
+ text: part.text,
36542
+ };
36863
36543
  }
36864
- else {
36865
- return text;
36544
+ else if (part.type == "tool-call") {
36545
+ return {
36546
+ type: "tool-call",
36547
+ toolCallId: part.toolCallId,
36548
+ toolName: part.toolName,
36549
+ args: (part.input || {}),
36550
+ };
36866
36551
  }
36867
- }
36552
+ return part;
36553
+ });
36554
+ }
36555
+ function convertToolResults(toolResults) {
36556
+ return toolResults.map((part) => {
36557
+ const output = part.output;
36558
+ return {
36559
+ type: "tool-result",
36560
+ toolCallId: part.toolCallId,
36561
+ toolName: part.toolName,
36562
+ result: output.type == "text" || output.type == "error-text"
36563
+ ? output.value
36564
+ : output.type == "json" || output.type == "error-json"
36565
+ ? output.value
36566
+ : output.value
36567
+ .map((s) => {
36568
+ if (s.type == "text") {
36569
+ return s.text;
36570
+ }
36571
+ else if (s.type == "media") {
36572
+ return JSON.stringify({
36573
+ data: s.data,
36574
+ mimeType: s.mediaType,
36575
+ });
36576
+ }
36577
+ })
36578
+ .join("\n"),
36579
+ };
36580
+ });
36868
36581
  }
36869
36582
 
36870
- class SimpleSseMcpClient {
36871
- constructor(sseServerUrl, clientName = "EkoMcpClient", headers = {}) {
36872
- this.protocolVersion = "2024-11-05";
36873
- this.sseUrl = sseServerUrl;
36874
- this.clientName = clientName;
36875
- this.headers = headers;
36876
- this.requestMap = new Map();
36877
- }
36878
- async connect(signal) {
36879
- Log.info("MCP Client, connecting...", this.sseUrl);
36880
- if (this.sseHandler && this.sseHandler.readyState == 1) {
36881
- this.sseHandler.close && this.sseHandler.close();
36882
- this.sseHandler = undefined;
36883
- }
36884
- this.pingTimer && clearInterval(this.pingTimer);
36885
- this.reconnectTimer && clearTimeout(this.reconnectTimer);
36886
- await new Promise((resolve) => {
36887
- const timer = setTimeout(resolve, 15000);
36888
- this.sseHandler = {
36889
- onopen: () => {
36890
- Log.info("MCP Client, connection successful", this.sseUrl);
36891
- clearTimeout(timer);
36892
- setTimeout(resolve, 200);
36893
- },
36894
- onmessage: (data) => this.onmessage(data),
36895
- onerror: (e) => {
36896
- Log.error("MCP Client, error: ", e);
36897
- clearTimeout(timer);
36898
- if (this.sseHandler?.readyState === 2) {
36899
- this.pingTimer && clearInterval(this.pingTimer);
36900
- this.reconnectTimer = setTimeout(() => {
36901
- this.connect();
36902
- }, 500);
36903
- }
36904
- resolve();
36905
- },
36906
- };
36907
- connectSse(this.sseUrl, this.sseHandler, this.headers, signal);
36908
- });
36909
- this.pingTimer = setInterval(() => this.ping(), 10000);
36583
+ class ChatContext {
36584
+ constructor(chatId, config) {
36585
+ this.chatId = chatId;
36586
+ this.config = config;
36587
+ this.ekoMap = new Map();
36588
+ this.globalVariables = new Map();
36910
36589
  }
36911
- onmessage(data) {
36912
- Log.debug("MCP Client, onmessage", this.sseUrl, data);
36913
- if (data.event == "endpoint") {
36914
- let uri = data.data;
36915
- let msgUrl;
36916
- let idx = this.sseUrl.indexOf("/", 10);
36917
- if (idx > -1) {
36918
- msgUrl = this.sseUrl.substring(0, idx) + uri;
36919
- }
36920
- else {
36921
- msgUrl = this.sseUrl + uri;
36922
- }
36923
- this.msgUrl = msgUrl;
36924
- this.initialize();
36925
- }
36926
- else if (data.event == "message") {
36927
- let message = JSON.parse(data.data);
36928
- let _resolve = this.requestMap.get(message.id);
36929
- _resolve && _resolve(message);
36930
- }
36590
+ getChatId() {
36591
+ return this.chatId;
36931
36592
  }
36932
- async initialize() {
36933
- await this.request("initialize", {
36934
- protocolVersion: this.protocolVersion,
36935
- capabilities: {
36936
- tools: {
36937
- listChanged: true,
36938
- },
36939
- sampling: {},
36940
- },
36941
- clientInfo: {
36942
- name: this.clientName,
36943
- version: "1.0.0",
36944
- },
36945
- });
36946
- try {
36947
- await this.request("notifications/initialized", {});
36948
- }
36949
- catch (ignored) { }
36593
+ getConfig() {
36594
+ return this.config;
36950
36595
  }
36951
- ping() {
36952
- this.request("ping", {});
36596
+ addEko(taskId, eko) {
36597
+ this.ekoMap.set(taskId, eko);
36953
36598
  }
36954
- async listTools(param, signal) {
36955
- const message = await this.request("tools/list", {
36956
- ...param,
36957
- }, signal);
36958
- return message.result.tools || [];
36599
+ getEko(taskId) {
36600
+ return this.ekoMap.get(taskId);
36959
36601
  }
36960
- async callTool(param, signal) {
36961
- const message = await this.request("tools/call", {
36962
- ...param,
36963
- }, signal);
36964
- return message.result;
36602
+ getGlobalVariables() {
36603
+ return this.globalVariables;
36965
36604
  }
36966
- async request(method, params, signal) {
36967
- const id = method.startsWith("notifications/") ? undefined : uuidv4();
36605
+ }
36606
+
36607
+ const TOOL_NAME$3 = "webpageQa";
36608
+ const WEBPAGE_QA_PROMPT = `
36609
+ You are a helpful assistant that can answer questions based on the provided webpage context.
36610
+
36611
+ # Webpage Context
36612
+ <webpage_contexts>
36613
+ {{contexts}}
36614
+ </webpage_contexts>
36615
+
36616
+ # User Question
36617
+ <user_question>
36618
+ {{userPrompt}}
36619
+ </user_question>
36620
+ <if language>
36621
+ <language>{{language}}</language>
36622
+ </if>
36623
+
36624
+ Answer user's question based on the webpage context, the answer should be in the same language as the user's question.
36625
+ `;
36626
+ class WebpageQaTool {
36627
+ constructor(chatContext, params) {
36628
+ this.name = TOOL_NAME$3;
36629
+ this.params = params;
36630
+ this.chatContext = chatContext;
36631
+ this.description = `This tool is designed only for handling simple web-related tasks, including summarizing webpage content, extracting data from web pages, translating webpage content, and converting webpage information into more easily understandable forms. It does not interact with or operate web pages. For more complex browser tasks, please use deepAction.It does not perform operations on the webpage itself, but only involves reading the page content. Users do not need to provide the web page content, as the tool can automatically extract the content of the web page based on the tabId to respond.`;
36632
+ this.parameters = {
36633
+ type: "object",
36634
+ properties: {
36635
+ language: {
36636
+ type: "string",
36637
+ description: "User language used, eg: English",
36638
+ },
36639
+ tabIds: {
36640
+ type: "array",
36641
+ description: "The browser tab ids to be used for the QA. When the user says 'left side' or 'current', it means current active tab.",
36642
+ items: { type: "integer" },
36643
+ },
36644
+ },
36645
+ required: ["tabIds", "language"],
36646
+ };
36647
+ }
36648
+ async execute(args, toolCall, messageId) {
36649
+ if (!global.browserService) {
36650
+ return {
36651
+ content: [
36652
+ {
36653
+ type: "text",
36654
+ text: "Error: not implemented",
36655
+ },
36656
+ ],
36657
+ };
36658
+ }
36659
+ const tabIds = args.tabIds;
36660
+ const language = args.language;
36661
+ const tabs = await global.browserService.extractPageContents(this.chatContext.getChatId(), tabIds);
36662
+ const chatConfig = this.chatContext.getConfig();
36663
+ const rlm = new RetryLanguageModel(chatConfig.llms, chatConfig.chatLlms);
36664
+ const prompt = PromptTemplate.render(global.prompts.get(GlobalPromptKey.webpage_qa_prompt) ||
36665
+ WEBPAGE_QA_PROMPT, {
36666
+ language: language,
36667
+ userPrompt: this.params.user
36668
+ .map((part) => (part.type == "text" ? part.text : ""))
36669
+ .join("\n")
36670
+ .trim(),
36671
+ contexts: this.buildTabContents(tabs),
36672
+ }).trim();
36673
+ const result = await rlm.callStream({
36674
+ temperature: 0.7,
36675
+ maxOutputTokens: config$1.maxOutputTokens,
36676
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
36677
+ });
36678
+ const stream = result.stream;
36679
+ const reader = stream.getReader();
36680
+ const streamId = uuidv4();
36681
+ const callback = this.params.callback.chatCallback;
36682
+ let text = "";
36968
36683
  try {
36969
- const callback = new Promise((resolve, reject) => {
36970
- if (signal) {
36971
- signal.addEventListener("abort", () => {
36972
- const error = new Error("Operation was interrupted");
36973
- error.name = "AbortError";
36974
- reject(error);
36684
+ while (true) {
36685
+ const { done, value } = await reader.read();
36686
+ if (done) {
36687
+ break;
36688
+ }
36689
+ const chunk = value;
36690
+ if (chunk.type == "text-delta") {
36691
+ text += chunk.delta;
36692
+ await callback.onMessage({
36693
+ streamType: "chat",
36694
+ chatId: this.chatContext.getChatId(),
36695
+ messageId: messageId,
36696
+ type: "tool_running",
36697
+ toolName: this.name,
36698
+ toolCallId: toolCall.toolCallId,
36699
+ text: text,
36700
+ streamId: streamId,
36701
+ streamDone: false,
36975
36702
  });
36976
36703
  }
36977
- id && this.requestMap.set(id, resolve);
36978
- });
36979
- Log.debug(`MCP Client, ${method}`, id, params);
36980
- const response = await fetch(this.msgUrl, {
36981
- method: "POST",
36982
- headers: {
36983
- "Content-Type": "application/json",
36984
- ...this.headers,
36985
- },
36986
- body: JSON.stringify({
36987
- jsonrpc: "2.0",
36988
- id: id,
36989
- method: method,
36990
- params: {
36991
- ...params,
36992
- },
36993
- }),
36994
- signal: signal,
36995
- });
36996
- const body = await response.text();
36997
- if (body == "Accepted") {
36998
- const message = await callback;
36999
- if (message.error) {
37000
- Log.error(`MCP ${method} error: ` + message.error);
37001
- throw new Error(`MCP ${method} error: ` +
37002
- (typeof message.error === "string"
37003
- ? message.error
37004
- : message.error.message));
36704
+ else if (chunk.type == "error") {
36705
+ throw new Error(chunk.error);
37005
36706
  }
37006
- if (message.result?.isError == true) {
37007
- if (message.result.content) {
37008
- throw new Error(`MCP ${method} error: ` +
37009
- (typeof message.result.content === "string"
37010
- ? message.result.content
37011
- : message.result.content[0].text));
37012
- }
37013
- else {
37014
- throw new Error(`MCP ${method} error: ` + JSON.stringify(message.result));
37015
- }
36707
+ else if (chunk.type == "finish") {
36708
+ break;
37016
36709
  }
37017
- return message;
37018
- }
37019
- else {
37020
- throw new Error(`MCP ${method} error:` + body);
37021
36710
  }
37022
36711
  }
37023
36712
  finally {
37024
- id && this.requestMap.delete(id);
37025
- }
37026
- }
37027
- isConnected() {
37028
- if (this.sseHandler && this.sseHandler.readyState == 1) {
37029
- return true;
37030
- }
37031
- return false;
37032
- }
37033
- async close() {
37034
- try {
37035
- await this.request("notifications/cancelled", {
37036
- requestId: uuidv4(),
37037
- reason: "User requested cancellation",
36713
+ reader.releaseLock();
36714
+ await callback.onMessage({
36715
+ streamType: "chat",
36716
+ chatId: this.chatContext.getChatId(),
36717
+ messageId: messageId,
36718
+ type: "tool_running",
36719
+ toolName: this.name,
36720
+ toolCallId: toolCall.toolCallId,
36721
+ text: text,
36722
+ streamId: streamId,
36723
+ streamDone: true,
37038
36724
  });
37039
36725
  }
37040
- catch (ignored) { }
37041
- this.pingTimer && clearInterval(this.pingTimer);
37042
- this.reconnectTimer && clearTimeout(this.reconnectTimer);
37043
- this.sseHandler && this.sseHandler.close && this.sseHandler.close();
37044
- this.pingTimer = undefined;
37045
- this.sseHandler = undefined;
37046
- this.reconnectTimer = undefined;
36726
+ return {
36727
+ content: [
36728
+ {
36729
+ type: "text",
36730
+ text: text,
36731
+ },
36732
+ ],
36733
+ };
36734
+ }
36735
+ buildTabContents(tabs) {
36736
+ return tabs
36737
+ .map((tab) => {
36738
+ return `<webpage>\nTabId: ${tab.tabId}\nTitle: ${tab.title}\nURL: ${tab.url}\nContent: ${sub(tab.content, 8000)}\n</webpage>`;
36739
+ })
36740
+ .join("\n");
37047
36741
  }
37048
36742
  }
37049
- async function connectSse(sseUrl, hander, headers = {}, _signal) {
37050
- try {
37051
- hander.readyState = 0;
37052
- const controller = new AbortController();
37053
- const signal = _signal
37054
- ? AbortSignal.any([controller.signal, _signal])
37055
- : controller.signal;
37056
- const response = await fetch(sseUrl, {
37057
- method: "GET",
37058
- headers: {
37059
- "Content-Type": "text/event-stream",
37060
- "Cache-Control": "no-cache",
37061
- ...headers,
36743
+
36744
+ const TOOL_NAME$2 = "webSearch";
36745
+ class WebSearchTool {
36746
+ constructor(chatContext, params) {
36747
+ this.name = TOOL_NAME$2;
36748
+ this.params = params;
36749
+ this.chatContext = chatContext;
36750
+ this.description = `Search the web for information using search engine API. This tool can perform web searches to find current information, news, articles, and other web content related to the query. It returns search results with titles, descriptions, URLs, and other relevant metadata, use this tool when users need the latest data/information and have NOT specified a particular platform or website, use the search tool.`;
36751
+ this.parameters = {
36752
+ type: "object",
36753
+ properties: {
36754
+ query: {
36755
+ type: "string",
36756
+ description: "The search query to execute. Use specific keywords and phrases for better results.",
36757
+ },
36758
+ language: {
36759
+ type: "string",
36760
+ description: "Language code for search results (e.g., 'en', 'zh', 'ja'). If not specified, will be auto-detected from query.",
36761
+ },
36762
+ count: {
36763
+ type: "integer",
36764
+ description: "Number of search results to return (default: 10, max: 50)",
36765
+ default: 10,
36766
+ minimum: 1,
36767
+ maximum: 50,
36768
+ },
37062
36769
  },
37063
- body: null,
37064
- keepalive: true,
37065
- signal: signal,
37066
- });
37067
- const reader = response.body?.getReader();
37068
- hander.close = () => {
37069
- controller.abort();
37070
- hander.readyState = 2;
37071
- Log.debug("McpClient close abort.", sseUrl);
36770
+ required: ["query", "keywords"],
37072
36771
  };
37073
- let str = "";
37074
- const decoder = new TextDecoder();
37075
- hander.readyState = 1;
37076
- hander.onopen();
37077
- while (hander.readyState == 1) {
37078
- const { value, done } = await reader?.read();
37079
- if (done) {
37080
- break;
37081
- }
37082
- const text = decoder.decode(value);
37083
- str += text;
37084
- if (str.indexOf("\n\n") > -1) {
37085
- const chunks = str.split("\n\n");
37086
- for (let i = 0; i < chunks.length - 1; i++) {
37087
- const chunk = chunks[i];
37088
- const chunkData = parseChunk(chunk);
37089
- hander.onmessage(chunkData);
37090
- }
37091
- str = chunks[chunks.length - 1];
37092
- }
37093
- }
37094
36772
  }
37095
- catch (e) {
37096
- if (e?.name !== "AbortError") {
37097
- Log.error("MCP Client, connectSse error:", e);
37098
- hander.onerror(e);
36773
+ async execute(args) {
36774
+ if (!global.chatService) {
36775
+ return {
36776
+ content: [
36777
+ {
36778
+ type: "text",
36779
+ text: "Error: not implemented",
36780
+ },
36781
+ ],
36782
+ };
37099
36783
  }
36784
+ const query = args.query;
36785
+ const language = args.language;
36786
+ const count = args.count || 10;
36787
+ const results = await global.chatService.websearch(this.chatContext.getChatId(), query, undefined, language, count);
36788
+ return Promise.resolve({
36789
+ content: [
36790
+ {
36791
+ type: "text",
36792
+ text: JSON.stringify(results.map((result) => {
36793
+ return {
36794
+ title: result.title,
36795
+ url: result.url,
36796
+ content: sub(result.content || result.snippet || "", 6000),
36797
+ };
36798
+ })),
36799
+ },
36800
+ ],
36801
+ });
37100
36802
  }
37101
- finally {
37102
- hander.readyState = 2;
36803
+ }
36804
+
36805
+ async function recursiveTextNode(node, callback) {
36806
+ if (node.type === "normal") {
36807
+ callback(node, node);
36808
+ }
36809
+ if (node.type === "forEach") {
36810
+ node.nodes.map((item) => recursiveTextNode(item, callback));
36811
+ }
36812
+ if (node.type === "watch") {
36813
+ node.triggerNodes.map((triggerNode) => recursiveTextNode(triggerNode, callback));
37103
36814
  }
37104
36815
  }
37105
- function parseChunk(chunk) {
37106
- const lines = chunk.split("\n");
37107
- const chunk_obj = {};
37108
- for (let j = 0; j < lines.length; j++) {
37109
- const line = lines[j];
37110
- if (line.startsWith("id:")) {
37111
- chunk_obj["id"] = line.substring(3).trim();
37112
- }
37113
- else if (line.startsWith("event:")) {
37114
- chunk_obj["event"] = line.substring(6).trim();
36816
+
36817
+ const TOOL_NAME$1 = "deepAction";
36818
+ const deep_action_description = "Delegate tasks to a Javis AI assistant for completion. This assistant can understand natural language instructions and has full control over both networked computers, browser agent, and multiple specialized agents ({agentNames}). The assistant can autonomously decide to use various software tools, browse the internet to query information, write code, and perform direct operations to complete tasks. He can deliver various digitized outputs (text reports, tables, images, music, videos, websites, deepSearch, programs, etc.) and handle design/analysis tasks. and execute operational tasks (such as batch following bloggers of specific topics on certain websites). For operational tasks, the focus is on completing the process actions rather than delivering final outputs, and the assistant can complete these types of tasks well. It should also be noted that users may actively mention deepsearch, which is also one of the capabilities of this tool. If users mention it, please explicitly tell the assistant to use deepsearch. Supports parallel execution of multiple tasks.";
36819
+ const deep_action_param_task_description = "Task description, please output the user's original instructions without omitting any information from the user's instructions, and use the same language as the user's question.";
36820
+ class DeepActionTool {
36821
+ constructor(chatContext, params) {
36822
+ this.name = TOOL_NAME$1;
36823
+ this.chatContext = chatContext;
36824
+ const agents = this.chatContext.getConfig().agents || [];
36825
+ const agentNames = agents.map((agent) => agent.Name).join(", ");
36826
+ const description = global.prompts.get(GlobalPromptKey.deep_action_description) ||
36827
+ deep_action_description;
36828
+ const paramTaskDescription = global.prompts.get(GlobalPromptKey.deep_action_param_task_description) ||
36829
+ deep_action_param_task_description;
36830
+ this.description = description.replace("{agentNames}", agentNames).trim();
36831
+ this.parameters = {
36832
+ type: "object",
36833
+ properties: {
36834
+ language: {
36835
+ type: "string",
36836
+ description: "User language used, eg: English",
36837
+ },
36838
+ taskDescription: {
36839
+ type: "string",
36840
+ description: paramTaskDescription.trim(),
36841
+ },
36842
+ tabIds: {
36843
+ type: "array",
36844
+ description: "Browser Tab IDs associated with this task, When user says 'left side' or 'current', it means current active tab",
36845
+ items: { type: "integer" },
36846
+ },
36847
+ dependentVariables: {
36848
+ type: "array",
36849
+ description: "The current task relies on variable data from prerequisite execution outputs. Provide the name of the dependent variable.",
36850
+ items: {
36851
+ type: "string",
36852
+ },
36853
+ },
36854
+ },
36855
+ required: ["language", "taskDescription"],
36856
+ };
36857
+ this.params = params;
36858
+ }
36859
+ async execute(args, toolCall, messageId) {
36860
+ const chatId = this.chatContext.getChatId();
36861
+ const language = args.language;
36862
+ const taskDescription = args.taskDescription;
36863
+ const tabIds = args.tabIds;
36864
+ const dependentVariables = args.dependentVariables;
36865
+ const config = this.chatContext.getConfig();
36866
+ const globalVariables = this.chatContext.getGlobalVariables();
36867
+ const eko = new Eko({
36868
+ ...config,
36869
+ callback: this.params.callback?.taskCallback,
36870
+ }, chatId);
36871
+ this.chatContext.addEko(messageId, eko);
36872
+ if (this.params.signal) {
36873
+ if (this.params.signal.aborted) {
36874
+ const error = new Error("Operation was interrupted");
36875
+ error.name = "AbortError";
36876
+ throw error;
36877
+ }
36878
+ this.params.signal.addEventListener("abort", () => {
36879
+ eko.abortTask(messageId, "User aborted");
36880
+ });
37115
36881
  }
37116
- else if (line.startsWith("data:")) {
37117
- chunk_obj["data"] = line.substring(5).trim();
36882
+ const attachments = this.params.user
36883
+ .filter((part) => part.type === "file")
36884
+ .filter((part) => part.data && part.data.length < 500)
36885
+ .map((part) => {
36886
+ return {
36887
+ file_name: part.filename,
36888
+ file_path: part.filePath,
36889
+ file_url: part.data,
36890
+ };
36891
+ });
36892
+ const taskWebsite = await this.gettaskWebsite(tabIds);
36893
+ const workflow = await eko.generate(taskDescription, messageId, {
36894
+ ...globalVariables,
36895
+ tabIds: tabIds,
36896
+ language: language,
36897
+ attachments: attachments,
36898
+ taskWebsite: taskWebsite,
36899
+ dependentVariables: dependentVariables,
36900
+ datetime: this.params.datetime || new Date().toLocaleString(),
36901
+ });
36902
+ const context = eko.getTask(messageId);
36903
+ console.log("==> workflow", workflow);
36904
+ const result = await eko.execute(messageId);
36905
+ const variableNames = [];
36906
+ if (context.variables && context.variables.size > 0) {
36907
+ workflow.agents
36908
+ .map((agent) => agent.nodes)
36909
+ .flat()
36910
+ .forEach((node) => {
36911
+ recursiveTextNode(node, async (textNode) => {
36912
+ if (textNode.output) {
36913
+ variableNames.push(textNode.output);
36914
+ globalVariables.set(textNode.output, context.variables.get(textNode.output));
36915
+ }
36916
+ });
36917
+ });
37118
36918
  }
37119
- else {
37120
- const idx = line.indexOf(":");
37121
- if (idx > -1) {
37122
- chunk_obj[line.substring(0, idx)] = line.substring(idx + 1).trim();
37123
- }
36919
+ return {
36920
+ content: [
36921
+ {
36922
+ type: "text",
36923
+ text: JSON.stringify({
36924
+ taskPlan: workflow.xml,
36925
+ subAgents: context.chain.agents.map((agent) => {
36926
+ return {
36927
+ agent: agent.agent.name,
36928
+ subTask: agent.agent.task,
36929
+ agentResult: sub(agent.agentResult || "", 800, true),
36930
+ };
36931
+ }),
36932
+ variables: variableNames,
36933
+ taskResult: result.result,
36934
+ success: result.success,
36935
+ }),
36936
+ },
36937
+ ],
36938
+ };
36939
+ }
36940
+ async gettaskWebsite(tabIds) {
36941
+ if (!global.browserService) {
36942
+ return [];
37124
36943
  }
36944
+ const tabs = await global.browserService.loadTabs(this.chatContext.getChatId(), tabIds);
36945
+ return tabs.map((tab) => {
36946
+ return {
36947
+ tabId: tab.tabId,
36948
+ title: tab.title,
36949
+ url: sub(tab.url, 300),
36950
+ };
36951
+ });
37125
36952
  }
37126
- return chunk_obj;
37127
36953
  }
37128
36954
 
37129
- class SimpleHttpMcpClient {
37130
- constructor(httpUrl, clientName = "EkoMcpClient", headers = {}) {
37131
- this.protocolVersion = "2025-06-18";
37132
- this.connected = false;
37133
- this.httpUrl = httpUrl;
37134
- this.clientName = clientName;
37135
- this.headers = headers;
37136
- }
37137
- async connect(signal) {
37138
- Log.info("MCP Client, connecting...", this.httpUrl);
37139
- this.mcpSessionId = null;
37140
- await this.request("initialize", {
37141
- protocolVersion: this.protocolVersion,
37142
- capabilities: {
37143
- tools: {
37144
- listChanged: true,
36955
+ const TOOL_NAME = "taskVariableStorage";
36956
+ class TaskVariableStorageTool {
36957
+ constructor(chatContext, params) {
36958
+ this.name = TOOL_NAME;
36959
+ this.params = params;
36960
+ this.chatContext = chatContext;
36961
+ this.description = `Used for storing, reading, and retrieving variable data, and maintaining input/output variables in task nodes.`;
36962
+ this.parameters = {
36963
+ type: "object",
36964
+ properties: {
36965
+ operation: {
36966
+ type: "string",
36967
+ description: "variable storage operation type.",
36968
+ enum: ["read_variable", "write_variable", "list_all_variable"],
36969
+ },
36970
+ name: {
36971
+ type: "string",
36972
+ description: "variable name, required when reading and writing variables, If reading variables, it supports reading multiple variables separated by commas.",
36973
+ },
36974
+ value: {
36975
+ type: "string",
36976
+ description: "variable value, required when writing variables",
37145
36977
  },
37146
- sampling: {},
37147
- },
37148
- clientInfo: {
37149
- name: this.clientName,
37150
- version: "1.0.0",
37151
36978
  },
37152
- }, signal);
37153
- if (this.mcpSessionId) {
37154
- try {
37155
- await this.request("notifications/initialized", {});
36979
+ required: ["operation"],
36980
+ };
36981
+ }
36982
+ async execute(args) {
36983
+ let operation = args.operation;
36984
+ let resultText = "";
36985
+ switch (operation) {
36986
+ case "read_variable": {
36987
+ if (!args.name) {
36988
+ resultText = "Error: name is required";
36989
+ }
36990
+ else {
36991
+ let result = {};
36992
+ let name = args.name;
36993
+ let keys = name.split(",");
36994
+ for (let i = 0; i < keys.length; i++) {
36995
+ let key = keys[i].trim();
36996
+ let value = this.chatContext.getGlobalVariables().get(key);
36997
+ result[key] = value;
36998
+ }
36999
+ resultText = JSON.stringify(result);
37000
+ }
37001
+ break;
37002
+ }
37003
+ case "write_variable": {
37004
+ if (!args.name) {
37005
+ resultText = "Error: name is required";
37006
+ break;
37007
+ }
37008
+ if (args.value == undefined) {
37009
+ resultText = "Error: value is required";
37010
+ break;
37011
+ }
37012
+ let key = args.name;
37013
+ this.chatContext.getGlobalVariables().set(key.trim(), args.value);
37014
+ resultText = "success";
37015
+ break;
37016
+ }
37017
+ case "list_all_variable": {
37018
+ resultText = JSON.stringify([
37019
+ ...this.chatContext.getGlobalVariables().keys(),
37020
+ ]);
37021
+ break;
37156
37022
  }
37157
- catch (ignored) { }
37158
37023
  }
37159
- this.connected = true;
37160
- }
37161
- async listTools(param, signal) {
37162
- const message = await this.request("tools/list", {
37163
- ...param,
37164
- }, signal);
37165
- return message.result.tools || [];
37024
+ return {
37025
+ content: [
37026
+ {
37027
+ type: "text",
37028
+ text: resultText || "",
37029
+ },
37030
+ ],
37031
+ };
37166
37032
  }
37167
- async callTool(param, signal) {
37168
- const message = await this.request("tools/call", {
37169
- ...param,
37170
- }, signal);
37171
- return message.result;
37033
+ }
37034
+
37035
+ const CHAT_SYSTEM_TEMPLATE = `
37036
+ You are {{name}}, it is an action-oriented assistant in the browser, a general-purpose intelligent agent running in the browser environment.
37037
+
37038
+ <tool_instructions>
37039
+ General Principles:
37040
+ - Only one tool can be called at a time.
37041
+ - Users may not be able to clearly describe their needs in a single conversation. When needs are ambiguous or lack details, assistant can appropriately initiate follow-up questions before making tool calls. Follow-up rounds should not exceed two rounds.
37042
+ - Users may switch topics multiple times during ongoing conversations. When calling tools, assistant must focus ONLY on the current user question and ignore previous conversation topics unless they are directly related to the current request. Each question should be treated as independent unless explicitly building on previous context.
37043
+
37044
+ For non-chat related tasks issued by users, the following tools need to be called to complete them:
37045
+ <if ${TOOL_NAME$1}Tool>
37046
+ - ${TOOL_NAME$1}: This tool is used to execute tasks, delegate to Javis AI assistant with full computer control.
37047
+ </if>
37048
+ <if ${TOOL_NAME$3}Tool>
37049
+ - ${TOOL_NAME$3}: When a user's query involves finding content in a webpage within a browser tab, extracting webpage content, summarizing webpage content, translating webpage content, read PDF page content, or converting webpage content into a more understandable format, this tool should be used. If the task requires performing actions based on webpage content, deepAction should be used. only needs to provide the required invocation parameters according to the tool's needs; users do not need to manually provide the content of the browser tab.
37050
+ </if>
37051
+ <if ${TOOL_NAME$2}Tool>
37052
+ - ${TOOL_NAME$2}: Search the web for information using search engine API. This tool can perform web searches to find current information, news, articles, and other web content related to the query. It returns search results with titles, descriptions, URLs, and other relevant metadata. Use this tool when you need to find current information from the internet that may not be available in your training data.
37053
+ </if>
37054
+ <if ${TOOL_NAME}Tool>
37055
+ - ${TOOL_NAME}: This tool is used to read output variables from task nodes and write input variables to task nodes, mainly used to retrieve variable results after task execution is completed.
37056
+ </if>
37057
+ </tool_instructions>
37058
+
37059
+ <if memory>
37060
+ The assistant always focuses on the user's current question and will not allow previous conversation turns or irrelevant memory content to interfere with the response to the user's current question. Each question should be handled independently unless it explicitly builds upon prior context.
37061
+ Before responding to user questions, the assistant intelligently analyzes the relevance of memories. When responding, the assistant first determines whether the user's current question is related to information in the retrieved memories, and only incorporates memory data when there is clear contextual relevance. If the user's question is unrelated to the retrieved memories, the assistant will directly respond to the current question without referencing memory content, ensuring the conversation flows naturally.
37062
+ Avoid forcing the use of memories when they are irrelevant to the current context, prioritizing the accuracy and relevance of responses over the inclusion of memories.
37063
+ <retrieved_memories>
37064
+ {{memory}}
37065
+ </retrieved_memories>
37066
+ </if>
37067
+
37068
+ <if tabs>
37069
+ The information about the browser tabs currently open by the user is as follows:
37070
+ <browser_tabs>
37071
+ {{tabs}}
37072
+ </browser_tabs>
37073
+ </if>
37074
+
37075
+ Current datetime: {{datetime}}
37076
+ The output language should match the user's conversation language.
37077
+ `;
37078
+ function getChatSystemPrompt(tools, datetime, memory, tabs) {
37079
+ const systemPrompt = global.prompts.get(GlobalPromptKey.chat_system) || CHAT_SYSTEM_TEMPLATE;
37080
+ const toolVars = {};
37081
+ for (let i = 0; i < tools.length; i++) {
37082
+ toolVars[tools[i].name + "Tool"] = true;
37172
37083
  }
37173
- isConnected() {
37174
- return this.connected;
37084
+ return PromptTemplate.render(systemPrompt, {
37085
+ name: config$1.name,
37086
+ datetime: datetime,
37087
+ memory: memory || "",
37088
+ tabs: getTabsInfo(tabs),
37089
+ ...toolVars,
37090
+ }).trim();
37091
+ }
37092
+ function getTabsInfo(tabs) {
37093
+ if (!tabs || tabs.length == 0) {
37094
+ return "Empty";
37175
37095
  }
37176
- async close() {
37177
- this.connected = false;
37178
- if (this.mcpSessionId) {
37179
- try {
37180
- await this.request("notifications/cancelled", {
37181
- requestId: uuidv4(),
37182
- reason: "User requested cancellation",
37183
- });
37184
- }
37185
- catch (ignored) { }
37186
- this.mcpSessionId = null;
37187
- }
37096
+ return JSON.stringify(tabs.slice(0, 10).map((tab) => {
37097
+ return {
37098
+ tabId: tab.tabId,
37099
+ title: sub(tab.title, 50),
37100
+ url: sub(tab.url, 300),
37101
+ active: tab.active,
37102
+ lastAccessed: tab.lastAccessed,
37103
+ };
37104
+ }), null, 2);
37105
+ }
37106
+
37107
+ class ChatAgent {
37108
+ constructor(config, chatId = uuidv4(), memory, tools) {
37109
+ this.tools = tools ?? [];
37110
+ this.memory = memory ?? new EkoMemory();
37111
+ this.chatContext = new ChatContext(chatId, config);
37112
+ global.chatMap.set(chatId, this.chatContext);
37188
37113
  }
37189
- async request(method, params, signal) {
37190
- try {
37191
- const id = method.startsWith("notifications/") ? undefined : uuidv4();
37192
- const extHeaders = {};
37193
- if (this.mcpSessionId && method !== "initialize") {
37194
- extHeaders["Mcp-Session-Id"] = this.mcpSessionId;
37195
- }
37196
- const response = await fetch(this.httpUrl, {
37197
- method: "POST",
37198
- headers: {
37199
- "Cache-Control": "no-cache",
37200
- "Content-Type": "application/json",
37201
- Accept: "application/json, text/event-stream",
37202
- "MCP-Protocol-Version": this.protocolVersion,
37203
- ...extHeaders,
37204
- ...this.headers,
37205
- },
37206
- body: JSON.stringify({
37207
- jsonrpc: "2.0",
37208
- id: id,
37209
- method: method,
37210
- params: {
37211
- ...params,
37212
- },
37213
- }),
37214
- keepalive: true,
37215
- signal: signal,
37216
- });
37217
- if (method.startsWith("notifications/")) {
37218
- return;
37219
- }
37220
- if (method == "initialize") {
37221
- this.mcpSessionId =
37222
- response.headers.get("Mcp-Session-Id") ||
37223
- response.headers.get("mcp-session-id");
37114
+ async chat(params) {
37115
+ return this.doChat(params, false);
37116
+ }
37117
+ async doChat(params, segmentedExecution) {
37118
+ const runStartTime = Date.now();
37119
+ let reactLoopNum = 0;
37120
+ let errorInfo = null;
37121
+ try {
37122
+ if (params.callback?.chatCallback) {
37123
+ await params.callback.chatCallback.onMessage({
37124
+ streamType: "chat",
37125
+ chatId: this.chatContext.getChatId(),
37126
+ messageId: params.messageId,
37127
+ type: "chat_start",
37128
+ });
37224
37129
  }
37225
- const contentType = response.headers.get("Content-Type") ||
37226
- response.headers.get("content-type") ||
37227
- "application/json";
37228
- if (contentType?.includes("text/event-stream")) {
37229
- // SSE
37230
- const reader = response.body?.getReader();
37231
- let str = "";
37232
- let message;
37233
- const decoder = new TextDecoder();
37234
- while (true) {
37235
- const { value, done } = await reader?.read();
37236
- if (done) {
37237
- break;
37238
- }
37239
- const text = decoder.decode(value);
37240
- str += text;
37241
- if (str.indexOf("\n\n") > -1) {
37242
- const chunks = str.split("\n\n");
37243
- for (let i = 0; i < chunks.length - 1; i++) {
37244
- const chunk = chunks[i];
37245
- const chunkData = this.parseChunk(chunk);
37246
- if (chunkData.event == "message") {
37247
- message = JSON.parse(chunkData.data);
37248
- if (message.id == id) {
37249
- return message;
37250
- }
37251
- }
37252
- }
37253
- str = chunks[chunks.length - 1];
37254
- }
37130
+ const chatTools = mergeTools(this.buildInnerTools(params), this.tools);
37131
+ await this.buildSystemPrompt(params, chatTools);
37132
+ await this.addUserMessage(params.messageId, params.user);
37133
+ const config = this.chatContext.getConfig();
37134
+ const rlm = new RetryLanguageModel(config.llms, config.chatLlms);
37135
+ for (; reactLoopNum < 15; reactLoopNum++) {
37136
+ const messages = this.memory.buildMessages();
37137
+ const results = await callChatLLM(params.messageId, this.chatContext, rlm, messages, convertTools(chatTools), undefined, 0, params.callback, params.signal);
37138
+ const finalResult = await this.handleCallResult(params.messageId, chatTools, results, params.callback);
37139
+ if (finalResult) {
37140
+ return finalResult;
37255
37141
  }
37256
- this.handleError(method, message);
37257
- return message;
37142
+ if (params.signal?.aborted) {
37143
+ const error = new Error("Operation was interrupted");
37144
+ error.name = "AbortError";
37145
+ throw error;
37146
+ }
37147
+ }
37148
+ reactLoopNum--;
37149
+ return "Unfinished";
37150
+ }
37151
+ catch (e) {
37152
+ Log.error("chat error: ", e);
37153
+ if (e instanceof Error) {
37154
+ errorInfo = e.name + ": " + e.message;
37258
37155
  }
37259
37156
  else {
37260
- // JSON
37261
- const message = await response.json();
37262
- this.handleError(method, message);
37263
- return message;
37157
+ errorInfo = String(e);
37264
37158
  }
37159
+ return errorInfo;
37265
37160
  }
37266
- catch (e) {
37267
- if (e?.name !== "AbortError") {
37268
- Log.error("MCP Client, connectSse error:", e);
37161
+ finally {
37162
+ if (params.callback?.chatCallback) {
37163
+ await params.callback.chatCallback.onMessage({
37164
+ streamType: "chat",
37165
+ chatId: this.chatContext.getChatId(),
37166
+ messageId: params.messageId,
37167
+ type: "chat_end",
37168
+ error: errorInfo,
37169
+ duration: Date.now() - runStartTime,
37170
+ reactLoopNum: reactLoopNum + 1,
37171
+ });
37269
37172
  }
37270
- throw e;
37271
37173
  }
37272
37174
  }
37273
- handleError(method, message) {
37274
- if (!message) {
37275
- throw new Error(`MCP ${method} error: no response`);
37175
+ async initMessages() {
37176
+ if (!global.chatService) {
37177
+ return;
37276
37178
  }
37277
- if (message?.error) {
37278
- Log.error(`MCP ${method} error: ` + message.error);
37279
- throw new Error(`MCP ${method} error: ` +
37280
- (typeof message.error === "string"
37281
- ? message.error
37282
- : message.error.message));
37179
+ const messages = this.memory.getMessages();
37180
+ if (messages.length == 0) {
37181
+ const messages = await global.chatService.loadMessages(this.chatContext.getChatId());
37182
+ if (messages && messages.length > 0) {
37183
+ await this.memory.addMessages(messages);
37184
+ }
37283
37185
  }
37284
- if (message.result?.isError == true) {
37285
- if (message.result.content) {
37286
- throw new Error(`MCP ${method} error: ` +
37287
- (typeof message.result.content === "string"
37288
- ? message.result.content
37289
- : message.result.content[0].text));
37186
+ }
37187
+ async buildSystemPrompt(params, chatTools) {
37188
+ let _memory = undefined;
37189
+ if (global.chatService) {
37190
+ try {
37191
+ const userPrompt = params.user
37192
+ .map((part) => (part.type == "text" ? part.text : ""))
37193
+ .join("\n")
37194
+ .trim();
37195
+ if (userPrompt) {
37196
+ _memory = await global.chatService.memoryRecall(this.chatContext.getChatId(), userPrompt);
37197
+ }
37290
37198
  }
37291
- else {
37292
- throw new Error(`MCP ${method} error: ` + JSON.stringify(message.result));
37199
+ catch (e) {
37200
+ Log.error("chat service memory recall error: ", e);
37293
37201
  }
37294
37202
  }
37295
- }
37296
- parseChunk(chunk) {
37297
- const lines = chunk.split("\n");
37298
- const chunk_obj = {};
37299
- for (let j = 0; j < lines.length; j++) {
37300
- const line = lines[j];
37301
- if (line.startsWith("id:")) {
37302
- chunk_obj["id"] = line.substring(3).trim();
37203
+ let _tabs = undefined;
37204
+ if (global.browserService) {
37205
+ try {
37206
+ _tabs = await global.browserService.loadTabs(this.chatContext.getChatId());
37303
37207
  }
37304
- else if (line.startsWith("event:")) {
37305
- chunk_obj["event"] = line.substring(6).trim();
37208
+ catch (e) {
37209
+ Log.error("browser service load tabs error: ", e);
37306
37210
  }
37307
- else if (line.startsWith("data:")) {
37308
- chunk_obj["data"] = line.substring(5).trim();
37211
+ }
37212
+ const datetime = params.datetime || new Date().toLocaleString();
37213
+ const systemPrompt = getChatSystemPrompt(chatTools, datetime, _memory, _tabs);
37214
+ this.memory.setSystemPrompt(systemPrompt);
37215
+ }
37216
+ async addUserMessage(messageId, user) {
37217
+ const message = {
37218
+ id: messageId,
37219
+ role: "user",
37220
+ timestamp: Date.now(),
37221
+ content: user,
37222
+ };
37223
+ await this.addMessages([message]);
37224
+ return message;
37225
+ }
37226
+ async addMessages(messages, storage = true) {
37227
+ await this.memory.addMessages(messages);
37228
+ if (storage && global.chatService) {
37229
+ await global.chatService.addMessage(this.chatContext.getChatId(), messages);
37230
+ }
37231
+ }
37232
+ buildInnerTools(params) {
37233
+ const tools = [];
37234
+ tools.push(new DeepActionTool(this.chatContext, params));
37235
+ if (global.browserService) {
37236
+ tools.push(new WebpageQaTool(this.chatContext, params));
37237
+ }
37238
+ tools.push(new WebSearchTool(this.chatContext, params));
37239
+ tools.push(new TaskVariableStorageTool(this.chatContext, params));
37240
+ return tools;
37241
+ }
37242
+ getChatContext() {
37243
+ return this.chatContext;
37244
+ }
37245
+ async handleCallResult(messageId, chatTools, results, chatStreamCallback) {
37246
+ let text = null;
37247
+ const toolResults = [];
37248
+ if (results.length == 0) {
37249
+ return null;
37250
+ }
37251
+ for (let i = 0; i < results.length; i++) {
37252
+ const result = results[i];
37253
+ if (result.type == "text") {
37254
+ text = result.text;
37255
+ continue;
37309
37256
  }
37310
- else {
37311
- const idx = line.indexOf(":");
37312
- if (idx > -1) {
37313
- chunk_obj[line.substring(0, idx)] = line.substring(idx + 1).trim();
37257
+ let toolResult;
37258
+ try {
37259
+ const args = typeof result.input == "string"
37260
+ ? JSON.parse(result.input || "{}")
37261
+ : result.input || {};
37262
+ const tool = getTool(chatTools, result.toolName);
37263
+ if (!tool) {
37264
+ throw new Error(result.toolName + " tool does not exist");
37314
37265
  }
37266
+ toolResult = await tool.execute(args, result, messageId);
37267
+ }
37268
+ catch (e) {
37269
+ Log.error("tool call error: ", result.toolName, result.input, e);
37270
+ toolResult = {
37271
+ content: [
37272
+ {
37273
+ type: "text",
37274
+ text: e + "",
37275
+ },
37276
+ ],
37277
+ isError: true,
37278
+ };
37279
+ }
37280
+ const callback = chatStreamCallback?.chatCallback;
37281
+ if (callback) {
37282
+ await callback.onMessage({
37283
+ streamType: "chat",
37284
+ chatId: this.chatContext.getChatId(),
37285
+ messageId: messageId,
37286
+ type: "tool_result",
37287
+ toolCallId: result.toolCallId,
37288
+ toolName: result.toolName,
37289
+ params: result.input || {},
37290
+ toolResult: toolResult,
37291
+ });
37315
37292
  }
37293
+ const llmToolResult = convertToolResult(result, toolResult);
37294
+ toolResults.push(llmToolResult);
37295
+ }
37296
+ await this.addMessages([
37297
+ {
37298
+ id: this.memory.genMessageId(),
37299
+ role: "assistant",
37300
+ timestamp: Date.now(),
37301
+ content: convertAssistantToolResults(results),
37302
+ },
37303
+ ]);
37304
+ if (toolResults.length > 0) {
37305
+ await this.addMessages([
37306
+ {
37307
+ id: this.memory.genMessageId(),
37308
+ role: "tool",
37309
+ timestamp: Date.now(),
37310
+ content: convertToolResults(toolResults),
37311
+ },
37312
+ ]);
37313
+ return null;
37314
+ }
37315
+ else {
37316
+ return text;
37316
37317
  }
37317
- return chunk_obj;
37318
37318
  }
37319
37319
  }
37320
37320
 
37321
- export { Agent, AgentChain, AgentContext, BaseBrowserAgent, BaseBrowserLabelsAgent, BaseBrowserScreenAgent, Chain, ChatAgent, ChatContext, TaskContext as Context, Eko, EkoMemory, ForeachTaskTool, HumanInteractTool, Log, Planner, RetryLanguageModel, SimpleHttpMcpClient, SimpleSseMcpClient, TaskContext, TaskNodeStatusTool, VariableStorageTool, WatchTriggerTool, buildAgentTree, buildSimpleAgentWorkflow, call_timeout, compressImageData, config$1 as config, convertToolSchema, Eko as default, extract_page_content, global, mergeTools, parseWorkflow, resetWorkflowXml, sub, toFile, toImage, uuidv4 };
37321
+ export { Agent, AgentChain, AgentContext, BaseBrowserAgent, BaseBrowserLabelsAgent, BaseBrowserScreenAgent, Chain, ChatAgent, ChatContext, TaskContext as Context, DeepActionTool, Eko, EkoMemory, ForeachTaskTool, HumanInteractTool, Log, Planner, PromptTemplate, RetryLanguageModel, SimpleHttpMcpClient, SimpleSseMcpClient, TaskContext, TaskNodeStatusTool, TaskVariableStorageTool, VariableStorageTool, WatchTriggerTool, WebSearchTool, WebpageQaTool, buildAgentTree, buildSimpleAgentWorkflow, call_timeout, compressImageData, config$1 as config, convertToolSchema, Eko as default, extract_page_content, global, mergeTools, parseWorkflow, resetWorkflowXml, sub, toFile, toImage, uuidv4 };
37322
37322
  //# sourceMappingURL=index.esm.js.map