@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
|
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.OpenAILLMStreaming = void 0;
|
|
4
4
|
exports.initializeCompletion = initializeCompletion;
|
|
5
5
|
exports.updateCompletion = updateCompletion;
|
|
6
|
-
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
7
6
|
const openai_1 = require("openai");
|
|
8
7
|
const assert_1 = require("assert");
|
|
9
|
-
const
|
|
8
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
9
|
+
const llm_1 = require("./llm");
|
|
10
10
|
const logger = (0, sdk_1.getLogger)();
|
|
11
11
|
function initialToolCallFunction(deltaFn) {
|
|
12
12
|
// export interface ChatCompletionChunk.Choice.Delta.ToolCall.Function {
|
|
@@ -152,6 +152,14 @@ function initializeCompletionMessage(delta) {
|
|
|
152
152
|
tool_calls: toolCalls,
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
+
function updateReasoning(message, reasoning) {
|
|
156
|
+
if (!message.reasoning) {
|
|
157
|
+
message.reasoning = reasoning;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
message.reasoning += reasoning;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
155
163
|
function updateCompletionMessage(message, delta) {
|
|
156
164
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
157
165
|
(0, assert_1.strict)(message.role === "assistant");
|
|
@@ -160,12 +168,20 @@ function updateCompletionMessage(message, delta) {
|
|
|
160
168
|
(0, assert_1.strict)(!message.audio);
|
|
161
169
|
(0, assert_1.strict)(message.tool_calls instanceof Array ||
|
|
162
170
|
typeof message.tool_calls === "undefined");
|
|
163
|
-
// export interface
|
|
171
|
+
// export interface ChatCompletionChunkChoiceDeltaWithReasoning {
|
|
164
172
|
// content?: string | null;
|
|
165
173
|
// function_call?: Delta.FunctionCall;
|
|
166
174
|
// refusal?: string | null;
|
|
167
175
|
// role?: 'developer' | 'system' | 'user' | 'assistant' | 'tool';
|
|
168
176
|
// tool_calls?: Array<Delta.ToolCall>;
|
|
177
|
+
//
|
|
178
|
+
// reasoning?: string;
|
|
179
|
+
// reasoning_details?: {
|
|
180
|
+
// type: "reasoning.text",
|
|
181
|
+
// text?: string,
|
|
182
|
+
// format?: string,
|
|
183
|
+
// index?:0
|
|
184
|
+
// }[]
|
|
169
185
|
// }
|
|
170
186
|
//
|
|
171
187
|
// ->
|
|
@@ -178,6 +194,8 @@ function updateCompletionMessage(message, delta) {
|
|
|
178
194
|
// audio?: ChatCompletionAudio | null;
|
|
179
195
|
// function_call?: ChatCompletionMessage.FunctionCall | null;
|
|
180
196
|
// tool_calls?: Array<ChatCompletionMessageToolCall>;
|
|
197
|
+
//
|
|
198
|
+
// reasoning?: string;
|
|
181
199
|
// }
|
|
182
200
|
if (delta.content) {
|
|
183
201
|
if (message.content) {
|
|
@@ -203,6 +221,10 @@ function updateCompletionMessage(message, delta) {
|
|
|
203
221
|
message.tool_calls = updateToolCalls(message.tool_calls, t);
|
|
204
222
|
}
|
|
205
223
|
}
|
|
224
|
+
const reasoning = (0, llm_1.choiceDeltaExtractReasoning)(delta);
|
|
225
|
+
if (reasoning) {
|
|
226
|
+
updateReasoning(message, reasoning);
|
|
227
|
+
}
|
|
206
228
|
}
|
|
207
229
|
function initializeCompletionChoice(chunkChoice) {
|
|
208
230
|
// export interface ChatCompletionChunk.Choice {
|
|
@@ -377,9 +399,9 @@ class OpenAILLMStreaming {
|
|
|
377
399
|
apiKey,
|
|
378
400
|
baseURL: apiUrl,
|
|
379
401
|
dangerouslyAllowBrowser: true,
|
|
380
|
-
defaultHeaders:
|
|
402
|
+
defaultHeaders: llm_1.XALIA_APP_HEADER,
|
|
381
403
|
});
|
|
382
|
-
this.model = model
|
|
404
|
+
this.model = model;
|
|
383
405
|
}
|
|
384
406
|
setModel(model) {
|
|
385
407
|
this.model = model;
|
|
@@ -390,7 +412,11 @@ class OpenAILLMStreaming {
|
|
|
390
412
|
getUrl() {
|
|
391
413
|
return this.openai.baseURL;
|
|
392
414
|
}
|
|
393
|
-
async getConversationResponse(messages, tools, onMessage) {
|
|
415
|
+
async getConversationResponse(messages, tools, onMessage, onReasoning) {
|
|
416
|
+
const reasoning = {
|
|
417
|
+
effort: "medium",
|
|
418
|
+
enabled: true,
|
|
419
|
+
};
|
|
394
420
|
const chunks = await this.openai.chat.completions.create({
|
|
395
421
|
model: this.model,
|
|
396
422
|
messages,
|
|
@@ -399,6 +425,7 @@ class OpenAILLMStreaming {
|
|
|
399
425
|
stream_options: {
|
|
400
426
|
include_usage: true,
|
|
401
427
|
},
|
|
428
|
+
extra_body: { reasoning },
|
|
402
429
|
});
|
|
403
430
|
// Check the type casting above
|
|
404
431
|
if (!chunks.iterator) {
|
|
@@ -430,6 +457,14 @@ class OpenAILLMStreaming {
|
|
|
430
457
|
await onMessage(delta.content, false);
|
|
431
458
|
}
|
|
432
459
|
}
|
|
460
|
+
if (onReasoning) {
|
|
461
|
+
const delta = chunk.choices[0]
|
|
462
|
+
?.delta;
|
|
463
|
+
const reasoning = (0, llm_1.choiceDeltaExtractReasoning)(delta);
|
|
464
|
+
if (reasoning) {
|
|
465
|
+
await onReasoning(reasoning);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
433
468
|
}
|
|
434
469
|
if (onMessage) {
|
|
435
470
|
await onMessage("", true);
|
|
@@ -15,7 +15,9 @@ class ChatClient {
|
|
|
15
15
|
// agent_uuid -> agent session data
|
|
16
16
|
userAgentSessionMap = new Map(),
|
|
17
17
|
// team_uuid -> team info
|
|
18
|
-
teams = new Map(),
|
|
18
|
+
teams = new Map(),
|
|
19
|
+
// Whether this client is in guest mode
|
|
20
|
+
isGuestMode = false, closed = false, currentSessionId = undefined,
|
|
19
21
|
// note: currentTeamId will not be reset when swtiching to a user session.
|
|
20
22
|
currentTeamId = undefined,
|
|
21
23
|
// session_uuid -> session client
|
|
@@ -28,6 +30,7 @@ class ChatClient {
|
|
|
28
30
|
this.eventHandler = eventHandler;
|
|
29
31
|
this.userAgentSessionMap = userAgentSessionMap;
|
|
30
32
|
this.teams = teams;
|
|
33
|
+
this.isGuestMode = isGuestMode;
|
|
31
34
|
this.closed = closed;
|
|
32
35
|
this.currentSessionId = currentSessionId;
|
|
33
36
|
this.currentTeamId = currentTeamId;
|
|
@@ -39,6 +42,8 @@ class ChatClient {
|
|
|
39
42
|
* Connect to server and create ChatClient instance
|
|
40
43
|
*/
|
|
41
44
|
static async init(url, token, eventHandler) {
|
|
45
|
+
// Determine if this is a guest token (format: guest_<hash>_<session-id>)
|
|
46
|
+
const isGuestMode = token.startsWith('guest_');
|
|
42
47
|
const connection = new connection_1.Connection({
|
|
43
48
|
url,
|
|
44
49
|
token,
|
|
@@ -69,7 +74,7 @@ class ChatClient {
|
|
|
69
74
|
});
|
|
70
75
|
if (!client) {
|
|
71
76
|
logger.info("Creating ChatClient");
|
|
72
|
-
client = new ChatClient(connection, eventHandler, userAgentSessionMap, teams);
|
|
77
|
+
client = new ChatClient(connection, eventHandler, userAgentSessionMap, teams, isGuestMode);
|
|
73
78
|
resolveClient(client);
|
|
74
79
|
}
|
|
75
80
|
else {
|
|
@@ -364,6 +369,22 @@ class ChatClient {
|
|
|
364
369
|
});
|
|
365
370
|
logger.debug(`[ChatClient] Sent session_delete_request for` + ` session ${sessionId}`);
|
|
366
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Delete an agent profile by sending control_agent_profile_delete message
|
|
374
|
+
* @param agentProfileUuid - The UUID of the agent profile to delete
|
|
375
|
+
* @returns void
|
|
376
|
+
*/
|
|
377
|
+
deleteAgentProfile(agentProfileUuid) {
|
|
378
|
+
if (this.closed) {
|
|
379
|
+
throw new Error("ChatClient is closed");
|
|
380
|
+
}
|
|
381
|
+
this.connection.send({
|
|
382
|
+
type: "control_agent_profile_delete",
|
|
383
|
+
agent_profile_uuid: agentProfileUuid,
|
|
384
|
+
});
|
|
385
|
+
logger.debug(`[ChatClient] Sent control_agent_profile_delete for profile ` +
|
|
386
|
+
agentProfileUuid);
|
|
387
|
+
}
|
|
367
388
|
/**
|
|
368
389
|
* Create a new team with initial members
|
|
369
390
|
* @param teamName
|
|
@@ -513,6 +534,9 @@ class ChatClient {
|
|
|
513
534
|
case "control_team_created":
|
|
514
535
|
this.handleTeamCreatedMessage(msg);
|
|
515
536
|
break;
|
|
537
|
+
case "control_team_members_updated":
|
|
538
|
+
this.handleTeamMembersUpdated(msg);
|
|
539
|
+
break;
|
|
516
540
|
default: {
|
|
517
541
|
const _exhaustive = msg;
|
|
518
542
|
throw new Error(`unexpected control msg: ${JSON.stringify(msg)}`);
|
|
@@ -541,8 +565,29 @@ class ChatClient {
|
|
|
541
565
|
}
|
|
542
566
|
void this.eventHandler.onMessage(msg, this);
|
|
543
567
|
}
|
|
544
|
-
handleAgentProfileDeleted(
|
|
545
|
-
|
|
568
|
+
handleAgentProfileDeleted(msg) {
|
|
569
|
+
logger.debug(`[ChatClient.handleAgentProfileDeleted] msg: ${JSON.stringify(msg)}`);
|
|
570
|
+
const profileUuid = msg.profile_uuid;
|
|
571
|
+
let found = false;
|
|
572
|
+
// Try to remove from user agent session map
|
|
573
|
+
if (this.userAgentSessionMap.has(profileUuid)) {
|
|
574
|
+
this.userAgentSessionMap.delete(profileUuid);
|
|
575
|
+
found = true;
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
// Try to remove from team agent session maps
|
|
579
|
+
for (const [_teamId, team] of this.teams.entries()) {
|
|
580
|
+
if (team.agentSessionMap.has(profileUuid)) {
|
|
581
|
+
team.agentSessionMap.delete(profileUuid);
|
|
582
|
+
found = true;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (!found) {
|
|
588
|
+
logger.warn(`[ChatClient] Agent profile ${profileUuid} not found in any session map`);
|
|
589
|
+
}
|
|
590
|
+
void this.eventHandler.onMessage(msg, this);
|
|
546
591
|
}
|
|
547
592
|
handleSessionMessage(msg) {
|
|
548
593
|
const sessionId = msg.session_id;
|
|
@@ -587,6 +632,19 @@ class ChatClient {
|
|
|
587
632
|
void this.eventHandler.onMessage(msg, this);
|
|
588
633
|
return;
|
|
589
634
|
}
|
|
635
|
+
handleTeamMembersUpdated(msg) {
|
|
636
|
+
const team = this.teams.get(msg.team_uuid);
|
|
637
|
+
if (!team) {
|
|
638
|
+
logger.warn(`[ChatClient] Received team_members_updated for unknown team ` +
|
|
639
|
+
msg.team_uuid);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
// Update the participants map
|
|
643
|
+
team.participants = new Map(msg.members.map((participant) => [participant.user_uuid, participant]));
|
|
644
|
+
logger.info(`[ChatClient] Updated team members for team ${msg.team_uuid}, ` +
|
|
645
|
+
`now has ${String(msg.members.length)} members`);
|
|
646
|
+
void this.eventHandler.onMessage(msg, this);
|
|
647
|
+
}
|
|
590
648
|
/**
|
|
591
649
|
* Handle session_info message which can be for:
|
|
592
650
|
* 1. A pending session join/create request
|
|
@@ -602,7 +660,7 @@ class ChatClient {
|
|
|
602
660
|
try {
|
|
603
661
|
logger.info(`[ChatClient] Creating SessionClient for session ${sessionId}`);
|
|
604
662
|
logger.info(`[ChatClient] msg: ${JSON.stringify(msg)}`);
|
|
605
|
-
const sessionClient = new sessionClient_1.SessionClient(sessionId, msg.saved_agent_profile, this.connection, msg.mcp_server_briefs, (0, database_1.createSessionParticipantMap)(msg.participants));
|
|
663
|
+
const sessionClient = new sessionClient_1.SessionClient(sessionId, msg.saved_agent_profile, this.connection, msg.mcp_server_briefs, (0, database_1.createSessionParticipantMap)(msg.participants), msg.agent_paused);
|
|
606
664
|
// we need to pass the session id if this is a new session
|
|
607
665
|
if (this.sessionJoinOrCreateRes.sessionId === "") {
|
|
608
666
|
this.sessionJoinOrCreateRes.sessionId = sessionId;
|
|
@@ -639,12 +697,18 @@ class ChatClient {
|
|
|
639
697
|
*/
|
|
640
698
|
updateAgentSessionMap(sessionInfo) {
|
|
641
699
|
const sessionId = sessionInfo.session_id;
|
|
700
|
+
// Skip agent session map updates for guest sessions since they don't
|
|
701
|
+
// need session organization
|
|
702
|
+
if (this.isGuestMode) {
|
|
703
|
+
logger.info(`[ChatClient] Skipping agent session map update for ` +
|
|
704
|
+
`guest session ${sessionId}`);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
642
707
|
if (sessionInfo.team_uuid) {
|
|
643
708
|
const teamInfo = this.teams.get(sessionInfo.team_uuid);
|
|
644
709
|
if (!teamInfo) {
|
|
645
710
|
throw new Error(`Team ${sessionInfo.team_uuid} not found in team list`);
|
|
646
711
|
}
|
|
647
|
-
//teamInfo.sessions.set(sessionId, ChatClient.toSessionData(sessionInfo));
|
|
648
712
|
const agentSessionMap = teamInfo.agentSessionMap;
|
|
649
713
|
if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
|
|
650
714
|
throw new Error(`[updateAgentSessionMap] team session ${sessionId}` +
|
|
@@ -660,9 +724,9 @@ class ChatClient {
|
|
|
660
724
|
}
|
|
661
725
|
}
|
|
662
726
|
/**
|
|
663
|
-
* Update the
|
|
664
|
-
*
|
|
665
|
-
* found.
|
|
727
|
+
* Update the SessionDescriptor info for an existing session, given a full
|
|
728
|
+
* ServerSessionInfo. This also passes the session info to the session
|
|
729
|
+
* client. An error is thrown if the session client is not found.
|
|
666
730
|
*/
|
|
667
731
|
updateSessionInfo(msg) {
|
|
668
732
|
const sessionId = msg.session_id;
|
|
@@ -676,6 +740,13 @@ class ChatClient {
|
|
|
676
740
|
}
|
|
677
741
|
}
|
|
678
742
|
addSessionToAgentSessionMap(msg) {
|
|
743
|
+
// Skip agent session map updates for guest sessions since they don't
|
|
744
|
+
// need session organization
|
|
745
|
+
if (this.isGuestMode) {
|
|
746
|
+
logger.info(`[ChatClient] Skipping agent session map add for ` +
|
|
747
|
+
`guest session ${msg.session_id}`);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
679
750
|
// get the correct agent session map
|
|
680
751
|
const agentSessionMap = msg.team_uuid
|
|
681
752
|
? this.teams.get(msg.team_uuid)?.agentSessionMap
|
|
@@ -690,20 +761,20 @@ class ChatClient {
|
|
|
690
761
|
throw new Error(`[addSessionToAgentSessionMap] Agent ${agentUuid}` +
|
|
691
762
|
` not found in agent session map`);
|
|
692
763
|
}
|
|
693
|
-
agentSession.sessions.push(
|
|
764
|
+
agentSession.sessions.push(sessionInfoToSessionDescriptor(msg));
|
|
694
765
|
agentSession.updated_at = new Date(msg.updated_at).getTime();
|
|
695
766
|
}
|
|
696
767
|
}
|
|
697
768
|
exports.ChatClient = ChatClient;
|
|
698
|
-
function
|
|
769
|
+
function sessionInfoToSessionDescriptor(msg) {
|
|
699
770
|
return {
|
|
700
771
|
session_uuid: msg.session_id,
|
|
701
772
|
title: msg.title,
|
|
702
773
|
team_uuid: msg.team_uuid,
|
|
703
774
|
agent_profile_uuid: msg.saved_agent_profile.uuid,
|
|
704
|
-
workspace: msg.workspace,
|
|
705
775
|
updated_at: msg.updated_at,
|
|
706
776
|
user_uuid: msg.owner_uuid,
|
|
777
|
+
agent_paused: msg.agent_paused,
|
|
707
778
|
};
|
|
708
779
|
}
|
|
709
780
|
/**
|
|
@@ -723,7 +794,7 @@ function doUpdateAgentSessionMap(agentSessionMap, sessionInfo) {
|
|
|
723
794
|
agent.sessions.map((session) => {
|
|
724
795
|
if (session.session_uuid === sessionId) {
|
|
725
796
|
updated = true;
|
|
726
|
-
return
|
|
797
|
+
return sessionInfoToSessionDescriptor(sessionInfo);
|
|
727
798
|
}
|
|
728
799
|
else {
|
|
729
800
|
return session;
|
|
@@ -5,7 +5,10 @@ const assert_1 = require("assert");
|
|
|
5
5
|
const uuid_1 = require("uuid");
|
|
6
6
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
7
7
|
const mcpServerManager_1 = require("../../agent/mcpServerManager");
|
|
8
|
+
const messages_1 = require("../protocol/messages");
|
|
8
9
|
const database_1 = require("../data/database");
|
|
10
|
+
const sessionFiles_1 = require("./sessionFiles");
|
|
11
|
+
const responseHandler_1 = require("./responseHandler");
|
|
9
12
|
const logger = (0, sdk_1.getLogger)();
|
|
10
13
|
class RemoteSudoMcpServerManager {
|
|
11
14
|
constructor(sender, briefs) {
|
|
@@ -143,20 +146,27 @@ class RemoteSudoMcpServerManager {
|
|
|
143
146
|
async shutdown() { }
|
|
144
147
|
}
|
|
145
148
|
class SessionClient {
|
|
146
|
-
constructor(sessionUUID, savedAgentProfile, sender, serverBriefs, participants) {
|
|
149
|
+
constructor(sessionUUID, savedAgentProfile, sender, serverBriefs, participants, agentPaused) {
|
|
150
|
+
const fileList = []; // Pass in?
|
|
147
151
|
this.sessionUUID = sessionUUID;
|
|
148
152
|
this.savedAgentProfile = savedAgentProfile;
|
|
149
153
|
this.sender = sender;
|
|
150
|
-
this.participants = participants;
|
|
151
|
-
this.sender = sender;
|
|
152
154
|
this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs);
|
|
155
|
+
this.sessionFiles = new sessionFiles_1.SessionFiles(sessionUUID, fileList, sender);
|
|
156
|
+
this.responseHandler = new responseHandler_1.ResponseHandler("session_error");
|
|
157
|
+
this.participants = participants;
|
|
153
158
|
this.systemPrompt = "";
|
|
154
159
|
this.model = "";
|
|
155
|
-
this.
|
|
160
|
+
this.agentPaused = agentPaused;
|
|
156
161
|
}
|
|
157
162
|
getSessionUUID() {
|
|
158
163
|
return this.sessionUUID;
|
|
159
164
|
}
|
|
165
|
+
/// This object can be queried to upload or download files, or get the
|
|
166
|
+
/// latest list. See `SessionFiles` for full usage.
|
|
167
|
+
getSessionFiles() {
|
|
168
|
+
return this.sessionFiles;
|
|
169
|
+
}
|
|
160
170
|
getSudoMcpServerManager() {
|
|
161
171
|
return this.smsm;
|
|
162
172
|
}
|
|
@@ -187,6 +197,15 @@ class SessionClient {
|
|
|
187
197
|
// Don't set model here. Wait until we get confirmation from the server.
|
|
188
198
|
this.sendSessionMessage({ type: "set_model", model });
|
|
189
199
|
}
|
|
200
|
+
getAgentPaused() {
|
|
201
|
+
return this.agentPaused;
|
|
202
|
+
}
|
|
203
|
+
setAgentPaused(paused) {
|
|
204
|
+
this.sendSessionMessage({
|
|
205
|
+
type: "set_agent_paused",
|
|
206
|
+
paused,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
190
209
|
userMessage(msg, imageB64) {
|
|
191
210
|
(0, assert_1.strict)(msg || imageB64, "Either message or image must be provided");
|
|
192
211
|
this.sendSessionMessage({ type: "msg", message: msg, imageB64 });
|
|
@@ -249,8 +268,7 @@ class SessionClient {
|
|
|
249
268
|
this.sender.send(enrichedMessage);
|
|
250
269
|
}
|
|
251
270
|
updateSessionInfo(sessionInfo) {
|
|
252
|
-
// TODO:
|
|
253
|
-
// throw new Error("[SessionClient.updateSessionInfo] not implemented");
|
|
271
|
+
// TODO: Determine the correct approach. Cache the workspace?
|
|
254
272
|
const infoStr = JSON.stringify(sessionInfo.workspace);
|
|
255
273
|
logger.debug(`[SessionClient] ignoring session info: ${infoStr}`);
|
|
256
274
|
}
|
|
@@ -278,7 +296,24 @@ class SessionClient {
|
|
|
278
296
|
server_name,
|
|
279
297
|
});
|
|
280
298
|
}
|
|
299
|
+
async shareSession() {
|
|
300
|
+
const msg = {
|
|
301
|
+
type: "share_session",
|
|
302
|
+
client_message_id: (0, uuid_1.v4)(),
|
|
303
|
+
session_id: this.sessionUUID,
|
|
304
|
+
};
|
|
305
|
+
this.sender.send(msg);
|
|
306
|
+
const response = await this.responseHandler.waitForResponse(msg);
|
|
307
|
+
if (response.type === "session_shared") {
|
|
308
|
+
return response.access_token;
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`unexpected response to "share"session": ${response.type}`);
|
|
311
|
+
}
|
|
281
312
|
handleMessage(message) {
|
|
313
|
+
if ((0, messages_1.isServerSessionFileMessage)(message)) {
|
|
314
|
+
this.sessionFiles.onMessage(message);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
282
317
|
switch (message.type) {
|
|
283
318
|
//
|
|
284
319
|
// State updates
|
|
@@ -318,6 +353,12 @@ class SessionClient {
|
|
|
318
353
|
case "user_removed":
|
|
319
354
|
this.participants.delete(message.user_uuid);
|
|
320
355
|
break;
|
|
356
|
+
case "session_shared":
|
|
357
|
+
this.responseHandler.onMessage(message);
|
|
358
|
+
break;
|
|
359
|
+
case "agent_paused":
|
|
360
|
+
this.agentPaused = message.paused;
|
|
361
|
+
break;
|
|
321
362
|
//
|
|
322
363
|
// Ignore other messages - the owner (the UI layer) can handle them at
|
|
323
364
|
// its discretion.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionFiles = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const assert_1 = require("assert");
|
|
6
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
7
|
+
const responseHandler_1 = require("./responseHandler");
|
|
8
|
+
const logger = (0, sdk_1.getLogger)();
|
|
9
|
+
/// Object for the UI to use to interact with the FileManager. If the UI
|
|
10
|
+
/// receives ServerSessionFileChanged or ServerSessionFileDeleted then it
|
|
11
|
+
/// should call `listFiles()` to get the new list of files, and optionally
|
|
12
|
+
/// request the latest content via `getFileContent`.
|
|
13
|
+
class SessionFiles {
|
|
14
|
+
constructor(sessionUUID, initialFileList, sender) {
|
|
15
|
+
this.sessionUUID = sessionUUID;
|
|
16
|
+
this.descriptors = new Map(initialFileList.map((d) => [d.name, d]));
|
|
17
|
+
this.responseHandler = new responseHandler_1.ResponseHandler(undefined);
|
|
18
|
+
this.sender = sender;
|
|
19
|
+
}
|
|
20
|
+
listFiles() {
|
|
21
|
+
return Array.from(this.descriptors.values());
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Retrieve file contents.
|
|
25
|
+
*/
|
|
26
|
+
async getFileContent(name) {
|
|
27
|
+
const msg = {
|
|
28
|
+
type: "session_file_get_content",
|
|
29
|
+
session_id: this.sessionUUID,
|
|
30
|
+
name,
|
|
31
|
+
client_message_id: (0, uuid_1.v4)(),
|
|
32
|
+
};
|
|
33
|
+
this.sender.send(msg);
|
|
34
|
+
const response = await this.responseHandler.waitForResponse(msg);
|
|
35
|
+
if (response.name !== name) {
|
|
36
|
+
throw new Error(`invalid name for file ${name}: ${JSON.stringify(response)}`);
|
|
37
|
+
}
|
|
38
|
+
return response.data_url;
|
|
39
|
+
}
|
|
40
|
+
deleteFile(name) {
|
|
41
|
+
const msg = {
|
|
42
|
+
type: "session_file_delete",
|
|
43
|
+
session_id: this.sessionUUID,
|
|
44
|
+
name,
|
|
45
|
+
client_message_id: (0, uuid_1.v4)(),
|
|
46
|
+
};
|
|
47
|
+
this.sender.send(msg);
|
|
48
|
+
}
|
|
49
|
+
putFileContent(name, summary, data_url) {
|
|
50
|
+
// TODO: eventually, we could wait for a response which includes an AI
|
|
51
|
+
// assigned name.
|
|
52
|
+
(0, assert_1.strict)(name, "for now, uploaded content must have a name");
|
|
53
|
+
const msg = {
|
|
54
|
+
type: "session_file_put_content",
|
|
55
|
+
session_id: this.sessionUUID,
|
|
56
|
+
name,
|
|
57
|
+
summary,
|
|
58
|
+
data_url,
|
|
59
|
+
client_message_id: (0, uuid_1.v4)(),
|
|
60
|
+
};
|
|
61
|
+
this.sender.send(msg);
|
|
62
|
+
}
|
|
63
|
+
onMessage(msg) {
|
|
64
|
+
logger.debug(`[SessionFiles.onMessage]: msg: ${JSON.stringify(msg)}`);
|
|
65
|
+
switch (msg.type) {
|
|
66
|
+
case "session_file_changed":
|
|
67
|
+
this.descriptors.set(msg.descriptor.name, msg.descriptor);
|
|
68
|
+
break;
|
|
69
|
+
case "session_file_deleted":
|
|
70
|
+
this.descriptors.delete(msg.name);
|
|
71
|
+
break;
|
|
72
|
+
case "session_file_content":
|
|
73
|
+
this.responseHandler.onMessage(msg);
|
|
74
|
+
break;
|
|
75
|
+
default: {
|
|
76
|
+
const _ = msg;
|
|
77
|
+
const msgStr = JSON.stringify(msg);
|
|
78
|
+
throw new Error(`[SessionFiles.onMessage] invalid message: ${msgStr}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
decodeSessionFileUrl(url) {
|
|
83
|
+
const u = new URL(url);
|
|
84
|
+
if (u.protocol !== "file+session:") {
|
|
85
|
+
throw new Error(`unexpected protocol ${u.protocol} (file+session)`);
|
|
86
|
+
}
|
|
87
|
+
if (u.port || u.search || u.hash) {
|
|
88
|
+
throw new Error("badly formed session file url: ${url}");
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
session_uuid: u.host || this.sessionUUID,
|
|
92
|
+
name: removeLeadingSlashes(u.pathname),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.SessionFiles = SessionFiles;
|
|
97
|
+
function removeLeadingSlashes(name) {
|
|
98
|
+
while (name.startsWith("/")) {
|
|
99
|
+
name = name.slice(1);
|
|
100
|
+
}
|
|
101
|
+
return name;
|
|
102
|
+
}
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
exports.ApiKeyManager = void 0;
|
|
6
6
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
7
7
|
const logger = (0, sdk_1.getLogger)();
|
|
8
|
+
const API_KEY_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
8
9
|
class ApiKeyManager {
|
|
9
10
|
constructor(db) {
|
|
10
11
|
this.db = db;
|
|
@@ -15,13 +16,43 @@ class ApiKeyManager {
|
|
|
15
16
|
const userInfo = await this.db.getUserDataFromApiKey(apiKey);
|
|
16
17
|
logger.info(`[ApiKeyManager] User info: ${JSON.stringify(userInfo)}`);
|
|
17
18
|
return userInfo;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates a standard api key of the form:
|
|
22
|
+
*
|
|
23
|
+
* <prefix>_<[A-Z][a-z][0-9]+....>
|
|
24
|
+
*
|
|
25
|
+
* matching the format used in the mcppro backend
|
|
26
|
+
*/
|
|
27
|
+
static createApiKey(prefix = ApiKeyManager.PREFIX, length = 32) {
|
|
28
|
+
// See mcppro/app/server/api_key.py:
|
|
29
|
+
//
|
|
30
|
+
// def create_api_key(prefix: str, length: int = 32) -> str:
|
|
31
|
+
// ...
|
|
32
|
+
const chars = Array.from({ length }, () => {
|
|
33
|
+
const i = Math.floor(Math.random() * API_KEY_ALPHABET.length);
|
|
34
|
+
return API_KEY_ALPHABET[i];
|
|
35
|
+
}).join("");
|
|
36
|
+
return `${prefix}_${chars}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates an api key of the form:
|
|
40
|
+
*
|
|
41
|
+
* <prefix>_<[A-Z][a-z][0-9]+....>/<payload>
|
|
42
|
+
*
|
|
43
|
+
* where <payload> is used to convey extra data.
|
|
44
|
+
*/
|
|
45
|
+
static createApiKeyWithPayload(prefix, payload, length = 32) {
|
|
46
|
+
return `${ApiKeyManager.createApiKey(prefix, length)}_${payload}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse token (containing an api and optional payload)
|
|
50
|
+
*/
|
|
51
|
+
static parseToken(token) {
|
|
52
|
+
const [prefix, apiKeyVal, payload] = token.split("_");
|
|
53
|
+
const apiKey = `${prefix}_${apiKeyVal}`;
|
|
54
|
+
return { prefix, apiKey, payload };
|
|
25
55
|
}
|
|
26
56
|
}
|
|
27
57
|
exports.ApiKeyManager = ApiKeyManager;
|
|
58
|
+
ApiKeyManager.PREFIX = "xmcp";
|