@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,900 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
|
|
3
|
+
import { getLogger, SavedAgentProfile } from "@xalia/xmcp/sdk";
|
|
4
|
+
|
|
5
|
+
import { Connection } from "./connection";
|
|
6
|
+
import { SessionClient } from "./sessionClient";
|
|
7
|
+
import { IChatClientEventHandler } from "./interfaces";
|
|
8
|
+
import {
|
|
9
|
+
SessionData,
|
|
10
|
+
TeamInfo,
|
|
11
|
+
TeamParticipant,
|
|
12
|
+
AgentSessionData,
|
|
13
|
+
} from "../data/dataModels";
|
|
14
|
+
import { createSessionParticipantMap } from "../data/database";
|
|
15
|
+
import {
|
|
16
|
+
ServerSessionScopedMessage,
|
|
17
|
+
ServerSessionInfo,
|
|
18
|
+
isServerSessionScopedMessage,
|
|
19
|
+
ServerControlError,
|
|
20
|
+
isServerControlMessage,
|
|
21
|
+
ServerControlMessage,
|
|
22
|
+
ServerControlSessionDeleted,
|
|
23
|
+
ServerControlTeamCreated,
|
|
24
|
+
ServerToClient,
|
|
25
|
+
ClientToServer,
|
|
26
|
+
ServerControlAgentProfileCreated,
|
|
27
|
+
ServerControlAgentProfileDeleted,
|
|
28
|
+
} from "../protocol/messages";
|
|
29
|
+
import { SESSION_CREATE_TIMEOUT, SESSION_JOIN_TIMEOUT } from "./constants";
|
|
30
|
+
import { ITeamManager } from "./teamManager";
|
|
31
|
+
import { buildAgentSessionMap } from "../utils/agentSessionMap";
|
|
32
|
+
|
|
33
|
+
const logger = getLogger();
|
|
34
|
+
|
|
35
|
+
// client team info, used for session tracking etc.
|
|
36
|
+
export type ClientTeamInfo = {
|
|
37
|
+
team_uuid: string;
|
|
38
|
+
team_name: string;
|
|
39
|
+
owner_uuid: string;
|
|
40
|
+
participants: Map<string, TeamParticipant>;
|
|
41
|
+
agentSessionMap: Map<string, AgentSessionData>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type SessionJoinOrCreateResolver = {
|
|
45
|
+
resolve: (
|
|
46
|
+
sessionClient: SessionClient,
|
|
47
|
+
sessionInfo: ServerSessionInfo
|
|
48
|
+
) => void;
|
|
49
|
+
reject: (error: Error) => void;
|
|
50
|
+
sessionId: string;
|
|
51
|
+
clientMessageId: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export class ChatClient implements ITeamManager {
|
|
55
|
+
private constructor(
|
|
56
|
+
private connection: Connection<ClientToServer, ServerToClient>,
|
|
57
|
+
private eventHandler: IChatClientEventHandler,
|
|
58
|
+
// agent_uuid -> agent session data
|
|
59
|
+
private userAgentSessionMap: Map<string, AgentSessionData> = new Map(),
|
|
60
|
+
// team_uuid -> team info
|
|
61
|
+
private teams: Map<string, ClientTeamInfo> = new Map(),
|
|
62
|
+
private closed: boolean = false,
|
|
63
|
+
private currentSessionId: string | undefined = undefined,
|
|
64
|
+
// note: currentTeamId will not be reset when swtiching to a user session.
|
|
65
|
+
private currentTeamId: string | undefined = undefined,
|
|
66
|
+
// session_uuid -> session client
|
|
67
|
+
private activeSessions: Map<string, SessionClient> = new Map(),
|
|
68
|
+
// session_uuid -> true if there is a new message.
|
|
69
|
+
private newMessage: Map<string, boolean> = new Map(),
|
|
70
|
+
// Track pending session join request, this should be a singleton
|
|
71
|
+
private sessionJoinOrCreateRes:
|
|
72
|
+
| SessionJoinOrCreateResolver
|
|
73
|
+
| undefined = undefined
|
|
74
|
+
) {}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Connect to server and create ChatClient instance
|
|
78
|
+
*/
|
|
79
|
+
static async init(
|
|
80
|
+
url: string,
|
|
81
|
+
token: string,
|
|
82
|
+
eventHandler: IChatClientEventHandler
|
|
83
|
+
): Promise<ChatClient> {
|
|
84
|
+
const connection = new Connection<ClientToServer, ServerToClient>({
|
|
85
|
+
url,
|
|
86
|
+
token,
|
|
87
|
+
});
|
|
88
|
+
return new Promise((resolveClient, reject) => {
|
|
89
|
+
let client: ChatClient | undefined = undefined;
|
|
90
|
+
const clientMessageId = uuidv4();
|
|
91
|
+
|
|
92
|
+
// Register session_info handler for initialization
|
|
93
|
+
connection.on("control_session_list", (msg) => {
|
|
94
|
+
// get user sessions, user agents, and team sessions
|
|
95
|
+
const userSessions = new Map<string, SessionData>();
|
|
96
|
+
msg.user_sessions.forEach((session) => {
|
|
97
|
+
userSessions.set(session.session_uuid, session);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const userAgents = new Map<string, SavedAgentProfile>();
|
|
101
|
+
msg.user_agents.forEach((agent) => {
|
|
102
|
+
userAgents.set(agent.uuid, agent);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const userAgentSessionMap = buildAgentSessionMap(
|
|
106
|
+
userSessions,
|
|
107
|
+
userAgents
|
|
108
|
+
);
|
|
109
|
+
logger.info(
|
|
110
|
+
`[ChatClient.init] session list msg: ${JSON.stringify(msg)}`
|
|
111
|
+
);
|
|
112
|
+
for (const agentSession of userAgentSessionMap.values()) {
|
|
113
|
+
logger.info(
|
|
114
|
+
`[ChatClient.init] agent: ` +
|
|
115
|
+
agentSession.agent_profile.profile_name
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const teams = new Map<string, ClientTeamInfo>();
|
|
120
|
+
msg.team_sessions.forEach((team) => {
|
|
121
|
+
teams.set(team.team_uuid, ChatClient.toClientTeamInfo(team));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!client) {
|
|
125
|
+
logger.info("Creating ChatClient");
|
|
126
|
+
client = new ChatClient(
|
|
127
|
+
connection,
|
|
128
|
+
eventHandler,
|
|
129
|
+
userAgentSessionMap,
|
|
130
|
+
teams
|
|
131
|
+
);
|
|
132
|
+
resolveClient(client);
|
|
133
|
+
} else {
|
|
134
|
+
client.userAgentSessionMap = userAgentSessionMap;
|
|
135
|
+
client.teams = teams;
|
|
136
|
+
void client.eventHandler.onMessage(msg, client);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Register general message handler for established client
|
|
141
|
+
connection.on("*", (msg) => {
|
|
142
|
+
if (!client) {
|
|
143
|
+
if (msg.type === "control_error") {
|
|
144
|
+
logger.error(
|
|
145
|
+
`[ChatClient] client error (init): ${JSON.stringify(msg)}`
|
|
146
|
+
);
|
|
147
|
+
reject(new Error(msg.message));
|
|
148
|
+
} else {
|
|
149
|
+
logger.error(
|
|
150
|
+
`[ChatClient] invalid message (init): ${JSON.stringify(msg.type)}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (isServerControlMessage(msg)) {
|
|
156
|
+
client.handleControlMessage(msg);
|
|
157
|
+
} else if (isServerSessionScopedMessage(msg)) {
|
|
158
|
+
client.handleSessionMessage(msg);
|
|
159
|
+
} else {
|
|
160
|
+
logger.error(`[ChatClient] unhandled msg: ${JSON.stringify(msg)}`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Register direct error callback for client-side errors.
|
|
165
|
+
connection.onError((errorMessage) => {
|
|
166
|
+
if (client) {
|
|
167
|
+
client.connection.close();
|
|
168
|
+
}
|
|
169
|
+
eventHandler.onError(`Connection error: ${errorMessage}`);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Start connection
|
|
173
|
+
connection
|
|
174
|
+
.connect()
|
|
175
|
+
.then(() => {
|
|
176
|
+
connection.send({
|
|
177
|
+
type: "control_get_session_list",
|
|
178
|
+
client_message_id: clientMessageId,
|
|
179
|
+
});
|
|
180
|
+
})
|
|
181
|
+
.catch((error: unknown) => {
|
|
182
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private static toClientTeamInfo(teamInfo: TeamInfo): ClientTeamInfo {
|
|
188
|
+
const sessions = new Map(
|
|
189
|
+
teamInfo.sessions.map((session) => [session.session_uuid, session])
|
|
190
|
+
);
|
|
191
|
+
const agents = new Map(teamInfo.agents.map((agent) => [agent.uuid, agent]));
|
|
192
|
+
const agentSessionMap = buildAgentSessionMap(sessions, agents);
|
|
193
|
+
return {
|
|
194
|
+
team_uuid: teamInfo.team_uuid,
|
|
195
|
+
team_name: teamInfo.team_name,
|
|
196
|
+
owner_uuid: teamInfo.owner_uuid,
|
|
197
|
+
participants: new Map(
|
|
198
|
+
teamInfo.participants.map((participant) => [
|
|
199
|
+
participant.user_uuid,
|
|
200
|
+
participant,
|
|
201
|
+
])
|
|
202
|
+
),
|
|
203
|
+
agentSessionMap,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get the current session.
|
|
209
|
+
* Will throw an error if the current session is not set.
|
|
210
|
+
* @param location - The location of the caller.
|
|
211
|
+
* @returns The current session client.
|
|
212
|
+
*/
|
|
213
|
+
public getCurrentSession(location: string): SessionClient {
|
|
214
|
+
const session = this.getCurrentSessionMaybe();
|
|
215
|
+
if (!session) {
|
|
216
|
+
throw new Error(`[${location}]: No current session`);
|
|
217
|
+
}
|
|
218
|
+
return session;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public getCurrentSessionMaybe(): SessionClient | undefined {
|
|
222
|
+
if (!this.currentSessionId) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return this.activeSessions.get(this.currentSessionId);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
public getCurrentAgentUuid(): string | undefined {
|
|
229
|
+
const session = this.getCurrentSessionMaybe();
|
|
230
|
+
if (!session) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
return session.getAgentUuid();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getUserAgentSessionMap(): Map<string, AgentSessionData> {
|
|
237
|
+
return this.userAgentSessionMap;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
getClientTeamInfo(teamUuid: string): ClientTeamInfo | undefined {
|
|
241
|
+
return this.teams.get(teamUuid);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ITeamManager -> getCurrentTeamInfo
|
|
245
|
+
getCurrentTeamInfo(): ClientTeamInfo | undefined {
|
|
246
|
+
if (!this.currentTeamId) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
return this.teams.get(this.currentTeamId);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ITeamManager -> getTeams
|
|
253
|
+
getTeams(): Map<string, ClientTeamInfo> {
|
|
254
|
+
return this.teams;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get the currently active session ID
|
|
259
|
+
*/
|
|
260
|
+
getCurrentSessionId(): string | undefined {
|
|
261
|
+
return this.currentSessionId;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get the currently team ID
|
|
266
|
+
*/
|
|
267
|
+
getCurrentTeamId(): string | undefined {
|
|
268
|
+
return this.currentTeamId;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ITeamManager -> setCurrentTeamId
|
|
272
|
+
setCurrentTeamId(teamUuid: string): void {
|
|
273
|
+
if (!this.teams.has(teamUuid)) {
|
|
274
|
+
throw new Error(`Team ${teamUuid} not found`);
|
|
275
|
+
}
|
|
276
|
+
this.currentTeamId = teamUuid;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Check if client is connected and operational
|
|
281
|
+
*/
|
|
282
|
+
isClosed(): boolean {
|
|
283
|
+
return this.closed;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
shutdown(): void {
|
|
287
|
+
this.connection.close();
|
|
288
|
+
this.closed = true;
|
|
289
|
+
this.activeSessions.clear();
|
|
290
|
+
this.newMessage.clear();
|
|
291
|
+
this.teams.clear();
|
|
292
|
+
this.currentSessionId = undefined;
|
|
293
|
+
this.currentTeamId = undefined;
|
|
294
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public async createNewSession(
|
|
298
|
+
newTitle: string,
|
|
299
|
+
agentProfileId?: string,
|
|
300
|
+
teamId?: string
|
|
301
|
+
): Promise<SessionClient> {
|
|
302
|
+
if (this.closed) {
|
|
303
|
+
throw new Error("ChatClient is closed");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (this.sessionJoinOrCreateRes) {
|
|
307
|
+
logger.error(
|
|
308
|
+
`[ChatClient] creating new session with ` +
|
|
309
|
+
`profile id ${JSON.stringify(agentProfileId)}`
|
|
310
|
+
);
|
|
311
|
+
throw new Error("Session join/create is already in progress");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return new Promise((resolve, reject) => {
|
|
315
|
+
const clientMessageId = uuidv4();
|
|
316
|
+
|
|
317
|
+
// Set up timeout for the request
|
|
318
|
+
const timeoutId = setTimeout(() => {
|
|
319
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
320
|
+
reject(new Error(`Session join timeout for creating new session`));
|
|
321
|
+
}, SESSION_CREATE_TIMEOUT);
|
|
322
|
+
|
|
323
|
+
// Track this pending request
|
|
324
|
+
this.sessionJoinOrCreateRes = {
|
|
325
|
+
resolve: (
|
|
326
|
+
sessionClient: SessionClient,
|
|
327
|
+
sessionInfo: ServerSessionInfo
|
|
328
|
+
) => {
|
|
329
|
+
const sessionId = this.sessionJoinOrCreateRes?.sessionId;
|
|
330
|
+
if (!sessionId) {
|
|
331
|
+
throw new Error("Session id is not set in resolving.");
|
|
332
|
+
}
|
|
333
|
+
if (sessionId !== sessionInfo.session_id) {
|
|
334
|
+
throw new Error("SessionInfo id mismatch");
|
|
335
|
+
}
|
|
336
|
+
clearTimeout(timeoutId);
|
|
337
|
+
this.activeSessions.set(sessionId, sessionClient);
|
|
338
|
+
this.currentSessionId = sessionId;
|
|
339
|
+
if (sessionInfo.team_uuid) {
|
|
340
|
+
this.currentTeamId = sessionInfo.team_uuid;
|
|
341
|
+
}
|
|
342
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
343
|
+
this.addSessionToAgentSessionMap(sessionInfo);
|
|
344
|
+
resolve(sessionClient);
|
|
345
|
+
},
|
|
346
|
+
reject: (error: Error) => {
|
|
347
|
+
clearTimeout(timeoutId);
|
|
348
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
349
|
+
reject(error);
|
|
350
|
+
},
|
|
351
|
+
sessionId: "",
|
|
352
|
+
clientMessageId,
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Send session_create message
|
|
356
|
+
try {
|
|
357
|
+
this.connection.send({
|
|
358
|
+
type: "control_session_create",
|
|
359
|
+
client_message_id: clientMessageId,
|
|
360
|
+
title: newTitle,
|
|
361
|
+
agent_profile_id: agentProfileId,
|
|
362
|
+
team_id: teamId,
|
|
363
|
+
});
|
|
364
|
+
logger.info(
|
|
365
|
+
`[ChatClient] Sent session_create for session ${newTitle}` +
|
|
366
|
+
` client message id: ${clientMessageId}`
|
|
367
|
+
);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
370
|
+
clearTimeout(timeoutId);
|
|
371
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Connect to an existing session by sending session_join message
|
|
378
|
+
* and waiting for session_info response from server
|
|
379
|
+
*/
|
|
380
|
+
async connectToSession(sessionId: string): Promise<SessionClient> {
|
|
381
|
+
if (this.closed) {
|
|
382
|
+
throw new Error("ChatClient is closed");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (this.sessionJoinOrCreateRes) {
|
|
386
|
+
logger.error(`[ChatClient] connecting to session ${sessionId}`);
|
|
387
|
+
throw new Error("Session join/create is already in progress");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// TODO: we can directly return a session client if we cache conversation.
|
|
391
|
+
// For now, we go through the join process.
|
|
392
|
+
return new Promise((resolve, reject) => {
|
|
393
|
+
const clientMessageId = uuidv4();
|
|
394
|
+
|
|
395
|
+
// Set up timeout for the request
|
|
396
|
+
const timeoutId = setTimeout(() => {
|
|
397
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
398
|
+
reject(new Error(`Session join timeout for session ${sessionId}`));
|
|
399
|
+
}, SESSION_JOIN_TIMEOUT);
|
|
400
|
+
|
|
401
|
+
// Track this pending request
|
|
402
|
+
this.sessionJoinOrCreateRes = {
|
|
403
|
+
resolve: (
|
|
404
|
+
sessionClient: SessionClient,
|
|
405
|
+
sessionInfo: ServerSessionInfo
|
|
406
|
+
) => {
|
|
407
|
+
clearTimeout(timeoutId);
|
|
408
|
+
this.activeSessions.set(sessionId, sessionClient);
|
|
409
|
+
this.currentSessionId = sessionId;
|
|
410
|
+
if (sessionInfo.team_uuid) {
|
|
411
|
+
this.currentTeamId = sessionInfo.team_uuid;
|
|
412
|
+
}
|
|
413
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
414
|
+
this.updateSessionInfo(sessionInfo);
|
|
415
|
+
logger.info(`[ChatClient] joined session ${sessionId}`);
|
|
416
|
+
resolve(sessionClient);
|
|
417
|
+
},
|
|
418
|
+
reject: (error: Error) => {
|
|
419
|
+
clearTimeout(timeoutId);
|
|
420
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
421
|
+
logger.error(
|
|
422
|
+
`[ChatClient] failed to join session` +
|
|
423
|
+
` ${sessionId}: ${error.message}`
|
|
424
|
+
);
|
|
425
|
+
reject(error);
|
|
426
|
+
},
|
|
427
|
+
sessionId,
|
|
428
|
+
clientMessageId,
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Send session_join message
|
|
432
|
+
try {
|
|
433
|
+
this.connection.send({
|
|
434
|
+
type: "control_session_join",
|
|
435
|
+
client_message_id: clientMessageId,
|
|
436
|
+
target_session_id: sessionId,
|
|
437
|
+
});
|
|
438
|
+
logger.debug(`[ChatClient] Sent session_join for session ${sessionId}`);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
this.sessionJoinOrCreateRes = undefined;
|
|
441
|
+
clearTimeout(timeoutId);
|
|
442
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Fetch the session list from the server
|
|
449
|
+
*/
|
|
450
|
+
fetchSessionList(): void {
|
|
451
|
+
this.connection.send({
|
|
452
|
+
type: "control_get_session_list",
|
|
453
|
+
client_message_id: uuidv4(),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Delete a session by sending session_delete_request message
|
|
459
|
+
* and waiting for session_deleted response from server
|
|
460
|
+
* @param sessionId
|
|
461
|
+
* @returns void
|
|
462
|
+
*/
|
|
463
|
+
deleteSession(sessionId: string): void {
|
|
464
|
+
if (this.closed) {
|
|
465
|
+
throw new Error("ChatClient is closed");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
this.connection.send({
|
|
469
|
+
type: "control_session_delete",
|
|
470
|
+
client_message_id: uuidv4(),
|
|
471
|
+
target_session_id: sessionId,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
logger.debug(
|
|
475
|
+
`[ChatClient] Sent session_delete_request for` + ` session ${sessionId}`
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Create a new team with initial members
|
|
481
|
+
* @param teamName
|
|
482
|
+
* @param initialMembers could be UUIDs or emails
|
|
483
|
+
*/
|
|
484
|
+
createNewTeam(teamName: string, initialMembers: string[]): void {
|
|
485
|
+
if (this.closed) {
|
|
486
|
+
throw new Error("ChatClient is closed");
|
|
487
|
+
}
|
|
488
|
+
// send team_create_request message
|
|
489
|
+
try {
|
|
490
|
+
this.connection.send({
|
|
491
|
+
type: "control_team_create",
|
|
492
|
+
client_message_id: uuidv4(),
|
|
493
|
+
team_name: teamName,
|
|
494
|
+
initial_members: initialMembers,
|
|
495
|
+
});
|
|
496
|
+
} catch (error) {
|
|
497
|
+
this.eventHandler.onError(String(error));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Add a user to a team
|
|
503
|
+
* @param teamId - UUID of the team
|
|
504
|
+
* @param userId - UUID of the user to add
|
|
505
|
+
*/
|
|
506
|
+
addTeamMember(teamId: string, userId: string): void {
|
|
507
|
+
if (this.closed) {
|
|
508
|
+
throw new Error("ChatClient is closed");
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
this.connection.send({
|
|
512
|
+
type: "control_add_team_user",
|
|
513
|
+
client_message_id: uuidv4(),
|
|
514
|
+
target_team_id: teamId,
|
|
515
|
+
user_uuid_or_email: userId,
|
|
516
|
+
});
|
|
517
|
+
} catch (error) {
|
|
518
|
+
this.eventHandler.onError(String(error));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Remove a user from a team
|
|
524
|
+
* @param teamId - UUID of the team
|
|
525
|
+
* @param userId - UUID of the user to remove
|
|
526
|
+
*/
|
|
527
|
+
removeTeamMember(teamId: string, userId: string): void {
|
|
528
|
+
if (this.closed) {
|
|
529
|
+
throw new Error("ChatClient is closed");
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
this.connection.send({
|
|
533
|
+
type: "control_remove_team_user",
|
|
534
|
+
client_message_id: uuidv4(),
|
|
535
|
+
target_team_id: teamId,
|
|
536
|
+
user_uuid_or_email: userId,
|
|
537
|
+
});
|
|
538
|
+
} catch (error) {
|
|
539
|
+
this.eventHandler.onError(String(error));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Create a new agent
|
|
545
|
+
* @param agentName - the agent name
|
|
546
|
+
* @param templateName - the template name
|
|
547
|
+
* @param teamUuid - the team uuid
|
|
548
|
+
*/
|
|
549
|
+
createNewAgent(
|
|
550
|
+
agentName: string,
|
|
551
|
+
templateName?: string,
|
|
552
|
+
teamUuid?: string
|
|
553
|
+
): void {
|
|
554
|
+
if (this.closed) {
|
|
555
|
+
throw new Error("ChatClient is closed");
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
this.connection.send({
|
|
559
|
+
type: "control_agent_profile_create",
|
|
560
|
+
title: agentName,
|
|
561
|
+
template_name: templateName,
|
|
562
|
+
team_uuid: teamUuid,
|
|
563
|
+
});
|
|
564
|
+
} catch (error) {
|
|
565
|
+
this.eventHandler.onError(String(error));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Delete a session from the agent session map
|
|
571
|
+
* @param agentSessionMap - the agent session map
|
|
572
|
+
* @param agentUuid - the agent uuid
|
|
573
|
+
* @param sessionId - the session id
|
|
574
|
+
* @returns true if the session is deleted, false otherwise
|
|
575
|
+
*/
|
|
576
|
+
private static deleteSessionFromAgentSessionMap(
|
|
577
|
+
agentSessionMap: Map<string, AgentSessionData>,
|
|
578
|
+
agentUuid: string,
|
|
579
|
+
sessionId: string
|
|
580
|
+
): boolean {
|
|
581
|
+
const agentSession = agentSessionMap.get(agentUuid);
|
|
582
|
+
if (!agentSession) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
const size = agentSession.sessions.length;
|
|
586
|
+
agentSession.sessions = agentSession.sessions.filter(
|
|
587
|
+
(session) => session.session_uuid !== sessionId
|
|
588
|
+
);
|
|
589
|
+
return size > agentSession.sessions.length;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
private handleSessionDeleted(msg: ServerControlSessionDeleted): void {
|
|
593
|
+
const sessionId = msg.session_id;
|
|
594
|
+
const agentUuid = msg.agent_profile_uuid;
|
|
595
|
+
logger.info(`[ChatClient] Session deleted: ${JSON.stringify(msg)}`);
|
|
596
|
+
let deleted = false;
|
|
597
|
+
if (msg.team_uuid) {
|
|
598
|
+
deleted = ChatClient.deleteSessionFromAgentSessionMap(
|
|
599
|
+
this.teams.get(msg.team_uuid)?.agentSessionMap ??
|
|
600
|
+
new Map<string, AgentSessionData>(),
|
|
601
|
+
agentUuid,
|
|
602
|
+
sessionId
|
|
603
|
+
);
|
|
604
|
+
} else {
|
|
605
|
+
deleted = ChatClient.deleteSessionFromAgentSessionMap(
|
|
606
|
+
this.userAgentSessionMap,
|
|
607
|
+
agentUuid,
|
|
608
|
+
sessionId
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
if (!deleted) {
|
|
612
|
+
// could be a race condition, we fetch the session list from the server
|
|
613
|
+
logger.warn(`[ChatClient] Session ${sessionId} is not in session map`);
|
|
614
|
+
this.connection.send({
|
|
615
|
+
type: "control_get_session_list",
|
|
616
|
+
client_message_id: uuidv4(),
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
this.activeSessions.delete(sessionId);
|
|
620
|
+
this.newMessage.delete(sessionId);
|
|
621
|
+
void this.eventHandler.onMessage(msg, this);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private handleControlMessage(msg: ServerControlMessage): void {
|
|
625
|
+
switch (msg.type) {
|
|
626
|
+
case "control_session_left":
|
|
627
|
+
this.activeSessions.delete(msg.session_id);
|
|
628
|
+
break;
|
|
629
|
+
case "control_agent_profile_created":
|
|
630
|
+
this.handleAgentProfileCreated(msg);
|
|
631
|
+
break;
|
|
632
|
+
case "control_agent_profile_deleted":
|
|
633
|
+
this.handleAgentProfileDeleted(msg);
|
|
634
|
+
break;
|
|
635
|
+
case "control_error":
|
|
636
|
+
this.handleControlError(msg);
|
|
637
|
+
break;
|
|
638
|
+
case "control_session_deleted":
|
|
639
|
+
this.handleSessionDeleted(msg);
|
|
640
|
+
break;
|
|
641
|
+
case "control_session_list":
|
|
642
|
+
logger.warn(`[ChatClient] Unexpected message in runtime: ${msg.type} `);
|
|
643
|
+
break;
|
|
644
|
+
case "control_team_created":
|
|
645
|
+
this.handleTeamCreatedMessage(msg);
|
|
646
|
+
break;
|
|
647
|
+
default: {
|
|
648
|
+
const _exhaustive: never = msg;
|
|
649
|
+
throw new Error(`unexpected control msg: ${JSON.stringify(msg)}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
void this.eventHandler.onMessage(msg, this);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
private handleAgentProfileCreated(msg: ServerControlAgentProfileCreated) {
|
|
656
|
+
logger.debug(
|
|
657
|
+
`[ChatClient.handleAgentProfileCreated] msg: ${JSON.stringify(msg)}`
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
const teamId = msg.profile.team_uuid;
|
|
661
|
+
const profileId = msg.profile.uuid;
|
|
662
|
+
const newAgentSession = {
|
|
663
|
+
agent_profile: msg.profile,
|
|
664
|
+
sessions: [],
|
|
665
|
+
updated_at: Date.now(),
|
|
666
|
+
};
|
|
667
|
+
if (teamId) {
|
|
668
|
+
const team = this.teams.get(teamId);
|
|
669
|
+
if (!team) {
|
|
670
|
+
throw new Error(`[handleAgentProfileCreated] no such team ${teamId}`);
|
|
671
|
+
}
|
|
672
|
+
team.agentSessionMap.set(profileId, newAgentSession);
|
|
673
|
+
} else {
|
|
674
|
+
this.userAgentSessionMap.set(profileId, newAgentSession);
|
|
675
|
+
}
|
|
676
|
+
void this.eventHandler.onMessage(msg, this);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
private handleAgentProfileDeleted(_msg: ServerControlAgentProfileDeleted) {
|
|
680
|
+
throw new Error("handleAgentProfileDeleted not implemented");
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
private handleSessionMessage(msg: ServerSessionScopedMessage): void {
|
|
684
|
+
const sessionId = msg.session_id;
|
|
685
|
+
|
|
686
|
+
// TODO: we need to handle create new session case here, previously create
|
|
687
|
+
// new session's param are passed by URL, this is no longer an option
|
|
688
|
+
// since we are not creating a new connection for each session.
|
|
689
|
+
// Handle session_info message for pending session joins
|
|
690
|
+
if (msg.type === "session_info") {
|
|
691
|
+
this.handleSessionInfoMessage(msg);
|
|
692
|
+
// notify the event handler, i.e. UI that the session info update
|
|
693
|
+
void this.eventHandler.onMessage(msg, this);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Handle messages for existing active sessions
|
|
698
|
+
if (this.activeSessions.has(sessionId)) {
|
|
699
|
+
const sessionClient = this.activeSessions.get(sessionId);
|
|
700
|
+
if (sessionClient) {
|
|
701
|
+
sessionClient.handleMessage(msg);
|
|
702
|
+
// we are only calling the onMessage for session scoped message for now.
|
|
703
|
+
void this.eventHandler.onMessage(msg, this);
|
|
704
|
+
} else {
|
|
705
|
+
throw new Error(`Session client not found for session ${sessionId}`);
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
// Session not active yet, mark as having new messages
|
|
709
|
+
this.newMessage.set(sessionId, true);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private handleTeamCreatedMessage(msg: ServerControlTeamCreated): void {
|
|
714
|
+
const teamInfo: TeamInfo = {
|
|
715
|
+
team_uuid: msg.team_uuid,
|
|
716
|
+
team_name: msg.team_name,
|
|
717
|
+
owner_uuid: msg.team_owner_uuid,
|
|
718
|
+
participants: msg.members,
|
|
719
|
+
sessions: [],
|
|
720
|
+
agents: [],
|
|
721
|
+
};
|
|
722
|
+
this.teams.set(teamInfo.team_uuid, ChatClient.toClientTeamInfo(teamInfo));
|
|
723
|
+
this.currentTeamId = teamInfo.team_uuid;
|
|
724
|
+
void this.eventHandler.onMessage(msg, this);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Handle session_info message which can be for:
|
|
730
|
+
* 1. A pending session join/create request
|
|
731
|
+
* 2. An update to an existing active session
|
|
732
|
+
* We update the session list here as well.
|
|
733
|
+
*/
|
|
734
|
+
private handleSessionInfoMessage(msg: ServerSessionInfo): void {
|
|
735
|
+
const sessionId = msg.session_id;
|
|
736
|
+
|
|
737
|
+
// There is a pending request, check if this is the response
|
|
738
|
+
if (
|
|
739
|
+
this.sessionJoinOrCreateRes &&
|
|
740
|
+
msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId
|
|
741
|
+
) {
|
|
742
|
+
const { resolve, reject } = this.sessionJoinOrCreateRes;
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
logger.info(
|
|
746
|
+
`[ChatClient] Creating SessionClient for session ${sessionId}`
|
|
747
|
+
);
|
|
748
|
+
logger.info(`[ChatClient] msg: ${JSON.stringify(msg)}`);
|
|
749
|
+
const sessionClient = new SessionClient(
|
|
750
|
+
sessionId,
|
|
751
|
+
msg.saved_agent_profile,
|
|
752
|
+
this.connection,
|
|
753
|
+
msg.mcp_server_briefs,
|
|
754
|
+
createSessionParticipantMap(msg.participants)
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
// we need to pass the session id if this is a new session
|
|
758
|
+
if (this.sessionJoinOrCreateRes.sessionId === "") {
|
|
759
|
+
this.sessionJoinOrCreateRes.sessionId = sessionId;
|
|
760
|
+
} else if (this.sessionJoinOrCreateRes.sessionId !== sessionId) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
`[ChatClient] session id mismatch: ` +
|
|
763
|
+
`${this.sessionJoinOrCreateRes.sessionId} !== ${sessionId}`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
resolve(sessionClient, msg);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
const errorMsg =
|
|
770
|
+
error instanceof Error ? error : new Error(String(error));
|
|
771
|
+
logger.error(
|
|
772
|
+
`[ChatClient] Failed to create SessionClient: ${errorMsg.message}`
|
|
773
|
+
);
|
|
774
|
+
reject(errorMsg);
|
|
775
|
+
}
|
|
776
|
+
} else {
|
|
777
|
+
this.updateSessionInfo(msg);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
handleControlError(msg: ServerControlError): void {
|
|
782
|
+
// reject the pending session join/create request if message id matches
|
|
783
|
+
if (
|
|
784
|
+
this.sessionJoinOrCreateRes &&
|
|
785
|
+
msg.client_message_id === this.sessionJoinOrCreateRes.clientMessageId
|
|
786
|
+
) {
|
|
787
|
+
this.sessionJoinOrCreateRes.reject(new Error(msg.message));
|
|
788
|
+
}
|
|
789
|
+
this.eventHandler.onError(`Server error: ${msg.message}`);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Update the session list. This is called when a session is updated.
|
|
794
|
+
* An error is thrown if the session id is not found.
|
|
795
|
+
* TODO: we might want to resync session map, for now, throw error to
|
|
796
|
+
* expose problems in the code.
|
|
797
|
+
*/
|
|
798
|
+
private updateAgentSessionMap(sessionInfo: ServerSessionInfo): void {
|
|
799
|
+
const sessionId = sessionInfo.session_id;
|
|
800
|
+
if (sessionInfo.team_uuid) {
|
|
801
|
+
const teamInfo = this.teams.get(sessionInfo.team_uuid);
|
|
802
|
+
if (!teamInfo) {
|
|
803
|
+
throw new Error(`Team ${sessionInfo.team_uuid} not found in team list`);
|
|
804
|
+
}
|
|
805
|
+
//teamInfo.sessions.set(sessionId, ChatClient.toSessionData(sessionInfo));
|
|
806
|
+
const agentSessionMap = teamInfo.agentSessionMap;
|
|
807
|
+
if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
|
|
808
|
+
throw new Error(
|
|
809
|
+
`[updateAgentSessionMap] team session ${sessionId}` +
|
|
810
|
+
` not found in agent session map`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
const agentSessionMap = this.userAgentSessionMap;
|
|
815
|
+
if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
|
|
816
|
+
throw new Error(
|
|
817
|
+
`[updateAgentSessionMap] user session ${sessionId}` +
|
|
818
|
+
` not found in agent session map`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Update the session info for an existing session. This passes the session
|
|
826
|
+
* info to the session client. An error is thrown if the session client is not
|
|
827
|
+
* found.
|
|
828
|
+
*/
|
|
829
|
+
private updateSessionInfo(msg: ServerSessionInfo): void {
|
|
830
|
+
const sessionId = msg.session_id;
|
|
831
|
+
const sessionClient = this.activeSessions.get(sessionId);
|
|
832
|
+
this.updateAgentSessionMap(msg);
|
|
833
|
+
if (sessionClient) {
|
|
834
|
+
sessionClient.updateSessionInfo(msg);
|
|
835
|
+
} else {
|
|
836
|
+
throw new Error(`Session client not found for session ${sessionId}`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private addSessionToAgentSessionMap(msg: ServerSessionInfo): void {
|
|
841
|
+
// get the correct agent session map
|
|
842
|
+
const agentSessionMap = msg.team_uuid
|
|
843
|
+
? this.teams.get(msg.team_uuid)?.agentSessionMap
|
|
844
|
+
: this.userAgentSessionMap;
|
|
845
|
+
if (!agentSessionMap) {
|
|
846
|
+
throw new Error(`Invalid team uuid ${JSON.stringify(msg.team_uuid)}`);
|
|
847
|
+
}
|
|
848
|
+
// update the agent session map
|
|
849
|
+
const agentUuid = msg.saved_agent_profile.uuid;
|
|
850
|
+
const agentSession = agentSessionMap.get(agentUuid);
|
|
851
|
+
if (!agentSession) {
|
|
852
|
+
throw new Error(
|
|
853
|
+
`[addSessionToAgentSessionMap] Agent ${agentUuid}` +
|
|
854
|
+
` not found in agent session map`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
agentSession.sessions.push(sessionInfoToSessionData(msg));
|
|
858
|
+
agentSession.updated_at = new Date(msg.updated_at).getTime();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function sessionInfoToSessionData(msg: ServerSessionInfo): SessionData {
|
|
863
|
+
return {
|
|
864
|
+
session_uuid: msg.session_id,
|
|
865
|
+
title: msg.title,
|
|
866
|
+
team_uuid: msg.team_uuid,
|
|
867
|
+
agent_profile_uuid: msg.saved_agent_profile.uuid,
|
|
868
|
+
workspace: msg.workspace,
|
|
869
|
+
updated_at: msg.updated_at,
|
|
870
|
+
user_uuid: msg.owner_uuid,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Update the agent session map
|
|
876
|
+
* @param agentSessionMap - the agent session map
|
|
877
|
+
* @param sessionInfo - the session info
|
|
878
|
+
* @returns true if the session is updated, false otherwise
|
|
879
|
+
*/
|
|
880
|
+
function doUpdateAgentSessionMap(
|
|
881
|
+
agentSessionMap: Map<string, AgentSessionData>,
|
|
882
|
+
sessionInfo: ServerSessionInfo
|
|
883
|
+
): boolean {
|
|
884
|
+
const sessionId = sessionInfo.session_id;
|
|
885
|
+
const agentUuid = sessionInfo.saved_agent_profile.uuid;
|
|
886
|
+
const agent = agentSessionMap.get(agentUuid);
|
|
887
|
+
if (!agent) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
let updated = false;
|
|
891
|
+
agent.sessions.map((session) => {
|
|
892
|
+
if (session.session_uuid === sessionId) {
|
|
893
|
+
updated = true;
|
|
894
|
+
return sessionInfoToSessionData(sessionInfo);
|
|
895
|
+
} else {
|
|
896
|
+
return session;
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
return updated;
|
|
900
|
+
}
|