@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
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DbMcpServerConfigs = void 0;
4
+ const database_1 = require("./database");
5
+ class DbMcpServerConfigs extends database_1.DbClientBase {
6
+ async clearConfigsForUser(user_uuid) {
7
+ const { error } = await this.client
8
+ .from("user_server_configs")
9
+ .delete()
10
+ .eq("user_uuid", user_uuid);
11
+ if (error) {
12
+ throw error;
13
+ }
14
+ }
15
+ async deleteConfigForUser(user_uuid, server_name) {
16
+ const { error } = await this.client
17
+ .from("user_server_configs")
18
+ .delete()
19
+ .eq("user_uuid", user_uuid)
20
+ .eq("server_name", server_name);
21
+ if (error) {
22
+ throw error;
23
+ }
24
+ }
25
+ async getConfigForUser(user_uuid, server_name) {
26
+ const { error, data } = await this.client
27
+ .from("user_server_configs")
28
+ .select("config")
29
+ .eq("user_uuid", user_uuid)
30
+ .eq("server_name", server_name)
31
+ .maybeSingle();
32
+ if (error) {
33
+ throw error;
34
+ }
35
+ return data ? data.config : undefined;
36
+ }
37
+ async setConfigForUser(user_uuid, server_name, config) {
38
+ const payload = { user_uuid, server_name, config };
39
+ const { error } = await this.client
40
+ .from("user_server_configs")
41
+ .upsert([payload], { onConflict: "user_uuid,server_name" });
42
+ if (error) {
43
+ throw error;
44
+ }
45
+ }
46
+ }
47
+ exports.DbMcpServerConfigs = DbMcpServerConfigs;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ //
3
+ // Connection-level Client Messages
4
+ //
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * Protocol constants for the multi-session chat system
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MAX_MESSAGE_SIZE = exports.CHAT_CLOSE_CODES = exports.MESSAGE_PROCESSING_TIMEOUT = exports.CONNECTION_READY_TIMEOUT = exports.DEFAULT_USER_MESSAGE_QUEUE_SIZE = exports.DEFAULT_MESSAGE_QUEUE_SIZE = exports.SERVER_VERSION = exports.CLIENT_VERSION = exports.PROTOCOL_VERSION = exports.AGENT_VERSION = void 0;
7
+ /**
8
+ * Current version of the agent package
9
+ */
10
+ exports.AGENT_VERSION = "0.5.9";
11
+ /**
12
+ * Protocol version for WebSocket communication
13
+ */
14
+ exports.PROTOCOL_VERSION = exports.AGENT_VERSION;
15
+ /**
16
+ * Client version sent in connection handshake
17
+ */
18
+ exports.CLIENT_VERSION = exports.AGENT_VERSION;
19
+ /**
20
+ * Server version sent in connection handshake responses
21
+ */
22
+ exports.SERVER_VERSION = exports.AGENT_VERSION;
23
+ /**
24
+ * Default message queue limits
25
+ */
26
+ exports.DEFAULT_MESSAGE_QUEUE_SIZE = 100;
27
+ exports.DEFAULT_USER_MESSAGE_QUEUE_SIZE = 10;
28
+ /**
29
+ * Connection timeouts (in milliseconds)
30
+ */
31
+ exports.CONNECTION_READY_TIMEOUT = 2000; // 2 seconds
32
+ exports.MESSAGE_PROCESSING_TIMEOUT = 10000; // 10 seconds
33
+ /**
34
+ * WebSocket close codes used by the chat system
35
+ * see: https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1
36
+ */
37
+ exports.CHAT_CLOSE_CODES = {
38
+ NORMAL_CLOSURE: 1000,
39
+ GOING_AWAY: 1001,
40
+ PROTOCOL_ERROR: 1002,
41
+ INVALID_DATA: 1007,
42
+ // 4000-4999: application-defined error codes
43
+ AUTH_FAILED: 4003,
44
+ SESSION_ERROR: 4402,
45
+ RATE_LIMITED: 4403,
46
+ };
47
+ /**
48
+ * Maximum message size in bytes
49
+ */
50
+ exports.MAX_MESSAGE_SIZE = 64 * 1024; // 64KB
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserAlreadyConnected = exports.ChatErrorMessage = exports.ChatFatalError = void 0;
4
+ /**
5
+ * An error resulting the the connection being closed
6
+ */
7
+ class ChatFatalError extends Error {
8
+ }
9
+ exports.ChatFatalError = ChatFatalError;
10
+ /**
11
+ * An error to be reported, but not resulting in termination of the
12
+ * connection.
13
+ */
14
+ class ChatErrorMessage extends Error {
15
+ }
16
+ exports.ChatErrorMessage = ChatErrorMessage;
17
+ class UserAlreadyConnected extends ChatFatalError {
18
+ constructor() {
19
+ super("User already connected to the conversation");
20
+ }
21
+ }
22
+ exports.UserAlreadyConnected = UserAlreadyConnected;
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isClientControlMessage = isClientControlMessage;
4
+ exports.isServerControlMessage = isServerControlMessage;
5
+ exports.isServerSessionScopedMessage = isServerSessionScopedMessage;
6
+ exports.decodeAssistantMessageParam = decodeAssistantMessageParam;
7
+ function isClientControlMessage(message) {
8
+ const msg = message;
9
+ switch (msg.type) {
10
+ case "control_agent_profile_create":
11
+ case "control_agent_profile_delete":
12
+ case "control_get_session_list":
13
+ case "control_session_create":
14
+ case "control_session_delete":
15
+ case "control_session_join":
16
+ case "control_team_create":
17
+ case "control_add_team_user":
18
+ case "control_remove_team_user":
19
+ return true;
20
+ default: {
21
+ const _ = msg;
22
+ return false;
23
+ }
24
+ }
25
+ }
26
+ //
27
+ // Type guard helper functions
28
+ //
29
+ function isServerControlMessage(message) {
30
+ const msg = message;
31
+ switch (msg.type) {
32
+ case "control_agent_profile_created":
33
+ case "control_agent_profile_deleted":
34
+ case "control_session_list":
35
+ case "control_session_left":
36
+ case "control_session_deleted":
37
+ case "control_team_created":
38
+ case "control_error":
39
+ return true;
40
+ default: {
41
+ const _ = msg;
42
+ return false;
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Type guard to check if a ServerToClient message is a session-scoped message
48
+ */
49
+ function isServerSessionScopedMessage(message) {
50
+ const msg = message;
51
+ switch (msg.type) {
52
+ case "session_error":
53
+ case "session_info":
54
+ case "user_msg":
55
+ case "agent_msg":
56
+ case "agent_msg_chunk":
57
+ case "user_joined":
58
+ case "user_left":
59
+ case "session_update":
60
+ case "tool_auto_approval_set":
61
+ case "tool_call":
62
+ case "tool_call_approval_result":
63
+ case "tool_call_result":
64
+ case "authentication_started":
65
+ case "authentication_finished":
66
+ case "user_typing":
67
+ case "mcp_server_added":
68
+ case "mcp_server_removed":
69
+ case "mcp_server_tool_enabled":
70
+ case "mcp_server_tool_disabled":
71
+ case "system_prompt_updated":
72
+ case "model_updated":
73
+ case "user_added":
74
+ case "user_removed":
75
+ case "authenticate":
76
+ case "approve_tool_call":
77
+ case "render_html":
78
+ return true;
79
+ default: {
80
+ const _ = msg;
81
+ return false;
82
+ }
83
+ }
84
+ }
85
+ function decodeAssistantMessageParam(msg) {
86
+ let text = "";
87
+ if (msg.audio) {
88
+ throw new Error("decodeAssistantMessageParam; audio unimplemented");
89
+ }
90
+ if (msg.content) {
91
+ if (typeof msg.content === "string") {
92
+ text = msg.content;
93
+ }
94
+ else if (msg.content instanceof Array) {
95
+ for (const c of msg.content) {
96
+ switch (c.type) {
97
+ case "text":
98
+ text += c.text;
99
+ break;
100
+ case "refusal":
101
+ text += c.refusal;
102
+ break;
103
+ default:
104
+ throw Error("unexpected AssistantMessageParam.content entry");
105
+ }
106
+ }
107
+ }
108
+ }
109
+ return text;
110
+ }
@@ -0,0 +1,405 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChatContextManager = void 0;
4
+ exports.resolveConversationWithCheckpoint = resolveConversationWithCheckpoint;
5
+ const assert_1 = require("assert");
6
+ const sdk_1 = require("@xalia/xmcp/sdk");
7
+ const agent_1 = require("../../agent/agent");
8
+ const compressingContextManager_1 = require("../../agent/compressingContextManager");
9
+ const conversation_1 = require("./conversation");
10
+ const sessionFileManager_1 = require("./sessionFileManager");
11
+ const logger = (0, sdk_1.getLogger)();
12
+ /**
13
+ * TODO: Until token-tracking is in place.
14
+ */
15
+ const COMPRESSION_TRIGGER_NUM_MESSAGES = parseInt(process.env["COMPRESSION_TRIGGER_NUM_MESSAGES"] || "80", 10);
16
+ /**
17
+ * A context manager for Agents interacting with the (potentially multi-user)
18
+ * chat conversations.
19
+ *
20
+ * - Recreate an LLM context from message history + checkpoints
21
+ *
22
+ * - Maintain a compressing LLM context, and generate new checkpoints
23
+ *
24
+ * - Maintain pending user messages, agent loop messages and messages to be
25
+ * committed to the DB
26
+ */
27
+ class ChatContextManager {
28
+ constructor(systemPrompt, sessionMessages, sessionUUID, defaultUserName, checkpoint = undefined, compressionAgentUrl, compressionAgentModel, compressionAgentApiKey, checkpointWriter, fileManager) {
29
+ const nextMessageIdx = (0, conversation_1.sessionMessagesToNextIndex)(sessionMessages);
30
+ const { messages: llmMessages } = resolveConversationWithCheckpoint(sessionMessages, checkpoint);
31
+ logger.debug(`[ChatContextManager]: llm messages: ${JSON.stringify(llmMessages)}`);
32
+ // Insert a system message placeholder into the context
33
+ this.sessionUUID = sessionUUID;
34
+ this.conversationMessages = (0, conversation_1.sessionMessagesToChatMessages)(sessionMessages, defaultUserName, sessionUUID);
35
+ this.pendingUserMessages = [];
36
+ this.llmContext = new compressingContextManager_1.CompressingContextManager(systemPrompt, llmMessages, compressionAgentUrl, compressionAgentModel, compressionAgentApiKey);
37
+ this.nextMessageIdx = nextMessageIdx;
38
+ this.startingLLMContextLength = undefined;
39
+ this.curAgentMsgIdx = undefined;
40
+ this.pendingMessages = undefined;
41
+ this.pendingCompression = false;
42
+ this.checkpointWriter = checkpointWriter;
43
+ this.fileManager = fileManager;
44
+ fileManager.setEventHandler(this);
45
+ this.fileManagerDescriptionsDirty = true;
46
+ }
47
+ // IContextManager.addMessages
48
+ addMessages(messages) {
49
+ this.llmContext.addMessages(messages);
50
+ }
51
+ // IContextManager.addMessage
52
+ addMessage(message) {
53
+ this.llmContext.addMessage(message);
54
+ }
55
+ // IContextManager.getLLMContext
56
+ getLLMContext() {
57
+ if (this.fileManagerDescriptionsDirty) {
58
+ const filesSummary = (0, sessionFileManager_1.listFilesForLLM)(this.fileManager);
59
+ logger.debug(`[ChatContextManager] filemanager summary:\n${filesSummary}`);
60
+ this.llmContext.setPromptFragment("file_manager", filesSummary);
61
+ this.fileManagerDescriptionsDirty = false;
62
+ }
63
+ return this.llmContext.getLLMContext();
64
+ }
65
+ // IContextManager.getAgentPrompt
66
+ getAgentPrompt() {
67
+ return this.llmContext.getAgentPrompt();
68
+ }
69
+ // IContextManager.setAgentPrompt
70
+ setAgentPrompt(prompt) {
71
+ this.llmContext.setAgentPrompt(prompt);
72
+ }
73
+ // IContextManager.setPromptFragment
74
+ setPromptFragment(fragmentID, prompt) {
75
+ this.llmContext.setPromptFragment(fragmentID, prompt);
76
+ }
77
+ // IContextManager.removePromptFragment
78
+ removePromptFragment(fragmentID) {
79
+ this.llmContext.removePromptFragment(fragmentID);
80
+ }
81
+ // ISessionFileManagerEventHandler.onFileDescriptorChange
82
+ onFileDescriptorChange(_desc) {
83
+ this.fileManagerDescriptionsDirty = true;
84
+ }
85
+ // ISessionFileManagerEventHandler.onFileChange
86
+ onFileChange(_entry) {
87
+ this.fileManagerDescriptionsDirty = true;
88
+ }
89
+ setWorkspace(userMessage) {
90
+ this.llmContext.setWorkspace(userMessage);
91
+ }
92
+ getWorkspace() {
93
+ return this.llmContext.getWorkspace();
94
+ }
95
+ // Get the conversation (to send to clients)
96
+ getConversationMessages() {
97
+ return this.conversationMessages.concat(this.pendingUserMessages);
98
+ }
99
+ processUserMessage(msg, from) {
100
+ // TODO: maintain a queue internally instead of relying on the caller to
101
+ // pass in our generated messages back into `startAgentResponse`.
102
+ const message_idx = this.getNextMessageIdx();
103
+ const userMessage = {
104
+ type: "user_msg",
105
+ session_id: this.sessionUUID,
106
+ message_idx,
107
+ message: msg.message,
108
+ user_uuid: from,
109
+ };
110
+ if (msg.imageB64) {
111
+ userMessage.imageB64 = msg.imageB64;
112
+ }
113
+ this.pendingUserMessages.push(userMessage);
114
+ return userMessage;
115
+ }
116
+ unprocessUserMessage(userMsg) {
117
+ // TODO: when we maintain a queue, remove the entry from the queue.
118
+ this.freeMessageIdx(userMsg.message_idx);
119
+ }
120
+ // TODO: Don't take the set of messages. Instead, have OpenSession or this
121
+ // class manage the interaction with the agent and ensure this process only
122
+ // happens one-at-a-time.
123
+ startAgentResponse(msgs) {
124
+ // Sanity check the state
125
+ (0, assert_1.strict)(typeof this.startingLLMContextLength === "undefined", "already processing");
126
+ (0, assert_1.strict)(typeof this.pendingMessages === "undefined", "already processing");
127
+ (0, assert_1.strict)(typeof this.curAgentMsgIdx === "undefined", "already processing");
128
+ // Sanity check the messages
129
+ const numMessages = this.pendingUserMessages.length;
130
+ (0, assert_1.strict)(numMessages > 0);
131
+ (0, assert_1.strict)(msgs.length === this.pendingUserMessages.length);
132
+ (0, assert_1.strict)(msgs[0].message_idx === this.pendingUserMessages[0].message_idx);
133
+ (0, assert_1.strict)(msgs[numMessages - 1].message_idx ===
134
+ this.pendingUserMessages[numMessages - 1].message_idx);
135
+ // Collect the pending user messages and allocate a starting index for
136
+ // agent messages and tool calls.
137
+ const pendingUserMessages = this.pendingUserMessages;
138
+ this.pendingUserMessages = [];
139
+ this.startingLLMContextLength = this.llmContext.getCommittedLength();
140
+ this.curAgentMsgIdx = this.getNextMessageIdx();
141
+ this.pendingMessages = pendingUserMessages;
142
+ // Compute the new llm messages
143
+ const llmUserMessages = [];
144
+ for (const msg of pendingUserMessages) {
145
+ const userMsg = (0, agent_1.createUserMessage)(msg.message, msg.imageB64, msg.user_uuid);
146
+ if (userMsg) {
147
+ llmUserMessages.push(userMsg);
148
+ }
149
+ }
150
+ return {
151
+ llmUserMessages,
152
+ agentFirstChunk: {
153
+ type: "agent_msg_chunk",
154
+ session_id: this.sessionUUID,
155
+ message_idx: this.curAgentMsgIdx,
156
+ message: "",
157
+ end: false,
158
+ },
159
+ };
160
+ }
161
+ endAgentResponse() {
162
+ (0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
163
+ (0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
164
+ (0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
165
+ const numPending = this.pendingMessages.length;
166
+ (0, assert_1.strict)(numPending > 1); // 1 user + 1 agent message
167
+ // Compute DB messages
168
+ const newSessionMessages = (0, conversation_1.chatMessagesToSessionMessages)(this.pendingMessages);
169
+ const newLLMMessages = this.llmContext.getPending();
170
+ const messageListError = (error) => {
171
+ throw new Error(`${error}:` +
172
+ `\n newSessionMessages: ${JSON.stringify(newSessionMessages)}` +
173
+ `\n this.pendingMessages: ${JSON.stringify(this.pendingMessages)}` +
174
+ `\n newLLMMessages: ${JSON.stringify(newLLMMessages)}`);
175
+ };
176
+ if (newSessionMessages.length !== numPending) {
177
+ messageListError("newSessionMessages.length !== numPending");
178
+ }
179
+ if (newLLMMessages.length !== numPending) {
180
+ messageListError("newLLMMessages.length !== numPending");
181
+ }
182
+ // The SessionMessages should satisfy:
183
+ // - sMsg.message_idx === pMsg.message_idx
184
+ // - sMsg.content === llmMsg
185
+ // this ensures all representations are aligned.
186
+ for (let i = 0; i < numPending; ++i) {
187
+ const sMsg = newSessionMessages[i];
188
+ const pMsg = this.pendingMessages[i];
189
+ const lMsg = newLLMMessages[i];
190
+ if (sMsg.content.role !== lMsg.role) {
191
+ messageListError(`newSessionMessages[${String(i)}].role !== ` +
192
+ `newLLMMessages[${String(i)}].role`);
193
+ }
194
+ if (JSON.stringify(sMsg.content) !== JSON.stringify(lMsg)) {
195
+ messageListError(`newSessionMessages[${String(i)}].content !== ` +
196
+ `newLLMMessages[${String(i)}].content`);
197
+ }
198
+ if (sMsg.message_idx !== pMsg.message_idx) {
199
+ messageListError(`newSessionMessages[${String(i)}].message_idx !== ` +
200
+ `pendingMessages[${String(i)}].message_idx`);
201
+ }
202
+ }
203
+ // Update our internal state and return the SessionMessages to write to
204
+ // the DB
205
+ this.llmContext.commit();
206
+ this.conversationMessages.push(...this.pendingMessages);
207
+ this.startingLLMContextLength = undefined;
208
+ this.pendingMessages = undefined;
209
+ this.curAgentMsgIdx = undefined;
210
+ // Kick off a compression?
211
+ this.checkCompression();
212
+ return newSessionMessages;
213
+ }
214
+ /**
215
+ * End the Agent message session with an error. Caller should not call
216
+ * `endAgentResponse` after calling this function.
217
+ *
218
+ * This function checks that nothing has been entered into the LLM context,
219
+ * and drops any new user messages or responses before the error.
220
+ */
221
+ revertAgentResponse(errMsg) {
222
+ logger.warn(`[ChatContextManager.revertAgentResponse] error: ${errMsg}`);
223
+ (0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
224
+ (0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
225
+ (0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
226
+ // Sanity check that no new messages were put into the context (The Agent
227
+ // is expected to only call `addMessage(s)` at the end of the Agent loop.
228
+ // (Note, we don't check for equality here, just in case the context was
229
+ // compressed while the Agent was executing).
230
+ const contextLength = this.llmContext.getCommittedLength();
231
+ if (contextLength > this.startingLLMContextLength) {
232
+ logger.error("[ChatContextManager.revertAgentResponse] llmContext has grown " +
233
+ `despite Agent error (${String(contextLength)}, ` +
234
+ `${String(this.startingLLMContextLength)})`);
235
+ }
236
+ // We simply reset the state, dropping any pending messages.
237
+ this.startingLLMContextLength = undefined;
238
+ this.pendingMessages = undefined;
239
+ this.curAgentMsgIdx = undefined;
240
+ return {
241
+ type: "session_error",
242
+ session_id: this.sessionUUID,
243
+ message: errMsg,
244
+ };
245
+ }
246
+ processAgentMessage(msg, end) {
247
+ (0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
248
+ (0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
249
+ (0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
250
+ const message = {
251
+ type: "agent_msg_chunk",
252
+ session_id: this.sessionUUID,
253
+ message_idx: this.getCurrentAgentMessageIdx(),
254
+ message: msg,
255
+ end,
256
+ };
257
+ return message;
258
+ }
259
+ /**
260
+ * Process a FULL Agent message (not chunks from stream). No message is
261
+ * required for broadcast as the calling code is expected to broadcast this
262
+ * as chunks.
263
+ */
264
+ processAgentResponse(result) {
265
+ (0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
266
+ (0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
267
+ (0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
268
+ // Insert this (full) agent response into the list of agent messages
269
+ const msg = {
270
+ type: "agent_msg",
271
+ session_id: this.sessionUUID,
272
+ message_idx: this.getNextMessageSubIdx(),
273
+ message: result,
274
+ };
275
+ this.pendingMessages.push(msg);
276
+ }
277
+ processToolCallResult(result) {
278
+ (0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
279
+ (0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
280
+ (0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
281
+ // Allocate the sub-index for this tool call result. It should not
282
+ // have been used already.
283
+ const message_idx = this.getNextMessageSubIdx();
284
+ const numPending = this.pendingMessages.length;
285
+ (0, assert_1.strict)(numPending > 0);
286
+ (0, assert_1.strict)(this.pendingMessages[numPending - 1].message_idx < message_idx);
287
+ const msg = {
288
+ type: "tool_call_result",
289
+ session_id: this.sessionUUID,
290
+ message_idx,
291
+ result,
292
+ };
293
+ this.pendingMessages.push(msg);
294
+ return msg;
295
+ }
296
+ getNextMessageIdx() {
297
+ const idx = this.nextMessageIdx;
298
+ this.nextMessageIdx += conversation_1.MESSAGE_INDEX_FULL_INCREMENT;
299
+ return idx;
300
+ }
301
+ /**
302
+ * Only reuse it if no other indices have been allocated. It's the callers
303
+ * responsibility to ensure this.
304
+ */
305
+ freeMessageIdx(messageIdx) {
306
+ (0, assert_1.strict)(messageIdx === this.nextMessageIdx - conversation_1.MESSAGE_INDEX_FULL_INCREMENT, "message idx cannot be free-ed");
307
+ this.nextMessageIdx = messageIdx;
308
+ }
309
+ /// Get the current index to use for streaming Agent chunks
310
+ getCurrentAgentMessageIdx() {
311
+ (0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
312
+ (0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
313
+ return this.curAgentMsgIdx;
314
+ }
315
+ getNextMessageSubIdx() {
316
+ (0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
317
+ (0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
318
+ const idx = this.curAgentMsgIdx;
319
+ this.curAgentMsgIdx += conversation_1.MESSAGE_INDEX_SUB_INCREMENT;
320
+ return idx;
321
+ }
322
+ checkCompression() {
323
+ if (this.pendingCompression) {
324
+ return;
325
+ }
326
+ // TODO: track tokens and use that to trigger compression
327
+ const numCommitted = this.llmContext.getCommittedLength();
328
+ if (numCommitted < COMPRESSION_TRIGGER_NUM_MESSAGES) {
329
+ return;
330
+ }
331
+ void this.runCompression();
332
+ }
333
+ async runCompression() {
334
+ (0, assert_1.strict)(!this.pendingCompression);
335
+ this.pendingCompression = true;
336
+ const checkpointIndex = this.conversationMessages[this.conversationMessages.length - 1]
337
+ .message_idx;
338
+ try {
339
+ const summary = await this.llmContext.compress();
340
+ const checkpoint = {
341
+ message_idx: checkpointIndex,
342
+ summary,
343
+ };
344
+ await this.checkpointWriter.writeCheckpoint(checkpoint);
345
+ }
346
+ catch (err) {
347
+ logger.warn(`[runCompression] error during compression: ${JSON.stringify(err)}`);
348
+ }
349
+ finally {
350
+ this.pendingCompression = false;
351
+ }
352
+ }
353
+ }
354
+ exports.ChatContextManager = ChatContextManager;
355
+ function resolveConversationWithCheckpoint(messages, checkpoint) {
356
+ const numMessages = messages.length;
357
+ let lastEntryIdx = 0;
358
+ if (numMessages !== 0) {
359
+ lastEntryIdx = messages[numMessages - 1].message_idx;
360
+ }
361
+ // Only keep the messages we care about
362
+ messages = messages.filter((msg) => msg.is_for_llm);
363
+ // If no checkpoint, return all messages
364
+ if (!checkpoint) {
365
+ return {
366
+ messages: messages.map((msg) => msg.content),
367
+ lastEntryIdx,
368
+ };
369
+ }
370
+ // Find the first entry in messages s.t. entry.message_idx >
371
+ // checkpoint.message_idx
372
+ const checkpointIdx = checkpoint.message_idx;
373
+ const keepIdx = (() => {
374
+ let idx = numMessages - 1;
375
+ while (idx >= 0) {
376
+ if (messages[idx].message_idx <= checkpointIdx) {
377
+ break;
378
+ }
379
+ idx--;
380
+ }
381
+ // Idx points to the first entry (from the end) with message_idx <=
382
+ // checkpointIdx, so the first entry with message_idx > checkpointIdx is
383
+ // idx+1 and We can then extract the required messages with
384
+ //
385
+ // message.slice(idx+1)
386
+ //
387
+ // Edge-cases:
388
+ //
389
+ // - loop exhausted, idx = -1, we want slice(0 = idx+1) (all messages)
390
+ //
391
+ // - loop exited on first entry (all entries are contained in the
392
+ // checkpoint), idx = numMessages - 1, we want
393
+ // slice(numMessages = idx + 1)
394
+ return idx + 1;
395
+ })();
396
+ const checkpointMessage = (0, compressingContextManager_1.createCheckpointMessage)(checkpoint.summary);
397
+ const llmMessages = [
398
+ checkpointMessage,
399
+ ...messages.slice(keepIdx).map((msg) => msg.content),
400
+ ];
401
+ return {
402
+ messages: llmMessages,
403
+ lastEntryIdx: Math.max(lastEntryIdx, checkpoint.message_idx),
404
+ };
405
+ }