@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.
- package/README.md +23 -8
- package/dist/agent/src/agent/agent.js +173 -96
- package/dist/agent/src/agent/agentUtils.js +82 -53
- package/dist/agent/src/agent/compressingContextManager.js +102 -0
- package/dist/agent/src/agent/context.js +189 -0
- package/dist/agent/src/agent/dummyLLM.js +46 -5
- package/dist/agent/src/agent/iAgentEventHandler.js +2 -0
- package/dist/agent/src/agent/mcpServerManager.js +22 -23
- package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
- package/dist/agent/src/agent/nullPlatform.js +14 -0
- package/dist/agent/src/agent/openAILLMStreaming.js +12 -7
- package/dist/agent/src/agent/promptProvider.js +63 -0
- package/dist/agent/src/agent/repeatLLM.js +5 -5
- package/dist/agent/src/agent/sudoMcpServerManager.js +11 -9
- package/dist/agent/src/agent/tokenAuth.js +7 -7
- package/dist/agent/src/agent/tools.js +1 -1
- package/dist/agent/src/chat/client/chatClient.js +733 -0
- package/dist/agent/src/chat/client/connection.js +209 -0
- package/dist/agent/src/chat/client/connection.test.js +188 -0
- package/dist/agent/src/chat/client/constants.js +5 -0
- package/dist/agent/src/chat/client/index.js +15 -0
- package/dist/agent/src/chat/client/interfaces.js +2 -0
- package/dist/agent/src/chat/client/responseHandler.js +105 -0
- package/dist/agent/src/chat/client/sessionClient.js +331 -0
- package/dist/agent/src/chat/client/teamManager.js +2 -0
- package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
- package/dist/agent/src/chat/data/dataModels.js +2 -0
- package/dist/agent/src/chat/data/database.js +749 -0
- package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
- package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
- package/dist/agent/src/chat/protocol/constants.js +50 -0
- package/dist/agent/src/chat/protocol/errors.js +22 -0
- package/dist/agent/src/chat/protocol/messages.js +110 -0
- package/dist/agent/src/chat/server/chatContextManager.js +405 -0
- package/dist/agent/src/chat/server/connectionManager.js +352 -0
- package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
- package/dist/agent/src/chat/server/conversation.js +198 -0
- package/dist/agent/src/chat/server/errorUtils.js +23 -0
- package/dist/agent/src/chat/server/openSession.js +869 -0
- package/dist/agent/src/chat/server/server.js +177 -0
- package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
- package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
- package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
- package/dist/agent/src/chat/server/tools.js +243 -0
- package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
- package/dist/agent/src/chat/utils/approvalManager.js +85 -0
- package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
- package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
- package/dist/agent/src/chat/utils/htmlToText.js +84 -0
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
- package/dist/agent/src/chat/utils/search.js +145 -0
- package/dist/agent/src/chat/utils/userResolver.js +46 -0
- package/dist/agent/src/chat/{websocket.js → utils/websocket.js} +2 -0
- package/dist/agent/src/test/agent.test.js +332 -0
- package/dist/agent/src/test/approvalManager.test.js +58 -0
- package/dist/agent/src/test/chatContextManager.test.js +392 -0
- package/dist/agent/src/test/clientServerConnection.test.js +158 -0
- package/dist/agent/src/test/compressingContextManager.test.js +65 -0
- package/dist/agent/src/test/context.test.js +83 -0
- package/dist/agent/src/test/conversation.test.js +89 -0
- package/dist/agent/src/test/db.test.js +262 -90
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
- package/dist/agent/src/test/dbTestTools.js +99 -0
- package/dist/agent/src/test/imageLoad.test.js +8 -7
- package/dist/agent/src/test/mcpServerManager.test.js +21 -18
- package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
- package/dist/agent/src/test/openaiStreaming.test.js +12 -11
- package/dist/agent/src/test/prompt.test.js +5 -4
- package/dist/agent/src/test/promptProvider.test.js +28 -0
- package/dist/agent/src/test/responseHandler.test.js +61 -0
- package/dist/agent/src/test/sudoMcpServerManager.test.js +14 -12
- package/dist/agent/src/test/testTools.js +109 -0
- package/dist/agent/src/test/tools.test.js +31 -0
- package/dist/agent/src/tool/agentChat.js +21 -10
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +235 -58
- package/dist/agent/src/tool/commandPrompt.js +15 -9
- package/dist/agent/src/tool/files.js +20 -16
- package/dist/agent/src/tool/nodePlatform.js +47 -3
- package/dist/agent/src/tool/options.js +4 -4
- package/dist/agent/src/tool/prompt.js +19 -13
- package/eslint.config.mjs +14 -1
- package/package.json +14 -6
- package/scripts/chat_server +8 -0
- package/scripts/setup_chat +7 -2
- package/scripts/shutdown_chat_server +3 -0
- package/scripts/test_chat +135 -17
- package/src/agent/agent.ts +270 -135
- package/src/agent/agentUtils.ts +136 -95
- package/src/agent/compressingContextManager.ts +164 -0
- package/src/agent/context.ts +268 -0
- package/src/agent/dummyLLM.ts +76 -8
- package/src/agent/iAgentEventHandler.ts +54 -0
- package/src/agent/iplatform.ts +1 -0
- package/src/agent/mcpServerManager.ts +32 -30
- package/src/agent/nullAgentEventHandler.ts +20 -0
- package/src/agent/nullPlatform.ts +13 -0
- package/src/agent/openAILLMStreaming.ts +12 -6
- package/src/agent/promptProvider.ts +87 -0
- package/src/agent/repeatLLM.ts +5 -5
- package/src/agent/sudoMcpServerManager.ts +13 -11
- package/src/agent/tokenAuth.ts +7 -7
- package/src/agent/tools.ts +3 -1
- package/src/chat/client/chatClient.ts +900 -0
- package/src/chat/client/connection.test.ts +241 -0
- package/src/chat/client/connection.ts +276 -0
- package/src/chat/client/constants.ts +3 -0
- package/src/chat/client/index.ts +18 -0
- package/src/chat/client/interfaces.ts +34 -0
- package/src/chat/client/responseHandler.ts +131 -0
- package/src/chat/client/sessionClient.ts +443 -0
- package/src/chat/client/teamManager.ts +29 -0
- package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
- package/src/chat/data/dataModels.ts +85 -0
- package/src/chat/data/database.ts +982 -0
- package/src/chat/data/dbMcpServerConfigs.ts +59 -0
- package/src/chat/protocol/connectionMessages.ts +49 -0
- package/src/chat/protocol/constants.ts +55 -0
- package/src/chat/protocol/errors.ts +16 -0
- package/src/chat/protocol/messages.ts +682 -0
- package/src/chat/server/README.md +127 -0
- package/src/chat/server/chatContextManager.ts +612 -0
- package/src/chat/server/connectionManager.test.ts +266 -0
- package/src/chat/server/connectionManager.ts +541 -0
- package/src/chat/server/conversation.ts +269 -0
- package/src/chat/server/errorUtils.ts +28 -0
- package/src/chat/server/openSession.ts +1332 -0
- package/src/chat/server/server.ts +177 -0
- package/src/chat/server/sessionFileManager.ts +239 -0
- package/src/chat/server/sessionRegistry.test.ts +138 -0
- package/src/chat/server/sessionRegistry.ts +1064 -0
- package/src/chat/server/test-utils/mockFactories.ts +422 -0
- package/src/chat/server/tools.ts +265 -0
- package/src/chat/utils/agentSessionMap.ts +76 -0
- package/src/chat/utils/approvalManager.ts +111 -0
- package/src/{utils → chat/utils}/asyncLock.ts +3 -3
- package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
- package/src/chat/utils/htmlToText.ts +61 -0
- package/src/chat/utils/multiAsyncQueue.ts +52 -0
- package/src/chat/utils/search.ts +139 -0
- package/src/chat/utils/userResolver.ts +48 -0
- package/src/chat/{websocket.ts → utils/websocket.ts} +2 -0
- package/src/test/agent.test.ts +487 -0
- package/src/test/approvalManager.test.ts +73 -0
- package/src/test/chatContextManager.test.ts +521 -0
- package/src/test/clientServerConnection.test.ts +207 -0
- package/src/test/compressingContextManager.test.ts +82 -0
- package/src/test/context.test.ts +105 -0
- package/src/test/conversation.test.ts +109 -0
- package/src/test/db.test.ts +351 -103
- package/src/test/dbMcpServerConfigs.test.ts +112 -0
- package/src/test/dbTestTools.ts +153 -0
- package/src/test/imageLoad.test.ts +7 -6
- package/src/test/mcpServerManager.test.ts +19 -14
- package/src/test/multiAsyncQueue.test.ts +125 -0
- package/src/test/openaiStreaming.test.ts +11 -10
- package/src/test/prompt.test.ts +4 -3
- package/src/test/promptProvider.test.ts +33 -0
- package/src/test/responseHandler.test.ts +78 -0
- package/src/test/sudoMcpServerManager.test.ts +22 -15
- package/src/test/testTools.ts +146 -0
- package/src/test/tools.test.ts +39 -0
- package/src/tool/agentChat.ts +26 -12
- package/src/tool/agentMain.ts +1 -1
- package/src/tool/chatMain.ts +283 -100
- package/src/tool/commandPrompt.ts +25 -9
- package/src/tool/files.ts +25 -19
- package/src/tool/nodePlatform.ts +52 -3
- package/src/tool/options.ts +4 -2
- package/src/tool/prompt.ts +22 -15
- package/test_data/dummyllm_script_crash.json +32 -0
- package/test_data/frog.png.b64 +1 -0
- package/vitest.config.ts +39 -0
- package/dist/agent/src/chat/client.js +0 -310
- package/dist/agent/src/chat/conversationManager.js +0 -502
- package/dist/agent/src/chat/db.js +0 -218
- package/dist/agent/src/chat/messages.js +0 -29
- package/dist/agent/src/chat/server.js +0 -158
- package/src/chat/client.ts +0 -445
- package/src/chat/conversationManager.ts +0 -730
- package/src/chat/db.ts +0 -304
- package/src/chat/messages.ts +0 -266
- package/src/chat/server.ts +0 -177
- /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,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
|
+
}
|