@xalia/agent 0.6.9 → 0.6.11

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 (199) hide show
  1. package/README.md +11 -0
  2. package/dist/agent/src/agent/agent.js +77 -18
  3. package/dist/agent/src/agent/agentUtils.js +3 -2
  4. package/dist/agent/src/agent/documentSummarizer.js +126 -0
  5. package/dist/agent/src/agent/dummyLLM.js +25 -22
  6. package/dist/agent/src/agent/imageGenLLM.js +22 -19
  7. package/dist/agent/src/agent/llm.js +1 -1
  8. package/dist/agent/src/agent/openAILLM.js +15 -12
  9. package/dist/agent/src/agent/openAILLMStreaming.js +68 -37
  10. package/dist/agent/src/agent/repeatLLM.js +16 -7
  11. package/dist/agent/src/agent/tokenCounter.js +390 -0
  12. package/dist/agent/src/agent/tokenCounter.test.js +206 -0
  13. package/dist/agent/src/agent/toolSettings.js +17 -0
  14. package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
  15. package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
  16. package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
  17. package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
  18. package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
  19. package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
  20. package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
  21. package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
  22. package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
  23. package/dist/agent/src/agent/tools/index.js +64 -0
  24. package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
  25. package/dist/agent/src/agent/tools/renderTool.js +89 -0
  26. package/dist/agent/src/agent/tools/utils.js +61 -0
  27. package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
  28. package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
  29. package/dist/agent/src/chat/client/chatClient.js +28 -0
  30. package/dist/agent/src/chat/client/index.js +4 -1
  31. package/dist/agent/src/chat/client/sessionClient.js +28 -2
  32. package/dist/agent/src/chat/constants.js +8 -0
  33. package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
  34. package/dist/agent/src/chat/protocol/messages.js +5 -0
  35. package/dist/agent/src/chat/server/chatContextManager.js +45 -25
  36. package/dist/agent/src/chat/server/conversation.js +3 -0
  37. package/dist/agent/src/chat/server/imageGeneratorTools.js +20 -8
  38. package/dist/agent/src/chat/server/openAIRouterLLM.js +0 -3
  39. package/dist/agent/src/chat/server/openSession.js +218 -55
  40. package/dist/agent/src/chat/server/promptRefiner.js +86 -0
  41. package/dist/agent/src/chat/server/server.js +5 -1
  42. package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
  43. package/dist/agent/src/chat/server/sessionRegistry.js +87 -0
  44. package/dist/agent/src/chat/server/titleGenerator.js +112 -0
  45. package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
  46. package/dist/agent/src/chat/server/tools.js +63 -287
  47. package/dist/agent/src/chat/utils/approvalManager.js +6 -3
  48. package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
  49. package/dist/agent/src/test/agent.test.js +16 -17
  50. package/dist/agent/src/test/chatContextManager.test.js +15 -3
  51. package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
  52. package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
  53. package/dist/agent/src/test/testTools.js +6 -1
  54. package/dist/agent/src/test/tools.test.js +27 -9
  55. package/dist/agent/src/tool/agentChat.js +5 -2
  56. package/dist/agent/src/tool/chatMain.js +34 -7
  57. package/dist/agent/src/tool/commandPrompt.js +2 -2
  58. package/dist/agent/src/tool/files.js +7 -8
  59. package/package.json +8 -2
  60. package/.env.development +0 -1
  61. package/.prettierrc.json +0 -11
  62. package/dist/agent/src/agent/tools.js +0 -44
  63. package/eslint.config.mjs +0 -38
  64. package/scripts/chat_server +0 -8
  65. package/scripts/git_message +0 -31
  66. package/scripts/git_wip +0 -21
  67. package/scripts/pr_message +0 -18
  68. package/scripts/pr_review +0 -16
  69. package/scripts/setup_chat +0 -90
  70. package/scripts/shutdown_chat_server +0 -42
  71. package/scripts/start_chat_server +0 -24
  72. package/scripts/sudomcp_import +0 -23
  73. package/scripts/test_chat +0 -308
  74. package/src/agent/agent.ts +0 -624
  75. package/src/agent/agentUtils.ts +0 -285
  76. package/src/agent/compressingContextManager.ts +0 -129
  77. package/src/agent/context.ts +0 -265
  78. package/src/agent/contextWithWorkspace.ts +0 -162
  79. package/src/agent/dummyLLM.ts +0 -126
  80. package/src/agent/iAgentEventHandler.ts +0 -64
  81. package/src/agent/imageGenLLM.ts +0 -97
  82. package/src/agent/imageGenerator.ts +0 -45
  83. package/src/agent/iplatform.ts +0 -18
  84. package/src/agent/llm.ts +0 -74
  85. package/src/agent/mcpServerManager.ts +0 -541
  86. package/src/agent/nullAgentEventHandler.ts +0 -26
  87. package/src/agent/nullPlatform.ts +0 -13
  88. package/src/agent/openAI.ts +0 -123
  89. package/src/agent/openAILLM.ts +0 -95
  90. package/src/agent/openAILLMStreaming.ts +0 -609
  91. package/src/agent/promptProvider.ts +0 -87
  92. package/src/agent/repeatLLM.ts +0 -50
  93. package/src/agent/sudoMcpServerManager.ts +0 -361
  94. package/src/agent/tokenAuth.ts +0 -50
  95. package/src/agent/tools.ts +0 -57
  96. package/src/chat/client/chatClient.ts +0 -922
  97. package/src/chat/client/connection.test.ts +0 -241
  98. package/src/chat/client/connection.ts +0 -286
  99. package/src/chat/client/constants.ts +0 -1
  100. package/src/chat/client/index.ts +0 -18
  101. package/src/chat/client/interfaces.ts +0 -34
  102. package/src/chat/client/sessionClient.ts +0 -537
  103. package/src/chat/client/sessionFiles.ts +0 -142
  104. package/src/chat/client/teamManager.ts +0 -29
  105. package/src/chat/data/apiKeyManager.ts +0 -76
  106. package/src/chat/data/dataModels.ts +0 -101
  107. package/src/chat/data/database.ts +0 -997
  108. package/src/chat/data/dbMcpServerConfigs.ts +0 -59
  109. package/src/chat/data/dbSessionFileModels.ts +0 -113
  110. package/src/chat/data/dbSessionFiles.ts +0 -99
  111. package/src/chat/data/dbSessionMessages.ts +0 -102
  112. package/src/chat/data/mimeTypes.ts +0 -58
  113. package/src/chat/protocol/connectionMessages.ts +0 -49
  114. package/src/chat/protocol/constants.ts +0 -55
  115. package/src/chat/protocol/errors.ts +0 -16
  116. package/src/chat/protocol/messages.ts +0 -846
  117. package/src/chat/server/README.md +0 -127
  118. package/src/chat/server/chatContextManager.ts +0 -639
  119. package/src/chat/server/connectionManager.test.ts +0 -246
  120. package/src/chat/server/connectionManager.ts +0 -506
  121. package/src/chat/server/conversation.ts +0 -316
  122. package/src/chat/server/errorUtils.ts +0 -28
  123. package/src/chat/server/imageGeneratorTools.ts +0 -160
  124. package/src/chat/server/openAIRouterLLM.ts +0 -171
  125. package/src/chat/server/openSession.ts +0 -1689
  126. package/src/chat/server/openSessionMessageSender.ts +0 -4
  127. package/src/chat/server/server.ts +0 -175
  128. package/src/chat/server/sessionFileManager.ts +0 -422
  129. package/src/chat/server/sessionRegistry.test.ts +0 -137
  130. package/src/chat/server/sessionRegistry.ts +0 -1425
  131. package/src/chat/server/test-utils/mockFactories.ts +0 -422
  132. package/src/chat/server/tools.ts +0 -397
  133. package/src/chat/utils/agentSessionMap.ts +0 -76
  134. package/src/chat/utils/approvalManager.ts +0 -183
  135. package/src/chat/utils/asyncLock.ts +0 -43
  136. package/src/chat/utils/asyncQueue.ts +0 -62
  137. package/src/chat/utils/htmlToText.ts +0 -61
  138. package/src/chat/utils/multiAsyncQueue.ts +0 -62
  139. package/src/chat/utils/responseAwaiter.ts +0 -181
  140. package/src/chat/utils/search.ts +0 -139
  141. package/src/chat/utils/userResolver.ts +0 -48
  142. package/src/chat/utils/websocket.ts +0 -16
  143. package/src/index.ts +0 -0
  144. package/src/test/agent.test.ts +0 -590
  145. package/src/test/approvalManager.test.ts +0 -141
  146. package/src/test/chatContextManager.test.ts +0 -527
  147. package/src/test/clientServerConnection.test.ts +0 -205
  148. package/src/test/compressingContextManager.test.ts +0 -77
  149. package/src/test/context.test.ts +0 -150
  150. package/src/test/contextTestTools.ts +0 -95
  151. package/src/test/conversation.test.ts +0 -109
  152. package/src/test/db.test.ts +0 -363
  153. package/src/test/dbMcpServerConfigs.test.ts +0 -112
  154. package/src/test/dbSessionFiles.test.ts +0 -258
  155. package/src/test/dbSessionMessages.test.ts +0 -85
  156. package/src/test/dbTestTools.ts +0 -157
  157. package/src/test/imageLoad.test.ts +0 -15
  158. package/src/test/mcpServerManager.test.ts +0 -114
  159. package/src/test/multiAsyncQueue.test.ts +0 -183
  160. package/src/test/openaiStreaming.test.ts +0 -177
  161. package/src/test/prompt.test.ts +0 -27
  162. package/src/test/promptProvider.test.ts +0 -33
  163. package/src/test/responseAwaiter.test.ts +0 -103
  164. package/src/test/sudoMcpServerManager.test.ts +0 -63
  165. package/src/test/testTools.ts +0 -171
  166. package/src/test/tools.test.ts +0 -39
  167. package/src/tool/agentChat.ts +0 -194
  168. package/src/tool/agentMain.ts +0 -180
  169. package/src/tool/chatMain.ts +0 -594
  170. package/src/tool/commandPrompt.ts +0 -264
  171. package/src/tool/files.ts +0 -84
  172. package/src/tool/main.ts +0 -25
  173. package/src/tool/nodePlatform.ts +0 -73
  174. package/src/tool/options.ts +0 -144
  175. package/src/tool/prompt.ts +0 -101
  176. package/test_data/background_test_profile.json +0 -6
  177. package/test_data/background_test_script.json +0 -11
  178. package/test_data/dummyllm_script_crash.json +0 -32
  179. package/test_data/dummyllm_script_image_gen.json +0 -19
  180. package/test_data/dummyllm_script_image_gen_fe.json +0 -29
  181. package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
  182. package/test_data/dummyllm_script_render_tool.json +0 -29
  183. package/test_data/dummyllm_script_simplecalc.json +0 -28
  184. package/test_data/dummyllm_script_test_auto_approve.json +0 -81
  185. package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
  186. package/test_data/frog.png +0 -0
  187. package/test_data/frog.png.b64 +0 -1
  188. package/test_data/git_message_profile.json +0 -4
  189. package/test_data/git_wip_system.txt +0 -5
  190. package/test_data/image_gen_test_profile.json +0 -5
  191. package/test_data/pr_message_profile.json +0 -4
  192. package/test_data/pr_review_profile.json +0 -4
  193. package/test_data/prompt_simplecalc.txt +0 -1
  194. package/test_data/simplecalc_profile.json +0 -4
  195. package/test_data/sudomcp_import_profile.json +0 -4
  196. package/test_data/test_script_profile.json +0 -8
  197. package/tsconfig.json +0 -13
  198. package/vitest.config.ts +0 -39
  199. /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
package/README.md CHANGED
@@ -25,6 +25,17 @@ Example of running agent can be found in [test script](../mcppro/scripts/test_sc
25
25
 
26
26
  ## Architecture
27
27
 
28
+ For a detailed explanation of the context system architecture, including how context is formed, compressed, and managed during agent execution, see:
29
+
30
+ **📚 [Context System Architecture Documentation](../context_system.md)**
31
+
32
+ This document covers:
33
+ - Context window structure and composition
34
+ - System prompt fragment injection
35
+ - Automatic context compression
36
+ - Session file handling
37
+ - Transaction-based message flow
38
+
28
39
  ### Core Components
29
40
 
30
41
  #### Agent (`src/agent/`)
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Agent = exports.AgentEx = exports.DEFAULT_LLM_URL = exports.AgentProfile = void 0;
3
+ exports.Agent = exports.AgentEx = exports.USER_STOP_MESSAGE = exports.DEFAULT_LLM_URL = exports.AgentProfile = void 0;
4
4
  exports.createUserMessage = createUserMessage;
5
5
  exports.createUserMessageEnsure = createUserMessageEnsure;
6
6
  exports.completionToAssistantMessageParam = completionToAssistantMessageParam;
@@ -9,9 +9,20 @@ var sdk_1 = require("@xalia/xmcp/sdk");
9
9
  Object.defineProperty(exports, "AgentProfile", { enumerable: true, get: function () { return sdk_1.AgentProfile; } });
10
10
  const sdk_2 = require("@xalia/xmcp/sdk");
11
11
  const mcpServerManager_1 = require("./mcpServerManager");
12
+ const toolSettings_1 = require("./toolSettings");
12
13
  exports.DEFAULT_LLM_URL = "http://localhost:5001/v1";
13
- const MAX_TOOL_CALL_RESPONSE_LENGTH = 4000;
14
+ /**
15
+ * The message to append to the agent output if the agent is interrupted by a
16
+ * signal from the user.
17
+ */
18
+ exports.USER_STOP_MESSAGE = " AGENT INTERRUPTED";
14
19
  const logger = (0, sdk_2.getLogger)();
20
+ /**
21
+ * An agent attached to an ILLM which updates a context via an
22
+ * IContextTransaction interface (where IContextTransaction is like a DB tx or
23
+ * DB writer, for staging changes and reading back state as-if those changes
24
+ * were applied).
25
+ */
15
26
  class AgentEx {
16
27
  constructor(mcpServerManager, llm) {
17
28
  /// The full list of tools, ready to pass to the LLM
@@ -21,15 +32,25 @@ class AgentEx {
21
32
  this.agentTools = new Map();
22
33
  this.mcpServerManager = mcpServerManager;
23
34
  this.llm = llm;
35
+ this.stopFlag = false;
36
+ this.stopFn = undefined;
24
37
  }
25
38
  async shutdown() {
39
+ this.stop("shutting down");
26
40
  return this.mcpServerManager.shutdown();
27
41
  }
42
+ stop(msg) {
43
+ this.stopFlag = true;
44
+ if (this.stopFn) {
45
+ this.stopFn(msg || exports.USER_STOP_MESSAGE);
46
+ }
47
+ }
28
48
  getMcpServerManager() {
29
49
  return this.mcpServerManager;
30
50
  }
31
51
  // TODO: rename
32
52
  async userMessagesRaw(contextTx, eventHandler) {
53
+ this.stopFlag = false;
33
54
  // New user messages have already been added to the `contextTx`.
34
55
  // Image and audio handling
35
56
  //
@@ -47,6 +68,11 @@ class AgentEx {
47
68
  contextTx.addMessage(message);
48
69
  // While there are tool calls to make, invoke them and loop
49
70
  while (message.tool_calls && message.tool_calls.length > 0) {
71
+ // Signal the event handler of the assistant message with tool calls
72
+ // BEFORE processing tool results. This ensures the order of messages
73
+ // in pendingMessages matches the order in the LLM context:
74
+ // [user, assistant(tool_calls), tool_result, assistant(final)]
75
+ eventHandler.onCompletion(message);
50
76
  // TODO: Execute all tool calls in parallel
51
77
  // [indexInContext, ToolCallResult][]
52
78
  const toolCallResults = [];
@@ -66,6 +92,11 @@ class AgentEx {
66
92
  };
67
93
  const toolResultHandle = contextTx.addMessage(toolResult);
68
94
  toolCallResults.push([toolResultHandle, result]);
95
+ // Immediately broadcast the tool result to the frontend for UI
96
+ // feedback. This ensures the frontend knows the tool executed
97
+ // successfully without waiting for the next LLM completion to
98
+ // finish streaming
99
+ eventHandler.onToolCallResult(toolResult);
69
100
  // If the tool call requested that its args be redacted, this can be
70
101
  // done now - before the next LLM invocation.
71
102
  if (result.overwriteArgs) {
@@ -74,9 +105,6 @@ class AgentEx {
74
105
  logger.debug(`agent message after update ${JSON.stringify(message)}`);
75
106
  }
76
107
  }
77
- // Now that any args have been overwritten, signal the event handler of
78
- // the prevoius completion.
79
- eventHandler.onCompletion(message);
80
108
  // Get a new completion using the untouched tool call results. Note
81
109
  // that, since we are deferring the `onToolCallResult` calls (so they
82
110
  // can be redacted), we must take care that the errors in
@@ -89,17 +117,15 @@ class AgentEx {
89
117
  }
90
118
  finally {
91
119
  // Now that the tool call results have been passed to the LLM, perform
92
- // any updates on them. Pass the (updated) tool-call-result LLM
93
- // messages to the event handler - note, we want to do this even if an
94
- // error occured, so that the caller has an up-to-date picture of the
95
- // context state when the error occured.
120
+ // any updates on them if overwriteResponse was requested. If so, send
121
+ // the updated tool result to the frontend to replace the original.
96
122
  toolCallResults.forEach(([handle, tcr]) => {
97
- const ctxMsg = contextTx.getMessage(handle);
98
123
  if (tcr.overwriteResponse) {
124
+ const ctxMsg = contextTx.getMessage(handle);
99
125
  ctxMsg.content = tcr.overwriteResponse;
126
+ (0, assert_1.strict)(ctxMsg.role === "tool");
127
+ eventHandler.onToolCallResult(ctxMsg);
100
128
  }
101
- (0, assert_1.strict)(ctxMsg.role === "tool");
102
- eventHandler.onToolCallResult(ctxMsg);
103
129
  });
104
130
  // Note, if an error DID occur, the ContextManager does not see any of
105
131
  // the new context.
@@ -110,6 +136,26 @@ class AgentEx {
110
136
  return { message, images: images.length === 0 ? undefined : images };
111
137
  }
112
138
  async chatCompletion(context, eventHandler) {
139
+ if (this.stopFlag) {
140
+ return {
141
+ id: "user_stopped",
142
+ choices: [
143
+ {
144
+ finish_reason: "stop",
145
+ index: 0,
146
+ message: {
147
+ content: exports.USER_STOP_MESSAGE,
148
+ role: "assistant",
149
+ refusal: null,
150
+ },
151
+ logprobs: null,
152
+ },
153
+ ],
154
+ created: Date.now(),
155
+ model: this.llm.getModel(),
156
+ object: "chat.completion",
157
+ };
158
+ }
113
159
  // Compute the full list of available tools
114
160
  let tools;
115
161
  const mcpTools = this.mcpServerManager.getOpenAITools();
@@ -119,7 +165,15 @@ class AgentEx {
119
165
  tools = enabledTools;
120
166
  }
121
167
  logger.debug(`[chatCompletion] tools: ${JSON.stringify(tools)}`);
122
- const completion = await this.llm.getConversationResponse(context, tools, eventHandler.onAgentMessage.bind(eventHandler), eventHandler.onReasoning.bind(eventHandler));
168
+ // Log system prompt length
169
+ if (context.length > 0 && context[0].role === "system") {
170
+ const systemPrompt = context[0].content;
171
+ logger.info(`[chatCompletion] System prompt length: ${String(systemPrompt.length)}`);
172
+ }
173
+ const { stop, completion: completionP } = await this.llm.getConversationResponse(context, tools, eventHandler.onAgentMessage.bind(eventHandler), eventHandler.onReasoning.bind(eventHandler));
174
+ this.stopFn = stop;
175
+ const completion = await completionP;
176
+ this.stopFn = undefined;
123
177
  logger.debug(`Received chat completion ${JSON.stringify(completion)}`);
124
178
  return completion;
125
179
  }
@@ -161,6 +215,9 @@ class AgentEx {
161
215
  * ChatCompletionToolMessageParam to be used in the conversation.
162
216
  */
163
217
  async doToolCall(toolCall, eventHandler) {
218
+ if (this.stopFlag) {
219
+ return { response: exports.USER_STOP_MESSAGE };
220
+ }
164
221
  // If the tool is and "agent" (internal) tool, we can just execute it.
165
222
  // Otherwise, call the event handler to get permission and invoke the
166
223
  // external tool handler.
@@ -210,11 +267,11 @@ class AgentEx {
210
267
  };
211
268
  }
212
269
  // Final sanity check on the tool call response length.
213
- if (result.response.length > MAX_TOOL_CALL_RESPONSE_LENGTH) {
270
+ if (result.response.length > toolSettings_1.MAX_TOOL_CALL_RESPONSE_LENGTH) {
214
271
  logger.warn("[Agent.doToolCall]: truncating tool call result.response for call:\n" +
215
272
  JSON.stringify(toolCall));
216
273
  result.response =
217
- result.response.slice(0, MAX_TOOL_CALL_RESPONSE_LENGTH) +
274
+ result.response.slice(0, toolSettings_1.MAX_TOOL_CALL_RESPONSE_LENGTH) +
218
275
  " ..truncated";
219
276
  }
220
277
  return result;
@@ -233,8 +290,10 @@ class AgentEx {
233
290
  }
234
291
  exports.AgentEx = AgentEx;
235
292
  /**
236
- * Higher-level abstraction over AgentEx, which abstracts out the transactions
237
- * to the context manager.
293
+ * Higher-level abstraction over AgentEx, which abstracts out the context
294
+ * transactions. A single agent is associated with an IContextManager and
295
+ * internally creates and commits transactions during each call to
296
+ * `userMessage*`.
238
297
  */
239
298
  class Agent {
240
299
  constructor(eventHandler, mcpServerManager, llm, contextManager) {
@@ -346,7 +405,7 @@ function createUserMessage(msg, imageB64, name) {
346
405
  }
347
406
  function createUserMessageEnsure(msg, imageB64, name) {
348
407
  const userMsg = createUserMessage(msg, imageB64, name);
349
- (0, assert_1.strict)(userMsg);
408
+ (0, assert_1.strict)(userMsg, "createUserMessageEnsure");
350
409
  return userMsg;
351
410
  }
352
411
  function completionToAssistantMessageParam(compMessage) {
@@ -55,8 +55,9 @@ async function createSpecializedLLM(model, platform) {
55
55
  if (model && model.startsWith("dummy:")) {
56
56
  llm = await dummyLLM_1.DummyLLM.initFromModelUrl(model, platform);
57
57
  }
58
- else if (model === "repeat") {
59
- llm = new repeatLLM_1.RepeatLLM();
58
+ else if (model && model.startsWith("repeat")) {
59
+ const prefix = model.startsWith("repeat:") ? model.slice(7) : "";
60
+ llm = new repeatLLM_1.RepeatLLM(prefix);
60
61
  }
61
62
  return llm;
62
63
  }
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LLMDocumentSummarizer = void 0;
4
+ exports.createDocumentSummarizer = createDocumentSummarizer;
5
+ exports.summarizeDocument = summarizeDocument;
6
+ const openAIRouterLLM_1 = require("../chat/server/openAIRouterLLM");
7
+ const sdk_1 = require("@xalia/xmcp/sdk");
8
+ const logger = (0, sdk_1.getLogger)();
9
+ const SUMMARY_MODEL = "google/gemini-2.5-flash";
10
+ const SUMMARY_MAX_TOKENS = 500;
11
+ const SUMMARY_TEMPERATURE = 0.3;
12
+ const SUMMARY_TIMEOUT_MS = 30000;
13
+ const MAX_CONTENT_LENGTH = 100000;
14
+ /**
15
+ * System prompt for document summarization, optimized for recall.
16
+ */
17
+ const SUMMARY_SYSTEM_PROMPT = `You are a document summarizer optimizing for RECALL. Create a summary ` +
18
+ `(3-10 sentences) that captures:
19
+ - Main topic and purpose of the document
20
+ - Key entities (names, organizations, places, dates, numbers)
21
+ - Important concepts, terms, and topics mentioned
22
+ - Any conclusions, results, or key findings
23
+
24
+ Include specific details that would help locate this document later.
25
+ Use keywords and phrases from the original text.
26
+ Do NOT include meta-commentary about the document format.
27
+ Output ONLY the summary text.`;
28
+ class LLMDocumentSummarizer {
29
+ constructor(model = SUMMARY_MODEL) {
30
+ this.model = model;
31
+ }
32
+ async summarize(content) {
33
+ if (!content || content.trim().length === 0) {
34
+ return "Empty document";
35
+ }
36
+ try {
37
+ const summary = await this.summarizeWithTimeout(content);
38
+ return this.sanitizeSummary(summary);
39
+ }
40
+ catch (error) {
41
+ const errorMsg = error instanceof Error ? error.message : String(error);
42
+ logger.warn(`[DocumentSummarizer] LLM summarization failed: ${errorMsg}, ` +
43
+ `using fallback`);
44
+ return this.fallbackSummary(content);
45
+ }
46
+ }
47
+ async summarizeWithTimeout(content) {
48
+ const timeoutPromise = new Promise((_, reject) => {
49
+ setTimeout(() => {
50
+ reject(new Error("Summary generation timeout"));
51
+ }, SUMMARY_TIMEOUT_MS);
52
+ });
53
+ const summaryPromise = this.callLLM(content);
54
+ return Promise.race([summaryPromise, timeoutPromise]);
55
+ }
56
+ async callLLM(content) {
57
+ const client = (0, openAIRouterLLM_1.getOpenAIClient)(this.model);
58
+ const truncatedContent = content.length > MAX_CONTENT_LENGTH
59
+ ? content.slice(0, MAX_CONTENT_LENGTH) + "\n\n[Content truncated...]"
60
+ : content;
61
+ const response = await client.chat.completions.create({
62
+ model: this.model,
63
+ messages: [
64
+ {
65
+ role: "system",
66
+ content: SUMMARY_SYSTEM_PROMPT,
67
+ },
68
+ {
69
+ role: "user",
70
+ content: `Please summarize this document:\n\n${truncatedContent}`,
71
+ },
72
+ ],
73
+ max_tokens: SUMMARY_MAX_TOKENS,
74
+ temperature: SUMMARY_TEMPERATURE,
75
+ });
76
+ const summary = response.choices[0]?.message?.content?.trim();
77
+ if (!summary) {
78
+ throw new Error("Empty response from LLM");
79
+ }
80
+ return summary;
81
+ }
82
+ sanitizeSummary(summary) {
83
+ return summary.replace(/\s+/g, " ").trim();
84
+ }
85
+ fallbackSummary(content) {
86
+ const cleaned = content.trim();
87
+ if (cleaned.length === 0) {
88
+ return "Empty document";
89
+ }
90
+ const firstParagraph = cleaned.split(/\n\n/)[0];
91
+ const maxLength = 500;
92
+ if (firstParagraph.length <= maxLength) {
93
+ return firstParagraph;
94
+ }
95
+ return cleaned.slice(0, maxLength).trim() + "...";
96
+ }
97
+ }
98
+ exports.LLMDocumentSummarizer = LLMDocumentSummarizer;
99
+ class FallbackDocumentSummarizer {
100
+ // eslint-disable-next-line @typescript-eslint/require-await
101
+ async summarize(content) {
102
+ const cleaned = content.trim();
103
+ if (cleaned.length === 0) {
104
+ return "Empty document";
105
+ }
106
+ const firstParagraph = cleaned.split(/\n\n/)[0];
107
+ const maxLength = 500;
108
+ if (firstParagraph.length <= maxLength) {
109
+ return firstParagraph;
110
+ }
111
+ return cleaned.slice(0, maxLength).trim() + "...";
112
+ }
113
+ }
114
+ function createDocumentSummarizer(model) {
115
+ if (process.env.DISABLE_LLM_SUMMARIES === "true") {
116
+ return new FallbackDocumentSummarizer();
117
+ }
118
+ return new LLMDocumentSummarizer(model);
119
+ }
120
+ /**
121
+ * Convenience function for one-off summarization.
122
+ */
123
+ async function summarizeDocument(content) {
124
+ const summarizer = createDocumentSummarizer();
125
+ return summarizer.summarize(content);
126
+ }
@@ -50,31 +50,34 @@ class DummyLLM {
50
50
  }
51
51
  async getConversationResponse(messages, _tools, onMessage, onReasoning) {
52
52
  await new Promise((r) => setTimeout(r, 0));
53
- (0, assert_1.strict)(this.idx < this.responses.length);
54
53
  this.lastRequest = messages;
55
- for (;;) {
56
- const response = this.responses[this.idx++];
57
- if (response.finish_reason === "error") {
58
- throw new Error(response.message);
59
- }
60
- if (response.finish_reason === "reasoning") {
61
- if (onReasoning) {
62
- await onReasoning(response.message);
54
+ const completion = (async () => {
55
+ for (;;) {
56
+ const idx = this.idx++;
57
+ const response = this.responses[idx % this.responses.length];
58
+ if (response.finish_reason === "error") {
59
+ throw new Error(response.message);
63
60
  }
64
- continue;
65
- }
66
- if (onMessage) {
67
- const message = response.message;
68
- void onMessage(message.content || "", true);
61
+ if (response.finish_reason === "reasoning") {
62
+ if (onReasoning) {
63
+ await onReasoning(response.message);
64
+ }
65
+ continue;
66
+ }
67
+ if (onMessage) {
68
+ const message = response.message;
69
+ void onMessage(message.content || "", true);
70
+ }
71
+ return {
72
+ id: String(idx),
73
+ choices: [response],
74
+ created: Date.now(),
75
+ model: "dummyLlmModel",
76
+ object: "chat.completion",
77
+ };
69
78
  }
70
- return {
71
- id: String(this.idx),
72
- choices: [response],
73
- created: Date.now(),
74
- model: "dummyLlmModel",
75
- object: "chat.completion",
76
- };
77
- }
79
+ })();
80
+ return { stop: () => { }, completion };
78
81
  }
79
82
  setModel(_model) {
80
83
  (0, assert_1.strict)(false, "unexpected call to setModel");
@@ -27,7 +27,7 @@ class ImageGenLLM {
27
27
  getUrl() {
28
28
  return this.openai.baseURL;
29
29
  }
30
- async getConversationResponse(messages, tools, onMessage) {
30
+ getConversationResponse(messages, tools, onMessage) {
31
31
  (0, assert_1.strict)(!tools || tools.length === 0, "tools not supported in ImageGenLLM");
32
32
  // Designed for image generation using openrouter, which tweaks the Create
33
33
  const params = {
@@ -37,25 +37,28 @@ class ImageGenLLM {
37
37
  modalities: ["image", "text"],
38
38
  };
39
39
  logger.info(`[ImageGenLLM] params; ${JSON.stringify(params)}`);
40
- const completion = (await this.openai.chat.completions.create(params));
41
- // logger.debug(
42
- // `[ImageGenLLM.getConversationResponse] completion:
43
- // ${JSON.stringify(completion)}`
44
- // );
45
- if (onMessage) {
46
- const message = completion.choices[0].message;
47
- if (message.content) {
48
- await onMessage(message.content, true);
40
+ const completion = (async () => {
41
+ const completion = (await this.openai.chat.completions.create(params));
42
+ // logger.debug(
43
+ // `[ImageGenLLM.getConversationResponse] completion:
44
+ // ${JSON.stringify(completion)}`
45
+ // );
46
+ if (onMessage) {
47
+ const message = completion.choices[0].message;
48
+ if (message.content) {
49
+ await onMessage(message.content, true);
50
+ }
51
+ if (message.images) {
52
+ message.images.forEach((image, index) => {
53
+ const imageUrl = image.image_url.url; // Base64 data URL
54
+ const truncated = imageUrl.substring(0, 50);
55
+ logger.info(`[ImageGenLLM] ${String(index + 1)}: ${truncated}...`);
56
+ });
57
+ }
49
58
  }
50
- if (message.images) {
51
- message.images.forEach((image, index) => {
52
- const imageUrl = image.image_url.url; // Base64 data URL
53
- const truncated = imageUrl.substring(0, 50);
54
- logger.info(`[ImageGenLLM] ${String(index + 1)}: ${truncated}...`);
55
- });
56
- }
57
- }
58
- return completion;
59
+ return completion;
60
+ })();
61
+ return Promise.resolve({ stop: () => { }, completion });
59
62
  }
60
63
  }
61
64
  exports.ImageGenLLM = ImageGenLLM;
@@ -2,6 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.XALIA_APP_HEADER = void 0;
4
4
  exports.XALIA_APP_HEADER = {
5
- "HTTP-Referer": "xalia.ai",
5
+ "HTTP-Referer": "https://xalia.ai",
6
6
  "X-Title": "Xalia",
7
7
  };
@@ -46,19 +46,22 @@ class OpenAILLM {
46
46
  getUrl() {
47
47
  return this.openai.baseURL;
48
48
  }
49
- async getConversationResponse(messages, tools, onMessage) {
50
- const completion = await this.openai.chat.completions.create({
51
- model: this.model,
52
- messages,
53
- tools,
54
- });
55
- if (onMessage) {
56
- const message = completion.choices[0].message;
57
- if (message.content) {
58
- await onMessage(message.content, true);
49
+ getConversationResponse(messages, tools, onMessage) {
50
+ const completion = (async () => {
51
+ const completion = await this.openai.chat.completions.create({
52
+ model: this.model,
53
+ messages,
54
+ tools,
55
+ });
56
+ if (onMessage) {
57
+ const message = completion.choices[0].message;
58
+ if (message.content) {
59
+ await onMessage(message.content, true);
60
+ }
59
61
  }
60
- }
61
- return completionFromOpenAI(completion);
62
+ return completionFromOpenAI(completion);
63
+ })();
64
+ return Promise.resolve({ stop: () => { }, completion });
62
65
  }
63
66
  }
64
67
  exports.OpenAILLM = OpenAILLM;
@@ -439,47 +439,78 @@ class OpenAILLMStreaming {
439
439
  if (!chunks.iterator) {
440
440
  throw new Error("not a stream");
441
441
  }
442
- let aggregatedMessage;
443
- for await (const chunk of chunks) {
444
- logger.debug(`[stream] chunk: ${JSON.stringify(chunk)}`);
445
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
446
- if (chunk.object !== "chat.completion.chunk") {
447
- // logger.warn("[stream]: unexpected message");
448
- continue;
449
- }
450
- if (!aggregatedMessage) {
451
- logger.debug(`[stream] first}`);
452
- const { initMessage } = initializeCompletion(chunk);
453
- aggregatedMessage = initMessage;
454
- }
455
- else {
456
- updateCompletion(aggregatedMessage, chunk);
457
- }
458
- if (onMessage) {
459
- // Inform the call of a message fragment if it contains any text.
460
- // Note: chunks may have zero choices (e.g., usage-only chunks), so
461
- // we safely access the first choice.
462
- const delta = chunk.choices[0]?.delta;
442
+ let stopMsg = undefined;
443
+ const stop = (msg) => {
444
+ stopMsg = msg;
445
+ };
446
+ const completion = (async () => {
447
+ // Completion built up over successive calls to processChunk.
448
+ let aggregatedMessage;
449
+ const processChunk = async (chunk) => {
463
450
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
464
- if (delta?.content) {
465
- await onMessage(delta.content, false);
451
+ if (chunk.object !== "chat.completion.chunk") {
452
+ // logger.warn("[stream]: unexpected message");
453
+ return;
466
454
  }
467
- }
468
- if (onReasoning) {
469
- const delta = chunk.choices[0]
470
- ?.delta;
471
- const reasoning = (0, openAI_1.choiceDeltaExtractReasoning)(delta);
472
- if (reasoning) {
473
- await onReasoning(reasoning);
455
+ if (!aggregatedMessage) {
456
+ logger.debug(`[stream] first}`);
457
+ const { initMessage } = initializeCompletion(chunk);
458
+ aggregatedMessage = initMessage;
459
+ }
460
+ else {
461
+ updateCompletion(aggregatedMessage, chunk);
474
462
  }
463
+ if (onMessage) {
464
+ // Inform the call of a message fragment if it contains any text.
465
+ // Note: chunks may have zero choices (e.g., usage-only chunks), so
466
+ // we safely access the first choice.
467
+ const delta = chunk.choices[0]?.delta;
468
+ // eslint-disable-next-line
469
+ if (delta?.content) {
470
+ await onMessage(delta.content, false);
471
+ }
472
+ }
473
+ if (onReasoning) {
474
+ const delta = chunk.choices[0]
475
+ ?.delta;
476
+ const reasoning = (0, openAI_1.choiceDeltaExtractReasoning)(delta);
477
+ if (reasoning) {
478
+ await onReasoning(reasoning);
479
+ }
480
+ }
481
+ };
482
+ // Process each chunk, checking for a stop signal.
483
+ for await (const chunk of chunks) {
484
+ logger.debug(`[stream] chunk: ${JSON.stringify(chunk)}`);
485
+ await processChunk(chunk);
486
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
487
+ if (stopMsg) {
488
+ const choice = {
489
+ delta: { content: stopMsg },
490
+ finish_reason: aggregatedMessage && aggregatedMessage.choices[0].finish_reason
491
+ ? null
492
+ : "stop",
493
+ index: 0,
494
+ };
495
+ await processChunk({
496
+ id: aggregatedMessage?.id || "user_stop_chunk",
497
+ created: aggregatedMessage?.created || Date.now(),
498
+ model: aggregatedMessage?.model || model,
499
+ object: "chat.completion.chunk",
500
+ choices: [choice],
501
+ });
502
+ break;
503
+ }
504
+ /* eslint-enable @typescript-eslint/no-unnecessary-condition */
475
505
  }
476
- }
477
- if (onMessage) {
478
- await onMessage("", true);
479
- }
480
- logger.debug(`[stream] final message: ${JSON.stringify(aggregatedMessage)}`);
481
- (0, assert_1.strict)(aggregatedMessage);
482
- return aggregatedMessage;
506
+ if (onMessage) {
507
+ await onMessage("", true);
508
+ }
509
+ logger.debug(`[stream] final message: ${JSON.stringify(aggregatedMessage)}`);
510
+ (0, assert_1.strict)(aggregatedMessage);
511
+ return aggregatedMessage;
512
+ })();
513
+ return { stop, completion };
483
514
  }
484
515
  }
485
516
  exports.OpenAILLMStreaming = OpenAILLMStreaming;