@xalia/agent 0.6.8 → 0.6.10

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 (152) hide show
  1. package/.env.development +6 -0
  2. package/.env.test +7 -0
  3. package/README.md +11 -0
  4. package/context_system.md +498 -0
  5. package/dist/agent/src/agent/agent.js +169 -87
  6. package/dist/agent/src/agent/agentUtils.js +24 -18
  7. package/dist/agent/src/agent/compressingContextManager.js +10 -14
  8. package/dist/agent/src/agent/context.js +101 -127
  9. package/dist/agent/src/agent/contextWithWorkspace.js +133 -0
  10. package/dist/agent/src/agent/documentSummarizer.js +126 -0
  11. package/dist/agent/src/agent/dummyLLM.js +25 -22
  12. package/dist/agent/src/agent/imageGenLLM.js +22 -25
  13. package/dist/agent/src/agent/imageGenerator.js +2 -10
  14. package/dist/agent/src/agent/llm.js +1 -1
  15. package/dist/agent/src/agent/openAILLM.js +15 -12
  16. package/dist/agent/src/agent/openAILLMStreaming.js +73 -39
  17. package/dist/agent/src/agent/repeatLLM.js +16 -7
  18. package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
  19. package/dist/agent/src/agent/tokenCounter.js +390 -0
  20. package/dist/agent/src/agent/tokenCounter.test.js +206 -0
  21. package/dist/agent/src/agent/toolSettings.js +17 -0
  22. package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
  23. package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
  24. package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
  25. package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
  26. package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
  27. package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
  28. package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
  29. package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
  30. package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
  31. package/dist/agent/src/agent/tools/index.js +64 -0
  32. package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
  33. package/dist/agent/src/agent/tools/renderTool.js +89 -0
  34. package/dist/agent/src/agent/tools/utils.js +61 -0
  35. package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
  36. package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
  37. package/dist/agent/src/chat/client/chatClient.js +63 -2
  38. package/dist/agent/src/chat/client/connection.js +6 -1
  39. package/dist/agent/src/chat/client/index.js +4 -1
  40. package/dist/agent/src/chat/client/sessionClient.js +28 -9
  41. package/dist/agent/src/chat/constants.js +8 -0
  42. package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
  43. package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
  44. package/dist/agent/src/chat/protocol/messages.js +9 -0
  45. package/dist/agent/src/chat/server/chatContextManager.js +186 -156
  46. package/dist/agent/src/chat/server/conversation.js +3 -0
  47. package/dist/agent/src/chat/server/imageGeneratorTools.js +39 -16
  48. package/dist/agent/src/chat/server/openAIRouterLLM.js +111 -0
  49. package/dist/agent/src/chat/server/openSession.js +253 -91
  50. package/dist/agent/src/chat/server/promptRefiner.js +86 -0
  51. package/dist/agent/src/chat/server/server.js +10 -2
  52. package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
  53. package/dist/agent/src/chat/server/sessionRegistry.js +152 -6
  54. package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
  55. package/dist/agent/src/chat/server/titleGenerator.js +112 -0
  56. package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
  57. package/dist/agent/src/chat/server/tools.js +64 -253
  58. package/dist/agent/src/chat/utils/approvalManager.js +6 -3
  59. package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
  60. package/dist/agent/src/test/agent.test.js +16 -17
  61. package/dist/agent/src/test/chatContextManager.test.js +44 -30
  62. package/dist/agent/src/test/clientServerConnection.test.js +1 -2
  63. package/dist/agent/src/test/compressingContextManager.test.js +22 -36
  64. package/dist/agent/src/test/context.test.js +55 -17
  65. package/dist/agent/src/test/contextTestTools.js +87 -0
  66. package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
  67. package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
  68. package/dist/agent/src/test/testTools.js +6 -1
  69. package/dist/agent/src/test/tools.test.js +27 -9
  70. package/dist/agent/src/tool/agentChat.js +5 -2
  71. package/dist/agent/src/tool/chatMain.js +56 -15
  72. package/dist/agent/src/tool/commandPrompt.js +2 -2
  73. package/dist/agent/src/tool/files.js +7 -8
  74. package/package.json +4 -1
  75. package/scripts/test_chat +195 -173
  76. package/src/agent/agent.ts +257 -137
  77. package/src/agent/agentUtils.ts +32 -20
  78. package/src/agent/compressingContextManager.ts +13 -44
  79. package/src/agent/context.ts +165 -159
  80. package/src/agent/contextWithWorkspace.ts +162 -0
  81. package/src/agent/documentSummarizer.ts +157 -0
  82. package/src/agent/dummyLLM.ts +27 -23
  83. package/src/agent/imageGenLLM.ts +28 -32
  84. package/src/agent/imageGenerator.ts +3 -18
  85. package/src/agent/llm.ts +2 -2
  86. package/src/agent/openAILLM.ts +17 -13
  87. package/src/agent/openAILLMStreaming.ts +99 -43
  88. package/src/agent/repeatLLM.ts +19 -7
  89. package/src/agent/sudoMcpServerManager.ts +41 -20
  90. package/src/agent/test_data/harrypotter.txt +6065 -0
  91. package/src/agent/tokenCounter.test.ts +243 -0
  92. package/src/agent/tokenCounter.ts +483 -0
  93. package/src/agent/toolSettings.ts +24 -0
  94. package/src/agent/tools/calculatorTool.ts +50 -0
  95. package/src/agent/tools/contentExtractors/pdfToText.ts +60 -0
  96. package/src/agent/tools/datetimeTool.ts +41 -0
  97. package/src/agent/tools/fileManager/fileManagerTool.ts +199 -0
  98. package/src/agent/tools/fileManager/index.ts +50 -0
  99. package/src/agent/tools/fileManager/memoryFileManager.ts +120 -0
  100. package/src/{chat/data → agent/tools/fileManager}/mimeTypes.ts +3 -1
  101. package/src/agent/tools/fileManager/prompt.ts +38 -0
  102. package/src/{chat/data/dbSessionFileModels.ts → agent/tools/fileManager/types.ts} +76 -0
  103. package/src/agent/tools/index.ts +49 -0
  104. package/src/agent/tools/openUrlTool.ts +62 -0
  105. package/src/agent/tools/renderTool.ts +92 -0
  106. package/src/agent/tools/utils.ts +74 -0
  107. package/src/{chat/utils/search.ts → agent/tools/webSearch.ts} +0 -1
  108. package/src/agent/tools/webSearchTool.ts +44 -0
  109. package/src/chat/client/chatClient.ts +92 -3
  110. package/src/chat/client/connection.ts +11 -1
  111. package/src/chat/client/index.ts +3 -0
  112. package/src/chat/client/sessionClient.ts +40 -11
  113. package/src/chat/client/sessionFiles.ts +1 -1
  114. package/src/chat/constants.ts +6 -0
  115. package/src/chat/data/dataModels.ts +12 -0
  116. package/src/chat/data/dbSessionFiles.ts +12 -4
  117. package/src/chat/data/dbSessionMessages.ts +34 -0
  118. package/src/chat/protocol/messages.ts +94 -14
  119. package/src/chat/server/chatContextManager.ts +255 -221
  120. package/src/chat/server/connectionManager.ts +1 -1
  121. package/src/chat/server/conversation.ts +3 -0
  122. package/src/chat/server/imageGeneratorTools.ts +62 -30
  123. package/src/chat/server/openAIRouterLLM.ts +168 -0
  124. package/src/chat/server/openSession.ts +381 -138
  125. package/src/chat/server/promptRefiner.ts +106 -0
  126. package/src/chat/server/server.ts +9 -2
  127. package/src/chat/server/sessionFileManager.ts +35 -306
  128. package/src/chat/server/sessionRegistry.test.ts +0 -1
  129. package/src/chat/server/sessionRegistry.ts +228 -4
  130. package/src/chat/server/titleGenerator.test.ts +103 -0
  131. package/src/chat/server/titleGenerator.ts +143 -0
  132. package/src/chat/server/tools.ts +92 -281
  133. package/src/chat/utils/approvalManager.ts +9 -3
  134. package/src/chat/utils/multiAsyncQueue.ts +4 -0
  135. package/src/test/agent.test.ts +25 -30
  136. package/src/test/chatContextManager.test.ts +68 -38
  137. package/src/test/clientServerConnection.test.ts +0 -2
  138. package/src/test/compressingContextManager.test.ts +29 -34
  139. package/src/test/context.test.ts +59 -15
  140. package/src/test/contextTestTools.ts +95 -0
  141. package/src/test/dbMcpServerConfigs.test.ts +4 -4
  142. package/src/test/dbSessionFiles.test.ts +16 -16
  143. package/src/test/testTools.ts +8 -3
  144. package/src/test/tools.test.ts +30 -5
  145. package/src/tool/agentChat.ts +12 -3
  146. package/src/tool/chatMain.ts +59 -18
  147. package/src/tool/commandPrompt.ts +2 -2
  148. package/src/tool/files.ts +1 -3
  149. package/dist/agent/src/agent/tools.js +0 -44
  150. package/src/agent/tools.ts +0 -57
  151. /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
  152. /package/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.ts +0 -0
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OpenSession = exports.ChatSessionAgentEventHandler = exports.ChatSessionMessageSender = exports.GUEST_TOKEN_PREFIX = exports.DEFAULT_NUM_MESSGAES = void 0;
3
+ exports.OpenSession = exports.ChatSessionAgentEventHandler = exports.ChatSessionMessageSender = exports.RACE_MODE_TIMEOUT_MS = exports.GUEST_TOKEN_PREFIX = exports.DEFAULT_NUM_MESSGAES = void 0;
4
4
  const assert_1 = require("assert");
5
+ const uuid_1 = require("uuid");
5
6
  const sdk_1 = require("@xalia/xmcp/sdk");
6
7
  const agent_1 = require("../../agent/agent");
8
+ const sudoMcpServerManager_1 = require("../../agent/sudoMcpServerManager");
7
9
  const agentUtils_1 = require("../../agent/agentUtils");
10
+ const titleGenerator_1 = require("./titleGenerator");
8
11
  const asyncQueue_1 = require("../utils/asyncQueue");
9
12
  const multiAsyncQueue_1 = require("../utils/multiAsyncQueue");
10
13
  const database_1 = require("../data/database");
@@ -14,11 +17,16 @@ const tools_1 = require("./tools");
14
17
  const chatContextManager_1 = require("./chatContextManager");
15
18
  const conversation_1 = require("./conversation");
16
19
  const sessionFileManager_1 = require("./sessionFileManager");
20
+ const fileManager_1 = require("../../agent/tools/fileManager");
21
+ const pdfToText_1 = require("../../agent/tools/contentExtractors/pdfToText");
22
+ const documentSummarizer_1 = require("../../agent/documentSummarizer");
17
23
  const errorUtils_1 = require("./errorUtils");
18
24
  const nodePlatform_1 = require("../../tool/nodePlatform");
19
25
  const dbMcpServerConfigs_1 = require("../data/dbMcpServerConfigs");
20
26
  const apiKeyManager_1 = require("../data/apiKeyManager");
21
27
  const dbSessionMessages_1 = require("../data/dbSessionMessages");
28
+ const openAIRouterLLM_1 = require("./openAIRouterLLM");
29
+ const responseAwaiter_1 = require("../utils/responseAwaiter");
22
30
  /**
23
31
  * The model to use when the AgentProfile does not specify one.
24
32
  */
@@ -28,6 +36,7 @@ const DEFAULT_CHAT_LLM_MODEL = process.env["DEFAULT_LLM_MODEL"] || "anthropic/cl
28
36
  */
29
37
  exports.DEFAULT_NUM_MESSGAES = 500;
30
38
  exports.GUEST_TOKEN_PREFIX = "guest";
39
+ exports.RACE_MODE_TIMEOUT_MS = 120000;
31
40
  const logger = (0, sdk_1.getLogger)();
32
41
  /**
33
42
  * Implementation of ICheckpointWriter that writes into the DB.
@@ -109,24 +118,25 @@ class ChatSessionPlatform {
109
118
  }
110
119
  }
111
120
  class ChatSessionAgentEventHandler {
112
- constructor(sessionUUID, sender, approvalManager, contextManager) {
121
+ constructor(sessionUUID, sender, approvalManager, contextTx, alt) {
113
122
  this.sessionUUID = sessionUUID;
114
123
  this.sender = sender;
115
124
  this.approvalManager = approvalManager;
116
- this.contextManager = contextManager;
125
+ this.contextTx = contextTx;
126
+ this.alt = alt;
117
127
  }
118
128
  onCompletion(result) {
119
- logger.debug(`[OpenSession.onCompletion] : ${JSON.stringify(result)}`);
129
+ logger.debug(`[OpenSession.onCompletion] ${this.alt || ""} : ${JSON.stringify(result)}`);
120
130
  // Nothing to broadcast. Caller will receive this via onAgentMessage.
121
- this.contextManager.processAgentResponse(result);
131
+ this.contextTx.processAgentResponse(result);
122
132
  }
123
133
  onImage(image) {
124
- logger.debug(`[OpenSession.onImage] : ${image.image_url.url}`);
134
+ logger.debug(`[OpenSession.onImage] ${this.alt || ""} : ${image.image_url.url}`);
125
135
  throw new Error("[OpenSession.onImage] unimplemented");
126
136
  }
127
137
  onToolCallResult(result) {
128
- logger.debug(`[onToolCallResult] : ${JSON.stringify(result)}`);
129
- const toolCallMessage = this.contextManager.processToolCallResult(result);
138
+ logger.debug(`[onToolCallResult] ${this.alt || ""} : ${JSON.stringify(result)}`);
139
+ const toolCallMessage = this.contextTx.processToolCallResult(result);
130
140
  this.sender.broadcast(toolCallMessage);
131
141
  }
132
142
  async onToolCall(toolCall, agentTool) {
@@ -137,12 +147,13 @@ class ChatSessionAgentEventHandler {
137
147
  type: "tool_call",
138
148
  tool_call: toolCall,
139
149
  session_id: this.sessionUUID,
150
+ ...(this.alt ? { alt: this.alt } : {}),
140
151
  });
141
152
  return true;
142
153
  }
143
154
  // TODO: Need a proper mapping to/from MCP calls to tool names
144
155
  const [serverName, tool] = toolCall.function.name.split("__");
145
- const { approved, requested } = await this.approvalManager.getApproval(serverName, tool, toolCall);
156
+ const { approved, requested } = await this.approvalManager.getApproval(serverName, tool, toolCall, this.alt);
146
157
  // For now, the frontend uses the tool_call data in the
147
158
  // "approve_tool_call" request to display the tool call data. If approval
148
159
  // was requested in this way, don't send the "tool_call" message as well.
@@ -151,25 +162,31 @@ class ChatSessionAgentEventHandler {
151
162
  type: "tool_call",
152
163
  tool_call: toolCall,
153
164
  session_id: this.sessionUUID,
165
+ ...(this.alt ? { alt: this.alt } : {}),
154
166
  });
155
167
  }
156
168
  return approved;
157
169
  }
158
170
  onAgentMessage(msg, end) {
159
- logger.debug(`[OpenSession.onAgentMessage] msg: ${msg}, end: ${String(end)}`);
171
+ logger.debug(`[OpenSession.onAgentMessage] ${this.alt || ""} msg: ${msg}, ` +
172
+ `end: ${String(end)}`);
160
173
  // Inform the contextManager and broadcast the ServerAgentMessageChunk
161
- const agentMsgChunk = this.contextManager.processAgentMessage(msg, end);
174
+ const agentMsgChunk = this.contextTx.processAgentMessageChunk(msg, end);
175
+ if (this.alt) {
176
+ agentMsgChunk.alt = this.alt;
177
+ }
162
178
  this.sender.broadcast(agentMsgChunk);
163
179
  return Promise.resolve();
164
180
  }
165
181
  onReasoning(reasoning) {
166
182
  return new Promise((r) => {
167
- logger.debug(`[OpenSession.onReasoning]${reasoning}`);
183
+ logger.debug(`[OpenSession.onReasoning] ${this.alt || ""} ${reasoning}`);
168
184
  if (reasoning.length > 0) {
169
185
  this.sender.broadcast({
170
186
  type: "agent_reasoning_chunk",
171
187
  reasoning,
172
188
  session_id: this.sessionUUID,
189
+ ...(this.alt ? { alt: this.alt } : {}),
173
190
  });
174
191
  }
175
192
  r();
@@ -188,7 +205,7 @@ exports.ChatSessionAgentEventHandler = ChatSessionAgentEventHandler;
188
205
  * tool approvals and other interactions).
189
206
  */
190
207
  class OpenSession {
191
- constructor(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, agentProfilePreferences, skillManager, contextManager, sender, approvalManager, fileManager) {
208
+ constructor(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, agentProfilePreferences, skillManager, contextManager, sender, approvalManager, fileManager, platform) {
192
209
  this.db = db;
193
210
  this.agent = agent;
194
211
  this.sessionUUID = sessionData.session_uuid;
@@ -206,25 +223,29 @@ class OpenSession {
206
223
  this.approvalManager = approvalManager;
207
224
  this.savedAgentProfile = savedAgentProfile;
208
225
  this.sessionFileManager = fileManager;
226
+ this.platform = platform;
227
+ this.raceModeAwaiter = responseAwaiter_1.ResponseAwaiter.init(undefined, (m) => m.message_id, exports.RACE_MODE_TIMEOUT_MS);
209
228
  this.isPersisted = isPersisted;
210
229
  this.sessionTitle = sessionData.title;
211
230
  this.sessionUpdatedAt = sessionData.updated_at;
212
231
  this.agentPaused = sessionData.agent_paused;
232
+ this.titleGenerator = (0, titleGenerator_1.createTitleGenerator)();
213
233
  fileManager.addEventHandler(this);
214
234
  this.updateParticipantsPrompt();
215
235
  }
216
- static async init(db, isPersisted, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, llmUrl, xmcpUrl, connectionManager) {
236
+ static async init(db, isPersisted, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, xmcpUrl, connectionManager) {
217
237
  const sessionId = sessionData.session_uuid;
218
238
  const fileManager = await sessionFileManager_1.ChatSessionFileManager.init(db, sessionId);
219
239
  const sender = new ChatSessionMessageSender(connectionManager, sessionParticipants);
220
240
  const platform = new ChatSessionPlatform(sender, sessionId, ownerData.uuid);
221
241
  const toolApprovalManager = new approvalManager_1.ToolApprovalManager(sessionData.session_uuid, savedAgentProfile.uuid, savedAgentProfile.preferences, sender, new approvalManager_1.DbAgentPreferencesWriter(db));
222
- const { agent, skillManager, contextManager } = await createContextAndAgent(sessionId, savedAgentProfile.profile.system_prompt, savedAgentProfile.profile.model || DEFAULT_CHAT_LLM_MODEL, sessionMessages, sessionData.workspace, sessionCheckpoint, ownerData, ownerApiKey, llmUrl, xmcpUrl, fileManager, sender, platform, toolApprovalManager);
223
- const openSession = new OpenSession(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, savedAgentProfile.preferences, skillManager, contextManager, sender, toolApprovalManager, fileManager);
242
+ const checkpointWriter = new DBCheckpointWriter(db, sessionId);
243
+ const { agent, skillManager, contextManager } = await createContextAndAgent(sessionId, savedAgentProfile.profile.system_prompt, savedAgentProfile.profile.model || DEFAULT_CHAT_LLM_MODEL, sessionMessages, sessionData.workspace, sessionCheckpoint, ownerData, ownerApiKey, xmcpUrl, fileManager, platform, checkpointWriter, sender);
244
+ const openSession = new OpenSession(db, agent, sessionData, savedAgentProfile, isPersisted, sessionParticipants, savedAgentProfile.preferences, skillManager, contextManager, sender, toolApprovalManager, fileManager, platform);
224
245
  // Note, MCP servers have not been enabled yet
225
246
  return openSession;
226
247
  }
227
- static async initWithEmptySession(db, sessionData, llmUrl, xmcpUrl, connectionManager) {
248
+ static async initWithEmptySession(db, sessionData, xmcpUrl, connectionManager) {
228
249
  const sessionMessages = [];
229
250
  const sessionCheckpoint = undefined;
230
251
  const { savedAgentProfile, ownerData, ownerApiKey } = await loadDataForEmptySession(db, sessionData.agent_profile_uuid, sessionData.user_uuid);
@@ -245,12 +266,12 @@ class OpenSession {
245
266
  role: "owner",
246
267
  });
247
268
  }
248
- return OpenSession.init(db, false /* isPersisted */, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, llmUrl, xmcpUrl, connectionManager);
269
+ return OpenSession.init(db, false /* isPersisted */, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, xmcpUrl, connectionManager);
249
270
  }
250
- static async initWithExistingSession(db, sessionId, llmUrl, xmcpUrl, connectionManager) {
271
+ static async initWithExistingSession(db, sessionId, xmcpUrl, connectionManager) {
251
272
  // Load session data from database
252
273
  const { sessionData, sessionMessages, sessionCheckpoint, savedAgentProfile, sessionParticipants, ownerData, ownerApiKey, } = await loadSessionData(db, sessionId);
253
- return OpenSession.init(db, true /* isPersisted */, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, llmUrl, xmcpUrl, connectionManager);
274
+ return OpenSession.init(db, true /* isPersisted */, sessionData, savedAgentProfile, sessionMessages, sessionParticipants, ownerData, ownerApiKey, sessionCheckpoint, xmcpUrl, connectionManager);
254
275
  }
255
276
  onEmpty() {
256
277
  logger.info(`[OpenSession.onEmpty] session ${this.sessionUUID}`);
@@ -295,14 +316,16 @@ class OpenSession {
295
316
  });
296
317
  });
297
318
  // send conversation history
298
- const conversationMessages = this.contextManager.getConversationMessages();
319
+ const conversationMessages = this.contextManager
320
+ .getConversationMessages()
321
+ .concat(this.userMessageQueue.getEntries());
299
322
  conversationMessages.forEach((message) => {
300
323
  connMgr.sendToConnection(connectionId, message);
301
324
  });
302
325
  // add MCP settings
303
326
  this.sendMcpSettings(connectionId);
304
327
  // add system prompt and model
305
- const agentProfile = this.agent.getAgentProfile();
328
+ const agentProfile = this.getAgentProfile();
306
329
  connMgr.sendToConnection(connectionId, {
307
330
  type: "system_prompt_updated",
308
331
  system_prompt: agentProfile.system_prompt,
@@ -378,30 +401,25 @@ class OpenSession {
378
401
  this.sender.broadcast(msg);
379
402
  }
380
403
  };
404
+ let errorMessage;
381
405
  if (err instanceof errors_1.ChatFatalError) {
382
406
  // ChatFatalError in session context should send error to specific user
383
407
  // or broadcast if no specific user context
384
- sendError({
385
- type: "session_error",
386
- message: err.message,
387
- session_id: this.sessionUUID,
388
- });
408
+ errorMessage = err.message;
389
409
  }
390
410
  else if (err instanceof errors_1.ChatErrorMessage) {
391
- sendError({
392
- type: "session_error",
393
- message: err.message,
394
- session_id: this.sessionUUID,
395
- });
411
+ errorMessage = err.message;
396
412
  }
397
413
  else {
398
- const errString = (0, errorUtils_1.getErrorString)(err);
399
- sendError({
400
- type: "session_error",
401
- message: errString,
402
- session_id: this.sessionUUID,
403
- });
414
+ errorMessage = (0, errorUtils_1.getErrorString)(err);
404
415
  }
416
+ logger.error(`[OpenSession.handleError] sessionUUID=${this.sessionUUID} ` +
417
+ `from=${from ?? "broadcast"}: ${errorMessage}`);
418
+ sendError({
419
+ type: "session_error",
420
+ message: errorMessage,
421
+ session_id: this.sessionUUID,
422
+ });
405
423
  return true;
406
424
  }
407
425
  // ISessionFileManagerEventHandler.onFileDeleted
@@ -412,6 +430,15 @@ class OpenSession {
412
430
  name,
413
431
  });
414
432
  }
433
+ broadcastContextUsage() {
434
+ const { used, max } = this.contextManager.getContextUsage();
435
+ this.sender.broadcast({
436
+ type: "context_usage",
437
+ session_id: this.sessionUUID,
438
+ used_tokens: used,
439
+ max_tokens: max,
440
+ });
441
+ }
415
442
  // ISessionFileManagerEventHandler.onFileChanged
416
443
  onFileChanged(entry, new_file) {
417
444
  this.sender.broadcast({
@@ -490,6 +517,9 @@ class OpenSession {
490
517
  case "msg":
491
518
  broadcastMsg = await this.handleUserMessage(msg, queuedMessage.from);
492
519
  break;
520
+ case "stop":
521
+ this.agent.stop();
522
+ break;
493
523
  case "add_mcp_server":
494
524
  broadcastMsg = await this.handleAddMcpServer(msg.server_name, msg.enable_all);
495
525
  break;
@@ -541,12 +571,12 @@ class OpenSession {
541
571
  case "share_session":
542
572
  await this.handleShareSession(msg, queuedMessage.from);
543
573
  break;
544
- case "add_mcp_server_from_url":
545
- void this.handleAddMcpServerFromUrl(msg, queuedMessage.from);
546
- break;
547
574
  case "get_mcp_resource":
548
575
  void this.handleGetMcpResource(msg, queuedMessage.from);
549
576
  break;
577
+ case "race_mode_result":
578
+ this.raceModeAwaiter.onMessage(msg);
579
+ break;
550
580
  default: {
551
581
  const exhaustive = msg; // Error => non-exhaustive switch-case.
552
582
  return exhaustive;
@@ -560,6 +590,10 @@ class OpenSession {
560
590
  }
561
591
  else {
562
592
  this.sender.broadcast(broadcastMsg);
593
+ // Broadcast context usage after user message
594
+ if (broadcastMsg.type === "user_msg") {
595
+ this.broadcastContextUsage();
596
+ }
563
597
  }
564
598
  }
565
599
  }
@@ -582,29 +616,6 @@ class OpenSession {
582
616
  session_id: this.sessionUUID,
583
617
  });
584
618
  }
585
- async handleAddMcpServerFromUrl(msg, from) {
586
- const skillManager = this.skillManager;
587
- try {
588
- const serverName = msg.server_name;
589
- await skillManager.addMcpServerWithStreamableHTTPUrl(serverName, msg.url);
590
- this.skillManager.enableAllTools(serverName);
591
- const server = skillManager.getMcpServer(serverName);
592
- const tools = server.getTools();
593
- const resources = server.getResources();
594
- const enabled_tools = Array.from(server.getEnabledTools().keys());
595
- this.sender.broadcast({
596
- type: "mcp_server_added",
597
- server_name: serverName,
598
- tools,
599
- resources,
600
- enabled_tools,
601
- session_id: this.sessionUUID,
602
- });
603
- }
604
- catch (err) {
605
- this.handleError(err, from);
606
- }
607
- }
608
619
  async handleGetMcpResource(msg, from) {
609
620
  try {
610
621
  const contents = await this.skillManager.getResource(msg.server_name, msg.uri);
@@ -637,33 +648,103 @@ class OpenSession {
637
648
  * `processUserMessage` logic when agent is paused. Trigger the context,
638
649
  * add the user messages and then extract the new DB messages.
639
650
  */
640
- processUserMessagePaused(msgs) {
641
- const { llmUserMessages } = this.contextManager.startAgentResponse(msgs);
642
- // Just send the user LLM messages direct to the ContextManager, so they
643
- // are available to the LLM once it is restarted.
644
- this.contextManager.addMessages(llmUserMessages);
645
- return this.contextManager.endAgentResponse();
651
+ async processUserMessagePaused(msgs) {
652
+ // Just send the user LLM user messages direct to the ContextManager, so
653
+ // they are available to the LLM once it is restarted.
654
+ const { contextTx } = await this.contextManager.startAgentResponse(msgs);
655
+ const result = this.contextManager.endAgentResponse(contextTx);
656
+ this.broadcastContextUsage();
657
+ return result;
646
658
  }
647
659
  async processUserMessagesActive(msgs) {
648
- const { llmUserMessages, agentFirstChunk } = this.contextManager.startAgentResponse(msgs);
660
+ // All accumulated messages (DB, Protocol and LLM) should be on the
661
+ // specialized contextTx.
662
+ const { contextTx, agentFirstChunk } = await this.contextManager.startAgentResponse(msgs);
649
663
  this.sender.broadcast(agentFirstChunk);
664
+ const eventHandler = new ChatSessionAgentEventHandler(this.sessionUUID, this.sender, this.approvalManager, contextTx);
650
665
  try {
651
- await this.agent.userMessagesRaw(llmUserMessages);
666
+ await this.agent.userMessagesRaw(contextTx, eventHandler);
652
667
  }
653
668
  catch (e) {
654
669
  logger.warn(`[OpenSession.processUserMessages] agent error: ${String(e)}`);
655
670
  // Errors during agent replies must be turned into messages.
656
671
  const errMsg = `error from LLM: ${String(e)}`;
657
- this.contextManager.revertAgentResponse(errMsg);
672
+ contextTx.revertAgentResponse(errMsg);
673
+ // TODO: This will prevent the user messages from being saved in the
674
+ // DB. Should we keep them?
658
675
  throw new Error(errMsg);
659
676
  }
660
- return this.contextManager.endAgentResponse();
677
+ const result = this.contextManager.endAgentResponse(contextTx);
678
+ this.broadcastContextUsage();
679
+ return result;
680
+ }
681
+ /**
682
+ * `processUserMessage` logic for race-mode.
683
+ */
684
+ async processUserMessagesRaceMode(msgs) {
685
+ // Create a second agent, same skillManager
686
+ const modelB = msgs[0].race_mode;
687
+ (0, assert_1.strict)(typeof modelB === "string");
688
+ const agentB = await (async () => {
689
+ try {
690
+ const llmB = await createLLM(modelB, this.platform);
691
+ return new agent_1.AgentEx(this.skillManager, llmB);
692
+ }
693
+ catch (e) {
694
+ logger.warn("[OpenSession.processUserMessages] error creating race agent: " +
695
+ String(e));
696
+ throw new Error(`error creating race: ${String(e)}`);
697
+ }
698
+ })();
699
+ const run = async (alt, msgs) => {
700
+ const { contextTx: contextTx, agentFirstChunk: agentFirstChunk } = await this.contextManager.startAgentResponse(msgs.slice());
701
+ this.sender.broadcast(agentFirstChunk);
702
+ const eventHandler = new ChatSessionAgentEventHandler(this.sessionUUID, this.sender, this.approvalManager, contextTx, alt);
703
+ try {
704
+ const agent = alt === "B" ? agentB : this.agent;
705
+ await agent.userMessagesRaw(contextTx, eventHandler);
706
+ }
707
+ catch (e) {
708
+ logger.warn(`[OpenSession.processUserMessages] agent ${alt} error: ${String(e)}`);
709
+ const errMsg = `error from LLM: ${String(e)}`;
710
+ contextTx.revertAgentResponse(errMsg);
711
+ throw new Error(errMsg);
712
+ }
713
+ return contextTx;
714
+ };
715
+ const [txA, txB] = await Promise.all([
716
+ run("A", msgs.slice()),
717
+ run("B", msgs),
718
+ ]);
719
+ // Ask which fork to commit
720
+ const answer = await (async () => {
721
+ const message_id = (0, uuid_1.v4)();
722
+ const answerP = this.raceModeAwaiter.waitForResponse(message_id);
723
+ const getResultMsg = {
724
+ type: "race_mode_get_result",
725
+ message_id,
726
+ alts: ["A", "B"],
727
+ session_id: this.sessionUUID,
728
+ };
729
+ this.sender.broadcast(getResultMsg);
730
+ return answerP;
731
+ })();
732
+ if (answer.result === "A") {
733
+ return this.contextManager.endAgentResponse(txA);
734
+ }
735
+ else {
736
+ return this.contextManager.endAgentResponse(txB);
737
+ }
738
+ // UI is responsible for switching model if the user wants to.
661
739
  }
662
740
  async processUserMessages(msgs) {
741
+ logger.debug(`[processUserMessages] msgs: ${JSON.stringify(msgs)}`);
663
742
  try {
664
743
  const newSessionMessages = this.agentPaused
665
- ? this.processUserMessagePaused(msgs)
666
- : await this.processUserMessagesActive(msgs);
744
+ ? await this.processUserMessagePaused(msgs)
745
+ : await (msgs[0].race_mode
746
+ ? this.processUserMessagesRaceMode(msgs)
747
+ : this.processUserMessagesActive(msgs));
667
748
  logger.debug("[processUserMessages] newSessionMessages: " +
668
749
  JSON.stringify(newSessionMessages));
669
750
  // Append to in-memory conversation and write to the DB
@@ -671,6 +752,8 @@ class OpenSession {
671
752
  await dbsm.append(this.sessionUUID, newSessionMessages);
672
753
  }
673
754
  catch (e) {
755
+ logger.error(`[processUserMessages] ERROR session=${this.sessionUUID}: ` +
756
+ String(e));
674
757
  if (!this.handleError(e)) {
675
758
  throw e;
676
759
  }
@@ -680,9 +763,16 @@ class OpenSession {
680
763
  // Return a ServerUserMessage for broadcast. The actual message is places
681
764
  // on a queue to be dealt with in another loop. This allows Agent
682
765
  // processing of user messages to depend on other messages.
683
- (0, assert_1.strict)(msg);
684
- (0, assert_1.strict)(from);
685
- // Assign the user message_idx and attempt to enqueue.
766
+ (0, assert_1.strict)(msg, "undefined user message");
767
+ (0, assert_1.strict)(from, "undefined user message sender");
768
+ logger.info(`[handleUserMessage] msg.attachedFiles: ${JSON.stringify(msg.attachedFiles
769
+ ? msg.attachedFiles.map((f) => ({
770
+ name: f.name,
771
+ data_url_length: f.data_url.length,
772
+ }))
773
+ : "none")}`);
774
+ // Assign the user message_idx first so we can check if this is
775
+ // the first message
686
776
  const user = this.sessionParticipants.get(from);
687
777
  if (!user) {
688
778
  throw new Error(`unrecognized user ${from}`);
@@ -691,6 +781,62 @@ class OpenSession {
691
781
  if (!userMessage) {
692
782
  return;
693
783
  }
784
+ // Handle attached files by adding them to session files
785
+ if (msg.attachedFiles && msg.attachedFiles.length > 0) {
786
+ const fileCount = String(msg.attachedFiles.length);
787
+ logger.info(`[handleUserMessage] Processing ${fileCount} attached files`);
788
+ // If this is the first message, set the session title before
789
+ // creating the session
790
+ if (userMessage.message_idx === conversation_1.MESSAGE_INDEX_START_VALUE &&
791
+ !this.isPersisted) {
792
+ this.sessionTitle = userMessage.message?.slice(0, 128) || "New Chat";
793
+ }
794
+ // If the session hasn't been persisted, it must be written to
795
+ // the DB first
796
+ if (!this.isPersisted) {
797
+ await this.createSessionInDB();
798
+ }
799
+ // Add each attached file to session files
800
+ for (const file of msg.attachedFiles) {
801
+ logger.info(`[handleUserMessage] Adding file ${file.name} to session files`);
802
+ // Extract content and generate summary for PDFs
803
+ let parsed_content;
804
+ let summary;
805
+ const mimeType = (0, fileManager_1.getMimeTypeFromDataUrl)(file.data_url);
806
+ if (mimeType === "application/pdf") {
807
+ try {
808
+ // Extract base64 data and convert to ArrayBuffer
809
+ const base64Data = file.data_url.split(",")[1];
810
+ const binaryString = atob(base64Data);
811
+ const bytes = new Uint8Array(binaryString.length);
812
+ for (let i = 0; i < binaryString.length; i++) {
813
+ bytes[i] = binaryString.charCodeAt(i);
814
+ }
815
+ const arrayBuffer = bytes.buffer;
816
+ // Extract text from PDF
817
+ const extractedText = await (0, pdfToText_1.pdfToText)(arrayBuffer);
818
+ parsed_content = {
819
+ version: 1,
820
+ text: extractedText,
821
+ };
822
+ // Generate summary from extracted text
823
+ if (extractedText.trim().length > 0) {
824
+ summary = await (0, documentSummarizer_1.summarizeDocument)(extractedText);
825
+ }
826
+ logger.info(`[handleUserMessage] Extracted ${String(extractedText.length)}` +
827
+ ` chars from PDF ${file.name}`);
828
+ }
829
+ catch (err) {
830
+ logger.warn(`[handleUserMessage] Failed to extract PDF content: ` +
831
+ String(err));
832
+ }
833
+ }
834
+ await this.sessionFileManager.putFileContent(file.name, summary, file.data_url, parsed_content);
835
+ }
836
+ // Remove attachedFiles from the message so they don't get
837
+ // included in the LLM context
838
+ msg = { ...msg, attachedFiles: undefined };
839
+ }
694
840
  // Special case for the first message of the session
695
841
  if (userMessage.message_idx === conversation_1.MESSAGE_INDEX_START_VALUE) {
696
842
  // No need to wait for this to complete before broadcasting.
@@ -730,8 +876,8 @@ class OpenSession {
730
876
  this.isPersisted = true;
731
877
  }
732
878
  async onFirstMessage(userMsg) {
733
- // Update title on the class before writing to the DB
734
- this.sessionTitle = userMsg.message?.slice(0, 128) || "New Chat";
879
+ // Generate title using LLM with automatic fallback
880
+ this.sessionTitle = await this.titleGenerator.generateTitle(userMsg.message || "");
735
881
  // The session may already have been saved (e.g. if the workspace is
736
882
  // updated before any messages are sent).
737
883
  if (!this.isPersisted) {
@@ -865,7 +1011,7 @@ class OpenSession {
865
1011
  return msgs;
866
1012
  }
867
1013
  async handleSetSystemPrompt(system_prompt) {
868
- this.agent.setSystemPrompt(system_prompt);
1014
+ this.contextManager.setAgentPrompt(system_prompt);
869
1015
  await this.updateAgentProfile();
870
1016
  return {
871
1017
  type: "system_prompt_updated",
@@ -874,7 +1020,7 @@ class OpenSession {
874
1020
  };
875
1021
  }
876
1022
  async handleSetModel(model) {
877
- this.agent.setModel(model);
1023
+ this.agent.llm.setModel(model);
878
1024
  await this.updateAgentProfile();
879
1025
  return { type: "model_updated", model, session_id: this.sessionUUID };
880
1026
  }
@@ -953,8 +1099,11 @@ class OpenSession {
953
1099
  }
954
1100
  return tool;
955
1101
  }
1102
+ getAgentProfile() {
1103
+ return new sdk_1.AgentProfile(this.agent.llm.getModel(), this.contextManager.getAgentPrompt(), this.skillManager.getMcpServerSettings());
1104
+ }
956
1105
  async updateAgentProfile() {
957
- const profile = this.agent.getAgentProfile();
1106
+ const profile = this.getAgentProfile();
958
1107
  logger.debug(`[updateAgentProfile]: uuid: ${this.agentProfileUUID} profile: ` +
959
1108
  JSON.stringify(profile));
960
1109
  return this.db.updateAgentProfile(this.agentProfileUUID, profile);
@@ -1093,15 +1242,28 @@ async function loadSessionData(db, sessionId) {
1093
1242
  sessionParticipants: (0, database_1.createSessionParticipantMap)(sessionParticipants),
1094
1243
  };
1095
1244
  }
1096
- async function createContextAndAgent(sessionUUID, systemPrompt, model, sessionMessages, workspace, sessionCheckpoint, ownerData, ownerApiKey, llmUrl, xmcpUrl, fileManager, sender, platform, approvalManager) {
1097
- const contextManager = new chatContextManager_1.ChatContextManager(systemPrompt, sessionMessages, sessionUUID, ownerData.uuid, sessionCheckpoint, llmUrl, model, ownerApiKey, undefined, // TODO
1098
- fileManager);
1245
+ async function createLLM(model, platform) {
1246
+ let llm = await (0, agentUtils_1.createSpecializedLLM)(model, platform);
1247
+ if (!llm) {
1248
+ llm = new openAIRouterLLM_1.OpenAIRouterLLM(model);
1249
+ }
1250
+ (0, assert_1.strict)(llm);
1251
+ return llm;
1252
+ }
1253
+ async function createContextAndAgent(sessionUUID, systemPrompt, model, sessionMessages, workspace, sessionCheckpoint, ownerData, ownerApiKey, xmcpUrl, fileManager, platform, checkpointWriter, messageSender) {
1254
+ // Create SkillManager
1255
+ const xmcpConfig = sdk_1.Configuration.new(ownerApiKey, xmcpUrl, false);
1256
+ const skillManager = await sudoMcpServerManager_1.SkillManager.initialize((url, authResultP, displayName) => {
1257
+ platform.openUrl(url, authResultP, displayName);
1258
+ }, xmcpConfig.backend_url, xmcpConfig.api_key, undefined /* authorizedUrl */);
1259
+ // Fn to create the llm. One invocation for the compression context, one
1260
+ // for the Agent.
1261
+ const llm = await createLLM(model, platform);
1262
+ const agent = new agent_1.AgentEx(skillManager, llm);
1263
+ await (0, tools_1.addDefaultChatTools)(agent, ownerData.timezone, platform, fileManager, messageSender);
1264
+ const contextManager = new chatContextManager_1.ChatContextManager(systemPrompt, sessionMessages, sessionUUID, ownerData.uuid, sessionCheckpoint, checkpointWriter, fileManager, await createLLM(model, platform));
1099
1265
  if (workspace) {
1100
1266
  contextManager.setWorkspace((0, agent_1.createUserMessage)(workspace.message, workspace.imageB64));
1101
1267
  }
1102
- const eventHandler = new ChatSessionAgentEventHandler(sessionUUID, sender, approvalManager, contextManager);
1103
- const xmcpConfig = sdk_1.Configuration.new(ownerApiKey, xmcpUrl, false);
1104
- const [agent, skillManager] = await (0, agentUtils_1.createAgentWithoutSkills)(llmUrl, model, eventHandler, platform, contextManager, ownerApiKey, xmcpConfig, undefined, true);
1105
- await (0, tools_1.addDefaultChatTools)(agent, ownerData.timezone, fileManager, llmUrl, ownerApiKey);
1106
1268
  return { agent, skillManager, contextManager };
1107
1269
  }