@xalia/agent 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/agent/src/agent/agent.js +3 -0
  2. package/dist/agent/src/agent/agentUtils.js +6 -12
  3. package/dist/agent/src/agent/mcpServerManager.js +1 -1
  4. package/dist/agent/src/agent/openAILLMStreaming.js +14 -7
  5. package/dist/agent/src/agent/sudoMcpServerManager.js +13 -13
  6. package/dist/agent/src/chat/client.js +24 -63
  7. package/dist/agent/src/chat/conversationManager.js +122 -12
  8. package/dist/agent/src/chat/db.js +9 -0
  9. package/dist/agent/src/chat/messages.js +27 -0
  10. package/dist/agent/src/chat/websocket.js +14 -0
  11. package/dist/agent/src/test/db.test.js +16 -0
  12. package/dist/agent/src/test/mcpServerManager.test.js +2 -2
  13. package/dist/agent/src/test/openaiStreaming.test.js +56 -28
  14. package/dist/agent/src/test/sudoMcpServerManager.test.js +10 -13
  15. package/dist/agent/src/tool/chatMain.js +7 -1
  16. package/dist/agent/src/tool/commandPrompt.js +9 -10
  17. package/package.json +1 -1
  18. package/src/agent/agent.ts +14 -4
  19. package/src/agent/agentUtils.ts +17 -23
  20. package/src/agent/mcpServerManager.ts +3 -1
  21. package/src/agent/openAILLMStreaming.ts +14 -7
  22. package/src/agent/sudoMcpServerManager.ts +17 -18
  23. package/src/chat/client.ts +35 -45
  24. package/src/chat/conversationManager.ts +147 -12
  25. package/src/chat/db.ts +14 -0
  26. package/src/chat/messages.ts +31 -0
  27. package/src/chat/websocket.ts +14 -0
  28. package/src/test/db.test.ts +22 -1
  29. package/src/test/mcpServerManager.test.ts +2 -2
  30. package/src/test/openaiStreaming.test.ts +64 -30
  31. package/src/test/sudoMcpServerManager.test.ts +11 -16
  32. package/src/tool/chatMain.ts +11 -2
  33. package/src/tool/commandPrompt.ts +8 -15
  34. package/dist/agent/src/chat/frontendClient.js +0 -74
  35. package/src/chat/frontendClient.ts +0 -123
@@ -92,6 +92,9 @@ class Agent {
92
92
  if (!userMessage) {
93
93
  return undefined;
94
94
  }
95
+ return this.userMessageRaw(userMessage);
96
+ }
97
+ async userMessageRaw(userMessage) {
95
98
  this.messages.push(userMessage);
96
99
  let completion = await this.chatCompletion();
97
100
  let message = completion.choices[0].message;
@@ -60,30 +60,24 @@ async function createAgent(llmUrl, model, systemPrompt, onMessage, onToolCall, p
60
60
  * Util function to create and initialize an Agent given an AgentProfile.
61
61
  */
62
62
  async function createAgentWithSkills(llmUrl, agentProfile, onMessage, onToolCall, platform, llmApiKey, sudomcpConfig, authorizedUrl, conversation, stream = false) {
63
- // Create agent
64
- logger.debug("[createAgentAndSudoMcpServerManager] creating agent ...");
65
- const agent = await createAgent(llmUrl, agentProfile.model, agentProfile.system_prompt, onMessage, onToolCall, platform, llmApiKey, stream);
66
- if (conversation) {
67
- agent.setConversation(conversation);
68
- }
69
63
  // Init SudoMcpServerManager
70
64
  logger.debug("[createAgentWithSkills] creating SudoMcpServerManager.");
71
- const sudoMcpServerManager = await sudoMcpServerManager_1.SkillManager.initialize(agent.getMcpServerManager(), platform.openUrl, sudomcpConfig.backend_url, sudomcpConfig.api_key, authorizedUrl);
65
+ const sudoMcpServerManager = await sudoMcpServerManager_1.SkillManager.initialize(platform.openUrl, sudomcpConfig.backend_url, sudomcpConfig.api_key, authorizedUrl);
72
66
  logger.debug("[createAgentWithSkills] restore mcp settings:" +
73
67
  JSON.stringify(agentProfile.mcp_settings));
74
68
  await sudoMcpServerManager.restoreMcpSettings(agentProfile.mcp_settings);
75
- logger.debug("[createAgentWithSkills] done");
69
+ // Create agent using the McpServerManager just created
70
+ const agent = await createAgentFromSkillManager(llmUrl, agentProfile, onMessage, onToolCall, platform, llmApiKey, sudoMcpServerManager, conversation, stream);
76
71
  return [agent, sudoMcpServerManager];
77
72
  }
78
73
  async function createAgentFromSkillManager(llmUrl, agentProfile, onMessage, onToolCall, platform, llmApiKey, skillManager, conversation, stream = false) {
79
74
  // Create agent
80
- logger.debug("[createAgentAndSudoMcpServerManager] creating agent ...");
81
- const mcpServerManager = skillManager.getMcpServerManager();
82
- const agent = await createAgent(llmUrl, agentProfile.model, agentProfile.system_prompt, onMessage, onToolCall, platform, llmApiKey, stream, mcpServerManager);
75
+ logger.debug("[createAgentFromSkillManager] creating agent ...");
76
+ const agent = await createAgent(llmUrl, agentProfile.model, agentProfile.system_prompt, onMessage, onToolCall, platform, llmApiKey, stream, skillManager);
83
77
  if (conversation) {
84
78
  agent.setConversation(conversation);
85
79
  }
86
- logger.debug("[createAgentWithSkills] done");
80
+ logger.debug("[createAgentFromSkillManager] done");
87
81
  return agent;
88
82
  }
89
83
  /**
@@ -121,7 +121,7 @@ class McpServerManager {
121
121
  getMcpServer(mcpServerName) {
122
122
  return this.getMcpServerInternal(mcpServerName);
123
123
  }
124
- async addMcpServer(mcpServerName, url, apiKey, tools) {
124
+ async addMcpServerWithSSEUrl(mcpServerName, url, apiKey, tools) {
125
125
  logger.debug(`Adding mcp server ${mcpServerName}: ${url}`);
126
126
  const sseTransportOptions = {};
127
127
  if (apiKey) {
@@ -263,9 +263,10 @@ function updateCompletionChoice(completionChoice, chunkChoice) {
263
263
  }
264
264
  function initializeCompletionChoices(chunkChoices) {
265
265
  // Technically, one choice could be done and the other still have some
266
- // content to stream. We keep it simple for now and assume only single
267
- // choices, which allows us to mark everything as done if any choice we hit is
268
- // done.
266
+ // content to stream. We keep it simple for now and allow zero or one
267
+ // choice per chunk, which allows us to mark everything as done if any
268
+ // choice we hit is done. Zero choices can occur in usage-only chunks at
269
+ // the end of the stream.
269
270
  (0, assert_1.strict)(chunkChoices.length < 2);
270
271
  let msgDone = false;
271
272
  const choices = [];
@@ -280,10 +281,11 @@ function initializeCompletionChoices(chunkChoices) {
280
281
  }
281
282
  function updateCompletionChoices(completionChoices, chunkChoices) {
282
283
  // Technically, one choice could be done and the other still have some
283
- // content to stream. We keep it simple for now and assume only single
284
- // choices, which allows us to mark everything as done if any choice we hit is
285
- // done.
286
- (0, assert_1.strict)(chunkChoices.length === 1);
284
+ // content to stream. We keep it simple for now and allow zero or one
285
+ // choice per chunk, which allows us to mark everything as done if any
286
+ // choice we hit is done. Zero choices can occur in usage-only chunks at
287
+ // the end of the stream.
288
+ (0, assert_1.strict)(chunkChoices.length < 2);
287
289
  (0, assert_1.strict)(completionChoices.length === 1);
288
290
  let msgDone = false;
289
291
  for (const chunkChoice of chunkChoices) {
@@ -391,6 +393,9 @@ class OpenAILLMStreaming {
391
393
  messages,
392
394
  tools,
393
395
  stream: true,
396
+ stream_options: {
397
+ include_usage: true,
398
+ },
394
399
  });
395
400
  // Check the type casting above
396
401
  if (!chunks.iterator) {
@@ -413,6 +418,8 @@ class OpenAILLMStreaming {
413
418
  }
414
419
  if (onMessage) {
415
420
  // Inform the call of a message fragment if it contains any text.
421
+ // Note: chunks may have zero choices (e.g., usage-only chunks), so
422
+ // we safely access the first choice.
416
423
  const delta = chunk.choices[0]?.delta;
417
424
  if (delta?.content) {
418
425
  await onMessage(delta.content, false);
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SkillManager = exports.LOCAL_SERVER_URL = void 0;
4
+ const mcpServerManager_1 = require("./mcpServerManager");
4
5
  const sdk_1 = require("@xalia/xmcp/sdk");
5
6
  const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
6
7
  const logger = (0, sdk_1.getLogger)();
@@ -25,11 +26,11 @@ class SanitizedServerBrief extends sdk_1.McpServerBrief {
25
26
  * Manages access to the catalogue of servers hosted by sudomcp. Supports
26
27
  * adding these servers to McpServerManager.
27
28
  */
28
- class SkillManager {
29
- constructor(mcpServerManager, apiClient, serverBriefs, serverBriefsMap, toolCache, openUrl,
29
+ class SkillManager extends mcpServerManager_1.McpServerManager {
30
+ constructor(apiClient, serverBriefs, serverBriefsMap, toolCache, openUrl,
30
31
  // Redirect to this page after successful authorization
31
32
  authorized_url) {
32
- this.mcpServerManager = mcpServerManager;
33
+ super();
33
34
  this.apiClient = apiClient;
34
35
  this.serverBriefs = serverBriefs;
35
36
  this.serverBriefsMap = serverBriefsMap;
@@ -41,13 +42,16 @@ class SkillManager {
41
42
  * Initialize an ApiClient to interface with SudoMCP backend and
42
43
  * fetch the current list of ServerBriefs.
43
44
  */
44
- static async initialize(mcpServerManager, openUrl, sudoMcpUrl, sudoMcpApiKey, authorized_url) {
45
+ static async initialize(openUrl, sudoMcpUrl, sudoMcpApiKey, authorized_url) {
45
46
  // TODO: Keep it on here and pass to `McpServerManager.addMcpServer`
46
47
  const apiClient = new sdk_1.ApiClient(sudoMcpUrl ?? sdk_1.DEFAULT_SERVER_URL, sudoMcpApiKey ?? "dummy_key");
47
48
  // Fetch server list
48
49
  const servers = await apiClient.listServers();
49
50
  const [mcpServers, mcpServersMap] = buildServersList(servers);
50
- return new SkillManager(mcpServerManager, apiClient, mcpServers, mcpServersMap, {}, openUrl, authorized_url);
51
+ return new SkillManager(apiClient, mcpServers, mcpServersMap, {}, openUrl, authorized_url);
52
+ }
53
+ async shutdown() {
54
+ return super.shutdown();
51
55
  }
52
56
  /// TODO: Bit awkward that we have to restore via this class, but it's the
53
57
  /// only class which knows how to restore (restart) the mcp servers.
@@ -73,12 +77,12 @@ class SkillManager {
73
77
  }
74
78
  if (enabled.length === 0) {
75
79
  logger.debug(` restoring "${serverName}": (all tools)`);
76
- this.mcpServerManager.enableAllTools(serverName);
80
+ this.enableAllTools(serverName);
77
81
  return;
78
82
  }
79
83
  logger.debug(` restoring "${serverName}": ${JSON.stringify(enabled)}`);
80
84
  for (const toolName of enabled) {
81
- this.mcpServerManager.enableTool(serverName, toolName);
85
+ this.enableTool(serverName, toolName);
82
86
  }
83
87
  };
84
88
  Object.entries(mcpConfig).map((e) => enableTools(e));
@@ -99,9 +103,6 @@ class SkillManager {
99
103
  getServerBriefs() {
100
104
  return this.serverBriefs;
101
105
  }
102
- getMcpServerManager() {
103
- return this.mcpServerManager;
104
- }
105
106
  /**
106
107
  * Return tool list for a given MCP server. Queries the backend
107
108
  * if necessary and caches the result.
@@ -131,10 +132,9 @@ class SkillManager {
131
132
  version: "1.0.0",
132
133
  });
133
134
  await connectServer(client, this.apiClient, mcpserver, this.openUrl, this.authorized_url);
134
- const msm = this.mcpServerManager;
135
- await msm.addMcpServerWithClient(client, serverName, tools);
135
+ await this.addMcpServerWithClient(client, serverName, tools);
136
136
  if (enableAll) {
137
- msm.enableAllTools(serverName);
137
+ this.enableAllTools(serverName);
138
138
  }
139
139
  }
140
140
  getOriginalName(serverName) {
@@ -1,48 +1,20 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.ChatClient = void 0;
37
4
  const assert_1 = require("assert");
38
- const websocket = __importStar(require("ws"));
39
5
  const sdk_1 = require("@xalia/xmcp/sdk");
40
6
  const mcpServerManager_1 = require("../agent/mcpServerManager");
7
+ const websocket_1 = require("./websocket");
41
8
  const logger = (0, sdk_1.getLogger)();
42
- class RemoteMcpServerManager {
43
- constructor(sender) {
44
- this.sender = sender;
9
+ class RemoteSudoMcpServerManager {
10
+ constructor(sender, briefs) {
45
11
  this.mcpServers = {};
12
+ this.sender = sender;
13
+ this.briefs = briefs;
14
+ this.briefsMap = {};
15
+ briefs.forEach((b) => {
16
+ this.briefsMap[b.name] = b;
17
+ });
46
18
  this.mcpServers = {};
47
19
  }
48
20
  hasMcpServer(mcpServerName) {
@@ -149,20 +121,6 @@ class RemoteMcpServerManager {
149
121
  }
150
122
  server.disableTool(toolName);
151
123
  }
152
- }
153
- class RemoteSudoMcpServerManager {
154
- constructor(sender, briefs, msm) {
155
- this.sender = sender;
156
- this.briefs = briefs;
157
- this.msm = msm;
158
- this.briefsMap = {};
159
- briefs.forEach((b) => {
160
- this.briefsMap[b.name] = b;
161
- });
162
- }
163
- getMcpServerManager() {
164
- return this.msm;
165
- }
166
124
  getServerBriefs() {
167
125
  return this.briefs;
168
126
  }
@@ -176,6 +134,7 @@ class RemoteSudoMcpServerManager {
176
134
  enable_all,
177
135
  });
178
136
  }
137
+ async shutdown() { }
179
138
  }
180
139
  class ChatClient {
181
140
  constructor(platform, ws, onMessageCB, onConnectionClosedCB, serverBriefs) {
@@ -184,8 +143,7 @@ class ChatClient {
184
143
  this.onMessageCB = onMessageCB;
185
144
  this.onConnectionClosedCB = onConnectionClosedCB;
186
145
  this.closed = false;
187
- this.msm = new RemoteMcpServerManager(this);
188
- this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs, this.msm);
146
+ this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs);
189
147
  this.systemPrompt = "";
190
148
  this.model = "";
191
149
  }
@@ -193,7 +151,7 @@ class ChatClient {
193
151
  return new Promise((resolveClient, e) => {
194
152
  const urlParams = new URLSearchParams(params);
195
153
  const url = `ws://${host}:${port}?${urlParams}`;
196
- const ws = new websocket.WebSocket(url, [token]);
154
+ const ws = new websocket_1.WebSocket(url, [token]);
197
155
  logger.info("created ws");
198
156
  let client = undefined;
199
157
  const onMsg = (msg) => {
@@ -231,17 +189,17 @@ class ChatClient {
231
189
  }
232
190
  };
233
191
  };
234
- ws.onclose = async (err) => {
192
+ ws.onclose = async (ev) => {
235
193
  logger.info("closed");
236
- logger.info(`[client] WebSocket connection closed: ${JSON.stringify(err)}`);
194
+ logger.info(`[client] WebSocket connection closed: ${JSON.stringify(ev)}`);
237
195
  if (client) {
238
196
  client.closed = true;
239
197
  }
240
198
  await onConnectionClosedCB();
241
199
  };
242
- ws.onerror = async (error) => {
243
- logger.error("[client] WebSocket error:", JSON.stringify(error));
244
- e(error);
200
+ ws.onerror = async (ev) => {
201
+ logger.error("[client] WebSocket error:", JSON.stringify(ev));
202
+ e(ev);
245
203
  if (client) {
246
204
  client.closed = true;
247
205
  }
@@ -291,6 +249,9 @@ class ChatClient {
291
249
  resetConversation() {
292
250
  throw "resetConversation not implemented for ChatClient";
293
251
  }
252
+ shutdown() {
253
+ throw "shutdown not implemented for ChatClient";
254
+ }
294
255
  sendMessage(message) {
295
256
  (0, assert_1.strict)(!this.closed);
296
257
  const data = JSON.stringify(message);
@@ -308,16 +269,16 @@ class ChatClient {
308
269
  // State updates
309
270
  //
310
271
  case "mcp_server_added":
311
- this.msm.onMcpServerAdded(message.server_name, message.tools, message.enabled_tools);
272
+ this.smsm.onMcpServerAdded(message.server_name, message.tools, message.enabled_tools);
312
273
  break;
313
274
  case "mcp_server_removed":
314
- this.msm.onMcpServerRemoved(message.server_name);
275
+ this.smsm.onMcpServerRemoved(message.server_name);
315
276
  break;
316
277
  case "mcp_server_tool_enabled":
317
- this.msm.onMcpServerToolEnabled(message.server_name, message.tool);
278
+ this.smsm.onMcpServerToolEnabled(message.server_name, message.tool);
318
279
  break;
319
280
  case "mcp_server_tool_disabled":
320
- this.msm.onMcpServerToolDisabled(message.server_name, message.tool);
281
+ this.smsm.onMcpServerToolDisabled(message.server_name, message.tool);
321
282
  break;
322
283
  case "system_prompt_updated":
323
284
  this.systemPrompt = message.system_prompt;
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConversationManager = exports.OpenSession = exports.UserAlreadyConnected = void 0;
4
+ exports.userMessageToChatMessage = userMessageToChatMessage;
5
+ exports.conversationToChatMessages = conversationToChatMessages;
4
6
  const uuid_1 = require("uuid");
5
7
  const assert_1 = require("assert");
6
8
  const sdk_1 = require("@xalia/xmcp/sdk");
9
+ const agent_1 = require("../agent/agent");
7
10
  const agentUtils_1 = require("../agent/agentUtils");
8
11
  const asyncQueue_1 = require("./asyncQueue");
9
12
  const asyncLock_1 = require("../utils/asyncLock");
@@ -33,23 +36,40 @@ class OpenSession {
33
36
  if (this.connections[userName]) {
34
37
  throw new UserAlreadyConnected();
35
38
  }
36
- // Inform any other participants, and add the WebSocket to the map.
39
+ // Inform any other participants, then add the new WebSocket to the map.
37
40
  this.broadcast({ type: "user_joined", user: userName });
38
41
  this.connections[userName] = ws;
39
- // TODO:
40
- //
41
- // - send conversation state (ServerHistory)
42
+ // Send MCP server briefs first (client expects this)
42
43
  const briefs = this.skillManager.getServerBriefs();
43
44
  this.sendTo(userName, { type: "mcp_server_briefs", server_briefs: briefs });
45
+ // Send conversation
46
+ const conversation = this.agent.getConversation();
47
+ const convMessages = conversationToChatMessages(conversation, userName);
48
+ for (const chatMsg of convMessages) {
49
+ this.sendTo(userName, chatMsg);
50
+ }
51
+ // Update the MSM state
52
+ const agentProfile = this.agent.getAgentProfile();
44
53
  const msm = this.agent.getMcpServerManager();
45
- const mcpServerNames = msm.getMcpServerNames();
46
- for (const serverName of mcpServerNames) {
47
- const info = msm.getMcpServer(serverName);
54
+ for (const server_name in agentProfile.mcp_settings) {
55
+ const tools = msm.getMcpServer(server_name).getTools();
56
+ const enabled_tools = agentProfile.mcp_settings[server_name];
48
57
  this.sendTo(userName, {
49
58
  type: "mcp_server_added",
50
- server_name: serverName,
51
- tools: info.getTools(),
52
- enabled_tools: Object.keys(info.getEnabledTools()),
59
+ server_name,
60
+ tools,
61
+ enabled_tools,
62
+ });
63
+ }
64
+ // Send system prompt and model
65
+ this.sendTo(userName, {
66
+ type: "system_prompt_updated",
67
+ system_prompt: agentProfile.system_prompt,
68
+ });
69
+ if (agentProfile.model) {
70
+ this.sendTo(userName, {
71
+ type: "model_updated",
72
+ model: agentProfile.model,
53
73
  });
54
74
  }
55
75
  ws.on("message", async (message) => {
@@ -140,7 +160,9 @@ class OpenSession {
140
160
  end,
141
161
  });
142
162
  }
143
- async onChatMessage(message, userToken) {
163
+ async onChatMessage(message,
164
+ // imageB64: string | undefined,
165
+ userToken) {
144
166
  // We manually broadcast the user's message here and start the agent
145
167
  // conversation, and then wait to get back all data from the agent before
146
168
  // processing further messages from clients.
@@ -149,10 +171,27 @@ class OpenSession {
149
171
  type: "user_msg",
150
172
  message_id: msgId,
151
173
  message,
174
+ // imageB64,
152
175
  from: userToken,
153
176
  });
154
177
  this.curAgentMsgId = `${msgId}-resp`;
155
- await this.agent.userMessageEx(message, undefined, userToken);
178
+ const userMessageParam = (0, agent_1.createUserMessage)(message,
179
+ /*imageB64*/ undefined, userToken);
180
+ if (!userMessageParam) {
181
+ logger.debug(`ignoring empty message: ${message}`);
182
+ return;
183
+ }
184
+ const assistantReply = await this.agent.userMessageRaw(userMessageParam);
185
+ if (assistantReply) {
186
+ // TODO: consider including all messages (including tool calls)
187
+ // const newEntries = agent.getTrailingEntries(prevConvLength);
188
+ // await this.db.sessionConversationAppend(this.sessionUUID, newEntries);
189
+ const newEntries = [
190
+ userMessageParam,
191
+ assistantReply,
192
+ ];
193
+ await this.db.sessionConversationAppend(this.sessionUUID, newEntries);
194
+ }
156
195
  }
157
196
  async onAddMcpServer(serverName, enableAll) {
158
197
  logger.info(`[onAddMcpServer]: Adding server ${serverName} (enable_all: ${enableAll})`);
@@ -390,3 +429,74 @@ class ConversationManager {
390
429
  }
391
430
  }
392
431
  exports.ConversationManager = ConversationManager;
432
+ function userMessageToChatMessage(userMessage, message_id, defaultName) {
433
+ const from = userMessage.name || defaultName;
434
+ if (typeof userMessage.content === "string") {
435
+ return {
436
+ type: "user_msg",
437
+ message_id,
438
+ message: userMessage.content,
439
+ from,
440
+ };
441
+ }
442
+ let message = "";
443
+ let image = undefined;
444
+ for (const content of userMessage.content) {
445
+ switch (content.type) {
446
+ case "text":
447
+ message += content.text;
448
+ break;
449
+ case "image_url":
450
+ (0, assert_1.strict)(!image, "only one image per message supported");
451
+ image = content.image_url;
452
+ break;
453
+ case "input_audio":
454
+ throw "userMessageToChatMessage: audio content not supported";
455
+ case "file":
456
+ throw "userMessageToChatMessage: file content not supported";
457
+ default:
458
+ throw "userMessageToChatMessage: unexpected content.type";
459
+ }
460
+ }
461
+ if (image) {
462
+ throw "unimpl: image content";
463
+ }
464
+ return {
465
+ type: "user_msg",
466
+ message_id,
467
+ message,
468
+ from,
469
+ };
470
+ }
471
+ function conversationToChatMessages(conversation, defaultName) {
472
+ const msgs = [];
473
+ for (const ccmp of conversation) {
474
+ const message_id = `message_${msgs.length}`;
475
+ switch (ccmp.role) {
476
+ case "developer":
477
+ throw "developer messages not handled yet";
478
+ case "assistant":
479
+ (0, assert_1.strict)(!ccmp.audio);
480
+ if (ccmp.content) {
481
+ msgs.push({
482
+ type: "agent_msg",
483
+ message: ccmp,
484
+ message_id,
485
+ });
486
+ }
487
+ // TODO: do we want to convert tool calls etc?
488
+ break;
489
+ case "user":
490
+ {
491
+ const msg = userMessageToChatMessage(ccmp, message_id, defaultName);
492
+ if (msg) {
493
+ msgs.push(msg);
494
+ }
495
+ }
496
+ break;
497
+ default:
498
+ break;
499
+ }
500
+ }
501
+ return msgs;
502
+ }
@@ -202,6 +202,15 @@ class Database {
202
202
  }
203
203
  return data[0].uuid;
204
204
  }
205
+ async sessionConversationAppend(session_id, newEntries) {
206
+ const { error } = await this.client.rpc("session_append_to_conversation", {
207
+ session_id,
208
+ messages: newEntries,
209
+ });
210
+ if (error) {
211
+ throw error;
212
+ }
213
+ }
205
214
  async clearSessions() {
206
215
  await this.client.from("sessions").delete().neq("uuid", "");
207
216
  }
@@ -1,2 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeAssistantMessageParam = decodeAssistantMessageParam;
4
+ function decodeAssistantMessageParam(msg) {
5
+ let text = "";
6
+ if (msg.audio) {
7
+ throw "decodeAssistantMessageParam; audio unimplemented";
8
+ }
9
+ if (msg.content) {
10
+ if (typeof msg.content === "string") {
11
+ text = msg.content;
12
+ }
13
+ else if (msg.content instanceof Array) {
14
+ for (const c of msg.content) {
15
+ switch (c.type) {
16
+ case "text":
17
+ text += c.text;
18
+ break;
19
+ case "refusal":
20
+ text += c.refusal;
21
+ break;
22
+ default:
23
+ throw Error("unexpected AssistantMessageParam.content entry");
24
+ }
25
+ }
26
+ }
27
+ }
28
+ return text;
29
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ // Browser and Node.js Universal WebSocket wrapper
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.WebSocket = void 0;
5
+ let WSClass;
6
+ if (typeof window !== "undefined" && typeof window.WebSocket !== "undefined") {
7
+ // Running in browser
8
+ exports.WebSocket = WSClass = window.WebSocket;
9
+ }
10
+ else {
11
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
12
+ const ws = require("ws");
13
+ exports.WebSocket = WSClass = ws.WebSocket;
14
+ }
@@ -79,6 +79,14 @@ describe("DB", () => {
79
79
  (0, chai_1.expect)(agentProfileFromDB).eql(AGENT_PROFILE_2);
80
80
  });
81
81
  it("should create and retrieve sessions", async function () {
82
+ const CONV_0 = [
83
+ { role: "user", content: "message 0" },
84
+ { role: "assistant", content: "message 1" },
85
+ ];
86
+ const CONV_1 = [
87
+ { role: "user", content: "message 2" },
88
+ { role: "assistant", content: "message 3" },
89
+ ];
82
90
  const db = getLocalDB();
83
91
  await db.clearAgentProfiles();
84
92
  await db.clearSessions();
@@ -92,5 +100,13 @@ describe("DB", () => {
92
100
  (0, chai_1.expect)(session.title).eql("test_session");
93
101
  (0, chai_1.expect)(session.user_uuid).eql("dummy_user");
94
102
  (0, chai_1.expect)(session.uuid).eql(sessionId);
103
+ // Append messages to empty conversation
104
+ await db.sessionConversationAppend(sessionId, CONV_0);
105
+ const session0 = (await db.getSessionById(sessionId));
106
+ (0, chai_1.expect)(session0.conversation).eql(CONV_0);
107
+ // Append further messages
108
+ await db.sessionConversationAppend(sessionId, CONV_1);
109
+ const session1 = (await db.getSessionById(sessionId));
110
+ (0, chai_1.expect)(session1.conversation).eql(CONV_0.concat(CONV_1));
95
111
  });
96
112
  });
@@ -21,7 +21,7 @@ async function shutdownAll() {
21
21
  describe("McpServerManager", async () => {
22
22
  it("should initialize with correct descriptions", async () => {
23
23
  const tm = getMcpServerManager();
24
- await tm.addMcpServer("simplecalc", "http://localhost:5001/mcpservers/sudomcp/simplecalc/session", "dummy_key");
24
+ await tm.addMcpServerWithSSEUrl("simplecalc", "http://localhost:5001/mcpservers/sudomcp/simplecalc/session", "dummy_key");
25
25
  const server = tm.getMcpServer("simplecalc");
26
26
  const availableTools = server.getTools();
27
27
  (0, chai_1.expect)(availableTools.length).greaterThan(0);
@@ -58,7 +58,7 @@ describe("McpServerManager", async () => {
58
58
  });
59
59
  it("add / remove servers", async () => {
60
60
  const tm = getMcpServerManager();
61
- await tm.addMcpServer("simplecalc", "http://localhost:5001/mcpservers/sudomcp/simplecalc/session", "dummy_key");
61
+ await tm.addMcpServerWithSSEUrl("simplecalc", "http://localhost:5001/mcpservers/sudomcp/simplecalc/session", "dummy_key");
62
62
  tm.enableAllTools("simplecalc");
63
63
  (0, chai_1.expect)(tm.getOpenAITools().length).greaterThan(0);
64
64
  // Remove server and check there is no server, and no openai entries.