@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
@@ -25,7 +25,7 @@ class McpServerInfo {
25
25
  }
26
26
  this.tools = tools;
27
27
  this.toolsMap = toolsMap;
28
- this.enabledToolsMap = {};
28
+ this.enabledToolsMap = new Map();
29
29
  }
30
30
  getEnabledTools() {
31
31
  return this.enabledToolsMap;
@@ -44,10 +44,10 @@ exports.McpServerInfo = McpServerInfo;
44
44
  */
45
45
  class McpServerInfoRW extends McpServerInfo {
46
46
  enableTool(toolName) {
47
- this.enabledToolsMap[toolName] = true;
47
+ this.enabledToolsMap.set(toolName, true);
48
48
  }
49
49
  disableTool(toolName) {
50
- delete this.enabledToolsMap[toolName];
50
+ this.enabledToolsMap.delete(toolName);
51
51
  }
52
52
  }
53
53
  exports.McpServerInfoRW = McpServerInfoRW;
@@ -59,13 +59,13 @@ exports.McpServerInfoRW = McpServerInfoRW;
59
59
  class McpServerInfoInternal extends McpServerInfoRW {
60
60
  constructor(client, tools) {
61
61
  super(tools);
62
- const callbacks = {};
62
+ const callbacks = new Map();
63
63
  for (const mcpTool of tools) {
64
64
  const toolName = mcpTool.name;
65
65
  // Create callback
66
66
  const callback = async (argStr) => {
67
67
  logger.debug(`cb for ${toolName} invoked with args (${typeof argStr}): ` +
68
- `${JSON.stringify(argStr)}`);
68
+ JSON.stringify(argStr));
69
69
  const argsObj = JSON.parse(argStr);
70
70
  const toolResult = await client.callTool({
71
71
  name: toolName,
@@ -82,7 +82,7 @@ class McpServerInfoInternal extends McpServerInfoRW {
82
82
  (0, assert_1.strict)(typeof content0Text === "string");
83
83
  return content0Text;
84
84
  };
85
- callbacks[toolName] = callback;
85
+ callbacks.set(toolName, callback);
86
86
  }
87
87
  this.client = client;
88
88
  this.callbacks = callbacks;
@@ -91,7 +91,7 @@ class McpServerInfoInternal extends McpServerInfoRW {
91
91
  await this.client.close();
92
92
  }
93
93
  getCallback(toolName) {
94
- return this.callbacks[toolName];
94
+ return this.callbacks.get(toolName);
95
95
  }
96
96
  }
97
97
  /**
@@ -101,27 +101,27 @@ class McpServerInfoInternal extends McpServerInfoRW {
101
101
  */
102
102
  class McpServerManager {
103
103
  constructor() {
104
- this.mcpServers = {};
104
+ this.mcpServers = new Map();
105
105
  this.enabledToolsDirty = true;
106
106
  this.enabledOpenAITools = [];
107
107
  }
108
108
  async shutdown() {
109
- await Promise.all(Object.keys(this.mcpServers).map((name) => {
109
+ await Promise.all(Array.from(this.mcpServers.entries()).map(([name, server]) => {
110
110
  logger.debug(`shutting down: ${name}...`);
111
- this.mcpServers[name].shutdown();
111
+ return server.shutdown();
112
112
  }));
113
- this.mcpServers = {};
113
+ this.mcpServers.clear();
114
114
  }
115
115
  hasMcpServer(mcpServerName) {
116
- return !!this.mcpServers[mcpServerName];
116
+ return this.mcpServers.has(mcpServerName);
117
117
  }
118
118
  getMcpServerNames() {
119
- return Object.keys(this.mcpServers);
119
+ return Array.from(this.mcpServers.keys());
120
120
  }
121
121
  getMcpServer(mcpServerName) {
122
122
  return this.getMcpServerInternal(mcpServerName);
123
123
  }
124
- async addMcpServer(mcpServerName, url, apiKey, tools) {
124
+ async addMcpServerWithSSEUrl(mcpServerName, url, apiKey, tools) {
125
125
  logger.debug(`Adding mcp server ${mcpServerName}: ${url}`);
126
126
  const sseTransportOptions = {};
127
127
  if (apiKey) {
@@ -154,7 +154,7 @@ class McpServerManager {
154
154
  const mcpTools = await client.listTools();
155
155
  tools = mcpTools.tools;
156
156
  }
157
- this.mcpServers[mcpServerName] = new McpServerInfoInternal(client, tools);
157
+ this.mcpServers.set(mcpServerName, new McpServerInfoInternal(client, tools));
158
158
  }
159
159
  catch (e) {
160
160
  await client.close();
@@ -163,7 +163,7 @@ class McpServerManager {
163
163
  }
164
164
  async removeMcpServer(mcpServerName) {
165
165
  const server = this.getMcpServerInternal(mcpServerName);
166
- delete this.mcpServers[mcpServerName];
166
+ this.mcpServers.delete(mcpServerName);
167
167
  await server.shutdown();
168
168
  this.enabledToolsDirty = true;
169
169
  }
@@ -215,7 +215,7 @@ class McpServerManager {
215
215
  const server = this.getMcpServerInternal(mcpServerName);
216
216
  const cb = server.getCallback(toolName);
217
217
  if (!cb) {
218
- throw `Unknown tool ${qualifiedToolName}`;
218
+ throw new Error(`Unknown tool ${qualifiedToolName}`);
219
219
  }
220
220
  return cb(JSON.stringify(args));
221
221
  }
@@ -230,8 +230,8 @@ class McpServerManager {
230
230
  //
231
231
  // may be interpreted as "all tools for <server>". If the client has left
232
232
  // a server with no tools enabled, we mark it as disabled.
233
- for (const [serverName, server] of Object.entries(this.mcpServers)) {
234
- const tools = Object.keys(server.getEnabledTools());
233
+ for (const [serverName, server] of this.mcpServers) {
234
+ const tools = Array.from(server.getEnabledTools().keys());
235
235
  if (tools.length > 0) {
236
236
  config[serverName] = tools;
237
237
  }
@@ -239,11 +239,11 @@ class McpServerManager {
239
239
  return config;
240
240
  }
241
241
  getMcpServerInternal(mcpServerName) {
242
- const server = this.mcpServers[mcpServerName];
242
+ const server = this.mcpServers.get(mcpServerName);
243
243
  if (server) {
244
244
  return server;
245
245
  }
246
- throw Error(`unknown server ${mcpServerName}`);
246
+ throw Error(`[getMcpServerInternal] unknown server ${mcpServerName}`);
247
247
  }
248
248
  }
249
249
  exports.McpServerManager = McpServerManager;
@@ -262,13 +262,12 @@ function splitQualifiedName(qualifiedToolName) {
262
262
  }
263
263
  function computeOpenAIToolList(mcpServers) {
264
264
  const openaiTools = [];
265
- for (const mcpServerName in mcpServers) {
266
- const mcpServer = mcpServers[mcpServerName];
265
+ for (const [mcpServerName, mcpServer] of mcpServers) {
267
266
  const tools = mcpServer.getTools();
268
267
  const enabled = mcpServer.getEnabledTools();
269
268
  for (const mcpTool of tools) {
270
269
  const toolName = mcpTool.name;
271
- if (enabled[toolName]) {
270
+ if (enabled.get(toolName)) {
272
271
  const qualifiedName = computeQualifiedName(mcpServerName, toolName);
273
272
  const openaiTool = mcpToolToOpenAITool(mcpTool, qualifiedName);
274
273
  openaiTools.push(openaiTool);
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NULL_AGENT_EVENT_HANDLER = void 0;
4
+ /**
5
+ * Trivial IAgentEventHandler implementation which does not track any messages
6
+ * and does not allow tool calls.
7
+ */
8
+ exports.NULL_AGENT_EVENT_HANDLER = {
9
+ onCompletion: () => { },
10
+ onToolCallResult: () => { },
11
+ onAgentMessage: () => {
12
+ return new Promise((r) => {
13
+ r();
14
+ });
15
+ },
16
+ onToolCall: () => {
17
+ return new Promise((r) => {
18
+ r(false);
19
+ });
20
+ },
21
+ };
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NULL_PLATFORM = void 0;
4
+ exports.NULL_PLATFORM = {
5
+ openUrl: () => {
6
+ throw new Error("null_platform openUrl called");
7
+ },
8
+ load: () => {
9
+ throw new Error("null_platform load called");
10
+ },
11
+ renderHTML: () => {
12
+ throw new Error("null_platform renderHTML called");
13
+ },
14
+ };
@@ -39,9 +39,6 @@ function updateToolCallFunction(existingFn, deltaFn // eslint-disable-line
39
39
  // The function can have either (or possibly both) field(s) empty.
40
40
  // `arguments` has been observered to arrive in chunks. The same is
41
41
  // probably true of `name`.
42
- if (!deltaFn) {
43
- return;
44
- }
45
42
  if (deltaFn.name) {
46
43
  existingFn.name += deltaFn.name;
47
44
  }
@@ -107,8 +104,8 @@ function updateToolCalls(toolCalls, deltaToolCall // eslint-disable-line
107
104
  if (typeof toolCalls === "undefined") {
108
105
  toolCalls = [];
109
106
  }
110
- const existing = toolCalls[deltaToolCall.index];
111
- if (!existing) {
107
+ if (deltaToolCall.index >= toolCalls.length ||
108
+ !toolCalls[deltaToolCall.index]) {
112
109
  toolCalls[deltaToolCall.index] = initialToolCall(deltaToolCall);
113
110
  }
114
111
  else {
@@ -118,6 +115,7 @@ function updateToolCalls(toolCalls, deltaToolCall // eslint-disable-line
118
115
  }
119
116
  function initializeCompletionMessage(delta) {
120
117
  (0, assert_1.strict)(delta.role === undefined || delta.role == "assistant");
118
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
121
119
  (0, assert_1.strict)(!delta.function_call);
122
120
  // export interface ChatCompletionChunk.Choice.Delta {
123
121
  // content?: string | null;
@@ -155,7 +153,9 @@ function initializeCompletionMessage(delta) {
155
153
  };
156
154
  }
157
155
  function updateCompletionMessage(message, delta) {
156
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
158
157
  (0, assert_1.strict)(message.role === "assistant");
158
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
159
159
  (0, assert_1.strict)(!message.function_call);
160
160
  (0, assert_1.strict)(!message.audio);
161
161
  (0, assert_1.strict)(message.tool_calls instanceof Array ||
@@ -187,6 +187,7 @@ function updateCompletionMessage(message, delta) {
187
187
  message.content = delta.content;
188
188
  }
189
189
  }
190
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
190
191
  (0, assert_1.strict)(!delta.function_call);
191
192
  if (delta.refusal) {
192
193
  if (message.refusal) {
@@ -255,7 +256,9 @@ function updateCompletionChoice(completionChoice, chunkChoice) {
255
256
  (0, assert_1.strict)(completionChoice.index === chunkChoice.index);
256
257
  updateCompletionMessage(completionChoice.message, chunkChoice.delta);
257
258
  if (chunkChoice.finish_reason) {
258
- (0, assert_1.strict)(completionChoice.finish_reason === null, `finish_reason already set: (${completionChoice.finish_reason})`);
259
+ (0, assert_1.strict)(
260
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
261
+ completionChoice.finish_reason === null, `finish_reason already set: (${completionChoice.finish_reason})`);
259
262
  completionChoice.finish_reason = chunkChoice.finish_reason;
260
263
  return true;
261
264
  }
@@ -263,9 +266,10 @@ function updateCompletionChoice(completionChoice, chunkChoice) {
263
266
  }
264
267
  function initializeCompletionChoices(chunkChoices) {
265
268
  // Technically, one choice could be done and the other still have some
266
- // content to stream. We keep it simple for now and assume only single
267
- // choices, which allows us to mark everything as done if any choice we hit is
268
- // done.
269
+ // content to stream. We keep it simple for now and allow zero or one
270
+ // choice per chunk, which allows us to mark everything as done if any
271
+ // choice we hit is done. Zero choices can occur in usage-only chunks at
272
+ // the end of the stream.
269
273
  (0, assert_1.strict)(chunkChoices.length < 2);
270
274
  let msgDone = false;
271
275
  const choices = [];
@@ -280,10 +284,11 @@ function initializeCompletionChoices(chunkChoices) {
280
284
  }
281
285
  function updateCompletionChoices(completionChoices, chunkChoices) {
282
286
  // Technically, one choice could be done and the other still have some
283
- // content to stream. We keep it simple for now and assume only single
284
- // choices, which allows us to mark everything as done if any choice we hit is
285
- // done.
286
- (0, assert_1.strict)(chunkChoices.length === 1);
287
+ // content to stream. We keep it simple for now and allow zero or one
288
+ // choice per chunk, which allows us to mark everything as done if any
289
+ // choice we hit is done. Zero choices can occur in usage-only chunks at
290
+ // the end of the stream.
291
+ (0, assert_1.strict)(chunkChoices.length < 2);
287
292
  (0, assert_1.strict)(completionChoices.length === 1);
288
293
  let msgDone = false;
289
294
  for (const chunkChoice of chunkChoices) {
@@ -391,14 +396,18 @@ class OpenAILLMStreaming {
391
396
  messages,
392
397
  tools,
393
398
  stream: true,
399
+ stream_options: {
400
+ include_usage: true,
401
+ },
394
402
  });
395
403
  // Check the type casting above
396
404
  if (!chunks.iterator) {
397
- throw "not a stream";
405
+ throw new Error("not a stream");
398
406
  }
399
407
  let aggregatedMessage;
400
408
  for await (const chunk of chunks) {
401
409
  logger.debug(`[stream] chunk: ${JSON.stringify(chunk)}`);
410
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
402
411
  if (chunk.object !== "chat.completion.chunk") {
403
412
  // logger.warn("[stream]: unexpected message");
404
413
  continue;
@@ -413,7 +422,10 @@ class OpenAILLMStreaming {
413
422
  }
414
423
  if (onMessage) {
415
424
  // Inform the call of a message fragment if it contains any text.
425
+ // Note: chunks may have zero choices (e.g., usage-only chunks), so
426
+ // we safely access the first choice.
416
427
  const delta = chunk.choices[0]?.delta;
428
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
417
429
  if (delta?.content) {
418
430
  await onMessage(delta.content, false);
419
431
  }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SystemPromptProvider = void 0;
4
+ const assert_1 = require("assert");
5
+ /**
6
+ * Manage a structured system-prompt, built up of several components which can
7
+ * be updated at different frequencies.
8
+ */
9
+ class SystemPromptProvider {
10
+ constructor(agentPrompt) {
11
+ this.agentPrompt = agentPrompt;
12
+ this.fragments = new Map();
13
+ this.fullPrompt = "";
14
+ this.updatePrompt = true;
15
+ }
16
+ /**
17
+ * This is not expected to change dynamically, but to be set exactly once at
18
+ * startup. Thus existing prompts are not recomputed if this is updated.
19
+ */
20
+ static setGlobalPrompt(globalPrompt) {
21
+ SystemPromptProvider.globalPrompt = globalPrompt || "";
22
+ }
23
+ getAgentPrompt() {
24
+ return this.agentPrompt;
25
+ }
26
+ setAgentPrompt(agentPrompt) {
27
+ this.agentPrompt = agentPrompt;
28
+ this.updatePrompt = true;
29
+ }
30
+ setFragment(fragmentID, prompt) {
31
+ this.fragments.set(fragmentID, prompt);
32
+ this.updatePrompt = true;
33
+ }
34
+ removeFragment(fragmentID) {
35
+ this.fragments.delete(fragmentID);
36
+ this.updatePrompt = true;
37
+ }
38
+ getSystemPrompt() {
39
+ if (this.updatePrompt) {
40
+ this.computePrompt();
41
+ }
42
+ (0, assert_1.strict)(!this.updatePrompt);
43
+ return this.fullPrompt;
44
+ }
45
+ computePrompt() {
46
+ const global = SystemPromptProvider.globalPrompt.trimEnd();
47
+ const globalPost = global.length > 0 ? "\n" : "";
48
+ let prompt = SystemPromptProvider.globalPrompt.trimEnd() +
49
+ globalPost +
50
+ this.agentPrompt.trimEnd();
51
+ // TODO: allow some control over the ordering?
52
+ for (const v of this.fragments.values()) {
53
+ prompt += "\n" + v.trimEnd();
54
+ }
55
+ this.fullPrompt = prompt;
56
+ this.updatePrompt = false;
57
+ }
58
+ }
59
+ exports.SystemPromptProvider = SystemPromptProvider;
60
+ /**
61
+ * A global prompt, common to all agents.
62
+ */
63
+ SystemPromptProvider.globalPrompt = "";
@@ -10,11 +10,11 @@ class RepeatLLM {
10
10
  return "repeat";
11
11
  }
12
12
  getUrl() {
13
- throw "cannot get url for RepeatLLM";
13
+ throw new Error("cannot get url for RepeatLLM");
14
14
  }
15
15
  async getConversationResponse(_messages, _tools, onMessage) {
16
- await new Promise((r) => setTimeout(r, 0));
17
- const content = `Message number ${this.idx++}`;
16
+ await new Promise((r) => setTimeout(r, 1000));
17
+ const content = `Message number ${String(this.idx++)}`;
18
18
  const response = {
19
19
  finish_reason: "stop",
20
20
  index: 0,
@@ -26,10 +26,10 @@ class RepeatLLM {
26
26
  },
27
27
  };
28
28
  if (onMessage) {
29
- onMessage(content, true);
29
+ void onMessage(content, true);
30
30
  }
31
31
  return {
32
- id: "" + this.idx,
32
+ id: String(this.idx),
33
33
  choices: [response],
34
34
  created: Date.now(),
35
35
  model: "dummyLlmModel",
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SkillManager = exports.LOCAL_SERVER_URL = void 0;
4
+ const mcpServerManager_1 = require("./mcpServerManager");
4
5
  const sdk_1 = require("@xalia/xmcp/sdk");
5
6
  const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
6
7
  const logger = (0, sdk_1.getLogger)();
@@ -25,11 +26,11 @@ class SanitizedServerBrief extends sdk_1.McpServerBrief {
25
26
  * Manages access to the catalogue of servers hosted by sudomcp. Supports
26
27
  * adding these servers to McpServerManager.
27
28
  */
28
- class SkillManager {
29
- constructor(mcpServerManager, apiClient, serverBriefs, serverBriefsMap, toolCache, openUrl,
29
+ class SkillManager extends mcpServerManager_1.McpServerManager {
30
+ constructor(apiClient, serverBriefs, serverBriefsMap, toolCache, openUrl,
30
31
  // Redirect to this page after successful authorization
31
32
  authorized_url) {
32
- this.mcpServerManager = mcpServerManager;
33
+ super();
33
34
  this.apiClient = apiClient;
34
35
  this.serverBriefs = serverBriefs;
35
36
  this.serverBriefsMap = serverBriefsMap;
@@ -41,18 +42,21 @@ class SkillManager {
41
42
  * Initialize an ApiClient to interface with SudoMCP backend and
42
43
  * fetch the current list of ServerBriefs.
43
44
  */
44
- static async initialize(mcpServerManager, openUrl, sudoMcpUrl, sudoMcpApiKey, authorized_url) {
45
+ static async initialize(openUrl, sudoMcpUrl, sudoMcpApiKey, authorized_url) {
45
46
  // TODO: Keep it on here and pass to `McpServerManager.addMcpServer`
46
47
  const apiClient = new sdk_1.ApiClient(sudoMcpUrl ?? sdk_1.DEFAULT_SERVER_URL, sudoMcpApiKey ?? "dummy_key");
47
48
  // Fetch server list
48
49
  const servers = await apiClient.listServers();
49
50
  const [mcpServers, mcpServersMap] = buildServersList(servers);
50
- return new SkillManager(mcpServerManager, apiClient, mcpServers, mcpServersMap, {}, openUrl, authorized_url);
51
+ return new SkillManager(apiClient, mcpServers, mcpServersMap, new Map(), 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.
54
58
  async restoreMcpSettings(mcpSettings) {
55
- await this.restoreConfiguration(mcpSettings);
59
+ return this.restoreConfiguration(mcpSettings);
56
60
  }
57
61
  /**
58
62
  * Load the configuration from sudomcp, and enable the specified tools.
@@ -73,15 +77,17 @@ class SkillManager {
73
77
  }
74
78
  if (enabled.length === 0) {
75
79
  logger.debug(` restoring "${serverName}": (all tools)`);
76
- this.mcpServerManager.enableAllTools(serverName);
80
+ this.enableAllTools(serverName);
77
81
  return;
78
82
  }
79
83
  logger.debug(` restoring "${serverName}": ${JSON.stringify(enabled)}`);
80
84
  for (const toolName of enabled) {
81
- this.mcpServerManager.enableTool(serverName, toolName);
85
+ this.enableTool(serverName, toolName);
82
86
  }
83
87
  };
84
- Object.entries(mcpConfig).map((e) => enableTools(e));
88
+ Object.entries(mcpConfig).map((e) => {
89
+ enableTools(e);
90
+ });
85
91
  }
86
92
  /**
87
93
  * Query backend for server list, clear tool cache.
@@ -91,31 +97,28 @@ class SkillManager {
91
97
  const [mcpServers, mcpServersMap] = buildServersList(servers);
92
98
  this.serverBriefs = mcpServers;
93
99
  this.serverBriefsMap = mcpServersMap;
94
- this.toolCache = {};
100
+ this.toolCache = new Map();
95
101
  }
96
- hasServer(serverName) {
102
+ hasServerBrief(serverName) {
97
103
  return !!this.serverBriefsMap[serverName];
98
104
  }
99
105
  getServerBriefs() {
100
106
  return this.serverBriefs;
101
107
  }
102
- getMcpServerManager() {
103
- return this.mcpServerManager;
104
- }
105
108
  /**
106
109
  * Return tool list for a given MCP server. Queries the backend
107
110
  * if necessary and caches the result.
108
111
  */
109
112
  async getServerTools(serverName) {
110
113
  // Check cache
111
- let tools = this.toolCache[serverName];
114
+ let tools = this.toolCache.get(serverName);
112
115
  if (tools) {
113
116
  return tools;
114
117
  }
115
118
  // Query backend (using the original name)
116
119
  const originalName = this.serverBriefsMap[serverName].originalName;
117
120
  tools = await this.apiClient.listTools(originalName);
118
- this.toolCache[serverName] = tools;
121
+ this.toolCache.set(serverName, tools);
119
122
  return tools;
120
123
  }
121
124
  /**
@@ -131,10 +134,9 @@ class SkillManager {
131
134
  version: "1.0.0",
132
135
  });
133
136
  await connectServer(client, this.apiClient, mcpserver, this.openUrl, this.authorized_url);
134
- const msm = this.mcpServerManager;
135
- await msm.addMcpServerWithClient(client, serverName, tools);
137
+ await this.addMcpServerWithClient(client, serverName, tools);
136
138
  if (enableAll) {
137
- msm.enableAllTools(serverName);
139
+ this.enableAllTools(serverName);
138
140
  }
139
141
  }
140
142
  getOriginalName(serverName) {
@@ -158,9 +160,9 @@ async function connectServer(client, apiClient, mcpServer, openUrl, authorized_u
158
160
  logger.info(`authenticate at url: ${url}`);
159
161
  openUrl(url, authenticatedP, mcpServer.title);
160
162
  const authResult = await authenticatedP;
161
- logger.info(`authResult: ${authResult}`);
163
+ logger.info(`authResult: ${String(authResult)}`);
162
164
  if (!authResult) {
163
- throw "authentication failed";
165
+ throw new Error("authentication failed");
164
166
  }
165
167
  return connectServer(client, apiClient, mcpServer, openUrl, authorized_url, true);
166
168
  }
@@ -6,16 +6,16 @@ class TokenAuth {
6
6
  this.token = token;
7
7
  }
8
8
  get redirectUrl() {
9
- throw "unimpl";
9
+ throw new Error("unimpl: redirectUrl");
10
10
  }
11
11
  get clientMetadata() {
12
- throw "unimpl";
12
+ throw new Error("unimpl: clientMetadata");
13
13
  }
14
14
  clientInformation() {
15
15
  return undefined;
16
16
  }
17
17
  saveClientInformation(_clientInformation) {
18
- throw "unimpl";
18
+ throw new Error("unimpl: saveClientInformation");
19
19
  }
20
20
  tokens() {
21
21
  return {
@@ -24,16 +24,16 @@ class TokenAuth {
24
24
  };
25
25
  }
26
26
  saveTokens(_tokens) {
27
- throw "unimpl";
27
+ throw new Error("unimpl: saveTokens");
28
28
  }
29
29
  redirectToAuthorization(_authorizationUrl) {
30
- throw "unimpl";
30
+ throw new Error("unimpl: redirectToAuthorization");
31
31
  }
32
32
  saveCodeVerifier(_codeVerifier) {
33
- throw "unimpl";
33
+ throw new Error("unimpl: saveCodeVerifier");
34
34
  }
35
35
  codeVerifier() {
36
- throw "unimpl";
36
+ throw new Error("unimpl: codeVerifier");
37
37
  }
38
38
  }
39
39
  exports.TokenAuth = TokenAuth;
@@ -35,7 +35,7 @@ exports.toolCallbacks = {
35
35
  if (!isTemperatureArgs(args) || !args.location) {
36
36
  return `Location required`;
37
37
  }
38
- return `The temperature in ${args.location} is 22 degrees ${args.unit}`;
38
+ return `The temperature in ${args.location} is 22 degrees ${args.unit ?? "celsius"}`;
39
39
  },
40
40
  };
41
41
  function displayToolCall(toolCall) {