@xalia/agent 0.5.7 → 0.6.0
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/README.md +23 -8
- package/dist/agent/src/agent/agent.js +176 -96
- package/dist/agent/src/agent/agentUtils.js +82 -59
- package/dist/agent/src/agent/compressingContextManager.js +102 -0
- package/dist/agent/src/agent/context.js +189 -0
- package/dist/agent/src/agent/dummyLLM.js +46 -5
- package/dist/agent/src/agent/mcpServerManager.js +23 -24
- package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
- package/dist/agent/src/agent/nullPlatform.js +14 -0
- package/dist/agent/src/agent/openAILLMStreaming.js +26 -14
- package/dist/agent/src/agent/promptProvider.js +63 -0
- package/dist/agent/src/agent/repeatLLM.js +5 -5
- package/dist/agent/src/agent/sudoMcpServerManager.js +23 -21
- package/dist/agent/src/agent/tokenAuth.js +7 -7
- package/dist/agent/src/agent/tools.js +1 -1
- package/dist/agent/src/chat/client/chatClient.js +733 -0
- package/dist/agent/src/chat/client/connection.js +209 -0
- package/dist/agent/src/chat/client/connection.test.js +188 -0
- package/dist/agent/src/chat/client/constants.js +5 -0
- package/dist/agent/src/chat/client/index.js +15 -0
- package/dist/agent/src/chat/client/interfaces.js +2 -0
- package/dist/agent/src/chat/client/responseHandler.js +105 -0
- package/dist/agent/src/chat/client/sessionClient.js +331 -0
- package/dist/agent/src/chat/client/teamManager.js +2 -0
- package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
- package/dist/agent/src/chat/data/dataModels.js +2 -0
- package/dist/agent/src/chat/data/database.js +749 -0
- package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
- package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
- package/dist/agent/src/chat/protocol/constants.js +50 -0
- package/dist/agent/src/chat/protocol/errors.js +22 -0
- package/dist/agent/src/chat/protocol/messages.js +110 -0
- package/dist/agent/src/chat/server/chatContextManager.js +405 -0
- package/dist/agent/src/chat/server/connectionManager.js +352 -0
- package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
- package/dist/agent/src/chat/server/conversation.js +198 -0
- package/dist/agent/src/chat/server/errorUtils.js +23 -0
- package/dist/agent/src/chat/server/openSession.js +869 -0
- package/dist/agent/src/chat/server/server.js +177 -0
- package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
- package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
- package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
- package/dist/agent/src/chat/server/tools.js +243 -0
- package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
- package/dist/agent/src/chat/utils/approvalManager.js +85 -0
- package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
- package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
- package/dist/agent/src/chat/utils/htmlToText.js +84 -0
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
- package/dist/agent/src/chat/utils/search.js +145 -0
- package/dist/agent/src/chat/utils/userResolver.js +46 -0
- package/dist/agent/src/chat/utils/websocket.js +16 -0
- package/dist/agent/src/test/agent.test.js +332 -0
- package/dist/agent/src/test/approvalManager.test.js +58 -0
- package/dist/agent/src/test/chatContextManager.test.js +392 -0
- package/dist/agent/src/test/clientServerConnection.test.js +158 -0
- package/dist/agent/src/test/compressingContextManager.test.js +65 -0
- package/dist/agent/src/test/context.test.js +83 -0
- package/dist/agent/src/test/conversation.test.js +89 -0
- package/dist/agent/src/test/db.test.js +271 -83
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
- package/dist/agent/src/test/dbTestTools.js +99 -0
- package/dist/agent/src/test/imageLoad.test.js +8 -7
- package/dist/agent/src/test/mcpServerManager.test.js +23 -20
- package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
- package/dist/agent/src/test/openaiStreaming.test.js +64 -35
- package/dist/agent/src/test/prompt.test.js +5 -4
- package/dist/agent/src/test/promptProvider.test.js +28 -0
- package/dist/agent/src/test/responseHandler.test.js +61 -0
- package/dist/agent/src/test/sudoMcpServerManager.test.js +24 -25
- package/dist/agent/src/test/testTools.js +109 -0
- package/dist/agent/src/test/tools.test.js +31 -0
- package/dist/agent/src/tool/agentChat.js +21 -10
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +241 -58
- package/dist/agent/src/tool/commandPrompt.js +22 -17
- package/dist/agent/src/tool/files.js +20 -16
- package/dist/agent/src/tool/nodePlatform.js +47 -3
- package/dist/agent/src/tool/options.js +4 -4
- package/dist/agent/src/tool/prompt.js +19 -13
- package/eslint.config.mjs +14 -1
- package/package.json +14 -6
- package/scripts/chat_server +8 -0
- package/scripts/setup_chat +7 -2
- package/scripts/shutdown_chat_server +3 -0
- package/scripts/test_chat +135 -17
- package/src/agent/agent.ts +283 -138
- package/src/agent/agentUtils.ts +143 -108
- package/src/agent/compressingContextManager.ts +164 -0
- package/src/agent/context.ts +268 -0
- package/src/agent/dummyLLM.ts +76 -8
- package/src/agent/iAgentEventHandler.ts +54 -0
- package/src/agent/iplatform.ts +1 -0
- package/src/agent/mcpServerManager.ts +35 -31
- package/src/agent/nullAgentEventHandler.ts +20 -0
- package/src/agent/nullPlatform.ts +13 -0
- package/src/agent/openAILLMStreaming.ts +26 -13
- package/src/agent/promptProvider.ts +87 -0
- package/src/agent/repeatLLM.ts +5 -5
- package/src/agent/sudoMcpServerManager.ts +30 -29
- package/src/agent/tokenAuth.ts +7 -7
- package/src/agent/tools.ts +3 -1
- package/src/chat/client/chatClient.ts +900 -0
- package/src/chat/client/connection.test.ts +241 -0
- package/src/chat/client/connection.ts +276 -0
- package/src/chat/client/constants.ts +3 -0
- package/src/chat/client/index.ts +18 -0
- package/src/chat/client/interfaces.ts +34 -0
- package/src/chat/client/responseHandler.ts +131 -0
- package/src/chat/client/sessionClient.ts +443 -0
- package/src/chat/client/teamManager.ts +29 -0
- package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
- package/src/chat/data/dataModels.ts +85 -0
- package/src/chat/data/database.ts +982 -0
- package/src/chat/data/dbMcpServerConfigs.ts +59 -0
- package/src/chat/protocol/connectionMessages.ts +49 -0
- package/src/chat/protocol/constants.ts +55 -0
- package/src/chat/protocol/errors.ts +16 -0
- package/src/chat/protocol/messages.ts +682 -0
- package/src/chat/server/README.md +127 -0
- package/src/chat/server/chatContextManager.ts +612 -0
- package/src/chat/server/connectionManager.test.ts +266 -0
- package/src/chat/server/connectionManager.ts +541 -0
- package/src/chat/server/conversation.ts +269 -0
- package/src/chat/server/errorUtils.ts +28 -0
- package/src/chat/server/openSession.ts +1332 -0
- package/src/chat/server/server.ts +177 -0
- package/src/chat/server/sessionFileManager.ts +239 -0
- package/src/chat/server/sessionRegistry.test.ts +138 -0
- package/src/chat/server/sessionRegistry.ts +1064 -0
- package/src/chat/server/test-utils/mockFactories.ts +422 -0
- package/src/chat/server/tools.ts +265 -0
- package/src/chat/utils/agentSessionMap.ts +76 -0
- package/src/chat/utils/approvalManager.ts +111 -0
- package/src/{utils → chat/utils}/asyncLock.ts +3 -3
- package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
- package/src/chat/utils/htmlToText.ts +61 -0
- package/src/chat/utils/multiAsyncQueue.ts +52 -0
- package/src/chat/utils/search.ts +139 -0
- package/src/chat/utils/userResolver.ts +48 -0
- package/src/chat/utils/websocket.ts +16 -0
- package/src/test/agent.test.ts +487 -0
- package/src/test/approvalManager.test.ts +73 -0
- package/src/test/chatContextManager.test.ts +521 -0
- package/src/test/clientServerConnection.test.ts +207 -0
- package/src/test/compressingContextManager.test.ts +82 -0
- package/src/test/context.test.ts +105 -0
- package/src/test/conversation.test.ts +109 -0
- package/src/test/db.test.ts +358 -89
- package/src/test/dbMcpServerConfigs.test.ts +112 -0
- package/src/test/dbTestTools.ts +153 -0
- package/src/test/imageLoad.test.ts +7 -6
- package/src/test/mcpServerManager.test.ts +21 -16
- package/src/test/multiAsyncQueue.test.ts +125 -0
- package/src/test/openaiStreaming.test.ts +71 -36
- package/src/test/prompt.test.ts +4 -3
- package/src/test/promptProvider.test.ts +33 -0
- package/src/test/responseHandler.test.ts +78 -0
- package/src/test/sudoMcpServerManager.test.ts +32 -30
- package/src/test/testTools.ts +146 -0
- package/src/test/tools.test.ts +39 -0
- package/src/tool/agentChat.ts +26 -12
- package/src/tool/agentMain.ts +1 -1
- package/src/tool/chatMain.ts +292 -100
- package/src/tool/commandPrompt.ts +28 -19
- package/src/tool/files.ts +25 -19
- package/src/tool/nodePlatform.ts +52 -3
- package/src/tool/options.ts +4 -2
- package/src/tool/prompt.ts +22 -15
- package/test_data/dummyllm_script_crash.json +32 -0
- package/test_data/frog.png.b64 +1 -0
- package/vitest.config.ts +39 -0
- package/dist/agent/src/chat/client.js +0 -349
- package/dist/agent/src/chat/conversationManager.js +0 -392
- package/dist/agent/src/chat/db.js +0 -209
- package/dist/agent/src/chat/frontendClient.js +0 -74
- package/dist/agent/src/chat/server.js +0 -158
- package/src/chat/client.ts +0 -455
- package/src/chat/conversationManager.ts +0 -595
- package/src/chat/db.ts +0 -290
- package/src/chat/frontendClient.ts +0 -123
- package/src/chat/messages.ts +0 -235
- package/src/chat/server.ts +0 -177
- /package/dist/agent/src/{chat/messages.js → agent/iAgentEventHandler.js} +0 -0
- /package/{frog.png → test_data/frog.png} +0 -0
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
// -*- typescript -*-
|
|
4
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
-
if (k2 === undefined) k2 = k;
|
|
6
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
-
}
|
|
10
|
-
Object.defineProperty(o, k2, desc);
|
|
11
|
-
}) : (function(o, m, k, k2) {
|
|
12
|
-
if (k2 === undefined) k2 = k;
|
|
13
|
-
o[k2] = m[k];
|
|
14
|
-
}));
|
|
15
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
-
}) : function(o, v) {
|
|
18
|
-
o["default"] = v;
|
|
19
|
-
});
|
|
20
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
21
|
-
var ownKeys = function(o) {
|
|
22
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
23
|
-
var ar = [];
|
|
24
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
25
|
-
return ar;
|
|
26
|
-
};
|
|
27
|
-
return ownKeys(o);
|
|
28
|
-
};
|
|
29
|
-
return function (mod) {
|
|
30
|
-
if (mod && mod.__esModule) return mod;
|
|
31
|
-
var result = {};
|
|
32
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
33
|
-
__setModuleDefault(result, mod);
|
|
34
|
-
return result;
|
|
35
|
-
};
|
|
36
|
-
})();
|
|
37
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.runServer = runServer;
|
|
39
|
-
const dotenv = __importStar(require("dotenv"));
|
|
40
|
-
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
41
|
-
const ws = __importStar(require("ws"));
|
|
42
|
-
const url_1 = require("url");
|
|
43
|
-
const conversationManager_1 = require("./conversationManager");
|
|
44
|
-
const db_1 = require("./db");
|
|
45
|
-
const apiKeyManager_1 = require("./apiKeyManager");
|
|
46
|
-
dotenv.config();
|
|
47
|
-
const logger = (0, sdk_1.getLogger)();
|
|
48
|
-
/**
|
|
49
|
-
* Try as id, then by user/name and return the AgentProfile uuid.
|
|
50
|
-
*/
|
|
51
|
-
async function resolveAgentProfileId(db, userData, agentProfileIdentifier) {
|
|
52
|
-
let ap = await db.getSavedAgentProfileById(agentProfileIdentifier);
|
|
53
|
-
logger.debug(`[resolveAgentProfileId]: by id: {JSON.stringify(ap)}`);
|
|
54
|
-
if (ap) {
|
|
55
|
-
return agentProfileIdentifier;
|
|
56
|
-
}
|
|
57
|
-
ap = await db.getSavedAgentProfileByName(userData.uuid, agentProfileIdentifier);
|
|
58
|
-
logger.debug(`[resolveAgentProfileId]: by name: {JSON.stringify(ap)}`);
|
|
59
|
-
if (ap) {
|
|
60
|
-
return ap.uuid;
|
|
61
|
-
}
|
|
62
|
-
logger.debug("[resolveAgentProfileId]: agent profile not found");
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
|
-
async function resolveSessionIdFromIdentifier(db, userData, sessionIdentifier) {
|
|
66
|
-
let session = undefined;
|
|
67
|
-
const compound = (0, db_1.resolveCompoundName)(sessionIdentifier);
|
|
68
|
-
if (typeof compound === "string") {
|
|
69
|
-
// Interpret as an id, or as a number under the current user.
|
|
70
|
-
session = await db.getSessionById(compound);
|
|
71
|
-
if (!session) {
|
|
72
|
-
session = await db.getSessionByName(userData.uuid, compound);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
session = await db.getSessionByName(compound[0], compound[1]);
|
|
77
|
-
}
|
|
78
|
-
return session?.uuid;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Expect parameters to be either:
|
|
82
|
-
* session_id - uuid, name, user/name
|
|
83
|
-
* agent_profile_id (optional) - uuid, name
|
|
84
|
-
*
|
|
85
|
-
* If session_idd resolves to an existing session, it is used (and
|
|
86
|
-
* agent_profile_id is ignored). Otherwise, session_id is used as the name of
|
|
87
|
-
* a new session to create using `agent_profile_id`. The session UUID is
|
|
88
|
-
* returned in either case.
|
|
89
|
-
*/
|
|
90
|
-
async function findOrCreateSession(db, userData, query) {
|
|
91
|
-
logger.debug(`[findOrCreateSession]: query: ${JSON.stringify(query)}`);
|
|
92
|
-
if (!query.session_id || typeof query.session_id !== "string") {
|
|
93
|
-
throw "session_id invalid or not present";
|
|
94
|
-
}
|
|
95
|
-
const sessionId = await resolveSessionIdFromIdentifier(db, userData, query.session_id);
|
|
96
|
-
if (sessionId) {
|
|
97
|
-
return sessionId;
|
|
98
|
-
}
|
|
99
|
-
const agentProfileIdParam = query.agent_profile_id;
|
|
100
|
-
logger.debug(`[findOrCreateSession]: agent: ${agentProfileIdParam}`);
|
|
101
|
-
if (!agentProfileIdParam) {
|
|
102
|
-
throw "no existing session, and no agent_profile_id given";
|
|
103
|
-
}
|
|
104
|
-
if (typeof agentProfileIdParam !== "string") {
|
|
105
|
-
throw "no existing session and invalid agent_profile_id";
|
|
106
|
-
}
|
|
107
|
-
logger.debug(`[findOrCreateSession]: creating session: ${query.session_id}`);
|
|
108
|
-
const agentProfileId = await resolveAgentProfileId(db, userData, agentProfileIdParam);
|
|
109
|
-
logger.debug(`[findOrCreateSession]: resolved agentProfileId: ${agentProfileId}`);
|
|
110
|
-
if (!agentProfileId) {
|
|
111
|
-
throw `no agent profile: ${agentProfileIdParam}`;
|
|
112
|
-
}
|
|
113
|
-
return db.createSession(userData.uuid, query.session_id, agentProfileId);
|
|
114
|
-
}
|
|
115
|
-
async function runServer(port, supabaseUrl, supabaseKey, llmUrl, xmcpUrl) {
|
|
116
|
-
return new Promise((r, _e) => {
|
|
117
|
-
const wss = new ws.Server({ port });
|
|
118
|
-
const db = new db_1.Database(supabaseUrl, supabaseKey);
|
|
119
|
-
const apiKeyManager = new apiKeyManager_1.ApiKeyManager(db);
|
|
120
|
-
const cm = new conversationManager_1.ConversationManager(db, llmUrl, xmcpUrl);
|
|
121
|
-
wss.on("connection", async (ws, req) => {
|
|
122
|
-
try {
|
|
123
|
-
logger.info(`[server] connection: ${req}`);
|
|
124
|
-
// Check header
|
|
125
|
-
logger.info(`[server] headers: ${JSON.stringify(req.headers)}`);
|
|
126
|
-
const apiKey = req.headers["sec-websocket-protocol"];
|
|
127
|
-
if (!apiKey) {
|
|
128
|
-
throw "empty api key";
|
|
129
|
-
}
|
|
130
|
-
const userData = await apiKeyManager.verifyApiKey(apiKey);
|
|
131
|
-
if (!userData) {
|
|
132
|
-
throw "invalid api key";
|
|
133
|
-
}
|
|
134
|
-
// Get sessionId
|
|
135
|
-
const { query } = (0, url_1.parse)(req.url || "", true);
|
|
136
|
-
const sessionId = await findOrCreateSession(db, userData, query);
|
|
137
|
-
logger.debug(`resolved session id: ${sessionId}`);
|
|
138
|
-
if (!sessionId) {
|
|
139
|
-
throw "failed to find/create session";
|
|
140
|
-
}
|
|
141
|
-
// Associate the ws, username with the conversation
|
|
142
|
-
await cm.join(sessionId, apiKey, apiKey, userData, ws);
|
|
143
|
-
}
|
|
144
|
-
catch (e) {
|
|
145
|
-
if (typeof e === "string") {
|
|
146
|
-
logger.warn(`[server]: error: ${e}`);
|
|
147
|
-
ws.close(4000, e);
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
logger.error(`other error: ${JSON.stringify(e)}`);
|
|
151
|
-
throw e;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
logger.info(`[server] started: ws://localhost:${port}/`);
|
|
156
|
-
r(wss);
|
|
157
|
-
});
|
|
158
|
-
}
|
package/src/chat/client.ts
DELETED
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
-
import { strict as assert } from "assert";
|
|
3
|
-
import * as websocket from "ws";
|
|
4
|
-
|
|
5
|
-
import { AgentProfile, McpServerBrief, getLogger } from "@xalia/xmcp/sdk";
|
|
6
|
-
|
|
7
|
-
import { ISkillManager } from "../agent/sudoMcpServerManager";
|
|
8
|
-
import {
|
|
9
|
-
IMcpServerManager,
|
|
10
|
-
McpServerInfo,
|
|
11
|
-
McpServerInfoRW,
|
|
12
|
-
} from "../agent/mcpServerManager";
|
|
13
|
-
|
|
14
|
-
import { ServerToClient, ClientToServer } from "./messages";
|
|
15
|
-
import { ChatCompletionMessageParam, IConversation } from "../agent/agent";
|
|
16
|
-
import { IPlatform } from "../agent/iplatform";
|
|
17
|
-
|
|
18
|
-
const logger = getLogger();
|
|
19
|
-
|
|
20
|
-
type OnMessageCallback = (msg: ServerToClient) => void;
|
|
21
|
-
|
|
22
|
-
type OnConnectionClosedCallback = () => Promise<void>;
|
|
23
|
-
|
|
24
|
-
interface IMessageSender {
|
|
25
|
-
sendMessage(message: ClientToServer): void;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class RemoteMcpServerManager implements IMcpServerManager {
|
|
29
|
-
private mcpServers: { [serverName: string]: McpServerInfoRW } = {};
|
|
30
|
-
|
|
31
|
-
constructor(private sender: IMessageSender) {
|
|
32
|
-
this.mcpServers = {};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
hasMcpServer(mcpServerName: string): boolean {
|
|
36
|
-
return !!this.mcpServers[mcpServerName];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getMcpServerNames(): string[] {
|
|
40
|
-
return Object.keys(this.mcpServers);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
getMcpServer(mcpServerName: string): McpServerInfo {
|
|
44
|
-
const server = this.mcpServers[mcpServerName];
|
|
45
|
-
if (server) {
|
|
46
|
-
return server;
|
|
47
|
-
}
|
|
48
|
-
throw Error(`unknown server ${mcpServerName}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async removeMcpServer(mcpServerName: string): Promise<void> {
|
|
52
|
-
if (!this.mcpServers[mcpServerName]) {
|
|
53
|
-
throw Error(`no server ${mcpServerName} (removeMcpServer)`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
this.sender.sendMessage({
|
|
57
|
-
type: "remove_mcp_server",
|
|
58
|
-
server_name: mcpServerName,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
enableAllTools(mcpServerName: string): void {
|
|
63
|
-
if (!this.mcpServers[mcpServerName]) {
|
|
64
|
-
throw Error(`no server ${mcpServerName} (enableAllTools)`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
this.sender.sendMessage({
|
|
68
|
-
type: "enable_all_mcp_server_tools",
|
|
69
|
-
server_name: mcpServerName,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
disableAllTools(mcpServerName: string): void {
|
|
74
|
-
if (!this.mcpServers[mcpServerName]) {
|
|
75
|
-
throw Error(`no server ${mcpServerName} (disableAllTools)`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
this.sender.sendMessage({
|
|
79
|
-
type: "disable_all_mcp_server_tools",
|
|
80
|
-
server_name: mcpServerName,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
enableTool(mcpServerName: string, toolName: string): void {
|
|
85
|
-
const server = this.mcpServers[mcpServerName];
|
|
86
|
-
if (!server) {
|
|
87
|
-
throw Error(`no server ${mcpServerName} (enableTool)`);
|
|
88
|
-
}
|
|
89
|
-
const tools = server.getTool(toolName);
|
|
90
|
-
if (!tools) {
|
|
91
|
-
throw Error(`no tool ${toolName} on server ${mcpServerName}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
this.sender.sendMessage({
|
|
95
|
-
type: "enable_mcp_server_tool",
|
|
96
|
-
server_name: mcpServerName,
|
|
97
|
-
tool: toolName,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
disableTool(mcpServerName: string, toolName: string): void {
|
|
102
|
-
const server = this.mcpServers[mcpServerName];
|
|
103
|
-
if (!server) {
|
|
104
|
-
throw Error(`no server ${mcpServerName} (disableTool)`);
|
|
105
|
-
}
|
|
106
|
-
const tools = server.getTool(toolName);
|
|
107
|
-
if (!tools) {
|
|
108
|
-
throw Error(`no tool ${toolName} on server ${mcpServerName}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.sender.sendMessage({
|
|
112
|
-
type: "disable_mcp_server_tool",
|
|
113
|
-
server_name: mcpServerName,
|
|
114
|
-
tool: toolName,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
onMcpServerAdded(
|
|
119
|
-
mcpServerName: string,
|
|
120
|
-
tools: Tool[],
|
|
121
|
-
enabled_tools: string[]
|
|
122
|
-
) {
|
|
123
|
-
logger.debug(
|
|
124
|
-
`[onMcpServerAdded]: ${mcpServerName}, tools: ${JSON.stringify(tools)}` +
|
|
125
|
-
`, enabled: ${JSON.stringify(enabled_tools)}`
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const mcpServerInfo = new McpServerInfoRW(tools);
|
|
129
|
-
for (const tool of enabled_tools) {
|
|
130
|
-
mcpServerInfo.enableTool(tool);
|
|
131
|
-
}
|
|
132
|
-
this.mcpServers[mcpServerName] = mcpServerInfo;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
onMcpServerRemoved(mcpServerName: string) {
|
|
136
|
-
delete this.mcpServers[mcpServerName];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
onMcpServerToolEnabled(mcpServerName: string, toolName: string) {
|
|
140
|
-
const server = this.mcpServers[mcpServerName];
|
|
141
|
-
if (!server) {
|
|
142
|
-
throw Error(`no server ${mcpServerName} (onMcpServerToolEnabled)`);
|
|
143
|
-
}
|
|
144
|
-
const tools = server.getTool(toolName);
|
|
145
|
-
if (!tools) {
|
|
146
|
-
throw Error(`no tool ${toolName} on server ${mcpServerName}`);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
server.enableTool(toolName);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
onMcpServerToolDisabled(mcpServerName: string, toolName: string) {
|
|
153
|
-
const server = this.mcpServers[mcpServerName];
|
|
154
|
-
if (!server) {
|
|
155
|
-
throw Error(`no server ${mcpServerName} (onMcpServerToolDisabled)`);
|
|
156
|
-
}
|
|
157
|
-
const tools = server.getTool(toolName);
|
|
158
|
-
if (!tools) {
|
|
159
|
-
throw Error(`no tool ${toolName} on server ${mcpServerName}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
server.disableTool(toolName);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
class RemoteSudoMcpServerManager implements ISkillManager {
|
|
167
|
-
private briefsMap: Record<string, McpServerBrief>;
|
|
168
|
-
|
|
169
|
-
constructor(
|
|
170
|
-
private sender: IMessageSender,
|
|
171
|
-
private briefs: McpServerBrief[],
|
|
172
|
-
private msm: RemoteMcpServerManager
|
|
173
|
-
) {
|
|
174
|
-
this.briefsMap = {};
|
|
175
|
-
briefs.forEach((b) => {
|
|
176
|
-
this.briefsMap[b.name] = b;
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
getMcpServerManager(): IMcpServerManager {
|
|
181
|
-
return this.msm;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
getServerBriefs(): McpServerBrief[] {
|
|
185
|
-
return this.briefs;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async addMcpServer(server_name: string, enable_all: boolean): Promise<void> {
|
|
189
|
-
if (!this.briefsMap[server_name]) {
|
|
190
|
-
throw Error(`no such server ${server_name} (addMcpServer)`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
this.sender.sendMessage({
|
|
194
|
-
type: "add_mcp_server",
|
|
195
|
-
server_name,
|
|
196
|
-
enable_all,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export class ChatClient implements IMessageSender, IConversation {
|
|
202
|
-
private platform: IPlatform;
|
|
203
|
-
private ws: websocket.WebSocket;
|
|
204
|
-
private onMessageCB: OnMessageCallback;
|
|
205
|
-
private onConnectionClosedCB: OnConnectionClosedCallback;
|
|
206
|
-
private closed: boolean;
|
|
207
|
-
private msm: RemoteMcpServerManager;
|
|
208
|
-
private smsm: RemoteSudoMcpServerManager;
|
|
209
|
-
private systemPrompt: string;
|
|
210
|
-
private model: string;
|
|
211
|
-
|
|
212
|
-
private constructor(
|
|
213
|
-
platform: IPlatform,
|
|
214
|
-
ws: websocket.WebSocket,
|
|
215
|
-
onMessageCB: OnMessageCallback,
|
|
216
|
-
onConnectionClosedCB: OnConnectionClosedCallback,
|
|
217
|
-
serverBriefs: McpServerBrief[]
|
|
218
|
-
) {
|
|
219
|
-
this.platform = platform;
|
|
220
|
-
this.ws = ws;
|
|
221
|
-
this.onMessageCB = onMessageCB;
|
|
222
|
-
this.onConnectionClosedCB = onConnectionClosedCB;
|
|
223
|
-
this.closed = false;
|
|
224
|
-
this.msm = new RemoteMcpServerManager(this);
|
|
225
|
-
this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs, this.msm);
|
|
226
|
-
this.systemPrompt = "";
|
|
227
|
-
this.model = "";
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
static async initWithParams(
|
|
231
|
-
host: string,
|
|
232
|
-
port: number,
|
|
233
|
-
token: string,
|
|
234
|
-
params: Record<string, string>,
|
|
235
|
-
onMessageCB: OnMessageCallback,
|
|
236
|
-
onConnectionClosedCB: OnConnectionClosedCallback,
|
|
237
|
-
platform: IPlatform
|
|
238
|
-
): Promise<ChatClient> {
|
|
239
|
-
return new Promise((resolveClient, e) => {
|
|
240
|
-
const urlParams = new URLSearchParams(params);
|
|
241
|
-
const url = `ws://${host}:${port}?${urlParams}`;
|
|
242
|
-
|
|
243
|
-
const ws = new websocket.WebSocket(url, [token]);
|
|
244
|
-
logger.info("created ws");
|
|
245
|
-
|
|
246
|
-
let client: ChatClient | undefined = undefined;
|
|
247
|
-
const onMsg = (msg: ServerToClient) => {
|
|
248
|
-
if (msg.type === "mcp_server_briefs") {
|
|
249
|
-
// This should be received once, as the first message at startup.
|
|
250
|
-
assert(!client);
|
|
251
|
-
client = new ChatClient(
|
|
252
|
-
platform,
|
|
253
|
-
ws,
|
|
254
|
-
onMessageCB,
|
|
255
|
-
onConnectionClosedCB,
|
|
256
|
-
msg.server_briefs
|
|
257
|
-
);
|
|
258
|
-
resolveClient(client);
|
|
259
|
-
} else {
|
|
260
|
-
assert(client);
|
|
261
|
-
|
|
262
|
-
// Pass all messages to our internal handler (to update mcp state,
|
|
263
|
-
// etc) before sending to the client code.
|
|
264
|
-
|
|
265
|
-
// logger.debug(`[ChatClient.init(onMsg)]: ${JSON.stringify(msg)}`);
|
|
266
|
-
client.handleMessageInternal(msg);
|
|
267
|
-
client.onMessageCB(msg);
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
ws.onopen = async () => {
|
|
272
|
-
logger.info("opened");
|
|
273
|
-
|
|
274
|
-
ws.onmessage = (ev: websocket.MessageEvent) => {
|
|
275
|
-
try {
|
|
276
|
-
const msgData = ev.data;
|
|
277
|
-
if (typeof msgData !== "string") {
|
|
278
|
-
throw `expected "string" data, got ${typeof msgData}`;
|
|
279
|
-
}
|
|
280
|
-
logger.debug(`[client.onmessage]: ${msgData}`);
|
|
281
|
-
onMsg(JSON.parse(msgData) as ServerToClient);
|
|
282
|
-
} catch (e) {
|
|
283
|
-
if (client) {
|
|
284
|
-
client.close();
|
|
285
|
-
}
|
|
286
|
-
throw e;
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
ws.onclose = async (err) => {
|
|
292
|
-
logger.info("closed");
|
|
293
|
-
logger.info(
|
|
294
|
-
`[client] WebSocket connection closed: ${JSON.stringify(err)}`
|
|
295
|
-
);
|
|
296
|
-
if (client) {
|
|
297
|
-
client.closed = true;
|
|
298
|
-
}
|
|
299
|
-
await onConnectionClosedCB();
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
ws.onerror = async (error) => {
|
|
303
|
-
logger.error("[client] WebSocket error:", JSON.stringify(error));
|
|
304
|
-
e(error);
|
|
305
|
-
|
|
306
|
-
if (client) {
|
|
307
|
-
client.closed = true;
|
|
308
|
-
}
|
|
309
|
-
await onConnectionClosedCB();
|
|
310
|
-
};
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
public static async init(
|
|
315
|
-
host: string,
|
|
316
|
-
port: number,
|
|
317
|
-
token: string,
|
|
318
|
-
onMessageCB: OnMessageCallback,
|
|
319
|
-
onConnectionClosedCB: OnConnectionClosedCallback,
|
|
320
|
-
platform: IPlatform,
|
|
321
|
-
sessionId: string = "untitled",
|
|
322
|
-
agentProfileId: string | undefined = undefined
|
|
323
|
-
): Promise<ChatClient> {
|
|
324
|
-
const params: Record<string, string> = { session_id: sessionId };
|
|
325
|
-
if (agentProfileId) {
|
|
326
|
-
params["agent_profile_id"] = agentProfileId;
|
|
327
|
-
}
|
|
328
|
-
return ChatClient.initWithParams(
|
|
329
|
-
host,
|
|
330
|
-
port,
|
|
331
|
-
token,
|
|
332
|
-
params,
|
|
333
|
-
onMessageCB,
|
|
334
|
-
onConnectionClosedCB,
|
|
335
|
-
platform
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
public getSudoMcpServerManager(): ISkillManager {
|
|
340
|
-
return this.smsm;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
public getConversation(): ChatCompletionMessageParam[] {
|
|
344
|
-
throw "unimpl";
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
public getAgentProfile(): AgentProfile {
|
|
348
|
-
throw "unimpl";
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
getSystemPrompt(): string {
|
|
352
|
-
return this.systemPrompt;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
setSystemPrompt(system_prompt: string): void {
|
|
356
|
-
// Don't set system prompt here. Wait until we get confirmation from the
|
|
357
|
-
// server.
|
|
358
|
-
this.sendMessage({ type: "set_system_prompt", system_prompt });
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
getModel(): string {
|
|
362
|
-
return this.model;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
setModel(model: string): void {
|
|
366
|
-
// Don't set model here. Wait until we get confirmation from the server.
|
|
367
|
-
this.sendMessage({ type: "set_model", model });
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
userMessage(msg?: string, imageB64?: string): void {
|
|
371
|
-
assert(msg);
|
|
372
|
-
assert(!imageB64, "images not supported in Chat yet");
|
|
373
|
-
|
|
374
|
-
this.sendMessage({
|
|
375
|
-
type: "msg",
|
|
376
|
-
message: msg,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
resetConversation(): void {
|
|
381
|
-
throw "resetConversation not implemented for ChatClient";
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
public sendMessage(message: ClientToServer): undefined {
|
|
385
|
-
assert(!this.closed);
|
|
386
|
-
const data = JSON.stringify(message);
|
|
387
|
-
logger.debug(`[ChatClient.sendMessage] ${data}`);
|
|
388
|
-
this.ws.send(data);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
public close(): void {
|
|
392
|
-
this.closed = true;
|
|
393
|
-
this.onConnectionClosedCB();
|
|
394
|
-
this.ws.close();
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
handleMessageInternal(message: ServerToClient): void {
|
|
398
|
-
switch (message.type) {
|
|
399
|
-
//
|
|
400
|
-
// State updates
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
case "mcp_server_added":
|
|
404
|
-
this.msm.onMcpServerAdded(
|
|
405
|
-
message.server_name,
|
|
406
|
-
message.tools,
|
|
407
|
-
message.enabled_tools
|
|
408
|
-
);
|
|
409
|
-
break;
|
|
410
|
-
case "mcp_server_removed":
|
|
411
|
-
this.msm.onMcpServerRemoved(message.server_name);
|
|
412
|
-
break;
|
|
413
|
-
case "mcp_server_tool_enabled":
|
|
414
|
-
this.msm.onMcpServerToolEnabled(message.server_name, message.tool);
|
|
415
|
-
break;
|
|
416
|
-
case "mcp_server_tool_disabled":
|
|
417
|
-
this.msm.onMcpServerToolDisabled(message.server_name, message.tool);
|
|
418
|
-
break;
|
|
419
|
-
case "system_prompt_updated":
|
|
420
|
-
this.systemPrompt = message.system_prompt;
|
|
421
|
-
break;
|
|
422
|
-
case "model_updated":
|
|
423
|
-
this.model = message.model;
|
|
424
|
-
break;
|
|
425
|
-
|
|
426
|
-
//
|
|
427
|
-
// Actions
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
case "open_url":
|
|
431
|
-
this.platform.openUrl(
|
|
432
|
-
message.url,
|
|
433
|
-
new Promise(() => {}), // TODO: Why do we need this? Remove it.
|
|
434
|
-
message.display_name
|
|
435
|
-
);
|
|
436
|
-
break;
|
|
437
|
-
case "approve_tool_call":
|
|
438
|
-
throw "unimpl approve_tool_call";
|
|
439
|
-
// break;
|
|
440
|
-
|
|
441
|
-
//
|
|
442
|
-
// Ignore other messages - the owner (the UI layer) can handle them at
|
|
443
|
-
// its discretion.
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
default:
|
|
447
|
-
logger.debug(
|
|
448
|
-
`[handleMessageInternal]: ignoring message: ${message.type}`
|
|
449
|
-
);
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// TODO: remove this
|