@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,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Agent = 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,52 +9,49 @@ 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)();
15
- class Agent {
16
- constructor(eventHandler, mcpServerManager, llm, contextManager) {
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
+ */
26
+ class AgentEx {
27
+ constructor(mcpServerManager, llm) {
17
28
  /// The full list of tools, ready to pass to the LLM
18
29
  this.tools = [];
19
30
  /// Handlers for "agent" (or "built-in") tools. These do not require
20
31
  /// approval from the user.
21
32
  this.agentTools = new Map();
22
- this.eventHandler = eventHandler;
23
33
  this.mcpServerManager = mcpServerManager;
24
34
  this.llm = llm;
25
- this.contextManager = contextManager;
26
- }
27
- static initializeWithLLM(eventHandler, llm, contextManager, mcpServerManager) {
28
- return new Agent(eventHandler, mcpServerManager ?? new mcpServerManager_1.McpServerManager(), llm, contextManager);
35
+ this.stopFlag = false;
36
+ this.stopFn = undefined;
29
37
  }
30
38
  async shutdown() {
39
+ this.stop("shutting down");
31
40
  return this.mcpServerManager.shutdown();
32
41
  }
33
- getAgentProfile() {
34
- return new sdk_2.AgentProfile(this.llm.getModel(), this.getSystemPrompt(), this.mcpServerManager.getMcpServerSettings());
35
- }
36
- getConversation() {
37
- const llmMessages = this.contextManager.getLLMContext();
38
- (0, assert_1.strict)(llmMessages[0].role === "system", "first message must have system role");
39
- return [...llmMessages.slice(1)];
42
+ stop(msg) {
43
+ this.stopFlag = true;
44
+ if (this.stopFn) {
45
+ this.stopFn(msg || exports.USER_STOP_MESSAGE);
46
+ }
40
47
  }
41
48
  getMcpServerManager() {
42
49
  return this.mcpServerManager;
43
50
  }
44
- /**
45
- * Like `userMessage`, but can be awaited, and accepts the user name.
46
- */
47
- async userMessageEx(msg, imageB64, name) {
48
- const userMessage = createUserMessage(msg, imageB64, name);
49
- if (!userMessage) {
50
- return undefined;
51
- }
52
- return this.userMessageRaw(userMessage);
53
- }
54
- async userMessageRaw(userMessage) {
55
- return this.userMessagesRaw([userMessage]);
56
- }
57
- async userMessagesRaw(userMessages) {
51
+ // TODO: rename
52
+ async userMessagesRaw(contextTx, eventHandler) {
53
+ this.stopFlag = false;
54
+ // New user messages have already been added to the `contextTx`.
58
55
  // Image and audio handling
59
56
  //
60
57
  // `ChatCompletions` (responses from the LLM) can contain `audio` and
@@ -63,19 +60,19 @@ class Agent {
63
60
  //
64
61
  // As such, our current approach is to extract all assistant-generated
65
62
  // media and return it separately.
66
- // Note: `getLLMContext` returns a copy to we can mutate this array
67
- const context = this.contextManager.getLLMContext();
68
- const newMessagesIdx = context.length;
69
- // Add the new user messages
70
- context.push(...userMessages);
71
63
  const images = [];
72
64
  // We convert the `ChatCompletionsMessage` into a
73
65
  // `ChatCompletionAssistantMessageParam` and extract image data.
74
- let completion = await this.chatCompletion(context);
75
- let message = this.processCompletion(completion, images);
76
- context.push(message);
66
+ let completion = await this.chatCompletion(contextTx.getLLMContext(), eventHandler);
67
+ let message = this.processCompletion(completion, images, eventHandler);
68
+ contextTx.addMessage(message);
77
69
  // While there are tool calls to make, invoke them and loop
78
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);
79
76
  // TODO: Execute all tool calls in parallel
80
77
  // [indexInContext, ToolCallResult][]
81
78
  const toolCallResults = [];
@@ -83,8 +80,7 @@ class Agent {
83
80
  // Execute the tool call, add the result to the context as an LLM
84
81
  // mesage, and record the index of the message alongside the result in
85
82
  // `toolCallResults`.
86
- const result = await this.doToolCall(toolCall);
87
- toolCallResults.push([context.length, result]);
83
+ const result = await this.doToolCall(toolCall, eventHandler);
88
84
  const toolResult = {
89
85
  role: "tool",
90
86
  tool_call_id: toolCall.id,
@@ -94,7 +90,13 @@ class Agent {
94
90
  ? { structuredContent: result.structuredContent }
95
91
  : {}),
96
92
  };
97
- context.push(toolResult);
93
+ const toolResultHandle = contextTx.addMessage(toolResult);
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);
98
100
  // If the tool call requested that its args be redacted, this can be
99
101
  // done now - before the next LLM invocation.
100
102
  if (result.overwriteArgs) {
@@ -103,63 +105,57 @@ class Agent {
103
105
  logger.debug(`agent message after update ${JSON.stringify(message)}`);
104
106
  }
105
107
  }
106
- // Now that any args have been overwritten, signal the event handler of
107
- // the prevoius completion.
108
- this.eventHandler.onCompletion(message);
109
108
  // Get a new completion using the untouched tool call results. Note
110
109
  // that, since we are deferring the `onToolCallResult` calls (so they
111
110
  // can be redacted), we must take care that the errors in
112
111
  // `chatCompletion` do not disrupt this, so the caller has a consistent
113
112
  // view of the conversation state.
114
113
  try {
115
- completion = await this.chatCompletion(context); // CAN THROW
116
- message = this.processCompletion(completion, images);
117
- context.push(message);
114
+ completion = await this.chatCompletion(contextTx.getLLMContext(), eventHandler);
115
+ message = this.processCompletion(completion, images, eventHandler);
116
+ contextTx.addMessage(message);
118
117
  }
119
118
  finally {
120
119
  // Now that the tool call results have been passed to the LLM, perform
121
- // any updates on them. Pass the (updated) tool-call-result LLM
122
- // messages to the event handler - note, we want to do this even if an
123
- // error occured, so that the caller has an up-to-date picture of the
124
- // context state when the error occured.
125
- toolCallResults.forEach(([indexInContext, tcr]) => {
126
- const ctxMsg = context[indexInContext];
120
+ // any updates on them if overwriteResponse was requested. If so, send
121
+ // the updated tool result to the frontend to replace the original.
122
+ toolCallResults.forEach(([handle, tcr]) => {
127
123
  if (tcr.overwriteResponse) {
124
+ const ctxMsg = contextTx.getMessage(handle);
128
125
  ctxMsg.content = tcr.overwriteResponse;
126
+ (0, assert_1.strict)(ctxMsg.role === "tool");
127
+ eventHandler.onToolCallResult(ctxMsg);
129
128
  }
130
- (0, assert_1.strict)(ctxMsg.role === "tool");
131
- this.eventHandler.onToolCallResult(ctxMsg);
132
129
  });
133
130
  // Note, if an error DID occur, the ContextManager does not see any of
134
131
  // the new context.
135
132
  }
136
133
  }
137
134
  // Signal the event handler of the final completion.
138
- this.eventHandler.onCompletion(message);
139
- // Add all new new messages to the context
140
- this.contextManager.addMessages(context.slice(newMessagesIdx));
135
+ eventHandler.onCompletion(message);
141
136
  return { message, images: images.length === 0 ? undefined : images };
142
137
  }
143
- userMessage(msg, imageB64) {
144
- void this.userMessageEx(msg, imageB64);
145
- }
146
- getModel() {
147
- return this.llm.getModel();
148
- }
149
- setModel(model) {
150
- logger.debug(`Set model ${model}`);
151
- this.llm.setModel(model);
152
- }
153
- getSystemPrompt() {
154
- return this.contextManager.getAgentPrompt();
155
- }
156
- /**
157
- * Set the system prompt
158
- */
159
- setSystemPrompt(systemMsg) {
160
- this.contextManager.setAgentPrompt(systemMsg);
161
- }
162
- async chatCompletion(context) {
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
+ }
163
159
  // Compute the full list of available tools
164
160
  let tools;
165
161
  const mcpTools = this.mcpServerManager.getOpenAITools();
@@ -169,7 +165,15 @@ class Agent {
169
165
  tools = enabledTools;
170
166
  }
171
167
  logger.debug(`[chatCompletion] tools: ${JSON.stringify(tools)}`);
172
- const completion = await this.llm.getConversationResponse(context, tools, this.eventHandler.onAgentMessage.bind(this.eventHandler), this.eventHandler.onReasoning.bind(this.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;
173
177
  logger.debug(`Received chat completion ${JSON.stringify(completion)}`);
174
178
  return completion;
175
179
  }
@@ -210,7 +214,10 @@ class Agent {
210
214
  * handler, informing the IAgentEventHandler of the result, and returns the
211
215
  * ChatCompletionToolMessageParam to be used in the conversation.
212
216
  */
213
- async doToolCall(toolCall) {
217
+ async doToolCall(toolCall, eventHandler) {
218
+ if (this.stopFlag) {
219
+ return { response: exports.USER_STOP_MESSAGE };
220
+ }
214
221
  // If the tool is and "agent" (internal) tool, we can just execute it.
215
222
  // Otherwise, call the event handler to get permission and invoke the
216
223
  // external tool handler.
@@ -221,7 +228,7 @@ class Agent {
221
228
  const isAgentTool = !!agentTool;
222
229
  if (isAgentTool) {
223
230
  // Internal (agent) tool
224
- if (!(await this.eventHandler.onToolCall(toolCall, true))) {
231
+ if (!(await eventHandler.onToolCall(toolCall, true))) {
225
232
  result = { response: "User denied tool request." };
226
233
  }
227
234
  else {
@@ -234,7 +241,7 @@ class Agent {
234
241
  // tool call data, get approval, and then invoke.
235
242
  const args = JSON.parse(toolCall.function.arguments || "{}");
236
243
  const tc = this.mcpServerManager.verifyToolCall(toolName, args);
237
- if (!(await this.eventHandler.onToolCall(toolCall, false))) {
244
+ if (!(await eventHandler.onToolCall(toolCall, false))) {
238
245
  result = { response: "User denied tool request." };
239
246
  }
240
247
  else {
@@ -260,27 +267,102 @@ class Agent {
260
267
  };
261
268
  }
262
269
  // Final sanity check on the tool call response length.
263
- if (result.response.length > MAX_TOOL_CALL_RESPONSE_LENGTH) {
270
+ if (result.response.length > toolSettings_1.MAX_TOOL_CALL_RESPONSE_LENGTH) {
264
271
  logger.warn("[Agent.doToolCall]: truncating tool call result.response for call:\n" +
265
272
  JSON.stringify(toolCall));
266
273
  result.response =
267
- result.response.slice(0, MAX_TOOL_CALL_RESPONSE_LENGTH) +
274
+ result.response.slice(0, toolSettings_1.MAX_TOOL_CALL_RESPONSE_LENGTH) +
268
275
  " ..truncated";
269
276
  }
270
277
  return result;
271
278
  }
272
- processCompletion(completion, images) {
279
+ processCompletion(completion, images, eventHandler) {
273
280
  // Add any images into the list, and call the event handler
274
281
  const compMessage = completion.choices[0].message;
275
282
  if (compMessage.images) {
276
283
  for (const image of compMessage.images) {
277
- this.eventHandler.onImage(image);
284
+ eventHandler.onImage(image);
278
285
  images.push(image);
279
286
  }
280
287
  }
281
288
  return completionToAssistantMessageParam(compMessage);
282
289
  }
283
290
  }
291
+ exports.AgentEx = AgentEx;
292
+ /**
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*`.
297
+ */
298
+ class Agent {
299
+ constructor(eventHandler, mcpServerManager, llm, contextManager) {
300
+ this.eventHandler = eventHandler;
301
+ this.contextManager = contextManager;
302
+ this.agentEx = new AgentEx(mcpServerManager, llm);
303
+ }
304
+ static initializeWithLLM(eventHandler, llm, contextManager, mcpServerManager) {
305
+ return new Agent(eventHandler, mcpServerManager ?? new mcpServerManager_1.McpServerManager(), llm, contextManager);
306
+ }
307
+ async shutdown() {
308
+ return this.agentEx.shutdown();
309
+ }
310
+ getAgentProfile() {
311
+ return new sdk_2.AgentProfile(this.agentEx.llm.getModel(), this.getSystemPrompt(), this.agentEx.mcpServerManager.getMcpServerSettings());
312
+ }
313
+ getConversation() {
314
+ const llmMessages = this.contextManager.getLLMContext();
315
+ (0, assert_1.strict)(llmMessages[0].role === "system", "first message must have system role");
316
+ return [...llmMessages.slice(1)];
317
+ }
318
+ getMcpServerManager() {
319
+ return this.agentEx.mcpServerManager;
320
+ }
321
+ /**
322
+ * Like `userMessage`, but can be awaited, and accepts the user name.
323
+ */
324
+ async userMessageEx(msg, imageB64, name) {
325
+ const userMessage = createUserMessage(msg, imageB64, name);
326
+ if (!userMessage) {
327
+ return undefined;
328
+ }
329
+ return this.userMessageRaw(userMessage);
330
+ }
331
+ async userMessageRaw(userMessage) {
332
+ return this.userMessagesRaw([userMessage]);
333
+ }
334
+ async userMessagesRaw(userMessages) {
335
+ const tx = await this.contextManager.startTx(userMessages);
336
+ const result = await this.agentEx.userMessagesRaw(tx, this.eventHandler);
337
+ await this.contextManager.commit(tx);
338
+ return result;
339
+ }
340
+ userMessage(msg, imageB64) {
341
+ void this.userMessageEx(msg, imageB64);
342
+ }
343
+ getModel() {
344
+ return this.agentEx.llm.getModel();
345
+ }
346
+ setModel(model) {
347
+ logger.debug(`Set model ${model}`);
348
+ this.agentEx.llm.setModel(model);
349
+ }
350
+ getSystemPrompt() {
351
+ return this.contextManager.getAgentPrompt();
352
+ }
353
+ /**
354
+ * Set the system prompt
355
+ */
356
+ setSystemPrompt(systemMsg) {
357
+ this.contextManager.setAgentPrompt(systemMsg);
358
+ }
359
+ addAgentToolProvider(toolProvider) {
360
+ return this.agentEx.addAgentToolProvider(toolProvider);
361
+ }
362
+ addAgentTool(tool, handler) {
363
+ this.agentEx.addAgentTool(tool, handler);
364
+ }
365
+ }
284
366
  exports.Agent = Agent;
285
367
  /**
286
368
  * Returns the ChatCompletionMessageParam constructed from (optional) text and
@@ -323,7 +405,7 @@ function createUserMessage(msg, imageB64, name) {
323
405
  }
324
406
  function createUserMessageEnsure(msg, imageB64, name) {
325
407
  const userMsg = createUserMessage(msg, imageB64, name);
326
- (0, assert_1.strict)(userMsg);
408
+ (0, assert_1.strict)(userMsg, "createUserMessageEnsure");
327
409
  return userMsg;
328
410
  }
329
411
  function completionToAssistantMessageParam(compMessage) {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAgentWithoutSkills = createAgentWithoutSkills;
4
4
  exports.createAgentWithSkills = createAgentWithSkills;
5
5
  exports.createAgentFromSkillManager = createAgentFromSkillManager;
6
+ exports.createSpecializedLLM = createSpecializedLLM;
6
7
  exports.createLLM = createLLM;
7
8
  exports.createNonInteractiveAgent = createNonInteractiveAgent;
8
9
  exports.runOneShot = runOneShot;
@@ -15,7 +16,6 @@ const dummyLLM_1 = require("./dummyLLM");
15
16
  const assert_1 = require("assert");
16
17
  const repeatLLM_1 = require("./repeatLLM");
17
18
  const context_1 = require("./context");
18
- const imageGenLLM_1 = require("./imageGenLLM");
19
19
  const logger = (0, sdk_1.getLogger)();
20
20
  async function createAgentWithoutSkills(llmUrl, model, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream = false) {
21
21
  // Init SudoMcpServerManager
@@ -46,30 +46,36 @@ async function createAgentFromSkillManager(llmUrl, model, eventHandler, platform
46
46
  logger.debug("[createAgentFromSkillManager] done");
47
47
  return agent;
48
48
  }
49
- async function createLLM(llmUrl, llmApiKey, model, stream = false, platform) {
49
+ /**
50
+ * Interpret the `model` string to create a specialized agent (dummy, repeat,
51
+ * etc) or return undefined if a specialized agent has not been requested.
52
+ */
53
+ async function createSpecializedLLM(model, platform) {
50
54
  let llm;
51
55
  if (model && model.startsWith("dummy:")) {
52
56
  llm = await dummyLLM_1.DummyLLM.initFromModelUrl(model, platform);
53
57
  }
54
- else if (model === "repeat") {
55
- 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);
61
+ }
62
+ return llm;
63
+ }
64
+ async function createLLM(llmUrl, llmApiKey, model, stream = false, platform) {
65
+ let llm = await createSpecializedLLM(model, platform);
66
+ if (llm) {
67
+ return llm;
68
+ }
69
+ // Regular Agent
70
+ if (!llmApiKey) {
71
+ throw new Error("Missing OpenAI API Key");
56
72
  }
57
- else if (model == imageGenLLM_1.DEFAULT_IMAGE_GEN_MODEL) {
58
- logger.info("ImageGenLLM");
59
- llm = new imageGenLLM_1.ImageGenLLM(llmApiKey, llmUrl, model);
73
+ logger.debug(`Initializing Agent: ${llmUrl ?? "unknown"} - ${model}`);
74
+ if (stream) {
75
+ llm = new openAILLMStreaming_1.OpenAILLMStreaming(llmApiKey, llmUrl, model);
60
76
  }
61
77
  else {
62
- // Regular Agent
63
- if (!llmApiKey) {
64
- throw new Error("Missing OpenAI API Key");
65
- }
66
- logger.debug(`Initializing Agent: ${llmUrl ?? "unknown"} - ${model}`);
67
- if (stream) {
68
- llm = new openAILLMStreaming_1.OpenAILLMStreaming(llmApiKey, llmUrl, model);
69
- }
70
- else {
71
- llm = new openAILLM_1.OpenAILLM(llmApiKey, llmUrl, model);
72
- }
78
+ llm = new openAILLM_1.OpenAILLM(llmApiKey, llmUrl, model);
73
79
  }
74
80
  (0, assert_1.strict)(llm);
75
81
  return llm;
@@ -7,10 +7,9 @@ exports.createSummary = createSummary;
7
7
  const assert_1 = require("assert");
8
8
  const sdk_1 = require("@xalia/xmcp/sdk");
9
9
  const agent_1 = require("./agent");
10
- const nullPlatform_1 = require("./nullPlatform");
11
- const agentUtils_1 = require("./agentUtils");
12
10
  const context_1 = require("./context");
13
11
  const nullAgentEventHandler_1 = require("./nullAgentEventHandler");
12
+ const contextWithWorkspace_1 = require("./contextWithWorkspace");
14
13
  const logger = (0, sdk_1.getLogger)();
15
14
  /**
16
15
  * System prompt used to generate a conversation summary.
@@ -30,12 +29,11 @@ function createCheckpointMessage(summary) {
30
29
  content: CHECKPOINT_MESSAGE_PREFIX + summary,
31
30
  };
32
31
  }
33
- async function createCompressionAgent(compressionAgentUrl, compressionAgentModel, compressionAgentApiKey) {
34
- const llm = await (0, agentUtils_1.createLLM)(compressionAgentUrl, compressionAgentApiKey, compressionAgentModel, false /* stream */, nullPlatform_1.NULL_PLATFORM);
32
+ function createCompressionAgent(llm) {
35
33
  return agent_1.Agent.initializeWithLLM(nullAgentEventHandler_1.NULL_AGENT_EVENT_HANDLER, llm, new context_1.ContextManager(COMPRESSION_SYSTEM_PROMPT, []));
36
34
  }
37
- async function createSummary(compressionAgentUrl, compressionAgentModel, compressionAgentApiKey, conversation) {
38
- const agent = await createCompressionAgent(compressionAgentUrl, compressionAgentModel, compressionAgentApiKey);
35
+ async function createSummary(llm, conversation) {
36
+ const agent = createCompressionAgent(llm);
39
37
  const agentResp = await agent.userMessageEx(JSON.stringify(conversation));
40
38
  if (!agentResp) {
41
39
  throw new Error("compression agent returned null");
@@ -50,12 +48,10 @@ async function createSummary(compressionAgentUrl, compressionAgentModel, compres
50
48
  * the Agent) is responsible for committing the conversation and triggering
51
49
  * compression.
52
50
  */
53
- class CompressingContextManager extends context_1.ContextManagerWithCommit {
54
- constructor(systemPrompt, messages, compressionAgentUrl, compressionAgentModel, compressionAgentApiKey) {
51
+ class CompressingContextManager extends contextWithWorkspace_1.ContextManagerWithWorkspace {
52
+ constructor(systemPrompt, messages, getLLM) {
55
53
  super(systemPrompt, messages);
56
- this.compressionAgentUrl = compressionAgentUrl;
57
- this.compressionAgentModel = compressionAgentModel;
58
- this.compressionAgentApiKey = compressionAgentApiKey;
54
+ this.getLLM = getLLM;
59
55
  this.compressingMessages = undefined;
60
56
  // Sanity check the conversation form.
61
57
  //
@@ -77,15 +73,15 @@ class CompressingContextManager extends context_1.ContextManagerWithCommit {
77
73
  }
78
74
  }
79
75
  async compress() {
80
- // Only select messages for compression if they have been committed.
81
- const numToCompress = this.getCommittedLength();
76
+ const numToCompress = super.numMessages();
82
77
  const messagesToCompress = this.leadingMessages(numToCompress);
83
78
  (0, assert_1.strict)(messagesToCompress.length === numToCompress);
84
79
  this.compressingMessages = numToCompress;
85
80
  (0, assert_1.strict)(this.compressingMessages > 1, "<2 messages commited in the context");
86
81
  logger.debug(`[CompressingContextManager] start (${String(this.compressingMessages)})`);
87
82
  try {
88
- const summary = await createSummary(this.compressionAgentUrl, this.compressionAgentModel, this.compressionAgentApiKey, messagesToCompress);
83
+ const llm = await this.getLLM();
84
+ const summary = await createSummary(llm, messagesToCompress);
89
85
  logger.debug(`[CompressingContextManager] summary: ${summary}`);
90
86
  // Replace the context `messages` and update `lastCommittedMessage`
91
87
  // index.