@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.
- 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,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
|
+
}
|