@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,207 @@
1
+ import { describe, it, expect, beforeAll, afterAll, vi } from "vitest";
2
+ import { Server } from "ws";
3
+ import { Connection } from "../chat/client/connection";
4
+ import { runServer } from "../chat/server/server";
5
+ import { Database } from "../chat/data/database";
6
+ import { getLogger } from "@xalia/xmcp/sdk";
7
+ import { SUPABASE_LOCAL_KEY, SUPABASE_LOCAL_URL } from "../chat/data/database";
8
+ import {
9
+ ClientControlGetSessionList,
10
+ ServerControlSessionList,
11
+ } from "../chat/protocol/messages";
12
+ import { userSessionDataCreate } from "../chat/server/sessionRegistry";
13
+
14
+ // Mock uuid to have predictable client message IDs
15
+ vi.mock("uuid", () => ({
16
+ v4: () => "mock-uuid-123",
17
+ }));
18
+
19
+ const logger = getLogger();
20
+
21
+ describe("Client-Server WebSocket Integration", () => {
22
+ let server: Server;
23
+ let db: Database;
24
+ const serverPort = 5105; // Different port to avoid conflicts
25
+ // Test API key data
26
+ const testApiKey = "integration_test";
27
+ const testUserUuid = "test_user_0";
28
+ const testUserName = "default";
29
+ const testScopes: string[] = [];
30
+ // Mock environment variables for server
31
+ const mockEnv = {
32
+ SUPABASE_URL: SUPABASE_LOCAL_URL,
33
+ SUPABASE_KEY: SUPABASE_LOCAL_KEY,
34
+ LLM_URL: "http://localhost:8080", // not used in this test
35
+ XMCP_URL: "http://localhost:8081", // not used in this test
36
+ };
37
+ beforeAll(async () => {
38
+ // Set up environment variables
39
+ Object.assign(process.env, mockEnv);
40
+ // Initialize database connection
41
+ db = new Database(mockEnv.SUPABASE_URL, mockEnv.SUPABASE_KEY);
42
+ // Try to insert test data, but don't fail if there are database issues
43
+ try {
44
+ await cleanupTestData();
45
+ await insertTestApiKey();
46
+ logger.info("[Test] Test data setup completed successfully");
47
+ } catch (error) {
48
+ logger.warn(
49
+ "[Test] Test data setup failed - will test with existing data:",
50
+ error
51
+ );
52
+ }
53
+ // Start the real server using runServer
54
+ server = await runServer(
55
+ serverPort,
56
+ mockEnv.SUPABASE_URL,
57
+ mockEnv.SUPABASE_KEY,
58
+ mockEnv.LLM_URL,
59
+ mockEnv.XMCP_URL
60
+ );
61
+ // Wait for server to be ready
62
+ await new Promise((resolve) => setTimeout(resolve, 500));
63
+ });
64
+ async function cleanupTestData() {
65
+ try {
66
+ // Delete test API key first (foreign key constraint)
67
+ await db
68
+ .getClientForTesting()
69
+ .from("api_keys")
70
+ .delete()
71
+ .eq("api_key", testApiKey);
72
+ // Delete test user
73
+ await db
74
+ .getClientForTesting()
75
+ .from("users")
76
+ .delete()
77
+ .eq("uuid", testUserUuid);
78
+ logger.info("[Test] Cleaned up existing test data");
79
+ } catch (error) {
80
+ logger.warn(
81
+ "[Test] Error during cleanup (expected if no data exists):",
82
+ error
83
+ );
84
+ }
85
+ }
86
+ async function insertTestApiKey() {
87
+ try {
88
+ // Use Database class methods which include timezone handling
89
+ await db.createUser(
90
+ testUserUuid,
91
+ "test@example.com",
92
+ testUserName,
93
+ "UTC"
94
+ );
95
+ logger.info("[Test] User created successfully");
96
+ await db.addApiKey(
97
+ testUserUuid,
98
+ testApiKey,
99
+ testUserName,
100
+ testScopes,
101
+ true // is_default
102
+ );
103
+ } catch (error) {
104
+ logger.error("[Test] Database method insert failed:", error);
105
+ throw error;
106
+ }
107
+ }
108
+ afterAll(async () => {
109
+ // Clean up test data
110
+ await cleanupTestData();
111
+ // Close server
112
+ server.close();
113
+ });
114
+ describe("Connection Setup and List Sessions", () => {
115
+ it("should establish connection with valid API key", async () => {
116
+ // Inject test agent profiles and sessions into the database
117
+ const agentProfile1 = await db.createAgentProfile(
118
+ testUserUuid,
119
+ undefined,
120
+ "Test agent profile 1",
121
+ {
122
+ model: undefined,
123
+ system_prompt: "You are a test agent",
124
+ mcp_settings: {},
125
+ }
126
+ );
127
+ const agentProfile2 = await db.createAgentProfile(
128
+ testUserUuid,
129
+ undefined,
130
+ "Test agent profile 2",
131
+ {
132
+ model: undefined,
133
+ system_prompt: "You are a test agent",
134
+ mcp_settings: {},
135
+ }
136
+ );
137
+ if (!agentProfile1 || !agentProfile2) {
138
+ throw new Error("Failed to create agent profiles");
139
+ }
140
+ const agentProfile1Id = agentProfile1.uuid;
141
+ const agentProfile2Id = agentProfile2.uuid;
142
+ const session1CreateData = userSessionDataCreate(
143
+ testUserUuid,
144
+ "Test Session 1",
145
+ agentProfile1Id
146
+ );
147
+ const session1Id = session1CreateData.session_uuid;
148
+ await db.sessionCreate(session1CreateData);
149
+ const session2CreateData = userSessionDataCreate(
150
+ testUserUuid,
151
+ "Test Session 2",
152
+ agentProfile2Id
153
+ );
154
+ const session2Id = session2CreateData.session_uuid;
155
+ await db.sessionCreate(session2CreateData);
156
+ // Log session creation for debugging
157
+ logger.info(`[Test] Created sessions: ${session1Id}, ${session2Id}`);
158
+ const client = new Connection({
159
+ url: `ws://localhost:${String(serverPort)}`,
160
+ token: testApiKey,
161
+ });
162
+ // Wait for session_list response
163
+ const responsePromise = new Promise<ServerControlSessionList>(
164
+ (resolve) => {
165
+ client.on("control_session_list", (msg) => {
166
+ resolve(msg as ServerControlSessionList);
167
+ });
168
+ }
169
+ );
170
+ await client.connect();
171
+ expect(client.getState()).toBe("ready");
172
+ client.send({
173
+ type: "control_get_session_list",
174
+ client_message_id: "test-msg-id",
175
+ } as ClientControlGetSessionList);
176
+ const response = await responsePromise;
177
+ expect(response.type).toBe("control_session_list");
178
+ expect(response.user_sessions).toBeDefined();
179
+ expect(response.user_sessions.length).toBe(2);
180
+ // Verify session details
181
+ const sessionIds = response.user_sessions.map((s) => s.session_uuid);
182
+ expect(sessionIds).toContain(session1Id);
183
+ expect(sessionIds).toContain(session2Id);
184
+ const session1Data = response.user_sessions.find(
185
+ (s) => s.session_uuid === session1Id
186
+ );
187
+ expect(session1Data?.title).toBe("Test Session 1");
188
+ const session2Data = response.user_sessions.find(
189
+ (s) => s.session_uuid === session2Id
190
+ );
191
+ expect(session2Data?.title).toBe("Test Session 2");
192
+ client.close();
193
+ }, 15000);
194
+ });
195
+ describe("Authentication Error Handling", () => {
196
+ it("should handle invalid API key gracefully", async () => {
197
+ const invalidApiKey = "invalid-test-key";
198
+ const client = new Connection({
199
+ url: `ws://localhost:${String(serverPort)}`,
200
+ token: invalidApiKey,
201
+ });
202
+ await expect(client.connect()).rejects.toThrow();
203
+ expect(client.getState()).toBe("closed");
204
+ client.close();
205
+ }, 4000);
206
+ });
207
+ });
@@ -0,0 +1,82 @@
1
+ import { expect } from "vitest";
2
+
3
+ import {
4
+ CompressingContextManager,
5
+ createCheckpointMessage,
6
+ } from "../agent/compressingContextManager";
7
+ import { ChatCompletionMessageParam } from "../agent/agent";
8
+
9
+ const MESSAGES: ChatCompletionMessageParam[] = [
10
+ { role: "user", content: "msg200" },
11
+ { role: "assistant", content: "msg300" },
12
+ { role: "user", content: "msg400" },
13
+ {
14
+ role: "assistant",
15
+ content: "msg500",
16
+ tool_calls: [
17
+ {
18
+ id: "tool_call_0",
19
+ type: "function",
20
+ function: { name: "tool1", arguments: "" },
21
+ },
22
+ ],
23
+ },
24
+ {
25
+ role: "tool",
26
+ content: "msg501",
27
+ tool_call_id: "tool_call_0",
28
+ },
29
+ { role: "assistant", content: "msg502" },
30
+ ];
31
+
32
+ describe("Compression context", () => {
33
+ it("context compression", async function () {
34
+ const ccm = new CompressingContextManager(
35
+ "sys_prompt",
36
+ [],
37
+ "", // llmUrl
38
+ "repeat", // llmModel
39
+ "" // llmApiKey
40
+ );
41
+
42
+ // Add first 2 messages
43
+ ccm.addMessages(MESSAGES.slice(0, 2));
44
+ ccm.commit();
45
+
46
+ // Start the compression
47
+ const compressionP = ccm.compress();
48
+
49
+ // Add more messages and update system prompt
50
+ ccm.addMessages(MESSAGES.slice(2));
51
+ ccm.setAgentPrompt("New system prompt");
52
+
53
+ // Wait for compression to complete, then get the new context
54
+ const summary = await compressionP;
55
+ const context = ccm.getLLMContext();
56
+
57
+ // Expect
58
+ // - callback happened
59
+ // - new context is as expected
60
+
61
+ const expectSummary = "Message number 0";
62
+ const expectContext: ChatCompletionMessageParam[] = [
63
+ {
64
+ role: "system",
65
+ content: "New system prompt",
66
+ },
67
+ createCheckpointMessage(expectSummary),
68
+ MESSAGES[2], // 400
69
+ MESSAGES[3], // 500
70
+ MESSAGES[4], // 501
71
+ MESSAGES[5], // 502
72
+ ];
73
+
74
+ expect(summary).eql(expectSummary);
75
+ expect(context).eql(expectContext);
76
+
77
+ // Pending messages shoudl still be intact
78
+
79
+ const pending = ccm.getPending();
80
+ expect(pending).eql(MESSAGES.slice(2));
81
+ });
82
+ });
@@ -0,0 +1,105 @@
1
+ import { expect } from "vitest";
2
+ import { strict as assert } from "assert";
3
+
4
+ import { ContextManagerWithWorkspace } from "../agent/context";
5
+ import { ChatCompletionMessageParam, createUserMessage } from "../agent/agent";
6
+ import { SystemPromptProvider } from "../agent/promptProvider";
7
+
8
+ describe("ContextManagerWithWorkspace", () => {
9
+ const msgs: ChatCompletionMessageParam[] = [
10
+ { role: "user", content: "message A" },
11
+ { role: "assistant", content: "message B" },
12
+ { role: "user", content: "message C" },
13
+ { role: "assistant", content: "message D" },
14
+ ];
15
+
16
+ it("should correctly manage messages", function () {
17
+ const cm = new ContextManagerWithWorkspace("sys_msg", msgs.slice(0, 2));
18
+
19
+ // set workspace and add new messages
20
+ cm.setWorkspace({ role: "user", content: "workspace1" });
21
+ cm.addMessages(msgs.slice(2));
22
+
23
+ // context with workspace
24
+ const ctx1 = cm.getLLMContext();
25
+ expect(ctx1).eql([
26
+ { role: "system", content: "sys_msg" },
27
+ ...msgs,
28
+ { role: "user", content: "workspace1" },
29
+ ]);
30
+
31
+ // context with new message and workspace
32
+ const lastMsg: ChatCompletionMessageParam = {
33
+ role: "user",
34
+ content: "hello",
35
+ };
36
+ cm.addMessage(lastMsg);
37
+ const ctx2 = cm.getLLMContext();
38
+ expect(ctx2).eql([
39
+ { role: "system", content: "sys_msg" },
40
+ ...msgs,
41
+ lastMsg,
42
+ { role: "user", content: "workspace1" },
43
+ ]);
44
+
45
+ // context without workspace
46
+ cm.setWorkspace(undefined);
47
+ const ctx3 = cm.getLLMContext();
48
+ expect(ctx3).eql([
49
+ { role: "system", content: "sys_msg" },
50
+ ...msgs,
51
+ lastMsg,
52
+ ]);
53
+ });
54
+
55
+ it("correctly includes all system prompt components", function () {
56
+ const cm = new ContextManagerWithWorkspace(
57
+ "agent_prompt",
58
+ msgs.slice(0, 2)
59
+ );
60
+ SystemPromptProvider.setGlobalPrompt("global");
61
+ cm.setPromptFragment("frag1", "FRAG1");
62
+
63
+ // Get the agentPrompt and llmContext, then undo the global prompt change
64
+ // (to ensure other tests are affected, even if this one fails).
65
+
66
+ const agentPrompt = cm.getAgentPrompt();
67
+ const llmContext = cm.getLLMContext();
68
+ SystemPromptProvider.setGlobalPrompt(undefined);
69
+
70
+ expect(agentPrompt).eql("agent_prompt");
71
+ expect(llmContext[0].content).eql("global\nagent_prompt\nFRAG1");
72
+ });
73
+
74
+ it("handles rich workspace messages", function () {
75
+ const cm = new ContextManagerWithWorkspace("sys_msg", msgs.slice(0, 2));
76
+
77
+ expect(cm.getLLMContext()).eql([
78
+ { role: "system", content: "sys_msg" },
79
+ ...msgs.slice(0, 2),
80
+ ]);
81
+
82
+ // set workspace and add new messages
83
+ const wsMsg = createUserMessage(
84
+ "text",
85
+ "data:image/png;base64;adsfadsfadf",
86
+ "user_name"
87
+ );
88
+ assert(wsMsg);
89
+ assert(wsMsg.content);
90
+ assert(typeof wsMsg.content !== "string");
91
+ cm.setWorkspace(wsMsg);
92
+
93
+ expect(cm.getLLMContext()).eql([
94
+ { role: "system", content: "sys_msg" },
95
+ ...msgs.slice(0, 2),
96
+ wsMsg,
97
+ ]);
98
+
99
+ cm.addMessages(msgs.slice(2));
100
+
101
+ // context with workspace
102
+ const ctx1 = cm.getLLMContext();
103
+ expect(ctx1).eql([{ role: "system", content: "sys_msg" }, ...msgs, wsMsg]);
104
+ });
105
+ });
@@ -0,0 +1,109 @@
1
+ import { expect } from "vitest";
2
+ import {
3
+ ChatCompletionAssistantMessageParam,
4
+ ChatCompletionMessageParam,
5
+ ChatCompletionUserMessageParam,
6
+ } from "../agent/agent";
7
+ import {
8
+ MESSAGE_INDEX_FULL_INCREMENT,
9
+ MESSAGE_INDEX_SUB_INCREMENT,
10
+ sessionMessagesToLLMConversation,
11
+ sessionMessagesToNextIndex,
12
+ } from "../chat/server/conversation";
13
+ import { SessionMessage } from "../chat/data/dataModels";
14
+
15
+ // Fake conversation values.
16
+ const AGENT: ChatCompletionAssistantMessageParam[] = [
17
+ { role: "assistant", content: "Message 0" },
18
+ { role: "assistant", content: "Message 1" },
19
+ { role: "assistant", content: "Message 2" },
20
+ ];
21
+
22
+ const USER: ChatCompletionUserMessageParam[] = [
23
+ { role: "user", content: "Message 0", name: "userA" },
24
+ { role: "user", content: "Message 1", name: "userA" },
25
+ { role: "user", content: "Message 2", name: "userA" },
26
+ ];
27
+
28
+ describe("Conversation tools", () => {
29
+ it("sessionMessagesToLLMConversation handles empty case", () => {
30
+ const nextMessageIdx = sessionMessagesToNextIndex([]);
31
+ const { firstIndex, conversation } = sessionMessagesToLLMConversation([]);
32
+
33
+ expect(nextMessageIdx).eql(0);
34
+ expect(firstIndex).eql(-1);
35
+ expect(conversation).eql([]);
36
+ });
37
+
38
+ it("sessionMessagesToLLMConversation extracts LLM messages", () => {
39
+ const messages: SessionMessage[] = [
40
+ {
41
+ message_idx: 0,
42
+ sender_uuid: "userA",
43
+ content: USER[0],
44
+ is_for_llm: true,
45
+ },
46
+ {
47
+ message_idx: 100,
48
+ content: {} as unknown as ChatCompletionMessageParam,
49
+ is_for_llm: false,
50
+ },
51
+ {
52
+ message_idx: 101,
53
+ content: AGENT[0],
54
+ is_for_llm: true,
55
+ },
56
+ ];
57
+ const expectConversation = [USER[0], AGENT[0]];
58
+ const expectNextIdx = 2 * MESSAGE_INDEX_FULL_INCREMENT;
59
+ const expectFirstIdx = 0;
60
+
61
+ const nextMessageIdx = sessionMessagesToNextIndex(messages);
62
+ const nextMessageIdx100 = sessionMessagesToNextIndex(messages.slice(0, 2));
63
+ const { firstIndex, conversation } =
64
+ sessionMessagesToLLMConversation(messages);
65
+ expect(conversation).eql(expectConversation);
66
+ expect(nextMessageIdx).eql(expectNextIdx);
67
+ expect(nextMessageIdx100).eql(expectNextIdx);
68
+ expect(firstIndex).eql(expectFirstIdx);
69
+ });
70
+
71
+ it("sessionMessagesToLLMConversation skips leading invalid", () => {
72
+ const messages: SessionMessage[] = [
73
+ {
74
+ message_idx: 3 * MESSAGE_INDEX_FULL_INCREMENT,
75
+ content: AGENT[0],
76
+ is_for_llm: true,
77
+ },
78
+ {
79
+ // Should be identified as the first message, since we can't start
80
+ // with an agent message
81
+ message_idx: 4 * MESSAGE_INDEX_FULL_INCREMENT,
82
+ sender_uuid: "userA",
83
+ content: USER[0],
84
+ is_for_llm: true,
85
+ },
86
+ {
87
+ message_idx: 5 * MESSAGE_INDEX_FULL_INCREMENT,
88
+ content: {} as unknown as ChatCompletionMessageParam,
89
+ is_for_llm: false,
90
+ },
91
+ {
92
+ message_idx:
93
+ 5 * MESSAGE_INDEX_FULL_INCREMENT + 3 * MESSAGE_INDEX_SUB_INCREMENT,
94
+ content: AGENT[1],
95
+ is_for_llm: true,
96
+ },
97
+ ];
98
+ const expectConversation = [USER[0], AGENT[1]];
99
+ const expectNextIdx = 6 * MESSAGE_INDEX_FULL_INCREMENT;
100
+ const expectFirstIdx = 4 * MESSAGE_INDEX_FULL_INCREMENT;
101
+
102
+ const nextMessageIdx = sessionMessagesToNextIndex(messages);
103
+ const { firstIndex, conversation } =
104
+ sessionMessagesToLLMConversation(messages);
105
+ expect(conversation).eql(expectConversation);
106
+ expect(nextMessageIdx).eql(expectNextIdx);
107
+ expect(firstIndex).eql(expectFirstIdx);
108
+ });
109
+ });