@xalia/agent 0.6.0 → 0.6.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.
- package/dist/agent/src/agent/agent.js +103 -54
- package/dist/agent/src/agent/agentUtils.js +22 -21
- package/dist/agent/src/agent/compressingContextManager.js +3 -2
- package/dist/agent/src/agent/dummyLLM.js +1 -3
- package/dist/agent/src/agent/imageGenLLM.js +67 -0
- package/dist/agent/src/agent/imageGenerator.js +43 -0
- package/dist/agent/src/agent/llm.js +27 -0
- package/dist/agent/src/agent/mcpServerManager.js +18 -6
- package/dist/agent/src/agent/nullAgentEventHandler.js +6 -0
- package/dist/agent/src/agent/openAILLM.js +3 -3
- package/dist/agent/src/agent/openAILLMStreaming.js +41 -6
- package/dist/agent/src/chat/client/chatClient.js +84 -13
- package/dist/agent/src/chat/client/sessionClient.js +47 -6
- package/dist/agent/src/chat/client/sessionFiles.js +102 -0
- package/dist/agent/src/chat/data/apiKeyManager.js +38 -7
- package/dist/agent/src/chat/data/database.js +83 -70
- package/dist/agent/src/chat/data/dbSessionFileModels.js +49 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +76 -0
- package/dist/agent/src/chat/data/dbSessionMessages.js +57 -0
- package/dist/agent/src/chat/data/mimeTypes.js +44 -0
- package/dist/agent/src/chat/protocol/messages.js +21 -0
- package/dist/agent/src/chat/server/chatContextManager.js +14 -7
- package/dist/agent/src/chat/server/connectionManager.js +14 -36
- package/dist/agent/src/chat/server/connectionManager.test.js +2 -16
- package/dist/agent/src/chat/server/conversation.js +69 -45
- package/dist/agent/src/chat/server/imageGeneratorTools.js +111 -0
- package/dist/agent/src/chat/server/openSession.js +205 -43
- package/dist/agent/src/chat/server/server.js +5 -8
- package/dist/agent/src/chat/server/sessionFileManager.js +171 -38
- package/dist/agent/src/chat/server/sessionRegistry.js +199 -32
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +12 -11
- package/dist/agent/src/chat/server/tools.js +27 -6
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +9 -1
- package/dist/agent/src/test/agent.test.js +15 -11
- package/dist/agent/src/test/chatContextManager.test.js +4 -0
- package/dist/agent/src/test/clientServerConnection.test.js +2 -2
- package/dist/agent/src/test/db.test.js +33 -70
- package/dist/agent/src/test/dbSessionFiles.test.js +179 -0
- package/dist/agent/src/test/dbSessionMessages.test.js +67 -0
- package/dist/agent/src/test/dbTestTools.js +6 -5
- package/dist/agent/src/test/imageLoad.test.js +1 -1
- package/dist/agent/src/test/mcpServerManager.test.js +1 -1
- package/dist/agent/src/test/multiAsyncQueue.test.js +50 -0
- package/dist/agent/src/test/testTools.js +12 -0
- package/dist/agent/src/tool/agentChat.js +25 -6
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +113 -4
- package/dist/agent/src/tool/commandPrompt.js +7 -3
- package/dist/agent/src/tool/files.js +23 -15
- package/dist/agent/src/tool/options.js +2 -2
- package/package.json +1 -1
- package/scripts/test_chat +124 -66
- package/src/agent/agent.ts +145 -38
- package/src/agent/agentUtils.ts +27 -21
- package/src/agent/compressingContextManager.ts +5 -4
- package/src/agent/context.ts +1 -1
- package/src/agent/dummyLLM.ts +1 -3
- package/src/agent/iAgentEventHandler.ts +15 -2
- package/src/agent/imageGenLLM.ts +99 -0
- package/src/agent/imageGenerator.ts +60 -0
- package/src/agent/llm.ts +128 -4
- package/src/agent/mcpServerManager.ts +26 -7
- package/src/agent/nullAgentEventHandler.ts +6 -0
- package/src/agent/openAILLM.ts +3 -8
- package/src/agent/openAILLMStreaming.ts +60 -14
- package/src/chat/client/chatClient.ts +119 -14
- package/src/chat/client/sessionClient.ts +75 -9
- package/src/chat/client/sessionFiles.ts +145 -0
- package/src/chat/data/apiKeyManager.ts +55 -7
- package/src/chat/data/dataModels.ts +16 -7
- package/src/chat/data/database.ts +107 -92
- package/src/chat/data/dbSessionFileModels.ts +91 -0
- package/src/chat/data/dbSessionFiles.ts +99 -0
- package/src/chat/data/dbSessionMessages.ts +68 -0
- package/src/chat/data/mimeTypes.ts +58 -0
- package/src/chat/protocol/messages.ts +127 -13
- package/src/chat/server/chatContextManager.ts +36 -13
- package/src/chat/server/connectionManager.test.ts +1 -22
- package/src/chat/server/connectionManager.ts +18 -53
- package/src/chat/server/conversation.ts +96 -57
- package/src/chat/server/imageGeneratorTools.ts +138 -0
- package/src/chat/server/openSession.ts +287 -49
- package/src/chat/server/server.ts +5 -11
- package/src/chat/server/sessionFileManager.ts +223 -63
- package/src/chat/server/sessionRegistry.ts +285 -41
- package/src/chat/server/test-utils/mockFactories.ts +13 -13
- package/src/chat/server/tools.ts +43 -8
- package/src/chat/utils/agentSessionMap.ts +2 -2
- package/src/chat/utils/multiAsyncQueue.ts +11 -1
- package/src/test/agent.test.ts +23 -14
- package/src/test/chatContextManager.test.ts +7 -2
- package/src/test/clientServerConnection.test.ts +3 -3
- package/src/test/compressingContextManager.test.ts +1 -1
- package/src/test/context.test.ts +2 -1
- package/src/test/conversation.test.ts +1 -1
- package/src/test/db.test.ts +41 -83
- package/src/test/dbSessionFiles.test.ts +258 -0
- package/src/test/dbSessionMessages.test.ts +85 -0
- package/src/test/dbTestTools.ts +9 -5
- package/src/test/imageLoad.test.ts +2 -2
- package/src/test/mcpServerManager.test.ts +3 -1
- package/src/test/multiAsyncQueue.test.ts +58 -0
- package/src/test/testTools.ts +15 -1
- package/src/tool/agentChat.ts +35 -7
- package/src/tool/agentMain.ts +7 -7
- package/src/tool/chatMain.ts +126 -5
- package/src/tool/commandPrompt.ts +10 -5
- package/src/tool/files.ts +30 -13
- package/src/tool/options.ts +1 -1
- package/test_data/dummyllm_script_image_gen.json +19 -0
- package/test_data/dummyllm_script_invoke_image_gen_tool.json +30 -0
- package/test_data/image_gen_test_profile.json +5 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { strict as assert } from "assert";
|
|
3
|
+
|
|
4
|
+
import { getLogger } from "@xalia/xmcp/sdk";
|
|
5
|
+
import { SessionFileDescriptor } from "../data/dbSessionFileModels";
|
|
6
|
+
import {
|
|
7
|
+
ClientToServer,
|
|
8
|
+
ServerSessionFileContent,
|
|
9
|
+
ServerSessionFileMessage,
|
|
10
|
+
} from "../protocol/messages";
|
|
11
|
+
import { ResponseHandler } from "./responseHandler";
|
|
12
|
+
import { IMessageSender } from "./connection";
|
|
13
|
+
|
|
14
|
+
const logger = getLogger();
|
|
15
|
+
|
|
16
|
+
type ClientRequestContent = Extract<
|
|
17
|
+
ClientToServer,
|
|
18
|
+
{ type: "session_file_get_content" }
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
/// Object for the UI to use to interact with the FileManager. If the UI
|
|
22
|
+
/// receives ServerSessionFileChanged or ServerSessionFileDeleted then it
|
|
23
|
+
/// should call `listFiles()` to get the new list of files, and optionally
|
|
24
|
+
/// request the latest content via `getFileContent`.
|
|
25
|
+
export class SessionFiles {
|
|
26
|
+
readonly sessionUUID: string;
|
|
27
|
+
readonly descriptors: Map<string, SessionFileDescriptor>;
|
|
28
|
+
readonly responseHandler: ResponseHandler<
|
|
29
|
+
ClientRequestContent,
|
|
30
|
+
ServerSessionFileContent
|
|
31
|
+
>;
|
|
32
|
+
readonly sender: IMessageSender<ClientToServer>;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
sessionUUID: string,
|
|
36
|
+
initialFileList: SessionFileDescriptor[],
|
|
37
|
+
sender: IMessageSender<ClientToServer>
|
|
38
|
+
) {
|
|
39
|
+
this.sessionUUID = sessionUUID;
|
|
40
|
+
this.descriptors = new Map(initialFileList.map((d) => [d.name, d]));
|
|
41
|
+
this.responseHandler = new ResponseHandler(undefined);
|
|
42
|
+
this.sender = sender;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
listFiles(): SessionFileDescriptor[] {
|
|
46
|
+
return Array.from(this.descriptors.values());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Retrieve file contents.
|
|
51
|
+
*/
|
|
52
|
+
async getFileContent(name: string): Promise<string> {
|
|
53
|
+
const msg: ClientToServer = {
|
|
54
|
+
type: "session_file_get_content",
|
|
55
|
+
session_id: this.sessionUUID,
|
|
56
|
+
name,
|
|
57
|
+
client_message_id: uuidv4(),
|
|
58
|
+
};
|
|
59
|
+
this.sender.send(msg);
|
|
60
|
+
const response = await this.responseHandler.waitForResponse(msg);
|
|
61
|
+
if (response.name !== name) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`invalid name for file ${name}: ${JSON.stringify(response)}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return response.data_url;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
deleteFile(name: string): void {
|
|
70
|
+
const msg: ClientToServer = {
|
|
71
|
+
type: "session_file_delete",
|
|
72
|
+
session_id: this.sessionUUID,
|
|
73
|
+
name,
|
|
74
|
+
client_message_id: uuidv4(),
|
|
75
|
+
};
|
|
76
|
+
this.sender.send(msg);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
putFileContent(
|
|
80
|
+
name: string | undefined,
|
|
81
|
+
summary: string | undefined,
|
|
82
|
+
data_url: string
|
|
83
|
+
): void {
|
|
84
|
+
// TODO: eventually, we could wait for a response which includes an AI
|
|
85
|
+
// assigned name.
|
|
86
|
+
assert(name, "for now, uploaded content must have a name");
|
|
87
|
+
|
|
88
|
+
const msg: ClientToServer = {
|
|
89
|
+
type: "session_file_put_content",
|
|
90
|
+
session_id: this.sessionUUID,
|
|
91
|
+
name,
|
|
92
|
+
summary,
|
|
93
|
+
data_url,
|
|
94
|
+
client_message_id: uuidv4(),
|
|
95
|
+
};
|
|
96
|
+
this.sender.send(msg);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onMessage(msg: ServerSessionFileMessage) {
|
|
100
|
+
logger.debug(`[SessionFiles.onMessage]: msg: ${JSON.stringify(msg)}`);
|
|
101
|
+
switch (msg.type) {
|
|
102
|
+
case "session_file_changed":
|
|
103
|
+
this.descriptors.set(msg.descriptor.name, msg.descriptor);
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case "session_file_deleted":
|
|
107
|
+
this.descriptors.delete(msg.name);
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case "session_file_content":
|
|
111
|
+
this.responseHandler.onMessage(msg);
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
default: {
|
|
115
|
+
const _: never = msg;
|
|
116
|
+
const msgStr = JSON.stringify(msg);
|
|
117
|
+
throw new Error(`[SessionFiles.onMessage] invalid message: ${msgStr}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
decodeSessionFileUrl(url: string): {
|
|
123
|
+
session_uuid: string;
|
|
124
|
+
name: string;
|
|
125
|
+
} {
|
|
126
|
+
const u = new URL(url);
|
|
127
|
+
if (u.protocol !== "file+session:") {
|
|
128
|
+
throw new Error(`unexpected protocol ${u.protocol} (file+session)`);
|
|
129
|
+
}
|
|
130
|
+
if (u.port || u.search || u.hash) {
|
|
131
|
+
throw new Error("badly formed session file url: ${url}");
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
session_uuid: u.host || this.sessionUUID,
|
|
135
|
+
name: removeLeadingSlashes(u.pathname),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function removeLeadingSlashes(name: string): string {
|
|
141
|
+
while (name.startsWith("/")) {
|
|
142
|
+
name = name.slice(1);
|
|
143
|
+
}
|
|
144
|
+
return name;
|
|
145
|
+
}
|
|
@@ -6,23 +6,71 @@ import { getLogger } from "@xalia/xmcp/sdk";
|
|
|
6
6
|
|
|
7
7
|
const logger = getLogger();
|
|
8
8
|
|
|
9
|
+
const API_KEY_ALPHABET =
|
|
10
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
11
|
+
|
|
9
12
|
export class ApiKeyManager {
|
|
10
13
|
constructor(private db: Database) {}
|
|
11
14
|
|
|
15
|
+
public static PREFIX = "xmcp";
|
|
16
|
+
|
|
12
17
|
public async verifyApiKey(apiKey: string): Promise<UserData | undefined> {
|
|
13
18
|
// TODO: Cache this
|
|
14
19
|
logger.info(`[ApiKeyManager] Verifying API key: ${apiKey}`);
|
|
15
20
|
const userInfo = await this.db.getUserDataFromApiKey(apiKey);
|
|
16
21
|
logger.info(`[ApiKeyManager] User info: ${JSON.stringify(userInfo)}`);
|
|
17
22
|
return userInfo;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a standard api key of the form:
|
|
27
|
+
*
|
|
28
|
+
* <prefix>_<[A-Z][a-z][0-9]+....>
|
|
29
|
+
*
|
|
30
|
+
* matching the format used in the mcppro backend
|
|
31
|
+
*/
|
|
32
|
+
public static createApiKey(
|
|
33
|
+
prefix: string = ApiKeyManager.PREFIX,
|
|
34
|
+
length: number = 32
|
|
35
|
+
): string {
|
|
36
|
+
// See mcppro/app/server/api_key.py:
|
|
37
|
+
//
|
|
38
|
+
// def create_api_key(prefix: str, length: int = 32) -> str:
|
|
39
|
+
// ...
|
|
40
|
+
|
|
41
|
+
const chars = Array.from({ length }, () => {
|
|
42
|
+
const i = Math.floor(Math.random() * API_KEY_ALPHABET.length);
|
|
43
|
+
return API_KEY_ALPHABET[i];
|
|
44
|
+
}).join("");
|
|
45
|
+
return `${prefix}_${chars}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates an api key of the form:
|
|
50
|
+
*
|
|
51
|
+
* <prefix>_<[A-Z][a-z][0-9]+....>/<payload>
|
|
52
|
+
*
|
|
53
|
+
* where <payload> is used to convey extra data.
|
|
54
|
+
*/
|
|
55
|
+
public static createApiKeyWithPayload(
|
|
56
|
+
prefix: string,
|
|
57
|
+
payload: string,
|
|
58
|
+
length: number = 32
|
|
59
|
+
): string {
|
|
60
|
+
return `${ApiKeyManager.createApiKey(prefix, length)}_${payload}`;
|
|
61
|
+
}
|
|
18
62
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Parse token (containing an api and optional payload)
|
|
65
|
+
*/
|
|
66
|
+
public static parseToken(token: string): {
|
|
67
|
+
prefix: string;
|
|
68
|
+
apiKey: string;
|
|
69
|
+
payload: string | undefined;
|
|
70
|
+
} {
|
|
71
|
+
const [prefix, apiKeyVal, payload] = token.split("_");
|
|
72
|
+
const apiKey = `${prefix}_${apiKeyVal}`;
|
|
25
73
|
|
|
26
|
-
|
|
74
|
+
return { prefix, apiKey, payload };
|
|
27
75
|
}
|
|
28
76
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SavedAgentProfile } from "@xalia/xmcp/sdk";
|
|
2
|
-
import { ChatCompletionMessageParam } from "../../agent/
|
|
2
|
+
import { ChatCompletionMessageParam } from "../../agent/llm";
|
|
3
3
|
|
|
4
4
|
export type UserMessageData = {
|
|
5
5
|
message?: string | undefined;
|
|
@@ -50,20 +50,29 @@ export type SessionCreateData = {
|
|
|
50
50
|
title: string;
|
|
51
51
|
agent_profile_uuid: string;
|
|
52
52
|
user_uuid: string;
|
|
53
|
+
|
|
53
54
|
team_uuid?: string | undefined;
|
|
54
|
-
|
|
55
|
+
access_token?: string;
|
|
56
|
+
agent_paused: boolean;
|
|
55
57
|
};
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
*
|
|
59
|
+
/*
|
|
60
|
+
* Description of (unjoined) sessions.
|
|
59
61
|
*/
|
|
60
|
-
export type
|
|
62
|
+
export type SessionDescriptor = SessionCreateData & {
|
|
61
63
|
updated_at: string;
|
|
62
64
|
};
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Data for existing session, including any workspace data
|
|
68
|
+
*/
|
|
69
|
+
export type SessionData = SessionDescriptor & {
|
|
70
|
+
workspace?: UserMessageData | undefined;
|
|
71
|
+
};
|
|
72
|
+
|
|
64
73
|
export type AgentSessionData = {
|
|
65
74
|
agent_profile: SavedAgentProfile;
|
|
66
|
-
sessions:
|
|
75
|
+
sessions: SessionDescriptor[];
|
|
67
76
|
updated_at: number;
|
|
68
77
|
};
|
|
69
78
|
|
|
@@ -75,7 +84,7 @@ export type TeamInfo = {
|
|
|
75
84
|
team_name: string;
|
|
76
85
|
owner_uuid: string;
|
|
77
86
|
participants: Array<TeamParticipant>;
|
|
78
|
-
sessions: Array<
|
|
87
|
+
sessions: Array<SessionDescriptor>;
|
|
79
88
|
agents: Array<SavedAgentProfile>;
|
|
80
89
|
};
|
|
81
90
|
|
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
TeamParticipant,
|
|
7
7
|
TeamRole,
|
|
8
8
|
SessionParticipantMap,
|
|
9
|
-
SessionData,
|
|
10
9
|
TeamInfo,
|
|
11
|
-
SessionMessage,
|
|
12
10
|
SessionCheckpoint,
|
|
13
11
|
UserMessageData,
|
|
14
12
|
SessionCreateData,
|
|
13
|
+
SessionDescriptor,
|
|
14
|
+
SessionData,
|
|
15
15
|
} from "./dataModels";
|
|
16
16
|
import { createClient, SupabaseClient } from "@supabase/supabase-js";
|
|
17
17
|
import type * as supabase from "../../../../supabase/database.types";
|
|
@@ -165,6 +165,16 @@ export class Database {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
async deleteUser(user_uuid: string): Promise<void> {
|
|
169
|
+
const { error } = await this.client
|
|
170
|
+
.from("users")
|
|
171
|
+
.delete()
|
|
172
|
+
.eq("uuid", user_uuid);
|
|
173
|
+
if (error) {
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
168
178
|
async addApiKey(
|
|
169
179
|
user_uuid: string,
|
|
170
180
|
api_key: string,
|
|
@@ -337,6 +347,16 @@ export class Database {
|
|
|
337
347
|
await this.client.from("agent_profiles").delete().neq("uuid", "");
|
|
338
348
|
}
|
|
339
349
|
|
|
350
|
+
async deleteAgentProfile(agentProfileUuid: string): Promise<void> {
|
|
351
|
+
const { error } = await this.client
|
|
352
|
+
.from("agent_profiles")
|
|
353
|
+
.delete()
|
|
354
|
+
.eq("uuid", agentProfileUuid);
|
|
355
|
+
if (error) {
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
340
360
|
//
|
|
341
361
|
// sessions
|
|
342
362
|
//
|
|
@@ -384,13 +404,44 @@ export class Database {
|
|
|
384
404
|
};
|
|
385
405
|
}
|
|
386
406
|
|
|
387
|
-
async
|
|
407
|
+
async sessionGetDescriptorById(
|
|
408
|
+
session_uuid: string
|
|
409
|
+
): Promise<SessionDescriptor | undefined> {
|
|
410
|
+
const { data, error } = await this.client
|
|
411
|
+
.from("sessions")
|
|
412
|
+
.select(
|
|
413
|
+
// eslint-disable-next-line max-len
|
|
414
|
+
"uuid,title,agent_profile_uuid,user_uuid,team_uuid,access_token,updated_at,agent_paused"
|
|
415
|
+
)
|
|
416
|
+
.eq("uuid", session_uuid)
|
|
417
|
+
.maybeSingle();
|
|
418
|
+
|
|
419
|
+
if (error) {
|
|
420
|
+
throw error;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!data) {
|
|
424
|
+
return undefined;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
...data,
|
|
429
|
+
access_token: data.access_token || undefined,
|
|
430
|
+
session_uuid: data.uuid,
|
|
431
|
+
updated_at: data.updated_at || new Date().toISOString(),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async sessionGetDescriptorByName(
|
|
388
436
|
user_uuid: string,
|
|
389
437
|
session_name: string
|
|
390
|
-
): Promise<
|
|
438
|
+
): Promise<SessionDescriptor | undefined> {
|
|
391
439
|
const { data, error } = await this.client
|
|
392
440
|
.from("sessions")
|
|
393
|
-
.select(
|
|
441
|
+
.select(
|
|
442
|
+
// eslint-disable-next-line max-len
|
|
443
|
+
"uuid,title,agent_profile_uuid,user_uuid,team_uuid,access_token,updated_at,agent_paused"
|
|
444
|
+
)
|
|
394
445
|
.eq("user_uuid", user_uuid)
|
|
395
446
|
.eq("title", session_name)
|
|
396
447
|
.maybeSingle();
|
|
@@ -399,11 +450,15 @@ export class Database {
|
|
|
399
450
|
logger.error(`[getSessionByName] error: ${JSON.stringify(error)}`);
|
|
400
451
|
throw error;
|
|
401
452
|
}
|
|
453
|
+
|
|
454
|
+
if (!data) {
|
|
455
|
+
return undefined;
|
|
456
|
+
}
|
|
457
|
+
|
|
402
458
|
return {
|
|
403
459
|
...data,
|
|
404
|
-
workspace: data.workspace || undefined,
|
|
405
|
-
owner_uuid: data.user_uuid,
|
|
406
460
|
session_uuid: data.uuid,
|
|
461
|
+
access_token: data.access_token || undefined,
|
|
407
462
|
updated_at: data.updated_at || new Date().toISOString(),
|
|
408
463
|
};
|
|
409
464
|
}
|
|
@@ -415,7 +470,7 @@ export class Database {
|
|
|
415
470
|
agent_profile_uuid: session_data.agent_profile_uuid,
|
|
416
471
|
user_uuid: session_data.user_uuid,
|
|
417
472
|
team_uuid: session_data.team_uuid,
|
|
418
|
-
|
|
473
|
+
access_token: session_data.access_token,
|
|
419
474
|
};
|
|
420
475
|
const { error } = await this.client.from("sessions").insert(payload);
|
|
421
476
|
if (error) {
|
|
@@ -424,9 +479,7 @@ export class Database {
|
|
|
424
479
|
}
|
|
425
480
|
|
|
426
481
|
async sessionUpdateTitle(session_uuid: string, title: string): Promise<void> {
|
|
427
|
-
const payload: supabase.TablesUpdate<"sessions"> = {
|
|
428
|
-
title,
|
|
429
|
-
};
|
|
482
|
+
const payload: supabase.TablesUpdate<"sessions"> = { title };
|
|
430
483
|
const { error } = await this.client
|
|
431
484
|
.from("sessions")
|
|
432
485
|
.update(payload)
|
|
@@ -452,6 +505,36 @@ export class Database {
|
|
|
452
505
|
}
|
|
453
506
|
}
|
|
454
507
|
|
|
508
|
+
async sessionUpdateAccessToken(
|
|
509
|
+
session_uuid: string,
|
|
510
|
+
access_token: string | undefined
|
|
511
|
+
): Promise<void> {
|
|
512
|
+
const payload: supabase.TablesUpdate<"sessions"> = {
|
|
513
|
+
access_token: access_token || null,
|
|
514
|
+
};
|
|
515
|
+
const { error } = await this.client
|
|
516
|
+
.from("sessions")
|
|
517
|
+
.update(payload)
|
|
518
|
+
.eq("uuid", session_uuid);
|
|
519
|
+
if (error) {
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async sessionSetAgentPaused(
|
|
525
|
+
session_uuid: string,
|
|
526
|
+
paused: boolean
|
|
527
|
+
): Promise<void> {
|
|
528
|
+
const payload: supabase.TablesUpdate<"sessions"> = { agent_paused: paused };
|
|
529
|
+
const { error } = await this.client
|
|
530
|
+
.from("sessions")
|
|
531
|
+
.update(payload)
|
|
532
|
+
.eq("uuid", session_uuid);
|
|
533
|
+
if (error) {
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
455
538
|
async sessionDeleteById(session_uuid: string): Promise<void> {
|
|
456
539
|
await this.client.from("sessions").delete().eq("uuid", session_uuid);
|
|
457
540
|
}
|
|
@@ -465,11 +548,12 @@ export class Database {
|
|
|
465
548
|
* @param user_uuid
|
|
466
549
|
* @returns SessionData[]
|
|
467
550
|
*/
|
|
468
|
-
async getUserSessions(user_uuid: string): Promise<
|
|
551
|
+
async getUserSessions(user_uuid: string): Promise<SessionDescriptor[]> {
|
|
469
552
|
const { data: userSessions, error: userSessionsError } = await this.client
|
|
470
553
|
.from("sessions")
|
|
471
554
|
.select(
|
|
472
|
-
|
|
555
|
+
// eslint-disable-next-line max-len
|
|
556
|
+
"uuid,title,agent_profile_uuid,updated_at,user_uuid,access_token,agent_paused"
|
|
473
557
|
)
|
|
474
558
|
.eq("user_uuid", user_uuid)
|
|
475
559
|
.is("team_uuid", null);
|
|
@@ -483,9 +567,10 @@ export class Database {
|
|
|
483
567
|
title: s.title,
|
|
484
568
|
team_uuid: undefined,
|
|
485
569
|
agent_profile_uuid: s.agent_profile_uuid,
|
|
486
|
-
|
|
570
|
+
access_token: s.access_token || undefined,
|
|
487
571
|
updated_at: s.updated_at,
|
|
488
572
|
user_uuid: s.user_uuid,
|
|
573
|
+
agent_paused: s.agent_paused,
|
|
489
574
|
}));
|
|
490
575
|
}
|
|
491
576
|
|
|
@@ -522,90 +607,22 @@ export class Database {
|
|
|
522
607
|
}
|
|
523
608
|
|
|
524
609
|
async sessionGetTeamUuid(session_uuid: string): Promise<string | undefined> {
|
|
525
|
-
const data = await this.
|
|
610
|
+
const data = await this.sessionGetDescriptorById(session_uuid);
|
|
526
611
|
if (data) {
|
|
527
612
|
return data.team_uuid;
|
|
528
613
|
}
|
|
529
614
|
return undefined;
|
|
530
615
|
}
|
|
531
616
|
|
|
532
|
-
//
|
|
533
|
-
// session_messages
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
async sessionMessagesClearConversation(session_uuid: string): Promise<void> {
|
|
537
|
-
const { error } = await this.client
|
|
538
|
-
.from("session_messages")
|
|
539
|
-
.delete()
|
|
540
|
-
.eq("session_uuid", session_uuid);
|
|
541
|
-
if (error) {
|
|
542
|
-
throw error;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
async sessionMessagesGetConversation(
|
|
547
|
-
session_uuid: string,
|
|
548
|
-
numEntries: number,
|
|
549
|
-
beforeIndex?: number
|
|
550
|
-
): Promise<SessionMessage[]> {
|
|
551
|
-
// Query all message for the given session, ordered high-to-low by
|
|
552
|
-
// message_idx, limited to `numEntries`. If `beforeIndex` is given, it
|
|
553
|
-
// means we get messages with `message_idx < beforeIndex`
|
|
554
|
-
|
|
555
|
-
let query = this.client
|
|
556
|
-
.from("session_messages")
|
|
557
|
-
.select("message_idx,sender_uuid,is_for_llm,content")
|
|
558
|
-
.eq("session_uuid", session_uuid);
|
|
559
|
-
if (beforeIndex) {
|
|
560
|
-
query = query.lt("message_idx", beforeIndex);
|
|
561
|
-
}
|
|
562
|
-
query = query.order("message_idx", { ascending: false }).limit(numEntries);
|
|
563
|
-
|
|
564
|
-
const { data, error } = await query;
|
|
565
|
-
if (error) {
|
|
566
|
-
throw error;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// To get the newest N messages, we've orded by index largest to smallest
|
|
570
|
-
// (newest first), but caller wants the message first to last, hence
|
|
571
|
-
// reverse the array.
|
|
572
|
-
|
|
573
|
-
return data
|
|
574
|
-
.map(({ sender_uuid, ...rest }) => {
|
|
575
|
-
return sender_uuid
|
|
576
|
-
? {
|
|
577
|
-
sender_uuid,
|
|
578
|
-
...rest,
|
|
579
|
-
}
|
|
580
|
-
: { ...rest };
|
|
581
|
-
})
|
|
582
|
-
.reverse();
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
async sessionMessagesAppend(
|
|
586
|
-
session_uuid: string,
|
|
587
|
-
messages: SessionMessage[]
|
|
588
|
-
): Promise<void> {
|
|
589
|
-
const payload = messages.map((m) => {
|
|
590
|
-
return { ...m, session_uuid };
|
|
591
|
-
});
|
|
592
|
-
const { error } = await this.client
|
|
593
|
-
.from("session_messages")
|
|
594
|
-
.insert(payload);
|
|
595
|
-
if (error) {
|
|
596
|
-
throw error;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
617
|
//
|
|
601
618
|
// session_checkpoints
|
|
602
619
|
//
|
|
603
620
|
|
|
604
|
-
async
|
|
621
|
+
async sessionCheckpointDelete(session_uuid: string): Promise<void> {
|
|
605
622
|
const { error } = await this.client
|
|
606
623
|
.from("session_checkpoints")
|
|
607
624
|
.delete()
|
|
608
|
-
.neq("session_uuid",
|
|
625
|
+
.neq("session_uuid", session_uuid);
|
|
609
626
|
if (error) {
|
|
610
627
|
throw error;
|
|
611
628
|
}
|
|
@@ -681,7 +698,7 @@ export class Database {
|
|
|
681
698
|
* @param teamUuid - UUID of the team
|
|
682
699
|
* @returns Array of agent profiles
|
|
683
700
|
*/
|
|
684
|
-
async
|
|
701
|
+
async agentProfilesGetByTeam(teamUuid: string): Promise<SavedAgentProfile[]> {
|
|
685
702
|
const { data, error } = await this.client
|
|
686
703
|
.from("agent_profiles")
|
|
687
704
|
.select("*")
|
|
@@ -859,7 +876,7 @@ export class Database {
|
|
|
859
876
|
const [participants, sessions, agents] = await Promise.all([
|
|
860
877
|
this.teamGetMembers(teamUuid),
|
|
861
878
|
this.teamGetSessions(teamUuid),
|
|
862
|
-
this.
|
|
879
|
+
this.agentProfilesGetByTeam(teamUuid),
|
|
863
880
|
]);
|
|
864
881
|
|
|
865
882
|
return {
|
|
@@ -951,12 +968,10 @@ export class Database {
|
|
|
951
968
|
* @param teamUuid - UUID of the team
|
|
952
969
|
* @returns Array of session data
|
|
953
970
|
*/
|
|
954
|
-
async teamGetSessions(teamUuid: string): Promise<
|
|
971
|
+
async teamGetSessions(teamUuid: string): Promise<SessionDescriptor[]> {
|
|
955
972
|
const { data, error } = await this.client
|
|
956
973
|
.from("sessions")
|
|
957
|
-
.select(
|
|
958
|
-
"uuid, title, agent_profile_uuid, workspace, updated_at, user_uuid"
|
|
959
|
-
)
|
|
974
|
+
.select("uuid,title,agent_profile_uuid,updated_at,user_uuid,agent_paused")
|
|
960
975
|
.eq("team_uuid", teamUuid);
|
|
961
976
|
|
|
962
977
|
if (error) {
|
|
@@ -968,9 +983,9 @@ export class Database {
|
|
|
968
983
|
title: session.title,
|
|
969
984
|
team_uuid: teamUuid,
|
|
970
985
|
agent_profile_uuid: session.agent_profile_uuid,
|
|
971
|
-
workspace: session.workspace || undefined,
|
|
972
986
|
updated_at: session.updated_at || new Date().toISOString(),
|
|
973
987
|
user_uuid: session.user_uuid,
|
|
988
|
+
agent_paused: session.agent_paused,
|
|
974
989
|
}));
|
|
975
990
|
}
|
|
976
991
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IMAGE_MIME_TYPES,
|
|
3
|
+
EXTENSION_TO_IMAGE_MIME_TYPE,
|
|
4
|
+
getMimeTypeFromDataUrl,
|
|
5
|
+
createDataUrlFromText,
|
|
6
|
+
createDataUrlFromBuffer,
|
|
7
|
+
} from "./mimeTypes";
|
|
8
|
+
|
|
9
|
+
export const SESSION_FILE_MIME_TYPES = [
|
|
10
|
+
...IMAGE_MIME_TYPES,
|
|
11
|
+
"text/plain",
|
|
12
|
+
"text/markdown",
|
|
13
|
+
"application/pdf",
|
|
14
|
+
"image/svg+xml",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
export type SessionFileMimeType = (typeof SESSION_FILE_MIME_TYPES)[number];
|
|
18
|
+
|
|
19
|
+
export function isSessionFileMimeType(
|
|
20
|
+
mime_type: unknown
|
|
21
|
+
): mime_type is SessionFileMimeType {
|
|
22
|
+
return (
|
|
23
|
+
typeof mime_type === "string" &&
|
|
24
|
+
(SESSION_FILE_MIME_TYPES as readonly string[]).includes(mime_type)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const SESSION_FILE_TEXT_MIME_TYPES = [
|
|
29
|
+
"text/plain",
|
|
30
|
+
"text/markdown",
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export type SessionFileTextMimeType =
|
|
34
|
+
(typeof SESSION_FILE_TEXT_MIME_TYPES)[number];
|
|
35
|
+
|
|
36
|
+
export function isSessionFileTextMimeType(
|
|
37
|
+
mime_type: unknown
|
|
38
|
+
): mime_type is SessionFileTextMimeType {
|
|
39
|
+
return (
|
|
40
|
+
typeof mime_type === "string" &&
|
|
41
|
+
(SESSION_FILE_TEXT_MIME_TYPES as readonly string[]).includes(mime_type)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const EXTENSION_TO_SESSION_FILE_MIME_TYPE: Record<
|
|
46
|
+
string,
|
|
47
|
+
SessionFileMimeType | undefined
|
|
48
|
+
> = {
|
|
49
|
+
...EXTENSION_TO_IMAGE_MIME_TYPE,
|
|
50
|
+
svg: "image/svg+xml",
|
|
51
|
+
pdf: "application/pdf",
|
|
52
|
+
txt: "text/plain",
|
|
53
|
+
md: "text/markdown",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type SessionFileDescriptor = {
|
|
57
|
+
name: string;
|
|
58
|
+
mime_type: SessionFileMimeType;
|
|
59
|
+
summary?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type SessionFileEntry = SessionFileDescriptor & { data_url: string };
|
|
63
|
+
|
|
64
|
+
export function getSessionFileMimeTypeFromDataUrl(
|
|
65
|
+
data_url: string
|
|
66
|
+
): SessionFileMimeType {
|
|
67
|
+
const mimeType = getMimeTypeFromDataUrl(data_url);
|
|
68
|
+
if (!isSessionFileMimeType(mimeType)) {
|
|
69
|
+
throw new Error(`${mimeType} is not a supported mime type`);
|
|
70
|
+
}
|
|
71
|
+
return mimeType;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createSessionFileDataUrl(
|
|
75
|
+
data: string,
|
|
76
|
+
mime_type: SessionFileTextMimeType
|
|
77
|
+
): string;
|
|
78
|
+
export function createSessionFileDataUrl(
|
|
79
|
+
data: Buffer,
|
|
80
|
+
mime_type: SessionFileMimeType
|
|
81
|
+
): string;
|
|
82
|
+
export function createSessionFileDataUrl(
|
|
83
|
+
data: string | Buffer,
|
|
84
|
+
mime_type: SessionFileMimeType | SessionFileTextMimeType
|
|
85
|
+
): string {
|
|
86
|
+
if (typeof data === "string") {
|
|
87
|
+
return createDataUrlFromText(data, mime_type);
|
|
88
|
+
} else {
|
|
89
|
+
return createDataUrlFromBuffer(data, mime_type);
|
|
90
|
+
}
|
|
91
|
+
}
|