@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
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import { getLogger } from "@xalia/xmcp/sdk";
|
|
2
|
-
import { ILLM } from "./llm";
|
|
3
1
|
import { OpenAI } from "openai";
|
|
4
2
|
import { strict as assert } from "assert";
|
|
5
|
-
|
|
3
|
+
|
|
4
|
+
import { getLogger } from "@xalia/xmcp/sdk";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ILLM,
|
|
8
|
+
ChatCompletionChunkChoiceDeltaWithReasoning,
|
|
9
|
+
ChatCompletionMessageWithReasoning,
|
|
10
|
+
Reasoning,
|
|
11
|
+
choiceDeltaExtractReasoning,
|
|
12
|
+
XALIA_APP_HEADER,
|
|
13
|
+
} from "./llm";
|
|
6
14
|
|
|
7
15
|
const logger = getLogger();
|
|
8
16
|
|
|
@@ -186,9 +194,20 @@ function initializeCompletionMessage(
|
|
|
186
194
|
};
|
|
187
195
|
}
|
|
188
196
|
|
|
197
|
+
function updateReasoning(
|
|
198
|
+
message: ChatCompletionMessageWithReasoning,
|
|
199
|
+
reasoning: string
|
|
200
|
+
) {
|
|
201
|
+
if (!message.reasoning) {
|
|
202
|
+
message.reasoning = reasoning;
|
|
203
|
+
} else {
|
|
204
|
+
message.reasoning += reasoning;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
189
208
|
function updateCompletionMessage(
|
|
190
|
-
message:
|
|
191
|
-
delta:
|
|
209
|
+
message: ChatCompletionMessageWithReasoning,
|
|
210
|
+
delta: ChatCompletionChunkChoiceDeltaWithReasoning
|
|
192
211
|
) {
|
|
193
212
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
194
213
|
assert(message.role === "assistant");
|
|
@@ -200,12 +219,20 @@ function updateCompletionMessage(
|
|
|
200
219
|
typeof message.tool_calls === "undefined"
|
|
201
220
|
);
|
|
202
221
|
|
|
203
|
-
// export interface
|
|
222
|
+
// export interface ChatCompletionChunkChoiceDeltaWithReasoning {
|
|
204
223
|
// content?: string | null;
|
|
205
224
|
// function_call?: Delta.FunctionCall;
|
|
206
225
|
// refusal?: string | null;
|
|
207
226
|
// role?: 'developer' | 'system' | 'user' | 'assistant' | 'tool';
|
|
208
227
|
// tool_calls?: Array<Delta.ToolCall>;
|
|
228
|
+
//
|
|
229
|
+
// reasoning?: string;
|
|
230
|
+
// reasoning_details?: {
|
|
231
|
+
// type: "reasoning.text",
|
|
232
|
+
// text?: string,
|
|
233
|
+
// format?: string,
|
|
234
|
+
// index?:0
|
|
235
|
+
// }[]
|
|
209
236
|
// }
|
|
210
237
|
//
|
|
211
238
|
// ->
|
|
@@ -218,6 +245,8 @@ function updateCompletionMessage(
|
|
|
218
245
|
// audio?: ChatCompletionAudio | null;
|
|
219
246
|
// function_call?: ChatCompletionMessage.FunctionCall | null;
|
|
220
247
|
// tool_calls?: Array<ChatCompletionMessageToolCall>;
|
|
248
|
+
//
|
|
249
|
+
// reasoning?: string;
|
|
221
250
|
// }
|
|
222
251
|
|
|
223
252
|
if (delta.content) {
|
|
@@ -242,6 +271,11 @@ function updateCompletionMessage(
|
|
|
242
271
|
message.tool_calls = updateToolCalls(message.tool_calls, t);
|
|
243
272
|
}
|
|
244
273
|
}
|
|
274
|
+
|
|
275
|
+
const reasoning = choiceDeltaExtractReasoning(delta);
|
|
276
|
+
if (reasoning) {
|
|
277
|
+
updateReasoning(message, reasoning);
|
|
278
|
+
}
|
|
245
279
|
}
|
|
246
280
|
|
|
247
281
|
function initializeCompletionChoice(
|
|
@@ -451,18 +485,14 @@ export class OpenAILLMStreaming implements ILLM {
|
|
|
451
485
|
private readonly openai: OpenAI;
|
|
452
486
|
private model: string;
|
|
453
487
|
|
|
454
|
-
constructor(
|
|
455
|
-
apiKey: string,
|
|
456
|
-
apiUrl: string | undefined,
|
|
457
|
-
model: string | undefined
|
|
458
|
-
) {
|
|
488
|
+
constructor(apiKey: string, apiUrl: string | undefined, model: string) {
|
|
459
489
|
this.openai = new OpenAI({
|
|
460
490
|
apiKey,
|
|
461
491
|
baseURL: apiUrl,
|
|
462
492
|
dangerouslyAllowBrowser: true,
|
|
463
493
|
defaultHeaders: XALIA_APP_HEADER,
|
|
464
494
|
});
|
|
465
|
-
this.model = model
|
|
495
|
+
this.model = model;
|
|
466
496
|
}
|
|
467
497
|
|
|
468
498
|
public setModel(model: string) {
|
|
@@ -480,8 +510,13 @@ export class OpenAILLMStreaming implements ILLM {
|
|
|
480
510
|
public async getConversationResponse(
|
|
481
511
|
messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
|
|
482
512
|
tools?: OpenAI.Chat.Completions.ChatCompletionTool[],
|
|
483
|
-
onMessage?: (msg: string, end: boolean) => Promise<void
|
|
513
|
+
onMessage?: (msg: string, end: boolean) => Promise<void>,
|
|
514
|
+
onReasoning?: (reasoning: string) => Promise<void>
|
|
484
515
|
): Promise<OpenAI.Chat.Completions.ChatCompletion> {
|
|
516
|
+
const reasoning: Reasoning = {
|
|
517
|
+
effort: "medium",
|
|
518
|
+
enabled: true,
|
|
519
|
+
};
|
|
485
520
|
const chunks = await this.openai.chat.completions.create({
|
|
486
521
|
model: this.model,
|
|
487
522
|
messages,
|
|
@@ -490,7 +525,8 @@ export class OpenAILLMStreaming implements ILLM {
|
|
|
490
525
|
stream_options: {
|
|
491
526
|
include_usage: true,
|
|
492
527
|
},
|
|
493
|
-
|
|
528
|
+
extra_body: { reasoning },
|
|
529
|
+
} as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming);
|
|
494
530
|
|
|
495
531
|
// Check the type casting above
|
|
496
532
|
if (!(chunks as unknown as { iterator: unknown }).iterator) {
|
|
@@ -498,6 +534,7 @@ export class OpenAILLMStreaming implements ILLM {
|
|
|
498
534
|
}
|
|
499
535
|
|
|
500
536
|
let aggregatedMessage: OpenAI.Chat.Completions.ChatCompletion | undefined;
|
|
537
|
+
|
|
501
538
|
for await (const chunk of chunks) {
|
|
502
539
|
logger.debug(`[stream] chunk: ${JSON.stringify(chunk)}`);
|
|
503
540
|
|
|
@@ -526,6 +563,15 @@ export class OpenAILLMStreaming implements ILLM {
|
|
|
526
563
|
await onMessage(delta.content, false);
|
|
527
564
|
}
|
|
528
565
|
}
|
|
566
|
+
|
|
567
|
+
if (onReasoning) {
|
|
568
|
+
const delta = chunk.choices[0]
|
|
569
|
+
?.delta as ChatCompletionChunkChoiceDeltaWithReasoning;
|
|
570
|
+
const reasoning = choiceDeltaExtractReasoning(delta);
|
|
571
|
+
if (reasoning) {
|
|
572
|
+
await onReasoning(reasoning);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
529
575
|
}
|
|
530
576
|
|
|
531
577
|
if (onMessage) {
|
|
@@ -6,10 +6,10 @@ import { Connection } from "./connection";
|
|
|
6
6
|
import { SessionClient } from "./sessionClient";
|
|
7
7
|
import { IChatClientEventHandler } from "./interfaces";
|
|
8
8
|
import {
|
|
9
|
-
SessionData,
|
|
10
9
|
TeamInfo,
|
|
11
10
|
TeamParticipant,
|
|
12
11
|
AgentSessionData,
|
|
12
|
+
SessionDescriptor,
|
|
13
13
|
} from "../data/dataModels";
|
|
14
14
|
import { createSessionParticipantMap } from "../data/database";
|
|
15
15
|
import {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
ServerControlMessage,
|
|
22
22
|
ServerControlSessionDeleted,
|
|
23
23
|
ServerControlTeamCreated,
|
|
24
|
+
ServerControlTeamMembersUpdated,
|
|
24
25
|
ServerToClient,
|
|
25
26
|
ClientToServer,
|
|
26
27
|
ServerControlAgentProfileCreated,
|
|
@@ -59,6 +60,8 @@ export class ChatClient implements ITeamManager {
|
|
|
59
60
|
private userAgentSessionMap: Map<string, AgentSessionData> = new Map(),
|
|
60
61
|
// team_uuid -> team info
|
|
61
62
|
private teams: Map<string, ClientTeamInfo> = new Map(),
|
|
63
|
+
// Whether this client is in guest mode
|
|
64
|
+
private isGuestMode: boolean = false,
|
|
62
65
|
private closed: boolean = false,
|
|
63
66
|
private currentSessionId: string | undefined = undefined,
|
|
64
67
|
// note: currentTeamId will not be reset when swtiching to a user session.
|
|
@@ -81,6 +84,9 @@ export class ChatClient implements ITeamManager {
|
|
|
81
84
|
token: string,
|
|
82
85
|
eventHandler: IChatClientEventHandler
|
|
83
86
|
): Promise<ChatClient> {
|
|
87
|
+
// Determine if this is a guest token (format: guest_<hash>_<session-id>)
|
|
88
|
+
const isGuestMode = token.startsWith('guest_');
|
|
89
|
+
|
|
84
90
|
const connection = new Connection<ClientToServer, ServerToClient>({
|
|
85
91
|
url,
|
|
86
92
|
token,
|
|
@@ -92,7 +98,7 @@ export class ChatClient implements ITeamManager {
|
|
|
92
98
|
// Register session_info handler for initialization
|
|
93
99
|
connection.on("control_session_list", (msg) => {
|
|
94
100
|
// get user sessions, user agents, and team sessions
|
|
95
|
-
const userSessions = new Map<string,
|
|
101
|
+
const userSessions = new Map<string, SessionDescriptor>();
|
|
96
102
|
msg.user_sessions.forEach((session) => {
|
|
97
103
|
userSessions.set(session.session_uuid, session);
|
|
98
104
|
});
|
|
@@ -127,7 +133,8 @@ export class ChatClient implements ITeamManager {
|
|
|
127
133
|
connection,
|
|
128
134
|
eventHandler,
|
|
129
135
|
userAgentSessionMap,
|
|
130
|
-
teams
|
|
136
|
+
teams,
|
|
137
|
+
isGuestMode
|
|
131
138
|
);
|
|
132
139
|
resolveClient(client);
|
|
133
140
|
} else {
|
|
@@ -476,6 +483,27 @@ export class ChatClient implements ITeamManager {
|
|
|
476
483
|
);
|
|
477
484
|
}
|
|
478
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Delete an agent profile by sending control_agent_profile_delete message
|
|
488
|
+
* @param agentProfileUuid - The UUID of the agent profile to delete
|
|
489
|
+
* @returns void
|
|
490
|
+
*/
|
|
491
|
+
deleteAgentProfile(agentProfileUuid: string): void {
|
|
492
|
+
if (this.closed) {
|
|
493
|
+
throw new Error("ChatClient is closed");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
this.connection.send({
|
|
497
|
+
type: "control_agent_profile_delete",
|
|
498
|
+
agent_profile_uuid: agentProfileUuid,
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
logger.debug(
|
|
502
|
+
`[ChatClient] Sent control_agent_profile_delete for profile ` +
|
|
503
|
+
agentProfileUuid
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
479
507
|
/**
|
|
480
508
|
* Create a new team with initial members
|
|
481
509
|
* @param teamName
|
|
@@ -644,6 +672,9 @@ export class ChatClient implements ITeamManager {
|
|
|
644
672
|
case "control_team_created":
|
|
645
673
|
this.handleTeamCreatedMessage(msg);
|
|
646
674
|
break;
|
|
675
|
+
case "control_team_members_updated":
|
|
676
|
+
this.handleTeamMembersUpdated(msg);
|
|
677
|
+
break;
|
|
647
678
|
default: {
|
|
648
679
|
const _exhaustive: never = msg;
|
|
649
680
|
throw new Error(`unexpected control msg: ${JSON.stringify(msg)}`);
|
|
@@ -676,8 +707,36 @@ export class ChatClient implements ITeamManager {
|
|
|
676
707
|
void this.eventHandler.onMessage(msg, this);
|
|
677
708
|
}
|
|
678
709
|
|
|
679
|
-
private handleAgentProfileDeleted(
|
|
680
|
-
|
|
710
|
+
private handleAgentProfileDeleted(msg: ServerControlAgentProfileDeleted) {
|
|
711
|
+
logger.debug(
|
|
712
|
+
`[ChatClient.handleAgentProfileDeleted] msg: ${JSON.stringify(msg)}`
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
const profileUuid = msg.profile_uuid;
|
|
716
|
+
let found = false;
|
|
717
|
+
|
|
718
|
+
// Try to remove from user agent session map
|
|
719
|
+
if (this.userAgentSessionMap.has(profileUuid)) {
|
|
720
|
+
this.userAgentSessionMap.delete(profileUuid);
|
|
721
|
+
found = true;
|
|
722
|
+
} else {
|
|
723
|
+
// Try to remove from team agent session maps
|
|
724
|
+
for (const [_teamId, team] of this.teams.entries()) {
|
|
725
|
+
if (team.agentSessionMap.has(profileUuid)) {
|
|
726
|
+
team.agentSessionMap.delete(profileUuid);
|
|
727
|
+
found = true;
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (!found) {
|
|
734
|
+
logger.warn(
|
|
735
|
+
`[ChatClient] Agent profile ${profileUuid} not found in any session map`
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
void this.eventHandler.onMessage(msg, this);
|
|
681
740
|
}
|
|
682
741
|
|
|
683
742
|
private handleSessionMessage(msg: ServerSessionScopedMessage): void {
|
|
@@ -725,6 +784,28 @@ export class ChatClient implements ITeamManager {
|
|
|
725
784
|
return;
|
|
726
785
|
}
|
|
727
786
|
|
|
787
|
+
private handleTeamMembersUpdated(msg: ServerControlTeamMembersUpdated): void {
|
|
788
|
+
const team = this.teams.get(msg.team_uuid);
|
|
789
|
+
if (!team) {
|
|
790
|
+
logger.warn(
|
|
791
|
+
`[ChatClient] Received team_members_updated for unknown team ` +
|
|
792
|
+
msg.team_uuid
|
|
793
|
+
);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Update the participants map
|
|
798
|
+
team.participants = new Map(
|
|
799
|
+
msg.members.map((participant) => [participant.user_uuid, participant])
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
logger.info(
|
|
803
|
+
`[ChatClient] Updated team members for team ${msg.team_uuid}, ` +
|
|
804
|
+
`now has ${String(msg.members.length)} members`
|
|
805
|
+
);
|
|
806
|
+
void this.eventHandler.onMessage(msg, this);
|
|
807
|
+
}
|
|
808
|
+
|
|
728
809
|
/**
|
|
729
810
|
* Handle session_info message which can be for:
|
|
730
811
|
* 1. A pending session join/create request
|
|
@@ -751,7 +832,8 @@ export class ChatClient implements ITeamManager {
|
|
|
751
832
|
msg.saved_agent_profile,
|
|
752
833
|
this.connection,
|
|
753
834
|
msg.mcp_server_briefs,
|
|
754
|
-
createSessionParticipantMap(msg.participants)
|
|
835
|
+
createSessionParticipantMap(msg.participants),
|
|
836
|
+
msg.agent_paused
|
|
755
837
|
);
|
|
756
838
|
|
|
757
839
|
// we need to pass the session id if this is a new session
|
|
@@ -797,12 +879,23 @@ export class ChatClient implements ITeamManager {
|
|
|
797
879
|
*/
|
|
798
880
|
private updateAgentSessionMap(sessionInfo: ServerSessionInfo): void {
|
|
799
881
|
const sessionId = sessionInfo.session_id;
|
|
882
|
+
|
|
883
|
+
// Skip agent session map updates for guest sessions since they don't
|
|
884
|
+
// need session organization
|
|
885
|
+
if (this.isGuestMode) {
|
|
886
|
+
logger.info(
|
|
887
|
+
`[ChatClient] Skipping agent session map update for ` +
|
|
888
|
+
`guest session ${sessionId}`
|
|
889
|
+
);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
800
893
|
if (sessionInfo.team_uuid) {
|
|
801
894
|
const teamInfo = this.teams.get(sessionInfo.team_uuid);
|
|
802
895
|
if (!teamInfo) {
|
|
803
896
|
throw new Error(`Team ${sessionInfo.team_uuid} not found in team list`);
|
|
804
897
|
}
|
|
805
|
-
|
|
898
|
+
|
|
806
899
|
const agentSessionMap = teamInfo.agentSessionMap;
|
|
807
900
|
if (!doUpdateAgentSessionMap(agentSessionMap, sessionInfo)) {
|
|
808
901
|
throw new Error(
|
|
@@ -822,9 +915,9 @@ export class ChatClient implements ITeamManager {
|
|
|
822
915
|
}
|
|
823
916
|
|
|
824
917
|
/**
|
|
825
|
-
* Update the
|
|
826
|
-
*
|
|
827
|
-
* found.
|
|
918
|
+
* Update the SessionDescriptor info for an existing session, given a full
|
|
919
|
+
* ServerSessionInfo. This also passes the session info to the session
|
|
920
|
+
* client. An error is thrown if the session client is not found.
|
|
828
921
|
*/
|
|
829
922
|
private updateSessionInfo(msg: ServerSessionInfo): void {
|
|
830
923
|
const sessionId = msg.session_id;
|
|
@@ -838,6 +931,16 @@ export class ChatClient implements ITeamManager {
|
|
|
838
931
|
}
|
|
839
932
|
|
|
840
933
|
private addSessionToAgentSessionMap(msg: ServerSessionInfo): void {
|
|
934
|
+
// Skip agent session map updates for guest sessions since they don't
|
|
935
|
+
// need session organization
|
|
936
|
+
if (this.isGuestMode) {
|
|
937
|
+
logger.info(
|
|
938
|
+
`[ChatClient] Skipping agent session map add for ` +
|
|
939
|
+
`guest session ${msg.session_id}`
|
|
940
|
+
);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
|
|
841
944
|
// get the correct agent session map
|
|
842
945
|
const agentSessionMap = msg.team_uuid
|
|
843
946
|
? this.teams.get(msg.team_uuid)?.agentSessionMap
|
|
@@ -854,20 +957,22 @@ export class ChatClient implements ITeamManager {
|
|
|
854
957
|
` not found in agent session map`
|
|
855
958
|
);
|
|
856
959
|
}
|
|
857
|
-
agentSession.sessions.push(
|
|
960
|
+
agentSession.sessions.push(sessionInfoToSessionDescriptor(msg));
|
|
858
961
|
agentSession.updated_at = new Date(msg.updated_at).getTime();
|
|
859
962
|
}
|
|
860
963
|
}
|
|
861
964
|
|
|
862
|
-
function
|
|
965
|
+
function sessionInfoToSessionDescriptor(
|
|
966
|
+
msg: ServerSessionInfo
|
|
967
|
+
): SessionDescriptor {
|
|
863
968
|
return {
|
|
864
969
|
session_uuid: msg.session_id,
|
|
865
970
|
title: msg.title,
|
|
866
971
|
team_uuid: msg.team_uuid,
|
|
867
972
|
agent_profile_uuid: msg.saved_agent_profile.uuid,
|
|
868
|
-
workspace: msg.workspace,
|
|
869
973
|
updated_at: msg.updated_at,
|
|
870
974
|
user_uuid: msg.owner_uuid,
|
|
975
|
+
agent_paused: msg.agent_paused,
|
|
871
976
|
};
|
|
872
977
|
}
|
|
873
978
|
|
|
@@ -891,7 +996,7 @@ function doUpdateAgentSessionMap(
|
|
|
891
996
|
agent.sessions.map((session) => {
|
|
892
997
|
if (session.session_uuid === sessionId) {
|
|
893
998
|
updated = true;
|
|
894
|
-
return
|
|
999
|
+
return sessionInfoToSessionDescriptor(sessionInfo);
|
|
895
1000
|
} else {
|
|
896
1001
|
return session;
|
|
897
1002
|
}
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import { ISkillManager } from "../../agent/sudoMcpServerManager";
|
|
13
13
|
import { McpServerInfo, McpServerInfoRW } from "../../agent/mcpServerManager";
|
|
14
|
-
import {
|
|
14
|
+
import { IConversation } from "../../agent/agent";
|
|
15
|
+
import { ChatCompletionMessageParam } from "../../agent/llm";
|
|
15
16
|
|
|
16
17
|
import {
|
|
17
18
|
ClientSessionMessage,
|
|
@@ -20,11 +21,15 @@ import {
|
|
|
20
21
|
ServerSessionInfo,
|
|
21
22
|
ClientSetWorkspace,
|
|
22
23
|
ClientToServer,
|
|
24
|
+
isServerSessionFileMessage,
|
|
23
25
|
} from "../protocol/messages";
|
|
24
26
|
import { SessionParticipantMap } from "../data/dataModels";
|
|
25
27
|
import { createSessionParticipantMap } from "../data/database";
|
|
26
28
|
import { ISessionMessageSender } from "./interfaces";
|
|
27
29
|
import { IMessageSender } from "./connection";
|
|
30
|
+
import { SessionFiles } from "./sessionFiles";
|
|
31
|
+
import { SessionFileDescriptor } from "../data/dbSessionFileModels";
|
|
32
|
+
import { ResponseHandler } from "./responseHandler";
|
|
28
33
|
|
|
29
34
|
const logger = getLogger();
|
|
30
35
|
|
|
@@ -201,28 +206,51 @@ class RemoteSudoMcpServerManager implements ISkillManager {
|
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
export class SessionClient implements ISessionMessageSender, IConversation {
|
|
204
|
-
private
|
|
209
|
+
private readonly sessionUUID: string;
|
|
210
|
+
private readonly savedAgentProfile: SavedAgentProfile;
|
|
211
|
+
private readonly sender: IMessageSender<ClientToServer>;
|
|
212
|
+
private readonly smsm: RemoteSudoMcpServerManager;
|
|
213
|
+
private readonly sessionFiles: SessionFiles;
|
|
214
|
+
private readonly responseHandler: ResponseHandler<
|
|
215
|
+
ClientSessionMessage,
|
|
216
|
+
ServerSessionScopedMessage
|
|
217
|
+
>;
|
|
218
|
+
private participants: SessionParticipantMap;
|
|
205
219
|
private systemPrompt: string;
|
|
206
220
|
private model: string;
|
|
221
|
+
private agentPaused: boolean;
|
|
207
222
|
|
|
208
223
|
public constructor(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
224
|
+
sessionUUID: string,
|
|
225
|
+
savedAgentProfile: SavedAgentProfile,
|
|
226
|
+
sender: IMessageSender<ClientToServer>,
|
|
212
227
|
serverBriefs: McpServerBrief[],
|
|
213
|
-
|
|
228
|
+
participants: SessionParticipantMap,
|
|
229
|
+
agentPaused: boolean
|
|
214
230
|
) {
|
|
231
|
+
const fileList: SessionFileDescriptor[] = []; // Pass in?
|
|
232
|
+
this.sessionUUID = sessionUUID;
|
|
233
|
+
this.savedAgentProfile = savedAgentProfile;
|
|
215
234
|
this.sender = sender;
|
|
216
235
|
this.smsm = new RemoteSudoMcpServerManager(this, serverBriefs);
|
|
236
|
+
this.sessionFiles = new SessionFiles(sessionUUID, fileList, sender);
|
|
237
|
+
this.responseHandler = new ResponseHandler("session_error");
|
|
238
|
+
this.participants = participants;
|
|
217
239
|
this.systemPrompt = "";
|
|
218
240
|
this.model = "";
|
|
219
|
-
this.
|
|
241
|
+
this.agentPaused = agentPaused;
|
|
220
242
|
}
|
|
221
243
|
|
|
222
244
|
public getSessionUUID(): string {
|
|
223
245
|
return this.sessionUUID;
|
|
224
246
|
}
|
|
225
247
|
|
|
248
|
+
/// This object can be queried to upload or download files, or get the
|
|
249
|
+
/// latest list. See `SessionFiles` for full usage.
|
|
250
|
+
public getSessionFiles(): SessionFiles {
|
|
251
|
+
return this.sessionFiles;
|
|
252
|
+
}
|
|
253
|
+
|
|
226
254
|
public getSudoMcpServerManager(): ISkillManager {
|
|
227
255
|
return this.smsm;
|
|
228
256
|
}
|
|
@@ -262,6 +290,17 @@ export class SessionClient implements ISessionMessageSender, IConversation {
|
|
|
262
290
|
this.sendSessionMessage({ type: "set_model", model });
|
|
263
291
|
}
|
|
264
292
|
|
|
293
|
+
getAgentPaused(): boolean {
|
|
294
|
+
return this.agentPaused;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
setAgentPaused(paused: boolean) {
|
|
298
|
+
this.sendSessionMessage({
|
|
299
|
+
type: "set_agent_paused",
|
|
300
|
+
paused,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
265
304
|
userMessage(msg?: string, imageB64?: string): void {
|
|
266
305
|
assert(msg || imageB64, "Either message or image must be provided");
|
|
267
306
|
|
|
@@ -343,8 +382,7 @@ export class SessionClient implements ISessionMessageSender, IConversation {
|
|
|
343
382
|
}
|
|
344
383
|
|
|
345
384
|
public updateSessionInfo(sessionInfo: ServerSessionInfo): void {
|
|
346
|
-
// TODO:
|
|
347
|
-
// throw new Error("[SessionClient.updateSessionInfo] not implemented");
|
|
385
|
+
// TODO: Determine the correct approach. Cache the workspace?
|
|
348
386
|
const infoStr = JSON.stringify(sessionInfo.workspace);
|
|
349
387
|
logger.debug(`[SessionClient] ignoring session info: ${infoStr}`);
|
|
350
388
|
}
|
|
@@ -382,7 +420,28 @@ export class SessionClient implements ISessionMessageSender, IConversation {
|
|
|
382
420
|
});
|
|
383
421
|
}
|
|
384
422
|
|
|
423
|
+
public async shareSession(): Promise<string> {
|
|
424
|
+
const msg: ClientToServer = {
|
|
425
|
+
type: "share_session",
|
|
426
|
+
client_message_id: uuidv4(),
|
|
427
|
+
session_id: this.sessionUUID,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
this.sender.send(msg);
|
|
431
|
+
const response = await this.responseHandler.waitForResponse(msg);
|
|
432
|
+
if (response.type === "session_shared") {
|
|
433
|
+
return response.access_token;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
throw new Error(`unexpected response to "share"session": ${response.type}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
385
439
|
handleMessage(message: ServerSessionScopedMessage): void {
|
|
440
|
+
if (isServerSessionFileMessage(message)) {
|
|
441
|
+
this.sessionFiles.onMessage(message);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
386
445
|
switch (message.type) {
|
|
387
446
|
//
|
|
388
447
|
// State updates
|
|
@@ -427,6 +486,13 @@ export class SessionClient implements ISessionMessageSender, IConversation {
|
|
|
427
486
|
case "user_removed":
|
|
428
487
|
this.participants.delete(message.user_uuid);
|
|
429
488
|
break;
|
|
489
|
+
case "session_shared":
|
|
490
|
+
this.responseHandler.onMessage(message);
|
|
491
|
+
break;
|
|
492
|
+
|
|
493
|
+
case "agent_paused":
|
|
494
|
+
this.agentPaused = message.paused;
|
|
495
|
+
break;
|
|
430
496
|
|
|
431
497
|
//
|
|
432
498
|
// Ignore other messages - the owner (the UI layer) can handle them at
|