@xalia/agent 0.5.8 → 0.6.0

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,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const sessionRegistry_1 = require("./sessionRegistry");
5
+ const mockFactories_1 = require("./test-utils/mockFactories");
6
+ // Mock the complex agent creation to avoid network calls
7
+ const mockCreateAgentWithoutSkills = vitest_1.vi.hoisted(() => vitest_1.vi.fn());
8
+ vitest_1.vi.mock("../../agent/agentUtils", () => ({
9
+ createAgentWithoutSkills: mockCreateAgentWithoutSkills,
10
+ }));
11
+ (0, vitest_1.describe)("SessionRegistry", () => {
12
+ let sessionRegistry;
13
+ let mockDatabase;
14
+ let mockUserConnectionManager;
15
+ (0, vitest_1.beforeEach)(() => {
16
+ vitest_1.vi.resetAllMocks();
17
+ // Setup the mock to return array with agent and skill manager
18
+ mockCreateAgentWithoutSkills.mockResolvedValue([
19
+ {
20
+ /* mock agent */
21
+ },
22
+ {
23
+ getServerBriefs: vitest_1.vi.fn().mockReturnValue([]),
24
+ /* mock skill manager */
25
+ },
26
+ ]);
27
+ // Create mocks using factories
28
+ mockDatabase = (0, mockFactories_1.createMockDatabase)();
29
+ mockUserConnectionManager = (0, mockFactories_1.createMockUserConnectionManager)();
30
+ // Setup standard behaviors
31
+ (0, mockFactories_1.setupStandardMockBehaviors)({
32
+ database: mockDatabase,
33
+ userConnectionManager: mockUserConnectionManager,
34
+ });
35
+ // Create SessionRegistry instance
36
+ sessionRegistry = new sessionRegistry_1.SessionRegistry(mockDatabase.mock, mockUserConnectionManager.mock, "http://llm-api.test", "http://xmcp-api.test");
37
+ });
38
+ (0, vitest_1.afterEach)(() => {
39
+ vitest_1.vi.restoreAllMocks();
40
+ });
41
+ (0, vitest_1.describe)("Session Membership Tracking", () => {
42
+ (0, vitest_1.it)("should correctly track user-session mapping", () => {
43
+ // Arrange - Setup session state using public API
44
+ const sessionId1 = "session-1";
45
+ const sessionId2 = "session-2";
46
+ const userId1 = mockFactories_1.MOCK_USERS.owner.uuid;
47
+ const userId2 = mockFactories_1.MOCK_USERS.participant.uuid;
48
+ // Mock database for session validation
49
+ mockDatabase.spies.sessionGetById.mockImplementation((sessionId) => {
50
+ if (sessionId === sessionId1 || sessionId === sessionId2) {
51
+ return Promise.resolve({
52
+ session_uuid: sessionId,
53
+ title: `Test Session`,
54
+ agent_profile_uuid: "agent-profile-1",
55
+ owner_uuid: userId1,
56
+ updated_at: "2025-01-01T00:00:00Z",
57
+ });
58
+ }
59
+ return Promise.resolve(null);
60
+ });
61
+ // Create mock sessions in openSessions map
62
+ const mockSession1 = (0, mockFactories_1.createMockOpenSession)(sessionId1);
63
+ const mockSession2 = (0, mockFactories_1.createMockOpenSession)(sessionId2);
64
+ mockSession1.spies.getSessionState.mockReturnValue([]);
65
+ mockSession2.spies.getSessionState.mockReturnValue([]);
66
+ // Use bracket notation to access private property for testing
67
+ const registryWithSessions = sessionRegistry;
68
+ registryWithSessions.openSessions.set(sessionId1, mockSession1.mock);
69
+ registryWithSessions.openSessions.set(sessionId2, mockSession2.mock);
70
+ const registryWithPrivate = sessionRegistry;
71
+ registryWithPrivate.addUserToSessionMemory(userId1, sessionId1);
72
+ registryWithPrivate.addUserToSessionMemory(userId2, sessionId1);
73
+ registryWithPrivate.addUserToSessionMemory(userId1, sessionId2);
74
+ // Assert - Test getUserSessions for both users
75
+ const user1Sessions = sessionRegistry.getUserSessions(userId1);
76
+ (0, vitest_1.expect)(user1Sessions).toEqual(new Set([sessionId1, sessionId2]));
77
+ const user2Sessions = sessionRegistry.getUserSessions(userId2);
78
+ (0, vitest_1.expect)(user2Sessions).toEqual(new Set([sessionId1]));
79
+ // Assert - Test getInMemorySessionUsers for both sessions
80
+ const session1Users = sessionRegistry.getInMemorySessionUsers(sessionId1);
81
+ (0, vitest_1.expect)(session1Users).toEqual(new Set([userId1, userId2]));
82
+ (0, vitest_1.expect)(session1Users.size).toBe(2);
83
+ const session2Users = sessionRegistry.getInMemorySessionUsers(sessionId2);
84
+ (0, vitest_1.expect)(session2Users).toEqual(new Set([userId1]));
85
+ (0, vitest_1.expect)(session2Users.size).toBe(1);
86
+ });
87
+ (0, vitest_1.it)("should return empty sets for non-existent sessions or users", () => {
88
+ // Act & Assert - Test methods with non-existent data
89
+ const nonExistentSessionUsers = sessionRegistry.getInMemorySessionUsers("non-existent-session");
90
+ (0, vitest_1.expect)(nonExistentSessionUsers).toEqual(new Set());
91
+ (0, vitest_1.expect)(nonExistentSessionUsers.size).toBe(0);
92
+ const nonExistentUserSessions = sessionRegistry.getUserSessions("non-existent-user");
93
+ (0, vitest_1.expect)(nonExistentUserSessions).toEqual(new Set());
94
+ (0, vitest_1.expect)(nonExistentUserSessions.size).toBe(0);
95
+ });
96
+ });
97
+ });
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ /**
3
+ * Common mock factories for chat server tests
4
+ * Provides consistent mock objects across all test suites
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MOCK_AGENT_PROFILES = exports.MOCK_SESSIONS = exports.MOCK_USERS = void 0;
8
+ exports.createMockDatabase = createMockDatabase;
9
+ exports.createMockUserConnectionManager = createMockUserConnectionManager;
10
+ exports.createMockSessionRegistry = createMockSessionRegistry;
11
+ exports.createMockApiKeyManager = createMockApiKeyManager;
12
+ exports.createMockWebSocket = createMockWebSocket;
13
+ exports.createMockOpenSession = createMockOpenSession;
14
+ exports.createMockSessionList = createMockSessionList;
15
+ exports.setupStandardMockBehaviors = setupStandardMockBehaviors;
16
+ const vitest_1 = require("vitest");
17
+ // =====================================
18
+ // Mock Data Constants
19
+ // =====================================
20
+ exports.MOCK_USERS = {
21
+ owner: {
22
+ uuid: "user-123",
23
+ nickname: "test-owner",
24
+ },
25
+ participant: {
26
+ uuid: "user-456",
27
+ nickname: "test-participant",
28
+ },
29
+ nonExistent: {
30
+ uuid: "user-999",
31
+ nickname: "non-existent",
32
+ },
33
+ };
34
+ exports.MOCK_SESSIONS = {
35
+ active: {
36
+ uuid: "session-abc",
37
+ title: "Test Session",
38
+ user_uuid: exports.MOCK_USERS.owner.uuid,
39
+ agent_profile_uuid: "agent-profile-1",
40
+ workspace: null,
41
+ team_uuid: null,
42
+ public: false,
43
+ updated_at: "2025-01-01T00:00:00Z",
44
+ },
45
+ secondary: {
46
+ uuid: "session-xyz",
47
+ title: "Secondary Session",
48
+ user_uuid: exports.MOCK_USERS.owner.uuid,
49
+ agent_profile_uuid: "agent-profile-2",
50
+ workspace: null,
51
+ team_uuid: null,
52
+ public: false,
53
+ updated_at: "2025-01-01T01:00:00Z",
54
+ },
55
+ };
56
+ exports.MOCK_AGENT_PROFILES = {
57
+ default: {
58
+ system_prompt: "You are a helpful assistant",
59
+ model: "gpt-4",
60
+ mcp_settings: {},
61
+ },
62
+ custom: {
63
+ system_prompt: "You are a specialized assistant",
64
+ model: "claude-3",
65
+ mcp_settings: {
66
+ filesystem: ["read_file", "write_file"],
67
+ },
68
+ },
69
+ };
70
+ // =====================================
71
+ // Mock Factory Functions
72
+ // =====================================
73
+ /**
74
+ * Creates a mock Database instance with common methods
75
+ */
76
+ function createMockDatabase() {
77
+ const spies = {
78
+ sessionCreate: vitest_1.vi.fn(),
79
+ sessionGetById: vitest_1.vi.fn(),
80
+ getUserFromUuid: vitest_1.vi.fn(),
81
+ getAgentProfileById: vitest_1.vi.fn(),
82
+ getSavedAgentProfileById: vitest_1.vi.fn(),
83
+ getUserApiKey: vitest_1.vi.fn(),
84
+ sessionParticipantAdd: vitest_1.vi.fn(),
85
+ sessionParticipantRemove: vitest_1.vi.fn(),
86
+ updateAgentProfile: vitest_1.vi.fn(),
87
+ sessionConversationAppend: vitest_1.vi.fn(),
88
+ sessionGetParticipants: vitest_1.vi.fn(),
89
+ getUserSessions: vitest_1.vi.fn(),
90
+ };
91
+ const mock = {
92
+ ...spies,
93
+ // Add any other Database methods that might be called
94
+ };
95
+ return { mock, spies };
96
+ }
97
+ /**
98
+ * Creates a mock IUserConnectionManager instance
99
+ */
100
+ function createMockUserConnectionManager() {
101
+ const spies = {
102
+ sendToUsers: vitest_1.vi.fn(),
103
+ sendToConnection: vitest_1.vi.fn(),
104
+ sendServerError: vitest_1.vi.fn(),
105
+ getLiveUserApiKey: vitest_1.vi.fn(),
106
+ };
107
+ const mock = {
108
+ sendToUsers: spies.sendToUsers,
109
+ sendToConnection: spies.sendToConnection,
110
+ sendServerError: spies.sendServerError,
111
+ getLiveUserApiKey: spies.getLiveUserApiKey,
112
+ };
113
+ return { mock, spies };
114
+ }
115
+ /**
116
+ * Creates a mock IMessageProcessor instance
117
+ */
118
+ function createMockSessionRegistry() {
119
+ const spies = {
120
+ processMessage: vitest_1.vi.fn(),
121
+ handleUserDisconnect: vitest_1.vi.fn(),
122
+ };
123
+ const mock = {
124
+ processMessage: spies.processMessage,
125
+ handleUserDisconnect: spies.handleUserDisconnect,
126
+ };
127
+ return { mock, spies };
128
+ }
129
+ /**
130
+ * Creates a mock ApiKeyManager instance
131
+ */
132
+ function createMockApiKeyManager() {
133
+ const spies = {
134
+ verifyApiKey: vitest_1.vi.fn(),
135
+ };
136
+ const mock = {
137
+ ...spies,
138
+ };
139
+ return { mock, spies };
140
+ }
141
+ /**
142
+ * Creates a mock WebSocket instance
143
+ */
144
+ function createMockWebSocket(readyState = 1) {
145
+ const spies = {
146
+ on: vitest_1.vi.fn(),
147
+ once: vitest_1.vi.fn(),
148
+ send: vitest_1.vi.fn(),
149
+ close: vitest_1.vi.fn(),
150
+ };
151
+ const mock = {
152
+ ...spies,
153
+ readyState, // Allow setting initial readyState
154
+ };
155
+ return { mock, spies };
156
+ }
157
+ /**
158
+ * Creates a mock OpenSession instance
159
+ */
160
+ function createMockOpenSession(sessionId = exports.MOCK_SESSIONS.active.uuid) {
161
+ const spies = {
162
+ addParticipant: vitest_1.vi.fn(),
163
+ leave: vitest_1.vi.fn(),
164
+ onClientMessage: vitest_1.vi.fn(),
165
+ getSessionState: vitest_1.vi.fn(),
166
+ broadcast: vitest_1.vi.fn(),
167
+ sendTo: vitest_1.vi.fn(),
168
+ onEmpty: vitest_1.vi.fn(),
169
+ };
170
+ const mock = {
171
+ ...spies,
172
+ sessionUUID: sessionId,
173
+ users: new Set(),
174
+ agentProfileUUID: exports.MOCK_SESSIONS.active.agent_profile_uuid,
175
+ sessionOwnerUserUUID: exports.MOCK_USERS.owner.uuid,
176
+ };
177
+ return { mock, spies };
178
+ }
179
+ /**
180
+ * Creates a standard set of session list items for testing
181
+ */
182
+ function createMockSessionList() {
183
+ return [
184
+ {
185
+ session_uuid: exports.MOCK_SESSIONS.active.uuid,
186
+ title: exports.MOCK_SESSIONS.active.title,
187
+ team_uuid: undefined,
188
+ agent_profile_uuid: exports.MOCK_SESSIONS.active.agent_profile_uuid,
189
+ workspace: undefined,
190
+ updated_at: exports.MOCK_SESSIONS.active.updated_at || "",
191
+ user_uuid: exports.MOCK_SESSIONS.active.user_uuid,
192
+ },
193
+ {
194
+ session_uuid: exports.MOCK_SESSIONS.secondary.uuid,
195
+ title: exports.MOCK_SESSIONS.secondary.title,
196
+ team_uuid: undefined,
197
+ agent_profile_uuid: exports.MOCK_SESSIONS.secondary.agent_profile_uuid,
198
+ workspace: undefined,
199
+ updated_at: exports.MOCK_SESSIONS.secondary.updated_at || "",
200
+ user_uuid: exports.MOCK_SESSIONS.secondary.user_uuid,
201
+ },
202
+ ];
203
+ }
204
+ /**
205
+ * Helper to setup common mock behaviors
206
+ */
207
+ function setupStandardMockBehaviors(mocks) {
208
+ // Setup database mocks
209
+ if (mocks.database) {
210
+ mocks.database.spies.getUserFromUuid.mockImplementation((userId) => {
211
+ if (userId === exports.MOCK_USERS.owner.uuid)
212
+ return Promise.resolve(exports.MOCK_USERS.owner);
213
+ if (userId === exports.MOCK_USERS.participant.uuid)
214
+ return Promise.resolve(exports.MOCK_USERS.participant);
215
+ return Promise.resolve(null);
216
+ });
217
+ mocks.database.spies.sessionGetById.mockImplementation((sessionId) => {
218
+ if (sessionId === exports.MOCK_SESSIONS.active.uuid)
219
+ return Promise.resolve(exports.MOCK_SESSIONS.active);
220
+ if (sessionId === exports.MOCK_SESSIONS.secondary.uuid)
221
+ return Promise.resolve(exports.MOCK_SESSIONS.secondary);
222
+ if (sessionId === "new-session-uuid")
223
+ return Promise.resolve({
224
+ ...exports.MOCK_SESSIONS.active,
225
+ uuid: "new-session-uuid",
226
+ });
227
+ return Promise.resolve(null);
228
+ });
229
+ mocks.database.spies.getAgentProfileById.mockImplementation((profileId) => {
230
+ if (profileId === "agent-profile-1")
231
+ return Promise.resolve(exports.MOCK_AGENT_PROFILES.default);
232
+ if (profileId === "agent-profile-2")
233
+ return Promise.resolve(exports.MOCK_AGENT_PROFILES.custom);
234
+ return Promise.resolve(null);
235
+ });
236
+ mocks.database.spies.getSavedAgentProfileById.mockImplementation((profileId) => {
237
+ if (profileId === "agent-profile-1") {
238
+ return Promise.resolve({
239
+ uuid: "agent-profile-1",
240
+ user_uuid: exports.MOCK_USERS.owner.uuid,
241
+ profile_name: "Default Profile",
242
+ profile: exports.MOCK_AGENT_PROFILES.default,
243
+ preferences: { auto_approve: {} },
244
+ });
245
+ }
246
+ if (profileId === "agent-profile-2") {
247
+ return Promise.resolve({
248
+ uuid: "agent-profile-2",
249
+ user_uuid: exports.MOCK_USERS.owner.uuid,
250
+ profile_name: "Custom Profile",
251
+ profile: exports.MOCK_AGENT_PROFILES.custom,
252
+ preferences: { auto_approve: {} },
253
+ });
254
+ }
255
+ return Promise.resolve(null);
256
+ });
257
+ mocks.database.spies.getUserApiKey.mockImplementation((userId) => {
258
+ if (userId === exports.MOCK_USERS.owner.uuid) {
259
+ return Promise.resolve("valid-api-key");
260
+ }
261
+ if (userId === exports.MOCK_USERS.participant.uuid) {
262
+ return Promise.resolve("participant-api-key");
263
+ }
264
+ return Promise.resolve(undefined);
265
+ });
266
+ mocks.database.spies.sessionCreate.mockImplementation(() => Promise.resolve());
267
+ mocks.database.spies.sessionParticipantAdd.mockImplementation(() => Promise.resolve());
268
+ mocks.database.spies.sessionParticipantRemove.mockImplementation(() => Promise.resolve());
269
+ // Setup sessionGetParticipants to return participants based on session
270
+ mocks.database.spies.sessionGetParticipants.mockImplementation((sessionId) => {
271
+ if (sessionId === exports.MOCK_SESSIONS.active.uuid) {
272
+ return Promise.resolve([
273
+ { user_uuid: exports.MOCK_USERS.owner.uuid, role: "owner" },
274
+ { user_uuid: exports.MOCK_USERS.participant.uuid, role: "participant" },
275
+ ]);
276
+ }
277
+ if (sessionId === exports.MOCK_SESSIONS.secondary.uuid) {
278
+ return Promise.resolve([
279
+ { user_uuid: exports.MOCK_USERS.owner.uuid, role: "owner" },
280
+ ]);
281
+ }
282
+ return Promise.resolve([]);
283
+ });
284
+ // Setup getUserSessions to return empty array by default
285
+ mocks.database.spies.getUserSessions.mockImplementation(() => Promise.resolve([]));
286
+ }
287
+ // Setup API key manager mocks
288
+ if (mocks.apiKeyManager) {
289
+ mocks.apiKeyManager.spies.verifyApiKey.mockImplementation((apiKey) => {
290
+ if (apiKey === "valid-api-key")
291
+ return Promise.resolve(exports.MOCK_USERS.owner);
292
+ if (apiKey === "participant-api-key")
293
+ return Promise.resolve(exports.MOCK_USERS.participant);
294
+ return Promise.resolve(null);
295
+ });
296
+ }
297
+ // Setup user connection manager mocks
298
+ if (mocks.userConnectionManager) {
299
+ mocks.userConnectionManager.spies.getLiveUserApiKey.mockImplementation((userId) => {
300
+ if (userId === exports.MOCK_USERS.owner.uuid)
301
+ return "valid-api-key";
302
+ if (userId === exports.MOCK_USERS.participant.uuid)
303
+ return "participant-api-key";
304
+ return undefined;
305
+ });
306
+ }
307
+ }
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ /**
3
+ * Collection of simple Agent tools.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calculatorTool = void 0;
7
+ exports.makeParseArgsFn = makeParseArgsFn;
8
+ exports.isoWithTimezone = isoWithTimezone;
9
+ exports.datetimeTool = datetimeTool;
10
+ exports.calculatorEval = calculatorEval;
11
+ exports.renderTool = renderTool;
12
+ exports.webSearchTool = webSearchTool;
13
+ exports.openURL = openURL;
14
+ exports.openURLTool = openURLTool;
15
+ exports.addDefaultChatTools = addDefaultChatTools;
16
+ const expr_eval_1 = require("expr-eval");
17
+ const htmlToText_1 = require("../utils/htmlToText");
18
+ const sdk_1 = require("@xalia/xmcp/sdk");
19
+ const search_1 = require("../utils/search");
20
+ const logger = (0, sdk_1.getLogger)();
21
+ /**
22
+ * Returns a function which parses an `args` struct and attempts to extract
23
+ * multiple string parameters with the given names. e.g.
24
+ *
25
+ * const parseFn = makeParseArgsFn(["arg0", "arg1"] as const)
26
+ *
27
+ * creates `parseFn: (args: unknown) => { arg0: string, arg1: string }` which
28
+ * can be used to parse tool arguments.
29
+ *
30
+ * NOTE, the complex type parameters ensures that the name list is a
31
+ * compile-time value, which in turn ensures that the return value of this
32
+ * function is well-typed.
33
+ */
34
+ function makeParseArgsFn(names) {
35
+ return (args) => {
36
+ if (typeof args !== "object") {
37
+ throw new Error(`invalid args: ${typeof args}`);
38
+ }
39
+ const argsObj = args;
40
+ for (const name of names) {
41
+ const val = argsObj[name];
42
+ if (typeof val !== "string") {
43
+ throw new Error(`invalid expr args.${name}: ${typeof val}`);
44
+ }
45
+ }
46
+ return argsObj;
47
+ };
48
+ }
49
+ const DATETIME_DESC = {
50
+ type: "function",
51
+ function: {
52
+ name: "time_now",
53
+ description: "Current time",
54
+ },
55
+ };
56
+ function isoWithTimezone(timeZone) {
57
+ return (new Intl.DateTimeFormat("sv-SE", {
58
+ timeZone,
59
+ year: "numeric",
60
+ month: "2-digit",
61
+ day: "2-digit",
62
+ hour: "2-digit",
63
+ minute: "2-digit",
64
+ second: "2-digit",
65
+ hour12: false,
66
+ timeZoneName: "short",
67
+ })
68
+ .format(new Date())
69
+ .replace(" ", "T") + ` (${timeZone})`);
70
+ }
71
+ function datetimeTool(timezone) {
72
+ // eslint-disable-next-line @typescript-eslint/require-await
73
+ const toolFn = async () => {
74
+ return { response: isoWithTimezone(timezone) };
75
+ };
76
+ return {
77
+ // eslint-disable-next-line @typescript-eslint/require-await
78
+ setup: async (agent) => {
79
+ agent.addAgentTool(DATETIME_DESC, toolFn);
80
+ },
81
+ };
82
+ }
83
+ const ARITHMETIC_DESC = {
84
+ type: "function",
85
+ function: {
86
+ name: "arithmetic",
87
+ description: "Evaluate arithmetic expression",
88
+ parameters: {
89
+ type: "object",
90
+ properties: {
91
+ expr: {
92
+ type: "string",
93
+ description: "Expression containing +-*/()",
94
+ },
95
+ },
96
+ required: ["expr"],
97
+ },
98
+ },
99
+ };
100
+ function calculatorEval(args) {
101
+ try {
102
+ return String(expr_eval_1.Parser.evaluate(args));
103
+ }
104
+ catch (e) {
105
+ if (typeof e.message === "string") {
106
+ return e.message;
107
+ }
108
+ return String(e);
109
+ }
110
+ }
111
+ exports.calculatorTool = {
112
+ // eslint-disable-next-line @typescript-eslint/require-await
113
+ setup: async (agent) => {
114
+ const getExpr = makeParseArgsFn(["expr"]);
115
+ // eslint-disable-next-line @typescript-eslint/require-await
116
+ const toolFn = async (_, args) => {
117
+ const { expr } = getExpr(args);
118
+ return { response: calculatorEval(expr) };
119
+ };
120
+ agent.addAgentTool(ARITHMETIC_DESC, toolFn);
121
+ },
122
+ };
123
+ const RENDER_DESC = {
124
+ type: "function",
125
+ function: {
126
+ name: "render",
127
+ description: "Display the given html fragment inside a div element",
128
+ parameters: {
129
+ type: "object",
130
+ properties: {
131
+ html: {
132
+ type: "string",
133
+ description: "HTML fragment to render",
134
+ },
135
+ },
136
+ required: ["html"],
137
+ },
138
+ },
139
+ };
140
+ function renderTool(platform) {
141
+ const getHtml = makeParseArgsFn(["html"]);
142
+ const toolFn = async (_, args) => {
143
+ const { html } = getHtml(args);
144
+ await platform.renderHTML(html);
145
+ return { response: "" };
146
+ };
147
+ return {
148
+ // eslint-disable-next-line @typescript-eslint/require-await
149
+ setup: async (agent) => {
150
+ agent.addAgentTool(RENDER_DESC, toolFn);
151
+ },
152
+ };
153
+ }
154
+ const WEB_SEARCH_DESC = {
155
+ type: "function",
156
+ function: {
157
+ name: "web_search",
158
+ description: "Web search",
159
+ parameters: {
160
+ type: "object",
161
+ properties: {
162
+ query: {
163
+ type: "string",
164
+ description: "Search query text",
165
+ },
166
+ },
167
+ required: ["query"],
168
+ },
169
+ },
170
+ };
171
+ function webSearchTool() {
172
+ const getQuery = makeParseArgsFn(["query"]);
173
+ const toolFn = async (_, args) => {
174
+ const { query } = getQuery(args);
175
+ logger.debug(`[web_search]: query: ${query}`);
176
+ const results = await (0, search_1.webSearch)(query);
177
+ logger.debug(`[web_search]: results: ${results}`);
178
+ return { response: JSON.stringify(results) };
179
+ };
180
+ return {
181
+ // eslint-disable-next-line @typescript-eslint/require-await
182
+ setup: async (agent) => {
183
+ agent.addAgentTool(WEB_SEARCH_DESC, toolFn);
184
+ },
185
+ };
186
+ }
187
+ // open_url
188
+ /**
189
+ * For now, this matches the duckduckgo-mcp-server length. Could extend
190
+ * depending on the application / model etc.
191
+ */
192
+ const _OPEN_URL_MAX_LENGTH_STR = process.env["OPEN_URL_MAX_LENGTH"] || "8000";
193
+ const OPEN_URL_MAX_LENGTH = parseInt(_OPEN_URL_MAX_LENGTH_STR, 10);
194
+ const OPEN_URL_DESC = {
195
+ type: "function",
196
+ function: {
197
+ name: "open_url",
198
+ description: "Download content from a URL",
199
+ parameters: {
200
+ type: "object",
201
+ properties: {
202
+ url: {
203
+ type: "string",
204
+ description: "URL to download",
205
+ },
206
+ },
207
+ required: ["url"],
208
+ },
209
+ },
210
+ };
211
+ async function openURL(url) {
212
+ const response = await fetch(url);
213
+ if (!response.ok) {
214
+ const status = String(response.status);
215
+ const code = response.statusText;
216
+ throw new Error(`Failed to fetch ${url}: ${status} ${code}`);
217
+ }
218
+ const html = await response.text();
219
+ return (0, htmlToText_1.htmlToText)(html, OPEN_URL_MAX_LENGTH);
220
+ }
221
+ function openURLTool() {
222
+ const getURL = makeParseArgsFn(["url"]);
223
+ const toolFn = async (_, args) => {
224
+ const { url } = getURL(args);
225
+ return { response: await openURL(url) };
226
+ };
227
+ return {
228
+ // eslint-disable-next-line @typescript-eslint/require-await
229
+ setup: async (agent) => {
230
+ agent.addAgentTool(OPEN_URL_DESC, toolFn);
231
+ },
232
+ };
233
+ }
234
+ /**
235
+ * Add a set of agent tools for chat sessions.
236
+ */
237
+ async function addDefaultChatTools(agent, timezone, platform) {
238
+ await agent.addAgentToolProvider(datetimeTool(timezone));
239
+ await agent.addAgentToolProvider(exports.calculatorTool);
240
+ await agent.addAgentToolProvider(renderTool(platform));
241
+ await agent.addAgentToolProvider(webSearchTool());
242
+ await agent.addAgentToolProvider(openURLTool());
243
+ }