@xalia/agent 0.5.0 → 0.5.2

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 (76) hide show
  1. package/README.md +46 -7
  2. package/dist/{agent.js → agent/src/agent/agent.js} +5 -4
  3. package/dist/{agentUtils.js → agent/src/agent/agentUtils.js} +10 -9
  4. package/dist/{mcpServerManager.js → agent/src/agent/mcpServerManager.js} +2 -1
  5. package/dist/{sudoMcpServerManager.js → agent/src/agent/sudoMcpServerManager.js} +4 -4
  6. package/dist/agent/src/chat/apiKeyManager.js +23 -0
  7. package/dist/agent/src/chat/asyncQueue.js +41 -0
  8. package/dist/agent/src/chat/client.js +126 -0
  9. package/dist/agent/src/chat/conversationManager.js +173 -0
  10. package/dist/agent/src/chat/db.js +186 -0
  11. package/dist/agent/src/chat/messages.js +2 -0
  12. package/dist/agent/src/chat/server.js +158 -0
  13. package/dist/agent/src/index.js +2 -0
  14. package/dist/agent/src/test/db.test.js +73 -0
  15. package/dist/{test → agent/src/test}/imageLoad.test.js +1 -1
  16. package/dist/{test → agent/src/test}/mcpServerManager.test.js +1 -1
  17. package/dist/{test → agent/src/test}/prompt.test.js +1 -1
  18. package/dist/{test → agent/src/test}/sudoMcpServerManager.test.js +3 -3
  19. package/dist/{chat.js → agent/src/tool/agentChat.js} +5 -5
  20. package/dist/{main.js → agent/src/tool/agentMain.js} +9 -15
  21. package/dist/agent/src/tool/chatMain.js +207 -0
  22. package/dist/agent/src/tool/main.js +54 -0
  23. package/dist/{options.js → agent/src/tool/options.js} +36 -2
  24. package/dist/agent/src/utils/asyncLock.js +45 -0
  25. package/dist/supabase/database.types.js +8 -0
  26. package/eslint.config.mjs +14 -14
  27. package/package.json +9 -15
  28. package/scripts/test_chat +84 -0
  29. package/src/{agent.ts → agent/agent.ts} +22 -11
  30. package/src/{agentUtils.ts → agent/agentUtils.ts} +13 -14
  31. package/src/{mcpServerManager.ts → agent/mcpServerManager.ts} +2 -1
  32. package/src/{sudoMcpServerManager.ts → agent/sudoMcpServerManager.ts} +3 -3
  33. package/src/chat/apiKeyManager.ts +24 -0
  34. package/src/chat/asyncQueue.ts +51 -0
  35. package/src/chat/client.ts +142 -0
  36. package/src/chat/conversationManager.ts +283 -0
  37. package/src/chat/db.ts +264 -0
  38. package/src/chat/messages.ts +91 -0
  39. package/src/chat/server.ts +177 -0
  40. package/src/test/db.test.ts +103 -0
  41. package/src/test/imageLoad.test.ts +1 -1
  42. package/src/test/mcpServerManager.test.ts +1 -1
  43. package/src/test/prompt.test.ts +1 -1
  44. package/src/test/sudoMcpServerManager.test.ts +6 -10
  45. package/src/{chat.ts → tool/agentChat.ts} +26 -24
  46. package/src/{main.ts → tool/agentMain.ts} +12 -19
  47. package/src/tool/chatMain.ts +250 -0
  48. package/src/{files.ts → tool/files.ts} +1 -1
  49. package/src/tool/main.ts +25 -0
  50. package/src/{nodePlatform.ts → tool/nodePlatform.ts} +1 -1
  51. package/src/{options.ts → tool/options.ts} +40 -1
  52. package/src/utils/asyncLock.ts +43 -0
  53. package/test_data/simplecalc_profile.json +1 -1
  54. package/test_data/sudomcp_import_profile.json +1 -1
  55. package/test_data/test_script_profile.json +1 -1
  56. package/tsconfig.json +1 -1
  57. package/scripts/test_script +0 -60
  58. /package/dist/{dummyLLM.js → agent/src/agent/dummyLLM.js} +0 -0
  59. /package/dist/{iplatform.js → agent/src/agent/iplatform.js} +0 -0
  60. /package/dist/{llm.js → agent/src/agent/llm.js} +0 -0
  61. /package/dist/{openAILLM.js → agent/src/agent/openAILLM.js} +0 -0
  62. /package/dist/{openAILLMStreaming.js → agent/src/agent/openAILLMStreaming.js} +0 -0
  63. /package/dist/{tokenAuth.js → agent/src/agent/tokenAuth.js} +0 -0
  64. /package/dist/{tools.js → agent/src/agent/tools.js} +0 -0
  65. /package/dist/{files.js → agent/src/tool/files.js} +0 -0
  66. /package/dist/{nodePlatform.js → agent/src/tool/nodePlatform.js} +0 -0
  67. /package/dist/{prompt.js → agent/src/tool/prompt.js} +0 -0
  68. /package/src/{dummyLLM.ts → agent/dummyLLM.ts} +0 -0
  69. /package/src/{iplatform.ts → agent/iplatform.ts} +0 -0
  70. /package/src/{llm.ts → agent/llm.ts} +0 -0
  71. /package/src/{openAILLM.ts → agent/openAILLM.ts} +0 -0
  72. /package/src/{openAILLMStreaming.ts → agent/openAILLMStreaming.ts} +0 -0
  73. /package/src/{tokenAuth.ts → agent/tokenAuth.ts} +0 -0
  74. /package/src/{tools.ts → agent/tools.ts} +0 -0
  75. /package/src/{test/prompt.test.src → index.ts} +0 -0
  76. /package/src/{prompt.ts → tool/prompt.ts} +0 -0
package/src/chat/db.ts ADDED
@@ -0,0 +1,264 @@
1
+ import { createClient, SupabaseClient } from "@supabase/supabase-js";
2
+ import type * as supabase from "../../../supabase/database.types";
3
+ import {
4
+ AgentProfile,
5
+ ApiKey,
6
+ getLogger,
7
+ SavedAgentProfile,
8
+ } from "@xalia/xmcp/sdk";
9
+
10
+ const logger = getLogger();
11
+
12
+ export const SUPABASE_LOCAL_URL = "http://127.0.0.1:54321";
13
+ export const SUPABASE_LOCAL_KEY =
14
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiw" +
15
+ "icm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJz" +
16
+ "dJsyH-qQwv8Hdp7fsn3W0YpN81IU";
17
+
18
+ /**
19
+ * 'name' -> 'name'
20
+ * 'space/name' -> ['space', 'name']
21
+ */
22
+ export function resolveCompoundName(name: string): string | [string, string] {
23
+ const components = name.split("/");
24
+ if (components.length === 1) {
25
+ return name;
26
+ }
27
+ if (components.length !== 2) {
28
+ throw "invalid compound name";
29
+ }
30
+ return components as [string, string];
31
+ }
32
+
33
+ export type UserData = {
34
+ user_uuid: supabase.Tables<"api_keys">["user_uuid"];
35
+ nickname: supabase.Tables<"users">["nickname"];
36
+ };
37
+
38
+ export class Database {
39
+ private client: SupabaseClient<Database>;
40
+
41
+ constructor(supabaseUrl: string, supabaseKey: string) {
42
+ this.client = createClient<Database>(supabaseUrl, supabaseKey);
43
+ }
44
+
45
+ async getUserDataFromApiKey(apiKey: string): Promise<UserData | undefined> {
46
+ const { data, error } = await this.client
47
+ .from("api_keys")
48
+ .select("user_uuid, users ( nickname )")
49
+ .eq("api_key", apiKey)
50
+ .maybeSingle<{
51
+ user_uuid: supabase.Tables<"api_keys">["user_uuid"];
52
+ users: {
53
+ nickname: supabase.Tables<"users">["nickname"];
54
+ };
55
+ }>();
56
+
57
+ logger.debug(
58
+ `[getUserDataFromApiKey]: got ${JSON.stringify({ data, error })}`
59
+ );
60
+
61
+ if (error) {
62
+ throw error;
63
+ }
64
+
65
+ if (data === null) {
66
+ return undefined;
67
+ }
68
+
69
+ return {
70
+ user_uuid: data.user_uuid,
71
+ nickname: data.users.nickname || `user ${data.user_uuid}`,
72
+ };
73
+ }
74
+
75
+ async createUser(
76
+ user_uuid: string,
77
+ email: string,
78
+ nickname: string,
79
+ timezone?: string
80
+ ): Promise<void> {
81
+ const payload: supabase.TablesInsert<"users"> = {
82
+ uuid: user_uuid,
83
+ email,
84
+ nickname,
85
+ timezone: timezone || "UTC",
86
+ };
87
+
88
+ const { error } = await this.client.from("users").insert(payload);
89
+ if (error) {
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ async addApiKey(
95
+ user_uuid: string,
96
+ api_key: string,
97
+ name: string,
98
+ scopes: string[],
99
+ is_default: boolean = false
100
+ ): Promise<ApiKey> {
101
+ const payload: supabase.TablesInsert<"api_keys"> = {
102
+ user_uuid,
103
+ api_key,
104
+ name,
105
+ scopes,
106
+ is_default,
107
+ };
108
+ const { data, error } = await this.client
109
+ .from("api_keys")
110
+ .insert(payload)
111
+ .select("*")
112
+ .maybeSingle();
113
+ if (error) {
114
+ throw error;
115
+ }
116
+
117
+ return data;
118
+ }
119
+
120
+ async getSavedAgentProfileById(
121
+ agentProfileId: string
122
+ ): Promise<SavedAgentProfile | undefined> {
123
+ const { data, error } = await this.client
124
+ .from("agent_profiles")
125
+ .select("*")
126
+ .eq("uuid", agentProfileId)
127
+ .maybeSingle<supabase.Tables<"agent_profiles">>();
128
+ if (error) {
129
+ throw error;
130
+ }
131
+
132
+ return data
133
+ ? SavedAgentProfile.fromJSONObj(data as Record<string, unknown>)
134
+ : undefined;
135
+ }
136
+
137
+ async getSavedAgentProfileByName(
138
+ user_uuid: string,
139
+ agentProfileName: string
140
+ ): Promise<SavedAgentProfile | undefined> {
141
+ const { data, error } = await this.client
142
+ .from("agent_profiles")
143
+ .select("*")
144
+ .eq("user_uuid", user_uuid)
145
+ .eq("profile_name", agentProfileName)
146
+ .maybeSingle<supabase.Tables<"agent_profiles">>();
147
+ if (error) {
148
+ throw error;
149
+ }
150
+
151
+ return data
152
+ ? SavedAgentProfile.fromJSONObj(data as Record<string, unknown>)
153
+ : undefined;
154
+ }
155
+
156
+ async getAgentProfileById(
157
+ agentProfileId: string
158
+ ): Promise<AgentProfile | undefined> {
159
+ const { data, error } = await this.client
160
+ .from("agent_profiles")
161
+ .select("profile")
162
+ .eq("uuid", agentProfileId)
163
+ .maybeSingle<{
164
+ profile: supabase.Tables<"agent_profiles">["profile"];
165
+ }>();
166
+ if (error) {
167
+ throw error;
168
+ }
169
+ return data
170
+ ? AgentProfile.fromJSONObj(data.profile as Record<string, unknown>)
171
+ : undefined;
172
+ }
173
+
174
+ async createAgentProfile(
175
+ user_uuid: string,
176
+ profileName: string,
177
+ profile: AgentProfile
178
+ ): Promise<string | undefined> {
179
+ const payload: supabase.TablesInsert<"agent_profiles"> = {
180
+ profile: profile as unknown as supabase.Json,
181
+ user_uuid,
182
+ profile_name: profileName,
183
+ };
184
+ const { data, error } = await this.client
185
+ .from("agent_profiles")
186
+ .upsert(payload)
187
+ .select("uuid");
188
+ if (error) {
189
+ throw error;
190
+ }
191
+
192
+ if (!data || !data[0] || !data[0].uuid) {
193
+ return undefined;
194
+ }
195
+
196
+ return data[0].uuid;
197
+ }
198
+
199
+ async clearAgentProfiles(): Promise<void> {
200
+ await this.client.from("agent_profiles").delete().neq("uuid", "");
201
+ }
202
+
203
+ // TODO: is there a session model?
204
+
205
+ async getSessionById(
206
+ session_uuid: string
207
+ ): Promise<supabase.Tables<"sessions"> | undefined> {
208
+ const { data, error } = await this.client
209
+ .from("sessions")
210
+ .select("*")
211
+ .eq("uuid", session_uuid)
212
+ .maybeSingle();
213
+ if (error) {
214
+ throw error;
215
+ }
216
+ return data;
217
+ }
218
+
219
+ async getSessionByName(
220
+ user_uuid: string,
221
+ session_name: string
222
+ ): Promise<supabase.Tables<"sessions"> | undefined> {
223
+ const { data, error } = await this.client
224
+ .from("sessions")
225
+ .select("*")
226
+ .eq("user_uuid", user_uuid)
227
+ .eq("title", session_name)
228
+ .maybeSingle();
229
+ if (error) {
230
+ throw error;
231
+ }
232
+ return data;
233
+ }
234
+
235
+ async createSession(
236
+ user_uuid: string,
237
+ title: string,
238
+ agentProfileId: string
239
+ ): Promise<string | undefined> {
240
+ const payload: supabase.TablesInsert<"sessions"> = {
241
+ agent_profile_uuid: agentProfileId,
242
+ user_uuid,
243
+ title: title,
244
+ conversation: [],
245
+ };
246
+ const { data, error } = await this.client
247
+ .from("sessions")
248
+ .upsert(payload)
249
+ .select("uuid");
250
+ if (error) {
251
+ throw error;
252
+ }
253
+
254
+ if (!data || !data[0] || !data[0].uuid) {
255
+ return undefined;
256
+ }
257
+
258
+ return data[0].uuid;
259
+ }
260
+
261
+ async clearSessions(): Promise<void> {
262
+ await this.client.from("sessions").delete().neq("uuid", "");
263
+ }
264
+ }
@@ -0,0 +1,91 @@
1
+ import {
2
+ ChatCompletionMessageParam,
3
+ ChatCompletionAssistantMessageParam,
4
+ ChatCompletionToolMessageParam,
5
+ ChatCompletionMessageToolCall,
6
+ } from "openai/resources.mjs";
7
+
8
+ /**
9
+ * Message from a user to the server
10
+ */
11
+ export type ClientUserMessage = {
12
+ type: "msg";
13
+ message: string;
14
+ };
15
+
16
+ /**
17
+ * (from server) Chat history
18
+ */
19
+ export type ServerHistory = {
20
+ type: "history";
21
+ /// Conversation history in the form we expect. The `name` attribute of
22
+ /// `ChatCompletionUserMessageParam` (role: "user") holds the original
23
+ /// message sender.
24
+ messages: ChatCompletionMessageParam[];
25
+ };
26
+
27
+ export type ServerUserJoined = {
28
+ type: "user_joined";
29
+ user: string;
30
+ };
31
+
32
+ export type ServerUserLeft = {
33
+ type: "user_left";
34
+ user: string;
35
+ };
36
+
37
+ /**
38
+ * Message from the server, informing us of a message in the chat history
39
+ */
40
+ export type ServerUserMessage = {
41
+ type: "user_msg";
42
+ message_id: string;
43
+ message: string;
44
+ from: string;
45
+ };
46
+
47
+ export type ServerAgentMessage = {
48
+ type: "agent_msg";
49
+ message_id: string;
50
+ message: ChatCompletionAssistantMessageParam;
51
+ };
52
+
53
+ export type ServerAgentMessageChunk = {
54
+ type: "agent_msg_chunk";
55
+ message_id: string;
56
+ message: string;
57
+ end: boolean;
58
+ };
59
+
60
+ /**
61
+ * For information only (to keep the chat window consistent).
62
+ */
63
+ export type ServerToolCall = {
64
+ type: "agent_tool_call";
65
+ message_id: string;
66
+ message: ChatCompletionMessageToolCall;
67
+ };
68
+
69
+ /**
70
+ * For information only (to keep the chat window consistent)
71
+ */
72
+ export type ServerToolCallResult = {
73
+ type: "tool_call_result";
74
+ message_id: string;
75
+ message: ChatCompletionToolMessageParam;
76
+ };
77
+
78
+ export type ServerTyping = {
79
+ type: "typing";
80
+ from: string;
81
+ };
82
+
83
+ export type ClientToServer = ClientUserMessage;
84
+
85
+ export type ServerToClient =
86
+ | ServerUserJoined
87
+ | ServerUserLeft
88
+ | ServerUserMessage
89
+ | ServerAgentMessage
90
+ | ServerAgentMessageChunk
91
+ | ServerTyping;
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ // -*- typescript -*-
3
+
4
+ import type * as supabase from "../../../supabase/database.types";
5
+ import * as dotenv from "dotenv";
6
+ import { getLogger } from "@xalia/xmcp/sdk";
7
+ import * as ws from "ws";
8
+ import { IncomingMessage } from "http";
9
+ import { parse } from "url";
10
+ import { ConversationManager } from "./conversationManager";
11
+ import { Database, UserData, resolveCompoundName } from "./db";
12
+ import { ApiKeyManager } from "./apiKeyManager";
13
+ import { ParsedUrlQuery } from "querystring";
14
+
15
+ dotenv.config();
16
+
17
+ const logger = getLogger();
18
+
19
+ /**
20
+ * Try as id, then by user/name and return the AgentProfile uuid.
21
+ */
22
+ async function resolveAgentProfileId(
23
+ db: Database,
24
+ userData: UserData,
25
+ agentProfileIdentifier: string
26
+ ): Promise<string | undefined> {
27
+ let ap = await db.getSavedAgentProfileById(agentProfileIdentifier);
28
+ logger.debug(`[resolveAgentProfileId]: by id: {JSON.stringify(ap)}`);
29
+ if (ap) {
30
+ return agentProfileIdentifier;
31
+ }
32
+
33
+ ap = await db.getSavedAgentProfileByName(
34
+ userData.user_uuid,
35
+ agentProfileIdentifier
36
+ );
37
+ logger.debug(`[resolveAgentProfileId]: by name: {JSON.stringify(ap)}`);
38
+ if (ap) {
39
+ return ap.uuid;
40
+ }
41
+
42
+ logger.debug("[resolveAgentProfileId]: agent profile not found");
43
+ return undefined;
44
+ }
45
+
46
+ async function resolveSessionIdFromIdentifier(
47
+ db: Database,
48
+ userData: UserData,
49
+ sessionIdentifier: string
50
+ ): Promise<string | undefined> {
51
+ let session: supabase.Tables<"sessions"> | undefined = undefined;
52
+ const compound = resolveCompoundName(sessionIdentifier);
53
+ if (typeof compound === "string") {
54
+ // Interpret as an id, or as a number under the current user.
55
+ session = await db.getSessionById(compound);
56
+ if (!session) {
57
+ session = await db.getSessionByName(userData.user_uuid, compound);
58
+ }
59
+ } else {
60
+ session = await db.getSessionByName(compound[0], compound[1]);
61
+ }
62
+
63
+ return session?.uuid;
64
+ }
65
+
66
+ /**
67
+ * Expect parameters to be either:
68
+ * session_id - uuid, name, user/name
69
+ * agent_profile_id (optional) - uuid, name
70
+ *
71
+ * If session_idd resolves to an existing session, it is used (and
72
+ * agent_profile_id is ignored). Otherwise, session_id is used as the name of
73
+ * a new session to create using `agent_profile_id`. The session UUID is
74
+ * returned in either case.
75
+ */
76
+ async function findOrCreateSession(
77
+ db: Database,
78
+ userData: UserData,
79
+ query: ParsedUrlQuery
80
+ ): Promise<string | undefined> {
81
+ logger.debug(`[findOrCreateSession]: query: ${JSON.stringify(query)}`);
82
+ if (!query.session_id || typeof query.session_id !== "string") {
83
+ throw "session_id invalid or not present";
84
+ }
85
+
86
+ const sessionId = await resolveSessionIdFromIdentifier(
87
+ db,
88
+ userData,
89
+ query.session_id
90
+ );
91
+ if (sessionId) {
92
+ return sessionId;
93
+ }
94
+
95
+ const agentProfileIdParam = query.agent_profile_id;
96
+ logger.debug(`[findOrCreateSession]: agent: ${agentProfileIdParam}`);
97
+ if (!agentProfileIdParam) {
98
+ throw "no existing session, and no agent_profile_id given";
99
+ }
100
+ if (typeof agentProfileIdParam !== "string") {
101
+ throw "no existing session and invalid agent_profile_id";
102
+ }
103
+
104
+ logger.debug(`[findOrCreateSession]: creating session: ${query.session_id}`);
105
+ const agentProfileId = await resolveAgentProfileId(
106
+ db,
107
+ userData,
108
+ agentProfileIdParam
109
+ );
110
+ logger.debug(
111
+ `[findOrCreateSession]: resolved agentProfileId: ${agentProfileId}`
112
+ );
113
+ if (!agentProfileId) {
114
+ throw `no agent profile: ${agentProfileIdParam}`;
115
+ }
116
+
117
+ return db.createSession(userData.user_uuid, query.session_id, agentProfileId);
118
+ }
119
+
120
+ export async function runServer(
121
+ port: number,
122
+ supabaseUrl: string,
123
+ supabaseKey: string,
124
+ llmUrl: string,
125
+ xmcpUrl: string
126
+ ): Promise<ws.Server> {
127
+ return new Promise((r, _e) => {
128
+ const wss = new ws.Server({ port });
129
+ const db = new Database(supabaseUrl, supabaseKey);
130
+ const apiKeyManager = new ApiKeyManager(db);
131
+ const cm = new ConversationManager(db, llmUrl, xmcpUrl);
132
+
133
+ wss.on("connection", async (ws: ws.WebSocket, req: IncomingMessage) => {
134
+ try {
135
+ logger.info(`[server] connection: ${req}`);
136
+
137
+ // Check header
138
+
139
+ logger.info(`[server] headers: ${JSON.stringify(req.headers)}`);
140
+ const apiKey = req.headers["sec-websocket-protocol"];
141
+ if (!apiKey) {
142
+ throw "empty api key";
143
+ }
144
+
145
+ const userData = await apiKeyManager.verifyApiKey(apiKey);
146
+ if (!userData) {
147
+ throw "invalid api key";
148
+ }
149
+
150
+ // Get sessionId
151
+
152
+ const { query } = parse(req.url || "", true);
153
+ const sessionId = await findOrCreateSession(db, userData, query);
154
+ logger.debug(`resolved session id: ${sessionId}`);
155
+ if (!sessionId) {
156
+ throw "failed to find/create session";
157
+ }
158
+
159
+ // Associate the ws, username with the conversation
160
+
161
+ await cm.join(sessionId, apiKey, apiKey, userData, ws);
162
+ } catch (e) {
163
+ if (typeof e === "string") {
164
+ logger.warn(`[server]: error: ${e}`);
165
+ ws.close(4000, e);
166
+ } else {
167
+ logger.error(`other error: ${JSON.stringify(e)}`);
168
+ throw e;
169
+ }
170
+ }
171
+ });
172
+
173
+ logger.info(`[server] started: ws://localhost:${port}/`);
174
+
175
+ r(wss);
176
+ });
177
+ }
@@ -0,0 +1,103 @@
1
+ import { expect } from "chai";
2
+ import { Database, SUPABASE_LOCAL_KEY, SUPABASE_LOCAL_URL } from "../chat/db";
3
+ import { AgentProfile } from "../agent/agent";
4
+ import { strict as assert } from "assert";
5
+ import { ApiClient } from "@xalia/xmcp/sdk";
6
+ import { LOCAL_SERVER_URL } from "../agent/sudoMcpServerManager";
7
+
8
+ function getLocalDB(): Database {
9
+ return new Database(SUPABASE_LOCAL_URL, SUPABASE_LOCAL_KEY);
10
+ }
11
+
12
+ const AGENT_PROFILE: AgentProfile = {
13
+ model: undefined,
14
+ system_prompt: "You are an unhelpful agent",
15
+ mcp_settings: {},
16
+ };
17
+
18
+ async function createDummyUser(db: Database): Promise<void> {
19
+ const apiClient = new ApiClient(LOCAL_SERVER_URL, "dummy_key");
20
+ const user = await apiClient.getUserBrief("dummy_user");
21
+ if (!user) {
22
+ await db.createUser("dummy_user", "a@b.com", "Dummy User");
23
+ await db.addApiKey("dummy_user", "dummy_key", "default", [], true);
24
+ if (!apiClient.getUserBrief("dummy_user")) {
25
+ throw "unable to create dummy user";
26
+ }
27
+ }
28
+ }
29
+
30
+ describe("DB", () => {
31
+ it("should get existing user", async function () {
32
+ const db = getLocalDB();
33
+ await createDummyUser(db);
34
+ const dummyUser = await db.getUserDataFromApiKey("dummy_key");
35
+ expect(dummyUser).eql({
36
+ user_uuid: "dummy_user",
37
+ nickname: "Dummy User",
38
+ });
39
+ });
40
+
41
+ it("should return undefined for non-existant user", async function () {
42
+ const db = getLocalDB();
43
+ const dummyUser = await db.getUserDataFromApiKey("no_such_key");
44
+ expect(dummyUser).to.equal(undefined);
45
+ });
46
+
47
+ it("should create and retrieve agent profiles", async function () {
48
+ const db = getLocalDB();
49
+
50
+ await db.clearAgentProfiles();
51
+ const agentProfileId = await db.createAgentProfile(
52
+ "dummy_user",
53
+ "test_profile",
54
+ AGENT_PROFILE
55
+ );
56
+ expect(agentProfileId).to.not.equal(undefined);
57
+ assert(agentProfileId);
58
+
59
+ const savedAgentProfile =
60
+ (await db.getSavedAgentProfileById(agentProfileId))!;
61
+ const savedAgentProfileByName = (await db.getSavedAgentProfileByName(
62
+ "dummy_user",
63
+ "test_profile"
64
+ ))!;
65
+ const agentProfile = (await db.getAgentProfileById(agentProfileId))!;
66
+
67
+ expect(savedAgentProfile.uuid).eql(agentProfileId);
68
+ expect(savedAgentProfile.profile).eql(AGENT_PROFILE);
69
+ expect(savedAgentProfile?.user_uuid).eql("dummy_user");
70
+ expect(savedAgentProfile?.profile_name).eql("test_profile");
71
+
72
+ expect(savedAgentProfileByName).eql(savedAgentProfile);
73
+
74
+ expect(agentProfile).eql(AGENT_PROFILE);
75
+ });
76
+
77
+ it("should create and retrieve sessions", async function () {
78
+ const db = getLocalDB();
79
+
80
+ await db.clearAgentProfiles();
81
+ await db.clearSessions();
82
+ const agentProfileId = await db.createAgentProfile(
83
+ "dummy_user",
84
+ "test_profile",
85
+ AGENT_PROFILE
86
+ );
87
+ assert(agentProfileId);
88
+
89
+ const sessionId = await db.createSession(
90
+ "dummy_user",
91
+ "test_session",
92
+ agentProfileId
93
+ );
94
+ assert(sessionId);
95
+ const session = (await db.getSessionById(sessionId))!;
96
+
97
+ expect(session.agent_profile_uuid).eql(agentProfileId);
98
+ expect(session.conversation).eql([]);
99
+ expect(session.title).eql("test_session");
100
+ expect(session.user_uuid).eql("dummy_user");
101
+ expect(session.uuid).eql(sessionId);
102
+ });
103
+ });
@@ -1,5 +1,5 @@
1
1
  import { expect } from "chai";
2
- import { loadImageB64OrUndefined } from "../files";
2
+ import { loadImageB64OrUndefined } from "../tool/files";
3
3
  import { strict as assert } from "assert";
4
4
 
5
5
  describe("Image loading", () => {
@@ -3,7 +3,7 @@ import {
3
3
  computeQualifiedName,
4
4
  McpServerManager,
5
5
  splitQualifiedName,
6
- } from "../mcpServerManager";
6
+ } from "../agent/mcpServerManager";
7
7
 
8
8
  // Handle automatically closing at the end of tests.
9
9
 
@@ -1,5 +1,5 @@
1
1
  import { expect } from "chai";
2
- import { parsePrompt } from "../prompt";
2
+ import { parsePrompt } from "../tool/prompt";
3
3
 
4
4
  describe("Prompt", () => {
5
5
  it("should recognise commands", async function () {
@@ -1,20 +1,16 @@
1
1
  import { expect } from "chai";
2
- import { McpServerManager } from "../mcpServerManager";
3
- import {
4
- SudoMcpServerManager,
5
- LOCAL_SERVER_URL,
6
- } from "../sudoMcpServerManager";
7
2
  import chalk from "chalk";
8
3
  import { Tool } from "@modelcontextprotocol/sdk/types.js";
9
4
 
10
- let managers: [McpServerManager, SudoMcpServerManager] | undefined = undefined;
5
+ import { McpServerManager } from "../agent/mcpServerManager";
6
+ import { SkillManager, LOCAL_SERVER_URL } from "../agent/sudoMcpServerManager";
11
7
 
12
- async function getServerManagers(): Promise<
13
- [McpServerManager, SudoMcpServerManager]
14
- > {
8
+ let managers: [McpServerManager, SkillManager] | undefined = undefined;
9
+
10
+ async function getServerManagers(): Promise<[McpServerManager, SkillManager]> {
15
11
  if (!managers) {
16
12
  const tm = new McpServerManager();
17
- const sm = await SudoMcpServerManager.initialize(
13
+ const sm = await SkillManager.initialize(
18
14
  tm,
19
15
  (_url: string) => {
20
16
  throw "unexpected call to openUrl";