@xalia/agent 0.5.8 → 0.6.1

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 (185) hide show
  1. package/README.md +23 -8
  2. package/dist/agent/src/agent/agent.js +173 -96
  3. package/dist/agent/src/agent/agentUtils.js +82 -53
  4. package/dist/agent/src/agent/compressingContextManager.js +102 -0
  5. package/dist/agent/src/agent/context.js +189 -0
  6. package/dist/agent/src/agent/dummyLLM.js +46 -5
  7. package/dist/agent/src/agent/iAgentEventHandler.js +2 -0
  8. package/dist/agent/src/agent/mcpServerManager.js +22 -23
  9. package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
  10. package/dist/agent/src/agent/nullPlatform.js +14 -0
  11. package/dist/agent/src/agent/openAILLMStreaming.js +12 -7
  12. package/dist/agent/src/agent/promptProvider.js +63 -0
  13. package/dist/agent/src/agent/repeatLLM.js +5 -5
  14. package/dist/agent/src/agent/sudoMcpServerManager.js +11 -9
  15. package/dist/agent/src/agent/tokenAuth.js +7 -7
  16. package/dist/agent/src/agent/tools.js +1 -1
  17. package/dist/agent/src/chat/client/chatClient.js +733 -0
  18. package/dist/agent/src/chat/client/connection.js +209 -0
  19. package/dist/agent/src/chat/client/connection.test.js +188 -0
  20. package/dist/agent/src/chat/client/constants.js +5 -0
  21. package/dist/agent/src/chat/client/index.js +15 -0
  22. package/dist/agent/src/chat/client/interfaces.js +2 -0
  23. package/dist/agent/src/chat/client/responseHandler.js +105 -0
  24. package/dist/agent/src/chat/client/sessionClient.js +331 -0
  25. package/dist/agent/src/chat/client/teamManager.js +2 -0
  26. package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
  27. package/dist/agent/src/chat/data/dataModels.js +2 -0
  28. package/dist/agent/src/chat/data/database.js +749 -0
  29. package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
  30. package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
  31. package/dist/agent/src/chat/protocol/constants.js +50 -0
  32. package/dist/agent/src/chat/protocol/errors.js +22 -0
  33. package/dist/agent/src/chat/protocol/messages.js +110 -0
  34. package/dist/agent/src/chat/server/chatContextManager.js +405 -0
  35. package/dist/agent/src/chat/server/connectionManager.js +352 -0
  36. package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
  37. package/dist/agent/src/chat/server/conversation.js +198 -0
  38. package/dist/agent/src/chat/server/errorUtils.js +23 -0
  39. package/dist/agent/src/chat/server/openSession.js +869 -0
  40. package/dist/agent/src/chat/server/server.js +177 -0
  41. package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
  42. package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
  43. package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
  44. package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
  45. package/dist/agent/src/chat/server/tools.js +243 -0
  46. package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
  47. package/dist/agent/src/chat/utils/approvalManager.js +85 -0
  48. package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
  49. package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
  50. package/dist/agent/src/chat/utils/htmlToText.js +84 -0
  51. package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
  52. package/dist/agent/src/chat/utils/search.js +145 -0
  53. package/dist/agent/src/chat/utils/userResolver.js +46 -0
  54. package/dist/agent/src/chat/{websocket.js → utils/websocket.js} +2 -0
  55. package/dist/agent/src/test/agent.test.js +332 -0
  56. package/dist/agent/src/test/approvalManager.test.js +58 -0
  57. package/dist/agent/src/test/chatContextManager.test.js +392 -0
  58. package/dist/agent/src/test/clientServerConnection.test.js +158 -0
  59. package/dist/agent/src/test/compressingContextManager.test.js +65 -0
  60. package/dist/agent/src/test/context.test.js +83 -0
  61. package/dist/agent/src/test/conversation.test.js +89 -0
  62. package/dist/agent/src/test/db.test.js +262 -90
  63. package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
  64. package/dist/agent/src/test/dbTestTools.js +99 -0
  65. package/dist/agent/src/test/imageLoad.test.js +8 -7
  66. package/dist/agent/src/test/mcpServerManager.test.js +21 -18
  67. package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
  68. package/dist/agent/src/test/openaiStreaming.test.js +12 -11
  69. package/dist/agent/src/test/prompt.test.js +5 -4
  70. package/dist/agent/src/test/promptProvider.test.js +28 -0
  71. package/dist/agent/src/test/responseHandler.test.js +61 -0
  72. package/dist/agent/src/test/sudoMcpServerManager.test.js +14 -12
  73. package/dist/agent/src/test/testTools.js +109 -0
  74. package/dist/agent/src/test/tools.test.js +31 -0
  75. package/dist/agent/src/tool/agentChat.js +21 -10
  76. package/dist/agent/src/tool/agentMain.js +1 -1
  77. package/dist/agent/src/tool/chatMain.js +235 -58
  78. package/dist/agent/src/tool/commandPrompt.js +15 -9
  79. package/dist/agent/src/tool/files.js +20 -16
  80. package/dist/agent/src/tool/nodePlatform.js +47 -3
  81. package/dist/agent/src/tool/options.js +4 -4
  82. package/dist/agent/src/tool/prompt.js +19 -13
  83. package/eslint.config.mjs +14 -1
  84. package/package.json +14 -6
  85. package/scripts/chat_server +8 -0
  86. package/scripts/setup_chat +7 -2
  87. package/scripts/shutdown_chat_server +3 -0
  88. package/scripts/test_chat +135 -17
  89. package/src/agent/agent.ts +270 -135
  90. package/src/agent/agentUtils.ts +136 -95
  91. package/src/agent/compressingContextManager.ts +164 -0
  92. package/src/agent/context.ts +268 -0
  93. package/src/agent/dummyLLM.ts +76 -8
  94. package/src/agent/iAgentEventHandler.ts +54 -0
  95. package/src/agent/iplatform.ts +1 -0
  96. package/src/agent/mcpServerManager.ts +32 -30
  97. package/src/agent/nullAgentEventHandler.ts +20 -0
  98. package/src/agent/nullPlatform.ts +13 -0
  99. package/src/agent/openAILLMStreaming.ts +12 -6
  100. package/src/agent/promptProvider.ts +87 -0
  101. package/src/agent/repeatLLM.ts +5 -5
  102. package/src/agent/sudoMcpServerManager.ts +13 -11
  103. package/src/agent/tokenAuth.ts +7 -7
  104. package/src/agent/tools.ts +3 -1
  105. package/src/chat/client/chatClient.ts +900 -0
  106. package/src/chat/client/connection.test.ts +241 -0
  107. package/src/chat/client/connection.ts +276 -0
  108. package/src/chat/client/constants.ts +3 -0
  109. package/src/chat/client/index.ts +18 -0
  110. package/src/chat/client/interfaces.ts +34 -0
  111. package/src/chat/client/responseHandler.ts +131 -0
  112. package/src/chat/client/sessionClient.ts +443 -0
  113. package/src/chat/client/teamManager.ts +29 -0
  114. package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
  115. package/src/chat/data/dataModels.ts +85 -0
  116. package/src/chat/data/database.ts +982 -0
  117. package/src/chat/data/dbMcpServerConfigs.ts +59 -0
  118. package/src/chat/protocol/connectionMessages.ts +49 -0
  119. package/src/chat/protocol/constants.ts +55 -0
  120. package/src/chat/protocol/errors.ts +16 -0
  121. package/src/chat/protocol/messages.ts +682 -0
  122. package/src/chat/server/README.md +127 -0
  123. package/src/chat/server/chatContextManager.ts +612 -0
  124. package/src/chat/server/connectionManager.test.ts +266 -0
  125. package/src/chat/server/connectionManager.ts +541 -0
  126. package/src/chat/server/conversation.ts +269 -0
  127. package/src/chat/server/errorUtils.ts +28 -0
  128. package/src/chat/server/openSession.ts +1332 -0
  129. package/src/chat/server/server.ts +177 -0
  130. package/src/chat/server/sessionFileManager.ts +239 -0
  131. package/src/chat/server/sessionRegistry.test.ts +138 -0
  132. package/src/chat/server/sessionRegistry.ts +1064 -0
  133. package/src/chat/server/test-utils/mockFactories.ts +422 -0
  134. package/src/chat/server/tools.ts +265 -0
  135. package/src/chat/utils/agentSessionMap.ts +76 -0
  136. package/src/chat/utils/approvalManager.ts +111 -0
  137. package/src/{utils → chat/utils}/asyncLock.ts +3 -3
  138. package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
  139. package/src/chat/utils/htmlToText.ts +61 -0
  140. package/src/chat/utils/multiAsyncQueue.ts +52 -0
  141. package/src/chat/utils/search.ts +139 -0
  142. package/src/chat/utils/userResolver.ts +48 -0
  143. package/src/chat/{websocket.ts → utils/websocket.ts} +2 -0
  144. package/src/test/agent.test.ts +487 -0
  145. package/src/test/approvalManager.test.ts +73 -0
  146. package/src/test/chatContextManager.test.ts +521 -0
  147. package/src/test/clientServerConnection.test.ts +207 -0
  148. package/src/test/compressingContextManager.test.ts +82 -0
  149. package/src/test/context.test.ts +105 -0
  150. package/src/test/conversation.test.ts +109 -0
  151. package/src/test/db.test.ts +351 -103
  152. package/src/test/dbMcpServerConfigs.test.ts +112 -0
  153. package/src/test/dbTestTools.ts +153 -0
  154. package/src/test/imageLoad.test.ts +7 -6
  155. package/src/test/mcpServerManager.test.ts +19 -14
  156. package/src/test/multiAsyncQueue.test.ts +125 -0
  157. package/src/test/openaiStreaming.test.ts +11 -10
  158. package/src/test/prompt.test.ts +4 -3
  159. package/src/test/promptProvider.test.ts +33 -0
  160. package/src/test/responseHandler.test.ts +78 -0
  161. package/src/test/sudoMcpServerManager.test.ts +22 -15
  162. package/src/test/testTools.ts +146 -0
  163. package/src/test/tools.test.ts +39 -0
  164. package/src/tool/agentChat.ts +26 -12
  165. package/src/tool/agentMain.ts +1 -1
  166. package/src/tool/chatMain.ts +283 -100
  167. package/src/tool/commandPrompt.ts +25 -9
  168. package/src/tool/files.ts +25 -19
  169. package/src/tool/nodePlatform.ts +52 -3
  170. package/src/tool/options.ts +4 -2
  171. package/src/tool/prompt.ts +22 -15
  172. package/test_data/dummyllm_script_crash.json +32 -0
  173. package/test_data/frog.png.b64 +1 -0
  174. package/vitest.config.ts +39 -0
  175. package/dist/agent/src/chat/client.js +0 -310
  176. package/dist/agent/src/chat/conversationManager.js +0 -502
  177. package/dist/agent/src/chat/db.js +0 -218
  178. package/dist/agent/src/chat/messages.js +0 -29
  179. package/dist/agent/src/chat/server.js +0 -158
  180. package/src/chat/client.ts +0 -445
  181. package/src/chat/conversationManager.ts +0 -730
  182. package/src/chat/db.ts +0 -304
  183. package/src/chat/messages.ts +0 -266
  184. package/src/chat/server.ts +0 -177
  185. /package/{frog.png → test_data/frog.png} +0 -0
package/README.md CHANGED
@@ -5,6 +5,7 @@ A TypeScript-based AI agent system with MCP (Model Context Protocol) support, mu
5
5
  ## Overview
6
6
 
7
7
  This agent provides two primary interfaces:
8
+
8
9
  - **Agent Mode**: Single-user conversational AI with tool support
9
10
  - **Chat Mode**: Multi-user chat server with shared AI agent sessions
10
11
 
@@ -20,28 +21,31 @@ yarn workspaces run build
20
21
 
21
22
  ### Basic Usage
22
23
 
23
- Example of running agent can be found in [test script](../mcppro/scripts/test_script).
24
+ Example of running agent can be found in [test script](../mcppro/scripts/test_script).
24
25
 
25
26
  ## Architecture
26
27
 
27
28
  ### Core Components
28
29
 
29
30
  #### Agent (`src/agent/`)
31
+
30
32
  - **`Agent`**: Main orchestrator class managing conversations, tools, and LLM interactions
31
- - **`McpServerManager`**: Manages MCP tool servers, enables/disables tools dynamically
33
+ - **`McpServerManager`**: Manages MCP tool servers, enables/disables tools dynamically
32
34
  - **`SkillManager`**: Interfaces with SudoMCP backend to discover and connect to hosted MCP servers
33
- - **LLM Implementations**:
35
+ - **LLM Implementations**:
34
36
  - `OpenAILLM`: Standard OpenAI API integration
35
37
  - `OpenAILLMStreaming`: Streaming response support
36
38
  - `DummyLLM`: Mock implementation for testing
37
39
 
38
40
  #### Chat System (`src/chat/`)
41
+
39
42
  - **`ChatClient`/`runServer`**: WebSocket-based real-time communication
40
43
  - **`ConversationManager`**: Orchestrates multi-user sessions with shared AI agent
41
44
  - **`Database`**: Supabase integration for user management, sessions, and agent profiles
42
45
  - **`ApiKeyManager`**: Authentication and authorization
43
46
 
44
47
  #### CLI Tools (`src/tool/`)
48
+
45
49
  - **`main.ts`**: Primary entry point with subcommands
46
50
  - **`agentMain.ts`**: Single-user agent mode implementation
47
51
  - **`chatMain.ts`**: Multi-user chat server/client implementation
@@ -51,28 +55,33 @@ Example of running agent can be found in [test script](../mcppro/scripts/test_sc
51
55
  ### Agent Mode
52
56
 
53
57
  **Basic conversation:**
58
+
54
59
  ```bash
55
60
  cli/agent-cli
56
61
  ```
57
62
 
58
63
  **One-shot with specific prompt:**
64
+
59
65
  ```bash
60
66
  echo "Explain quantum computing" > prompt.txt
61
67
  cli/agent-cli -1 --prompt prompt.txt
62
68
  ```
63
69
 
64
70
  **With image analysis:**
71
+
65
72
  ```bash
66
73
  echo "Describe this image" > prompt.txt
67
74
  cli/agent-cli --image photo.jpg --prompt prompt.txt
68
75
  ```
69
76
 
70
77
  **Using agent profile:**
78
+
71
79
  ```bash
72
80
  cli/agent-cli --agent-profile agent/test_data/simplecalc_profile.json
73
81
  ```
74
82
 
75
83
  **Auto-approve tools:**
84
+
76
85
  ```bash
77
86
  echo "Calculate 15 * 23" > prompt.txt
78
87
  cli/agent-cli --approve-tools --prompt prompt.txt
@@ -81,11 +90,13 @@ cli/agent-cli --approve-tools --prompt prompt.txt
81
90
  ### Chat Mode
82
91
 
83
92
  **Start server:**
93
+
84
94
  ```bash
85
95
  node dist/agent/src/tool/main.js chat server --port 5003
86
96
  ```
87
97
 
88
98
  **Connect client:**
99
+
89
100
  ```bash
90
101
  node dist/agent/src/tool/main.js chat client \
91
102
  --session "project_discussion" \
@@ -93,6 +104,7 @@ node dist/agent/src/tool/main.js chat client \
93
104
  ```
94
105
 
95
106
  **Run scripted conversation:**
107
+
96
108
  ```bash
97
109
  node dist/agent/src/tool/main.js chat client \
98
110
  --session "test" \
@@ -104,18 +116,21 @@ node dist/agent/src/tool/main.js chat client \
104
116
  While in agent mode, use these commands:
105
117
 
106
118
  **Tool Management:**
119
+
107
120
  - `/ls` - List available MCP servers
108
- - `/lt` - List current tools (enabled marked with *)
121
+ - `/lt` - List current tools (enabled marked with \*)
109
122
  - `/as <server>` - Add and enable all tools from server
110
123
  - `/e <server> <tool>` - Enable specific tool
111
124
  - `/d <server> <tool>` - Disable specific tool
112
125
 
113
126
  **Media and Data:**
127
+
114
128
  - `:i image.jpg` - Include image with next message
115
129
  - `/wc conversation.json` - Save conversation to file
116
130
  - `/wa profile.json` - Save agent profile to file
117
131
 
118
132
  **General:**
133
+
119
134
  - `/h` - Show help menu
120
135
  - `/q` - Quit
121
136
 
@@ -129,7 +144,7 @@ LLM_URL=http://localhost:5001/v1 # LLM API endpoint
129
144
  LLM_API_KEY=your_openai_key # API key for LLM
130
145
  LLM_MODEL=gpt-4o # Model name
131
146
 
132
- # SudoMCP Integration
147
+ # SudoMCP Integration
133
148
  XMCP_URL=http://localhost:5001/ # SudoMCP backend URL
134
149
  API_KEY=your_sudomcp_key # SudoMCP API key
135
150
 
@@ -150,7 +165,7 @@ Agent profiles define the AI's behavior and available tools:
150
165
  "model": "gpt-4o",
151
166
  "system_prompt": "You are a helpful coding assistant.",
152
167
  "mcp_settings": {
153
- "github": ["create_issue", "list_repos"],
168
+ "github": ["create_issue", "list_repos"]
154
169
  }
155
170
  }
156
171
  ```
@@ -197,7 +212,7 @@ yarn test -- --grep "DB"
197
212
  # Git commit message generation
198
213
  ./scripts/git_message
199
214
 
200
- # PR description generation
215
+ # PR description generation
201
216
  ./scripts/pr_message
202
217
 
203
218
  # Code review assistance
@@ -227,6 +242,6 @@ Save and restore conversation state:
227
242
  # Save conversation
228
243
  cli/agent agent --conversation-output saved_conversation.json
229
244
 
230
- # Restore conversation
245
+ # Restore conversation
231
246
  cli/agent --conversation saved_conversation.json
232
247
  ```
@@ -35,33 +35,30 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.Agent = exports.AgentProfile = void 0;
37
37
  exports.createUserMessage = createUserMessage;
38
+ exports.createUserMessageEnsure = createUserMessageEnsure;
38
39
  const dotenv = __importStar(require("dotenv"));
39
40
  const mcpServerManager_1 = require("./mcpServerManager");
40
41
  const assert_1 = require("assert");
41
42
  const sdk_1 = require("@xalia/xmcp/sdk");
42
43
  var sdk_2 = require("@xalia/xmcp/sdk");
43
44
  Object.defineProperty(exports, "AgentProfile", { enumerable: true, get: function () { return sdk_2.AgentProfile; } });
45
+ const MAX_TOOL_CALL_RESPONSE_LENGTH = 4000;
44
46
  dotenv.config();
45
47
  const logger = (0, sdk_1.getLogger)();
46
48
  class Agent {
47
- constructor(onMessage, onToolCall, messages, mcpServerManager, tools, llm) {
48
- this.onMessage = onMessage;
49
- this.onToolCall = onToolCall;
50
- this.messages = messages;
49
+ constructor(eventHandler, mcpServerManager, llm, contextManager) {
50
+ /// The full list of tools, ready to pass to the LLM
51
+ this.tools = [];
52
+ /// Handlers for "agent" (or "built-in") tools. These do not require
53
+ /// approval from the user.
54
+ this.agentTools = new Map();
55
+ this.eventHandler = eventHandler;
51
56
  this.mcpServerManager = mcpServerManager;
52
- this.tools = tools;
53
57
  this.llm = llm;
54
- this.toolHandlers = {};
55
- }
56
- static async initializeWithLLM(onMessage, onToolCall, systemPrompt, llm, mcpServerManager) {
57
- // Initialize messages with system prompt
58
- const messages = [
59
- {
60
- role: "system",
61
- content: systemPrompt ?? "You are a helpful assistant",
62
- },
63
- ];
64
- return new Agent(onMessage, onToolCall, messages, mcpServerManager ?? new mcpServerManager_1.McpServerManager(), [], llm);
58
+ this.contextManager = contextManager;
59
+ }
60
+ static initializeWithLLM(eventHandler, llm, contextManager, mcpServerManager) {
61
+ return new Agent(eventHandler, mcpServerManager ?? new mcpServerManager_1.McpServerManager(), llm, contextManager);
65
62
  }
66
63
  async shutdown() {
67
64
  return this.mcpServerManager.shutdown();
@@ -70,16 +67,9 @@ class Agent {
70
67
  return new sdk_1.AgentProfile(this.llm.getModel(), this.getSystemPrompt(), this.mcpServerManager.getMcpServerSettings());
71
68
  }
72
69
  getConversation() {
73
- (0, assert_1.strict)(this.messages[0].role == "system", "first message must have system role");
74
- // Return a copy so future modifications to `this.messages` don't impact
75
- // the callers copy.
76
- return structuredClone(this.messages.slice(1));
77
- }
78
- setConversation(messages) {
79
- (0, assert_1.strict)(this.messages[0].role == "system");
80
- (0, assert_1.strict)(messages.length === 0 || messages[0].role != "system", "conversation contains system msg");
81
- const newMessages = [this.messages[0]];
82
- this.messages = newMessages.concat(structuredClone(messages));
70
+ const llmMessages = this.contextManager.getLLMContext();
71
+ (0, assert_1.strict)(llmMessages[0].role === "system", "first message must have system role");
72
+ return [...llmMessages.slice(1)];
83
73
  }
84
74
  getMcpServerManager() {
85
75
  return this.mcpServerManager;
@@ -95,45 +85,80 @@ class Agent {
95
85
  return this.userMessageRaw(userMessage);
96
86
  }
97
87
  async userMessageRaw(userMessage) {
98
- this.messages.push(userMessage);
99
- let completion = await this.chatCompletion();
88
+ return this.userMessagesRaw([userMessage]);
89
+ }
90
+ async userMessagesRaw(userMessages) {
91
+ // Note: `getLLMContext` returns a copy to we can mutate this array
92
+ const context = this.contextManager.getLLMContext();
93
+ const newMessagesIdx = context.length;
94
+ // Add the new user messages
95
+ context.push(...userMessages);
96
+ let completion = await this.chatCompletion(context);
100
97
  let message = completion.choices[0].message;
101
- this.messages.push(message);
102
- // While there are tool calls to make, make them and loop
98
+ context.push(message);
99
+ // While there are tool calls to make, invoke them and loop
103
100
  while (message.tool_calls && message.tool_calls.length > 0) {
101
+ // TODO: Execute all tool calls in parallel
102
+ // [indexInContext, ToolCallResult][]
103
+ const toolCallResults = [];
104
104
  for (const toolCall of message.tool_calls ?? []) {
105
- const approval = await this.onToolCall(toolCall);
106
- if (approval) {
107
- try {
108
- const result = await this.doToolCall(toolCall);
109
- logger.debug(`tool call result ${JSON.stringify(result)}`);
110
- this.messages.push(result);
111
- }
112
- catch (e) {
113
- logger.error(`tool call error: ${e}`);
114
- this.messages.push({
115
- role: "tool",
116
- tool_call_id: toolCall.id,
117
- content: "Tool call failed.",
118
- });
119
- }
120
- }
121
- else {
122
- this.messages.push({
123
- role: "tool",
124
- tool_call_id: toolCall.id,
125
- content: "User denied tool use request.",
126
- });
105
+ // Execute the tool call, add the result to the context as an LLM
106
+ // mesage, and record the index of the message alongside the result in
107
+ // `toolCallResults`.
108
+ const result = await this.doToolCall(toolCall);
109
+ toolCallResults.push([context.length, result]);
110
+ context.push({
111
+ role: "tool",
112
+ tool_call_id: toolCall.id,
113
+ content: result.response,
114
+ });
115
+ // If the tool call requested that its args be redacted, this can be
116
+ // done now - before the next LLM invocation.
117
+ if (result.overwriteArgs) {
118
+ logger.debug(`updating args for toolcall ${toolCall.id}: ${result.overwriteArgs}`);
119
+ toolCall.function.arguments = result.overwriteArgs;
120
+ logger.debug(`agent message after update ${JSON.stringify(message)}`);
127
121
  }
128
122
  }
129
- completion = await this.chatCompletion();
130
- message = completion.choices[0].message;
131
- this.messages.push(message);
123
+ // Now that any args have been overwritten, signal the event handler of
124
+ // the prevoius completion.
125
+ this.eventHandler.onCompletion(message);
126
+ // Get a new completion using the untouched tool call results. Note
127
+ // that, since we are deferring the `onToolCallResult` calls (so they
128
+ // can be redacted), we must take care that the errors in
129
+ // `chatCompletion` do not disrupt this, so the caller has a consistent
130
+ // view of the conversation state.
131
+ try {
132
+ completion = await this.chatCompletion(context); // CAN THROW
133
+ message = completion.choices[0].message;
134
+ context.push(message);
135
+ }
136
+ finally {
137
+ // Now that the tool call results have been passed to the LLM, perform
138
+ // any updates on them. Pass the (updated) tool-call-result LLM
139
+ // messages to the event handler - note, we want to do this even if
140
+ // the an error occured, so that the caller has an up-to-date picture
141
+ // of the context state when the error occured.
142
+ toolCallResults.forEach(([indexInContext, tcr]) => {
143
+ const ctxMsg = context[indexInContext];
144
+ if (tcr.overwriteResponse) {
145
+ ctxMsg.content = tcr.overwriteResponse;
146
+ }
147
+ (0, assert_1.strict)(ctxMsg.role === "tool");
148
+ this.eventHandler.onToolCallResult(ctxMsg);
149
+ });
150
+ // Note, if an error DID occur, the ContextManager does not see any of
151
+ // the new context.
152
+ }
132
153
  }
154
+ // Signal the event handler of the final completion.
155
+ this.eventHandler.onCompletion(message);
156
+ // Add all new new messages to the context
157
+ this.contextManager.addMessages(context.slice(newMessagesIdx));
133
158
  return completion.choices[0].message;
134
159
  }
135
160
  userMessage(msg, imageB64) {
136
- this.userMessageEx(msg, imageB64);
161
+ void this.userMessageEx(msg, imageB64);
137
162
  }
138
163
  getModel() {
139
164
  return this.llm.getModel();
@@ -142,69 +167,116 @@ class Agent {
142
167
  logger.debug(`Set model ${model}`);
143
168
  this.llm.setModel(model);
144
169
  }
145
- /**
146
- * Clear the conversation.
147
- */
148
- resetConversation() {
149
- (0, assert_1.strict)(this.messages.length > 0);
150
- // Keep only the system message
151
- this.messages.splice(1);
152
- }
153
170
  getSystemPrompt() {
154
- (0, assert_1.strict)(this.messages[0].role === "system");
155
- return this.messages[0].content;
171
+ return this.contextManager.getAgentPrompt();
156
172
  }
157
173
  /**
158
174
  * Set the system prompt
159
175
  */
160
176
  setSystemPrompt(systemMsg) {
161
- (0, assert_1.strict)(this.messages[0].role === "system");
162
- this.messages[0].content = systemMsg;
177
+ this.contextManager.setAgentPrompt(systemMsg);
163
178
  }
164
- async chatCompletion() {
179
+ async chatCompletion(context) {
180
+ // Compute the full list of available tools
165
181
  let tools;
166
- const enabledTools = this.tools.concat(this.mcpServerManager.getOpenAITools());
182
+ const mcpTools = this.mcpServerManager.getOpenAITools();
183
+ const enabledTools = this.tools.concat(mcpTools);
167
184
  if (enabledTools.length > 0) {
168
185
  tools = enabledTools;
169
186
  }
170
- // logger.debug(
171
- // `chatCompletion: tools: ${JSON.stringify(tools, undefined, 2)}`
172
- // );
173
- const completion = await this.llm.getConversationResponse(this.messages, tools, this.onMessage);
187
+ const completion = await this.llm.getConversationResponse(context, tools, this.eventHandler.onAgentMessage.bind(this.eventHandler));
174
188
  logger.debug(`Received chat completion ${JSON.stringify(completion)}`);
175
189
  return completion;
176
190
  }
177
- toolNames() {
178
- return this.mcpServerManager
179
- .getOpenAITools()
180
- .map((tool) => tool.function.name);
191
+ addAgentToolProvider(toolProvider) {
192
+ return toolProvider.setup(this);
181
193
  }
182
- addTool(tool, handler) {
194
+ addAgentTool(tool, handler) {
183
195
  const name = tool.function.name;
184
- if (this.toolHandlers[name]) {
185
- throw `tool ${name} already added`;
196
+ if (this.agentTools.has(name)) {
197
+ throw new Error(`tool ${name} already added`);
186
198
  }
187
199
  logger.debug(`Adding tool ${name}`);
188
200
  this.tools.push(tool);
189
- this.toolHandlers[name] = handler;
201
+ this.agentTools.set(name, { handler });
202
+ }
203
+ removeAgentTool(name) {
204
+ if (!this.agentTools.has(name)) {
205
+ logger.warn(`[removeTool] tool ${name} not present`);
206
+ }
207
+ // Find idx of the tool in the list
208
+ const idx = (() => {
209
+ let idx = 0;
210
+ while (idx < this.tools.length) {
211
+ if (this.tools[idx].function.name === name) {
212
+ return idx;
213
+ }
214
+ idx++;
215
+ }
216
+ return -1;
217
+ })();
218
+ (0, assert_1.strict)(idx > -1);
219
+ // Remove entries
220
+ this.tools.splice(idx, 1);
221
+ this.agentTools.delete(name);
190
222
  }
223
+ /**
224
+ * Handle the details of getting approval (if required), invoking the tool
225
+ * handler, informing the IAgentEventHandler of the result, and returns the
226
+ * OpenAI.ChatCompletionToolMessageParam to be used in the conversation.
227
+ */
191
228
  async doToolCall(toolCall) {
192
- const name = toolCall.function.name;
193
- const args = JSON.parse(toolCall.function.arguments);
194
- let result = undefined;
195
- const handler = this.toolHandlers[name];
196
- if (handler) {
197
- logger.debug(` found agent tool ${name} ...`);
198
- result = handler(args);
229
+ // If the tool is and "agent" (internal) tool, we can just execute it.
230
+ // Otherwise, call the event handler to get permission and invoke the
231
+ // external tool handler.
232
+ let result;
233
+ try {
234
+ const toolName = toolCall.function.name;
235
+ const agentTool = this.agentTools.get(toolName);
236
+ const isAgentTool = !!agentTool;
237
+ const approve = await this.eventHandler.onToolCall(toolCall, isAgentTool);
238
+ if (!approve) {
239
+ result = { response: "User denied tool request." };
240
+ }
241
+ else if (isAgentTool) {
242
+ // Internal (agent) tool
243
+ const args = JSON.parse(toolCall.function.arguments);
244
+ result = await agentTool.handler(this, args);
245
+ }
246
+ else {
247
+ // McpServer tool call (agentTool === undefined)
248
+ const args = JSON.parse(toolCall.function.arguments);
249
+ result = {
250
+ response: await this.mcpServerManager.invoke(toolName, args),
251
+ };
252
+ logger.debug(`tool call result ${JSON.stringify(result)}`);
253
+ }
199
254
  }
200
- else {
201
- result = await this.mcpServerManager.invoke(name, args);
255
+ catch (e) {
256
+ let msg;
257
+ if (e instanceof Error) {
258
+ msg = e.message;
259
+ }
260
+ else if (typeof e === "string") {
261
+ msg = e;
262
+ }
263
+ else {
264
+ msg = String(e);
265
+ }
266
+ logger.error(`tool call error: ${msg}`);
267
+ result = {
268
+ response: `tool call error: ${msg}`,
269
+ };
202
270
  }
203
- return {
204
- role: "tool",
205
- tool_call_id: toolCall.id,
206
- content: result.toString(),
207
- };
271
+ // Final sanity check on the tool call response length.
272
+ if (result.response.length > MAX_TOOL_CALL_RESPONSE_LENGTH) {
273
+ logger.warn("[Agent.doToolCall]: truncating tool call result.response for call:\n" +
274
+ JSON.stringify(toolCall));
275
+ result.response =
276
+ result.response.slice(0, MAX_TOOL_CALL_RESPONSE_LENGTH) +
277
+ " ..truncated";
278
+ }
279
+ return result;
208
280
  }
209
281
  }
210
282
  exports.Agent = Agent;
@@ -247,3 +319,8 @@ function createUserMessage(msg, imageB64, name) {
247
319
  name,
248
320
  };
249
321
  }
322
+ function createUserMessageEnsure(msg, imageB64, name) {
323
+ const userMsg = createUserMessage(msg, imageB64, name);
324
+ (0, assert_1.strict)(userMsg);
325
+ return userMsg;
326
+ }