@xalia/agent 0.5.7 → 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 +176 -96
- package/dist/agent/src/agent/agentUtils.js +82 -59
- 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/mcpServerManager.js +23 -24
- 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 +26 -14
- 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 +23 -21
- 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/utils/websocket.js +16 -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 +271 -83
- 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 +23 -20
- package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
- package/dist/agent/src/test/openaiStreaming.test.js +64 -35
- 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 +24 -25
- 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 +241 -58
- package/dist/agent/src/tool/commandPrompt.js +22 -17
- 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 +283 -138
- package/src/agent/agentUtils.ts +143 -108
- 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 +35 -31
- package/src/agent/nullAgentEventHandler.ts +20 -0
- package/src/agent/nullPlatform.ts +13 -0
- package/src/agent/openAILLMStreaming.ts +26 -13
- package/src/agent/promptProvider.ts +87 -0
- package/src/agent/repeatLLM.ts +5 -5
- package/src/agent/sudoMcpServerManager.ts +30 -29
- 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/utils/websocket.ts +16 -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 +358 -89
- 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 +21 -16
- package/src/test/multiAsyncQueue.test.ts +125 -0
- package/src/test/openaiStreaming.test.ts +71 -36
- 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 +32 -30
- 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 +292 -100
- package/src/tool/commandPrompt.ts +28 -19
- 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 -349
- package/dist/agent/src/chat/conversationManager.js +0 -392
- package/dist/agent/src/chat/db.js +0 -209
- package/dist/agent/src/chat/frontendClient.js +0 -74
- package/dist/agent/src/chat/server.js +0 -158
- package/src/chat/client.ts +0 -455
- package/src/chat/conversationManager.ts +0 -595
- package/src/chat/db.ts +0 -290
- package/src/chat/frontendClient.ts +0 -123
- package/src/chat/messages.ts +0 -235
- package/src/chat/server.ts +0 -177
- /package/dist/agent/src/{chat/messages.js → agent/iAgentEventHandler.js} +0 -0
- /package/{frog.png → test_data/frog.png} +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import { AgentProfile } from "../agent/agent";
|
|
3
|
+
import {
|
|
4
|
+
Database,
|
|
5
|
+
SUPABASE_LOCAL_KEY,
|
|
6
|
+
SUPABASE_LOCAL_URL,
|
|
7
|
+
} from "../chat/data/database";
|
|
8
|
+
|
|
9
|
+
export function getLocalDB(): Database {
|
|
10
|
+
return new Database(SUPABASE_LOCAL_URL, SUPABASE_LOCAL_KEY);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const AGENT_PROFILE: AgentProfile = {
|
|
14
|
+
model: undefined,
|
|
15
|
+
system_prompt: "You are an unhelpful agent",
|
|
16
|
+
mcp_settings: {},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const AGENT_PROFILE_2: AgentProfile = {
|
|
20
|
+
model: undefined,
|
|
21
|
+
system_prompt: "You are a very helpful agent.",
|
|
22
|
+
mcp_settings: { simplecalc: [] },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/// User description
|
|
26
|
+
export type TestUserSpec = {
|
|
27
|
+
type: "spec";
|
|
28
|
+
uuid: string;
|
|
29
|
+
email: string;
|
|
30
|
+
name: string;
|
|
31
|
+
api_key: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/// User created in the DB
|
|
35
|
+
export type TestUser = {
|
|
36
|
+
uuid: string;
|
|
37
|
+
email: string;
|
|
38
|
+
name: string;
|
|
39
|
+
api_key: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let counter = 1;
|
|
43
|
+
|
|
44
|
+
export function randomString(length: number = 16): string {
|
|
45
|
+
const CHARS =
|
|
46
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
47
|
+
let result = "";
|
|
48
|
+
const array = new Uint8Array(length);
|
|
49
|
+
crypto.getRandomValues(array);
|
|
50
|
+
for (let i = 0; i < length; i++) {
|
|
51
|
+
result += CHARS[array[i] % CHARS.length];
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function testUserSpec(): TestUserSpec {
|
|
57
|
+
const uuid = randomString() + String(counter++);
|
|
58
|
+
const name = `user_${uuid}`;
|
|
59
|
+
const email = `${name}@host.com`;
|
|
60
|
+
const api_key = `apikey_${uuid}`;
|
|
61
|
+
return { type: "spec", uuid, name, email, api_key };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Test user data that gets reset for each test
|
|
65
|
+
export async function createTestUser(
|
|
66
|
+
db: Database,
|
|
67
|
+
user?: TestUserSpec
|
|
68
|
+
): Promise<TestUser> {
|
|
69
|
+
user = user || testUserSpec();
|
|
70
|
+
await db.createUser(user.uuid, user.email, user.name);
|
|
71
|
+
await db.addApiKey(user.uuid, user.api_key, "default", [], true);
|
|
72
|
+
return user;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function cleanupTestUser(
|
|
76
|
+
db: Database,
|
|
77
|
+
user: TestUser
|
|
78
|
+
// api_key: string
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
try {
|
|
81
|
+
await db.getClientForTesting().from("users").delete().eq("uuid", user.uuid);
|
|
82
|
+
} catch (_error) {
|
|
83
|
+
// Ignore cleanup errors
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function createTestSession(
|
|
88
|
+
db: Database,
|
|
89
|
+
user1: TestUserSpec
|
|
90
|
+
): Promise<{
|
|
91
|
+
agentProfileId: string;
|
|
92
|
+
sessionId: string;
|
|
93
|
+
}> {
|
|
94
|
+
await createTestUser(db, user1);
|
|
95
|
+
|
|
96
|
+
const agentProfile = await db.createAgentProfile(
|
|
97
|
+
user1.uuid,
|
|
98
|
+
undefined,
|
|
99
|
+
"test_profile",
|
|
100
|
+
AGENT_PROFILE
|
|
101
|
+
);
|
|
102
|
+
assert(agentProfile);
|
|
103
|
+
|
|
104
|
+
const sessionId = Database.sessionNewUUID();
|
|
105
|
+
await db.sessionCreate({
|
|
106
|
+
session_uuid: sessionId,
|
|
107
|
+
user_uuid: user1.uuid,
|
|
108
|
+
title: "test_session",
|
|
109
|
+
agent_profile_uuid: agentProfile.uuid,
|
|
110
|
+
});
|
|
111
|
+
assert(sessionId);
|
|
112
|
+
|
|
113
|
+
return { agentProfileId: agentProfile.uuid, sessionId };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function createTestTeamSession(
|
|
117
|
+
db: Database,
|
|
118
|
+
teamUuid: string,
|
|
119
|
+
testUsers: TestUserSpec[]
|
|
120
|
+
): Promise<{
|
|
121
|
+
agentProfileId: string;
|
|
122
|
+
sessionId: string;
|
|
123
|
+
teamId: string;
|
|
124
|
+
}> {
|
|
125
|
+
const [user1, _] = await Promise.all([
|
|
126
|
+
createTestUser(db, testUsers[0]),
|
|
127
|
+
createTestUser(db, testUsers[1]),
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
const agentProfile = await db.createAgentProfile(
|
|
131
|
+
user1.uuid,
|
|
132
|
+
undefined,
|
|
133
|
+
// teamUuid,
|
|
134
|
+
"test_profile",
|
|
135
|
+
AGENT_PROFILE
|
|
136
|
+
);
|
|
137
|
+
assert(agentProfile);
|
|
138
|
+
|
|
139
|
+
const teamId = await db.createTeam(teamUuid, user1.uuid);
|
|
140
|
+
assert(teamId);
|
|
141
|
+
|
|
142
|
+
const sessionId = Database.sessionNewUUID();
|
|
143
|
+
await db.sessionCreate({
|
|
144
|
+
session_uuid: sessionId,
|
|
145
|
+
team_uuid: teamId,
|
|
146
|
+
user_uuid: user1.uuid,
|
|
147
|
+
title: "test_session",
|
|
148
|
+
agent_profile_uuid: agentProfile.uuid,
|
|
149
|
+
});
|
|
150
|
+
assert(sessionId);
|
|
151
|
+
|
|
152
|
+
return { agentProfileId: agentProfile.uuid, sessionId, teamId };
|
|
153
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
2
3
|
import { loadImageB64OrUndefined } from "../tool/files";
|
|
3
4
|
import { strict as assert } from "assert";
|
|
4
5
|
|
|
5
6
|
describe("Image loading", () => {
|
|
6
|
-
it("convert to data url", async
|
|
7
|
-
const imageB64 = loadImageB64OrUndefined("frog.png");
|
|
8
|
-
|
|
7
|
+
it("convert to data url", async () => {
|
|
8
|
+
const imageB64 = loadImageB64OrUndefined("test_data/frog.png");
|
|
9
|
+
console.log(`imageB64: ${imageB64 || ""}`);
|
|
9
10
|
|
|
10
|
-
expect(imageB64).
|
|
11
|
+
expect(imageB64).toBeTypeOf("string");
|
|
11
12
|
assert(typeof imageB64 === "string");
|
|
12
|
-
expect(imageB64.startsWith("data:image/png;base64")).
|
|
13
|
+
expect(imageB64.startsWith("data:image/png;base64")).toBe(true);
|
|
13
14
|
});
|
|
14
15
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
import { describe, it, expect, afterAll } from "vitest";
|
|
2
3
|
import {
|
|
3
4
|
computeQualifiedName,
|
|
4
5
|
McpServerManager,
|
|
@@ -12,7 +13,7 @@ let idx = 0;
|
|
|
12
13
|
|
|
13
14
|
function getMcpServerManager(): McpServerManager {
|
|
14
15
|
const tm = new McpServerManager();
|
|
15
|
-
const name = "mgr" + ++idx;
|
|
16
|
+
const name = "mgr" + String(++idx);
|
|
16
17
|
managers[name] = tm;
|
|
17
18
|
return tm;
|
|
18
19
|
}
|
|
@@ -25,10 +26,12 @@ async function shutdownAll() {
|
|
|
25
26
|
managers = {};
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
describe("McpServerManager",
|
|
29
|
+
describe("McpServerManager", () => {
|
|
30
|
+
afterAll(shutdownAll);
|
|
31
|
+
|
|
29
32
|
it("should initialize with correct descriptions", async (): Promise<void> => {
|
|
30
33
|
const tm = getMcpServerManager();
|
|
31
|
-
await tm.
|
|
34
|
+
await tm.addMcpServerWithSSEUrl(
|
|
32
35
|
"simplecalc",
|
|
33
36
|
"http://localhost:5001/mcpservers/sudomcp/simplecalc/session",
|
|
34
37
|
"dummy_key"
|
|
@@ -36,17 +39,17 @@ describe("McpServerManager", async () => {
|
|
|
36
39
|
|
|
37
40
|
const server = tm.getMcpServer("simplecalc");
|
|
38
41
|
const availableTools = server.getTools();
|
|
39
|
-
expect(availableTools.length).
|
|
42
|
+
expect(availableTools.length).toBeGreaterThan(0);
|
|
40
43
|
|
|
41
44
|
// Initially, no tools are enabled.
|
|
42
|
-
expect(tm.getOpenAITools().length).
|
|
45
|
+
expect(tm.getOpenAITools().length).toBe(0);
|
|
43
46
|
|
|
44
47
|
// Enable a tool and check we get a description for it.
|
|
45
48
|
const toolName = availableTools[0].name;
|
|
46
49
|
{
|
|
47
50
|
tm.enableTool("simplecalc", toolName);
|
|
48
51
|
const tools = tm.getOpenAITools();
|
|
49
|
-
expect(tools.length).
|
|
52
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
50
53
|
console.log(`tools: ${JSON.stringify(tools, undefined, 2)}`);
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -61,9 +64,9 @@ describe("McpServerManager", async () => {
|
|
|
61
64
|
{
|
|
62
65
|
tm.disableTool("simplecalc", toolName);
|
|
63
66
|
const tools = tm.getOpenAITools();
|
|
64
|
-
expect(tools.length).
|
|
67
|
+
expect(tools.length).toBe(0);
|
|
65
68
|
}
|
|
66
|
-
}
|
|
69
|
+
}, 10000);
|
|
67
70
|
|
|
68
71
|
it("qualified names", async () => {
|
|
69
72
|
const serverName = "server";
|
|
@@ -72,27 +75,29 @@ describe("McpServerManager", async () => {
|
|
|
72
75
|
|
|
73
76
|
const [_serverName, _toolName] = splitQualifiedName(qualifiedName);
|
|
74
77
|
|
|
75
|
-
expect(_serverName).
|
|
76
|
-
expect(_toolName).
|
|
78
|
+
expect(_serverName).toBe(serverName);
|
|
79
|
+
expect(_toolName).toBe(toolName);
|
|
77
80
|
});
|
|
78
81
|
|
|
79
82
|
it("add / remove servers", async () => {
|
|
80
83
|
const tm = getMcpServerManager();
|
|
81
84
|
|
|
82
|
-
await tm.
|
|
85
|
+
await tm.addMcpServerWithSSEUrl(
|
|
83
86
|
"simplecalc",
|
|
84
87
|
"http://localhost:5001/mcpservers/sudomcp/simplecalc/session",
|
|
85
88
|
"dummy_key"
|
|
86
89
|
);
|
|
87
90
|
tm.enableAllTools("simplecalc");
|
|
88
|
-
expect(tm.getOpenAITools().length).
|
|
91
|
+
expect(tm.getOpenAITools().length).toBeGreaterThan(0);
|
|
89
92
|
|
|
90
93
|
// Remove server and check there is no server, and no openai entries.
|
|
91
94
|
|
|
92
95
|
await tm.removeMcpServer("simplecalc");
|
|
93
|
-
expect(tm.getOpenAITools().length).
|
|
96
|
+
expect(tm.getOpenAITools().length).toBe(0);
|
|
94
97
|
expect(() => {
|
|
95
98
|
tm.getMcpServer("simplecalc");
|
|
96
|
-
}).
|
|
99
|
+
}).toThrow();
|
|
97
100
|
});
|
|
98
|
-
})
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterAll(shutdownAll);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { MultiAsyncQueue } from "../chat/utils/multiAsyncQueue";
|
|
3
|
+
|
|
4
|
+
describe("MultiAsyncQueue", () => {
|
|
5
|
+
it("should process all waiting elements in one go", async () => {
|
|
6
|
+
// Place a 1 on the queue. Inside the process fn, place a 2 and 3 on the
|
|
7
|
+
// queue. Next invocation of process should see BOTH 2 AND 3.
|
|
8
|
+
|
|
9
|
+
const context = { q: undefined as MultiAsyncQueue<number> | undefined };
|
|
10
|
+
const seen: number[] = [];
|
|
11
|
+
const process = (elements: number[]): Promise<void> => {
|
|
12
|
+
console.log(`elements: ${JSON.stringify(elements)}`);
|
|
13
|
+
expect(context.q).toBeDefined();
|
|
14
|
+
|
|
15
|
+
for (const e of elements) {
|
|
16
|
+
seen.push(e);
|
|
17
|
+
}
|
|
18
|
+
if (seen.length === 1) {
|
|
19
|
+
expect(seen[0]).toBe(1);
|
|
20
|
+
expect(context.q?.tryEnqueue(2)).toBe(true);
|
|
21
|
+
expect(context.q?.tryEnqueue(3)).toBe(true);
|
|
22
|
+
} else if (seen.length === 3) {
|
|
23
|
+
expect(seen[1]).toBe(2);
|
|
24
|
+
expect(seen[2]).toBe(3);
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error("unexpected elements");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
context.q = new MultiAsyncQueue<number>(process);
|
|
33
|
+
expect(context.q).toBeDefined();
|
|
34
|
+
expect(context.q.tryEnqueue(1)).toBe(true);
|
|
35
|
+
|
|
36
|
+
await new Promise<void>((r) => {
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
expect(seen).toEqual([1, 2, 3]);
|
|
39
|
+
r();
|
|
40
|
+
}, 100);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should reject items when queue is full", async () => {
|
|
45
|
+
let processCount = 0;
|
|
46
|
+
let resolveProcess: (() => void) | undefined;
|
|
47
|
+
|
|
48
|
+
// Create a process function that blocks until we resolve it
|
|
49
|
+
const process = async (_elements: number[]): Promise<void> => {
|
|
50
|
+
processCount++;
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
resolveProcess = resolve;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Create queue with maxBacklog of 2
|
|
57
|
+
const queue = new MultiAsyncQueue<number>(process, 2);
|
|
58
|
+
|
|
59
|
+
// First item triggers (async) processing
|
|
60
|
+
expect(queue.tryEnqueue(1)).toBe(true);
|
|
61
|
+
expect(queue.getLength()).toBe(1); // Item still there until we yield
|
|
62
|
+
await new Promise((r) => setTimeout(r, 1));
|
|
63
|
+
expect(queue.getLength()).toBe(0); // Item moved to processing
|
|
64
|
+
|
|
65
|
+
// Add items up to backlog limit while first batch is processing
|
|
66
|
+
expect(queue.tryEnqueue(2)).toBe(true);
|
|
67
|
+
expect(queue.getLength()).toBe(1);
|
|
68
|
+
expect(queue.tryEnqueue(3)).toBe(true);
|
|
69
|
+
expect(queue.getLength()).toBe(2);
|
|
70
|
+
|
|
71
|
+
// Should reject when at max capacity
|
|
72
|
+
expect(queue.tryEnqueue(4)).toBe(false);
|
|
73
|
+
expect(queue.getLength()).toBe(2);
|
|
74
|
+
|
|
75
|
+
// Resolve the first batch processing
|
|
76
|
+
expect(resolveProcess).toBeDefined();
|
|
77
|
+
resolveProcess?.();
|
|
78
|
+
|
|
79
|
+
// Wait for next batch to start
|
|
80
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
81
|
+
|
|
82
|
+
// Now queue should have processed the first batch and be working on [2, 3]
|
|
83
|
+
expect(processCount).toBe(2);
|
|
84
|
+
|
|
85
|
+
// Should be able to add more items now
|
|
86
|
+
expect(queue.tryEnqueue(5)).toBe(true);
|
|
87
|
+
|
|
88
|
+
// Clean up by resolving the second batch
|
|
89
|
+
expect(resolveProcess).toBeDefined();
|
|
90
|
+
resolveProcess?.();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should handle empty queue correctly", async () => {
|
|
94
|
+
const processedBatches: number[][] = [];
|
|
95
|
+
|
|
96
|
+
const process = (elements: number[]): Promise<void> => {
|
|
97
|
+
processedBatches.push([...elements]);
|
|
98
|
+
return Promise.resolve();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const queue = new MultiAsyncQueue<number>(process);
|
|
102
|
+
|
|
103
|
+
// Queue starts empty
|
|
104
|
+
expect(queue.getLength()).toBe(0);
|
|
105
|
+
|
|
106
|
+
// Enqueue and process one item
|
|
107
|
+
expect(queue.tryEnqueue(1)).toBe(true);
|
|
108
|
+
|
|
109
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
110
|
+
|
|
111
|
+
expect(processedBatches).toHaveLength(1);
|
|
112
|
+
expect(processedBatches[0]).toEqual([1]);
|
|
113
|
+
|
|
114
|
+
// Queue should be empty again
|
|
115
|
+
expect(queue.getLength()).toBe(0);
|
|
116
|
+
|
|
117
|
+
// Should be able to enqueue again
|
|
118
|
+
expect(queue.tryEnqueue(2)).toBe(true);
|
|
119
|
+
|
|
120
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
121
|
+
|
|
122
|
+
expect(processedBatches).toHaveLength(2);
|
|
123
|
+
expect(processedBatches[1]).toEqual([2]);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
2
3
|
|
|
3
4
|
import { OpenAI } from "openai";
|
|
4
5
|
import {
|
|
@@ -6,7 +7,9 @@ import {
|
|
|
6
7
|
updateCompletion,
|
|
7
8
|
} from "../agent/openAILLMStreaming";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
|
11
|
+
|
|
12
|
+
const TEST_STANDARD: ChatCompletionChunk[] = [
|
|
10
13
|
{
|
|
11
14
|
id: "chatcmpl-BzBqzVs5w0kU5we3KIUN1Q4FAZXjg",
|
|
12
15
|
choices: [
|
|
@@ -34,7 +37,7 @@ const TEST_STANDARD: OpenAI.Chat.Completions.ChatCompletionChunk[] = [
|
|
|
34
37
|
},
|
|
35
38
|
];
|
|
36
39
|
|
|
37
|
-
const TEST_TRAILING_USAGE:
|
|
40
|
+
const TEST_TRAILING_USAGE: ChatCompletionChunk[] = [
|
|
38
41
|
{
|
|
39
42
|
id: "gen-1753923406-nsIKHyFRoJqkUntBnQTw",
|
|
40
43
|
choices: [
|
|
@@ -73,14 +76,63 @@ const TEST_TRAILING_USAGE: OpenAI.Chat.Completions.ChatCompletionChunk[] = [
|
|
|
73
76
|
},
|
|
74
77
|
];
|
|
75
78
|
|
|
79
|
+
const AGGREGATED_MESSAGE: OpenAI.Chat.Completions.ChatCompletion = {
|
|
80
|
+
id: "gen-1753923406-nsIKHyFRoJqkUntBnQTw",
|
|
81
|
+
choices: [
|
|
82
|
+
{
|
|
83
|
+
message: {
|
|
84
|
+
content: "test",
|
|
85
|
+
role: "assistant",
|
|
86
|
+
refusal: null,
|
|
87
|
+
tool_calls: undefined,
|
|
88
|
+
},
|
|
89
|
+
finish_reason: "stop",
|
|
90
|
+
index: 0,
|
|
91
|
+
logprobs: null,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
created: 1753923406,
|
|
95
|
+
model: "openai/gpt-4o",
|
|
96
|
+
object: "chat.completion",
|
|
97
|
+
service_tier: undefined,
|
|
98
|
+
system_fingerprint: "fp_a288987b44",
|
|
99
|
+
usage: {
|
|
100
|
+
completion_tokens: 50,
|
|
101
|
+
prompt_tokens: 271,
|
|
102
|
+
total_tokens: 321,
|
|
103
|
+
completion_tokens_details: { reasoning_tokens: 0 },
|
|
104
|
+
prompt_tokens_details: { cached_tokens: 0 },
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const TEST_TRAILING_USAGE_WITH_3_CHUNKS: ChatCompletionChunk[] = [
|
|
109
|
+
{
|
|
110
|
+
...TEST_TRAILING_USAGE[0],
|
|
111
|
+
choices: [
|
|
112
|
+
{
|
|
113
|
+
delta: { content: "test", role: "assistant" },
|
|
114
|
+
finish_reason: null,
|
|
115
|
+
index: 0,
|
|
116
|
+
logprobs: null,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
...TEST_TRAILING_USAGE[1],
|
|
122
|
+
choices: [{ ...TEST_TRAILING_USAGE[1].choices[0], finish_reason: "stop" }],
|
|
123
|
+
usage: undefined,
|
|
124
|
+
},
|
|
125
|
+
{ ...TEST_TRAILING_USAGE[1], choices: [] },
|
|
126
|
+
];
|
|
127
|
+
|
|
76
128
|
describe("OpenAI Streaming", () => {
|
|
77
|
-
it("should support standard termination", async
|
|
129
|
+
it("should support standard termination", async () => {
|
|
78
130
|
const chunks = TEST_STANDARD;
|
|
79
|
-
expect(chunks.length).
|
|
131
|
+
expect(chunks.length).toBe(2);
|
|
80
132
|
const { initMessage } = initializeCompletion(chunks[0]);
|
|
81
133
|
updateCompletion(initMessage, chunks[1]);
|
|
82
134
|
|
|
83
|
-
expect(initMessage).
|
|
135
|
+
expect(initMessage).toEqual({
|
|
84
136
|
id: "chatcmpl-BzBqzVs5w0kU5we3KIUN1Q4FAZXjg",
|
|
85
137
|
choices: [
|
|
86
138
|
{
|
|
@@ -104,39 +156,22 @@ describe("OpenAI Streaming", () => {
|
|
|
104
156
|
});
|
|
105
157
|
});
|
|
106
158
|
|
|
107
|
-
it("should support trailing usage", async
|
|
159
|
+
it("should support trailing usage", async () => {
|
|
108
160
|
const chunks = TEST_TRAILING_USAGE;
|
|
109
|
-
expect(chunks.length).
|
|
161
|
+
expect(chunks.length).toBe(2);
|
|
110
162
|
const { initMessage } = initializeCompletion(chunks[0]);
|
|
111
163
|
updateCompletion(initMessage, chunks[1]);
|
|
112
164
|
|
|
113
|
-
expect(initMessage).
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
index: 0,
|
|
125
|
-
logprobs: null,
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
created: 1753923406,
|
|
129
|
-
model: "openai/gpt-4o",
|
|
130
|
-
object: "chat.completion",
|
|
131
|
-
service_tier: undefined,
|
|
132
|
-
system_fingerprint: "fp_a288987b44",
|
|
133
|
-
usage: {
|
|
134
|
-
completion_tokens: 50,
|
|
135
|
-
prompt_tokens: 271,
|
|
136
|
-
total_tokens: 321,
|
|
137
|
-
completion_tokens_details: { reasoning_tokens: 0 },
|
|
138
|
-
prompt_tokens_details: { cached_tokens: 0 },
|
|
139
|
-
},
|
|
140
|
-
});
|
|
165
|
+
expect(initMessage).toEqual(AGGREGATED_MESSAGE);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should support trailing usage with 3 chunks", async () => {
|
|
169
|
+
const chunks = TEST_TRAILING_USAGE_WITH_3_CHUNKS;
|
|
170
|
+
expect(chunks.length).toBe(3);
|
|
171
|
+
const { initMessage } = initializeCompletion(chunks[0]);
|
|
172
|
+
updateCompletion(initMessage, chunks[1]);
|
|
173
|
+
updateCompletion(initMessage, chunks[2]);
|
|
174
|
+
|
|
175
|
+
expect(initMessage).toEqual(AGGREGATED_MESSAGE);
|
|
141
176
|
});
|
|
142
177
|
});
|
package/src/test/prompt.test.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
2
3
|
import { parsePrompt } from "../tool/commandPrompt";
|
|
3
4
|
|
|
4
5
|
describe("Prompt", () => {
|
|
5
|
-
it("should recognise commands", async
|
|
6
|
+
it("should recognise commands", async () => {
|
|
6
7
|
const expectedResults = {
|
|
7
8
|
"some text ": { msg: "some text", cmds: undefined },
|
|
8
9
|
":i image.png some text": { msg: "some text", cmds: ["i", "image.png"] },
|
|
@@ -20,7 +21,7 @@ describe("Prompt", () => {
|
|
|
20
21
|
|
|
21
22
|
for (const [prompt, expected] of Object.entries(expectedResults)) {
|
|
22
23
|
const res = parsePrompt(prompt);
|
|
23
|
-
expect(res).
|
|
24
|
+
expect(res).toEqual(expected);
|
|
24
25
|
}
|
|
25
26
|
});
|
|
26
27
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { expect } from "vitest";
|
|
2
|
+
import { SystemPromptProvider } from "../agent/promptProvider";
|
|
3
|
+
|
|
4
|
+
describe("SystemPromptProvider", () => {
|
|
5
|
+
it("correctly includes all components", function () {
|
|
6
|
+
SystemPromptProvider.setGlobalPrompt("global");
|
|
7
|
+
const pp = new SystemPromptProvider("agent");
|
|
8
|
+
pp.setFragment("frag1", "FRAG1");
|
|
9
|
+
|
|
10
|
+
expect(pp.getSystemPrompt()).eql("global\nagent\nFRAG1");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("handles update/remove of components", function () {
|
|
14
|
+
SystemPromptProvider.setGlobalPrompt("global");
|
|
15
|
+
const pp = new SystemPromptProvider("agent");
|
|
16
|
+
pp.setFragment("frag1", "FRAG1");
|
|
17
|
+
|
|
18
|
+
// Update system and frag1. Add frag2, frag3. Remove frag3.
|
|
19
|
+
|
|
20
|
+
pp.setAgentPrompt("agent2");
|
|
21
|
+
pp.setFragment("frag1", "FRAG11");
|
|
22
|
+
pp.setFragment("frag2", "FRAG2");
|
|
23
|
+
pp.setFragment("frag3", "FRAG3");
|
|
24
|
+
pp.removeFragment("frag2");
|
|
25
|
+
|
|
26
|
+
// Result should contain system, frag1, frag3. NOT frag2.
|
|
27
|
+
|
|
28
|
+
expect(pp.getSystemPrompt().startsWith("global\nagent2\n")).eql(true);
|
|
29
|
+
expect(pp.getSystemPrompt().includes("FRAG11")).eql(true);
|
|
30
|
+
expect(pp.getSystemPrompt().includes("FRAG3")).eql(true);
|
|
31
|
+
expect(pp.getSystemPrompt().includes("FRAG2")).eql(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { expect } from "vitest";
|
|
2
|
+
import { ResponseHandler } from "../chat/client/responseHandler";
|
|
3
|
+
|
|
4
|
+
type ClientA = { type: "a"; client_message_id: string; value: number };
|
|
5
|
+
type ClientB = { type: "b"; client_message_id: string; data: string };
|
|
6
|
+
type ClientMsg = ClientA | ClientB;
|
|
7
|
+
|
|
8
|
+
type ServerA = { type: "a"; client_message_id: string; value: number };
|
|
9
|
+
type ServerB = { type: "b"; data: string };
|
|
10
|
+
type ServerErr = { type: "err"; client_message_id: string; message: string };
|
|
11
|
+
type ServerMsg = ServerA | ServerB | ServerErr;
|
|
12
|
+
|
|
13
|
+
describe("ResponseHandler", () => {
|
|
14
|
+
const req: ClientMsg = {
|
|
15
|
+
type: "a",
|
|
16
|
+
client_message_id: "id_a",
|
|
17
|
+
value: 3,
|
|
18
|
+
};
|
|
19
|
+
const response: ServerMsg = {
|
|
20
|
+
type: "a",
|
|
21
|
+
client_message_id: "id_a",
|
|
22
|
+
value: 4,
|
|
23
|
+
};
|
|
24
|
+
const otherResponse: ServerMsg = {
|
|
25
|
+
type: "a",
|
|
26
|
+
client_message_id: "id_b",
|
|
27
|
+
value: 4,
|
|
28
|
+
};
|
|
29
|
+
const error: ServerMsg = {
|
|
30
|
+
type: "err",
|
|
31
|
+
client_message_id: "id_a",
|
|
32
|
+
message: "error",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
it("should resolve", async function () {
|
|
36
|
+
const rh = new ResponseHandler<ClientMsg, ServerMsg>("err", 100);
|
|
37
|
+
const responseP = rh.waitForResponse(req);
|
|
38
|
+
rh.onMessage(otherResponse);
|
|
39
|
+
rh.onMessage(response);
|
|
40
|
+
rh.onMessage(error);
|
|
41
|
+
|
|
42
|
+
expect(await responseP).eql(response);
|
|
43
|
+
|
|
44
|
+
// This (late) messages should be ignored.
|
|
45
|
+
rh.onMessage(otherResponse);
|
|
46
|
+
rh.onMessage(error);
|
|
47
|
+
rh.onMessage(response);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should handle errors", async function () {
|
|
51
|
+
const rh = new ResponseHandler<ClientMsg, ServerMsg>("err", 100);
|
|
52
|
+
const responseP = rh.waitForResponse(req);
|
|
53
|
+
rh.onMessage(otherResponse);
|
|
54
|
+
rh.onMessage(error);
|
|
55
|
+
rh.onMessage(response);
|
|
56
|
+
|
|
57
|
+
await expect(responseP).rejects.toThrow(error.message);
|
|
58
|
+
|
|
59
|
+
// This (late) messages should be ignored.
|
|
60
|
+
rh.onMessage(otherResponse);
|
|
61
|
+
rh.onMessage(error);
|
|
62
|
+
rh.onMessage(response);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should handle timeouts", async function () {
|
|
66
|
+
const rh = new ResponseHandler<ClientMsg, ServerMsg>("err", 100);
|
|
67
|
+
const responseP = rh.waitForResponse(req);
|
|
68
|
+
rh.onMessage(otherResponse);
|
|
69
|
+
rh.onMessage({ type: "a", client_message_id: "id_b", value: 4 });
|
|
70
|
+
|
|
71
|
+
await expect(responseP).rejects.toThrow();
|
|
72
|
+
|
|
73
|
+
// This (late) messages should be ignored.
|
|
74
|
+
rh.onMessage(otherResponse);
|
|
75
|
+
rh.onMessage(error);
|
|
76
|
+
rh.onMessage(response);
|
|
77
|
+
});
|
|
78
|
+
});
|