@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.
- package/dist/agent/src/agent/agent.js +3 -0
- package/dist/agent/src/agent/agentUtils.js +6 -12
- package/dist/agent/src/agent/mcpServerManager.js +1 -1
- package/dist/agent/src/agent/openAILLMStreaming.js +14 -7
- package/dist/agent/src/agent/sudoMcpServerManager.js +13 -13
- package/dist/agent/src/chat/client.js +24 -63
- package/dist/agent/src/chat/conversationManager.js +122 -12
- package/dist/agent/src/chat/db.js +9 -0
- package/dist/agent/src/chat/messages.js +27 -0
- package/dist/agent/src/chat/websocket.js +14 -0
- package/dist/agent/src/test/db.test.js +16 -0
- package/dist/agent/src/test/mcpServerManager.test.js +2 -2
- package/dist/agent/src/test/openaiStreaming.test.js +56 -28
- package/dist/agent/src/test/sudoMcpServerManager.test.js +10 -13
- package/dist/agent/src/tool/chatMain.js +7 -1
- package/dist/agent/src/tool/commandPrompt.js +9 -10
- package/package.json +1 -1
- package/src/agent/agent.ts +14 -4
- package/src/agent/agentUtils.ts +17 -23
- package/src/agent/mcpServerManager.ts +3 -1
- package/src/agent/openAILLMStreaming.ts +14 -7
- package/src/agent/sudoMcpServerManager.ts +17 -18
- package/src/chat/client.ts +35 -45
- package/src/chat/conversationManager.ts +147 -12
- package/src/chat/db.ts +14 -0
- package/src/chat/messages.ts +31 -0
- package/src/chat/websocket.ts +14 -0
- package/src/test/db.test.ts +22 -1
- package/src/test/mcpServerManager.test.ts +2 -2
- package/src/test/openaiStreaming.test.ts +64 -30
- package/src/test/sudoMcpServerManager.test.ts +11 -16
- package/src/tool/chatMain.ts +11 -2
- package/src/tool/commandPrompt.ts +8 -15
- package/dist/agent/src/chat/frontendClient.js +0 -74
- 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(
|
|
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
|
-
|
|
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("[
|
|
81
|
-
const
|
|
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("[
|
|
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
|
|
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
|
|
267
|
-
//
|
|
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
|
|
284
|
-
//
|
|
285
|
-
// done.
|
|
286
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
135
|
-
await msm.addMcpServerWithClient(client, serverName, tools);
|
|
135
|
+
await this.addMcpServerWithClient(client, serverName, tools);
|
|
136
136
|
if (enableAll) {
|
|
137
|
-
|
|
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
|
|
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.
|
|
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
|
|
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 (
|
|
192
|
+
ws.onclose = async (ev) => {
|
|
235
193
|
logger.info("closed");
|
|
236
|
-
logger.info(`[client] WebSocket connection closed: ${JSON.stringify(
|
|
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 (
|
|
243
|
-
logger.error("[client] WebSocket error:", JSON.stringify(
|
|
244
|
-
e(
|
|
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.
|
|
272
|
+
this.smsm.onMcpServerAdded(message.server_name, message.tools, message.enabled_tools);
|
|
312
273
|
break;
|
|
313
274
|
case "mcp_server_removed":
|
|
314
|
-
this.
|
|
275
|
+
this.smsm.onMcpServerRemoved(message.server_name);
|
|
315
276
|
break;
|
|
316
277
|
case "mcp_server_tool_enabled":
|
|
317
|
-
this.
|
|
278
|
+
this.smsm.onMcpServerToolEnabled(message.server_name, message.tool);
|
|
318
279
|
break;
|
|
319
280
|
case "mcp_server_tool_disabled":
|
|
320
|
-
this.
|
|
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,
|
|
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
|
-
//
|
|
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
|
|
46
|
-
|
|
47
|
-
const
|
|
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
|
|
51
|
-
tools
|
|
52
|
-
enabled_tools
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|