@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.
Files changed (186) hide show
  1. package/README.md +23 -8
  2. package/dist/agent/src/agent/agent.js +176 -96
  3. package/dist/agent/src/agent/agentUtils.js +82 -59
  4. package/dist/agent/src/agent/compressingContextManager.js +102 -0
  5. package/dist/agent/src/agent/context.js +189 -0
  6. package/dist/agent/src/agent/dummyLLM.js +46 -5
  7. package/dist/agent/src/agent/mcpServerManager.js +23 -24
  8. package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
  9. package/dist/agent/src/agent/nullPlatform.js +14 -0
  10. package/dist/agent/src/agent/openAILLMStreaming.js +26 -14
  11. package/dist/agent/src/agent/promptProvider.js +63 -0
  12. package/dist/agent/src/agent/repeatLLM.js +5 -5
  13. package/dist/agent/src/agent/sudoMcpServerManager.js +23 -21
  14. package/dist/agent/src/agent/tokenAuth.js +7 -7
  15. package/dist/agent/src/agent/tools.js +1 -1
  16. package/dist/agent/src/chat/client/chatClient.js +733 -0
  17. package/dist/agent/src/chat/client/connection.js +209 -0
  18. package/dist/agent/src/chat/client/connection.test.js +188 -0
  19. package/dist/agent/src/chat/client/constants.js +5 -0
  20. package/dist/agent/src/chat/client/index.js +15 -0
  21. package/dist/agent/src/chat/client/interfaces.js +2 -0
  22. package/dist/agent/src/chat/client/responseHandler.js +105 -0
  23. package/dist/agent/src/chat/client/sessionClient.js +331 -0
  24. package/dist/agent/src/chat/client/teamManager.js +2 -0
  25. package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
  26. package/dist/agent/src/chat/data/dataModels.js +2 -0
  27. package/dist/agent/src/chat/data/database.js +749 -0
  28. package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
  29. package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
  30. package/dist/agent/src/chat/protocol/constants.js +50 -0
  31. package/dist/agent/src/chat/protocol/errors.js +22 -0
  32. package/dist/agent/src/chat/protocol/messages.js +110 -0
  33. package/dist/agent/src/chat/server/chatContextManager.js +405 -0
  34. package/dist/agent/src/chat/server/connectionManager.js +352 -0
  35. package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
  36. package/dist/agent/src/chat/server/conversation.js +198 -0
  37. package/dist/agent/src/chat/server/errorUtils.js +23 -0
  38. package/dist/agent/src/chat/server/openSession.js +869 -0
  39. package/dist/agent/src/chat/server/server.js +177 -0
  40. package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
  41. package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
  42. package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
  43. package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
  44. package/dist/agent/src/chat/server/tools.js +243 -0
  45. package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
  46. package/dist/agent/src/chat/utils/approvalManager.js +85 -0
  47. package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
  48. package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
  49. package/dist/agent/src/chat/utils/htmlToText.js +84 -0
  50. package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
  51. package/dist/agent/src/chat/utils/search.js +145 -0
  52. package/dist/agent/src/chat/utils/userResolver.js +46 -0
  53. package/dist/agent/src/chat/utils/websocket.js +16 -0
  54. package/dist/agent/src/test/agent.test.js +332 -0
  55. package/dist/agent/src/test/approvalManager.test.js +58 -0
  56. package/dist/agent/src/test/chatContextManager.test.js +392 -0
  57. package/dist/agent/src/test/clientServerConnection.test.js +158 -0
  58. package/dist/agent/src/test/compressingContextManager.test.js +65 -0
  59. package/dist/agent/src/test/context.test.js +83 -0
  60. package/dist/agent/src/test/conversation.test.js +89 -0
  61. package/dist/agent/src/test/db.test.js +271 -83
  62. package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
  63. package/dist/agent/src/test/dbTestTools.js +99 -0
  64. package/dist/agent/src/test/imageLoad.test.js +8 -7
  65. package/dist/agent/src/test/mcpServerManager.test.js +23 -20
  66. package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
  67. package/dist/agent/src/test/openaiStreaming.test.js +64 -35
  68. package/dist/agent/src/test/prompt.test.js +5 -4
  69. package/dist/agent/src/test/promptProvider.test.js +28 -0
  70. package/dist/agent/src/test/responseHandler.test.js +61 -0
  71. package/dist/agent/src/test/sudoMcpServerManager.test.js +24 -25
  72. package/dist/agent/src/test/testTools.js +109 -0
  73. package/dist/agent/src/test/tools.test.js +31 -0
  74. package/dist/agent/src/tool/agentChat.js +21 -10
  75. package/dist/agent/src/tool/agentMain.js +1 -1
  76. package/dist/agent/src/tool/chatMain.js +241 -58
  77. package/dist/agent/src/tool/commandPrompt.js +22 -17
  78. package/dist/agent/src/tool/files.js +20 -16
  79. package/dist/agent/src/tool/nodePlatform.js +47 -3
  80. package/dist/agent/src/tool/options.js +4 -4
  81. package/dist/agent/src/tool/prompt.js +19 -13
  82. package/eslint.config.mjs +14 -1
  83. package/package.json +14 -6
  84. package/scripts/chat_server +8 -0
  85. package/scripts/setup_chat +7 -2
  86. package/scripts/shutdown_chat_server +3 -0
  87. package/scripts/test_chat +135 -17
  88. package/src/agent/agent.ts +283 -138
  89. package/src/agent/agentUtils.ts +143 -108
  90. package/src/agent/compressingContextManager.ts +164 -0
  91. package/src/agent/context.ts +268 -0
  92. package/src/agent/dummyLLM.ts +76 -8
  93. package/src/agent/iAgentEventHandler.ts +54 -0
  94. package/src/agent/iplatform.ts +1 -0
  95. package/src/agent/mcpServerManager.ts +35 -31
  96. package/src/agent/nullAgentEventHandler.ts +20 -0
  97. package/src/agent/nullPlatform.ts +13 -0
  98. package/src/agent/openAILLMStreaming.ts +26 -13
  99. package/src/agent/promptProvider.ts +87 -0
  100. package/src/agent/repeatLLM.ts +5 -5
  101. package/src/agent/sudoMcpServerManager.ts +30 -29
  102. package/src/agent/tokenAuth.ts +7 -7
  103. package/src/agent/tools.ts +3 -1
  104. package/src/chat/client/chatClient.ts +900 -0
  105. package/src/chat/client/connection.test.ts +241 -0
  106. package/src/chat/client/connection.ts +276 -0
  107. package/src/chat/client/constants.ts +3 -0
  108. package/src/chat/client/index.ts +18 -0
  109. package/src/chat/client/interfaces.ts +34 -0
  110. package/src/chat/client/responseHandler.ts +131 -0
  111. package/src/chat/client/sessionClient.ts +443 -0
  112. package/src/chat/client/teamManager.ts +29 -0
  113. package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
  114. package/src/chat/data/dataModels.ts +85 -0
  115. package/src/chat/data/database.ts +982 -0
  116. package/src/chat/data/dbMcpServerConfigs.ts +59 -0
  117. package/src/chat/protocol/connectionMessages.ts +49 -0
  118. package/src/chat/protocol/constants.ts +55 -0
  119. package/src/chat/protocol/errors.ts +16 -0
  120. package/src/chat/protocol/messages.ts +682 -0
  121. package/src/chat/server/README.md +127 -0
  122. package/src/chat/server/chatContextManager.ts +612 -0
  123. package/src/chat/server/connectionManager.test.ts +266 -0
  124. package/src/chat/server/connectionManager.ts +541 -0
  125. package/src/chat/server/conversation.ts +269 -0
  126. package/src/chat/server/errorUtils.ts +28 -0
  127. package/src/chat/server/openSession.ts +1332 -0
  128. package/src/chat/server/server.ts +177 -0
  129. package/src/chat/server/sessionFileManager.ts +239 -0
  130. package/src/chat/server/sessionRegistry.test.ts +138 -0
  131. package/src/chat/server/sessionRegistry.ts +1064 -0
  132. package/src/chat/server/test-utils/mockFactories.ts +422 -0
  133. package/src/chat/server/tools.ts +265 -0
  134. package/src/chat/utils/agentSessionMap.ts +76 -0
  135. package/src/chat/utils/approvalManager.ts +111 -0
  136. package/src/{utils → chat/utils}/asyncLock.ts +3 -3
  137. package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
  138. package/src/chat/utils/htmlToText.ts +61 -0
  139. package/src/chat/utils/multiAsyncQueue.ts +52 -0
  140. package/src/chat/utils/search.ts +139 -0
  141. package/src/chat/utils/userResolver.ts +48 -0
  142. package/src/chat/utils/websocket.ts +16 -0
  143. package/src/test/agent.test.ts +487 -0
  144. package/src/test/approvalManager.test.ts +73 -0
  145. package/src/test/chatContextManager.test.ts +521 -0
  146. package/src/test/clientServerConnection.test.ts +207 -0
  147. package/src/test/compressingContextManager.test.ts +82 -0
  148. package/src/test/context.test.ts +105 -0
  149. package/src/test/conversation.test.ts +109 -0
  150. package/src/test/db.test.ts +358 -89
  151. package/src/test/dbMcpServerConfigs.test.ts +112 -0
  152. package/src/test/dbTestTools.ts +153 -0
  153. package/src/test/imageLoad.test.ts +7 -6
  154. package/src/test/mcpServerManager.test.ts +21 -16
  155. package/src/test/multiAsyncQueue.test.ts +125 -0
  156. package/src/test/openaiStreaming.test.ts +71 -36
  157. package/src/test/prompt.test.ts +4 -3
  158. package/src/test/promptProvider.test.ts +33 -0
  159. package/src/test/responseHandler.test.ts +78 -0
  160. package/src/test/sudoMcpServerManager.test.ts +32 -30
  161. package/src/test/testTools.ts +146 -0
  162. package/src/test/tools.test.ts +39 -0
  163. package/src/tool/agentChat.ts +26 -12
  164. package/src/tool/agentMain.ts +1 -1
  165. package/src/tool/chatMain.ts +292 -100
  166. package/src/tool/commandPrompt.ts +28 -19
  167. package/src/tool/files.ts +25 -19
  168. package/src/tool/nodePlatform.ts +52 -3
  169. package/src/tool/options.ts +4 -2
  170. package/src/tool/prompt.ts +22 -15
  171. package/test_data/dummyllm_script_crash.json +32 -0
  172. package/test_data/frog.png.b64 +1 -0
  173. package/vitest.config.ts +39 -0
  174. package/dist/agent/src/chat/client.js +0 -349
  175. package/dist/agent/src/chat/conversationManager.js +0 -392
  176. package/dist/agent/src/chat/db.js +0 -209
  177. package/dist/agent/src/chat/frontendClient.js +0 -74
  178. package/dist/agent/src/chat/server.js +0 -158
  179. package/src/chat/client.ts +0 -455
  180. package/src/chat/conversationManager.ts +0 -595
  181. package/src/chat/db.ts +0 -290
  182. package/src/chat/frontendClient.ts +0 -123
  183. package/src/chat/messages.ts +0 -235
  184. package/src/chat/server.ts +0 -177
  185. /package/dist/agent/src/{chat/messages.js → agent/iAgentEventHandler.js} +0 -0
  186. /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
- }
@@ -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