@xalia/agent 0.6.9 → 0.6.10
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/.env.development +6 -1
- package/.env.test +7 -0
- package/README.md +11 -0
- package/context_system.md +498 -0
- package/dist/agent/src/agent/agent.js +77 -18
- package/dist/agent/src/agent/agentUtils.js +3 -2
- package/dist/agent/src/agent/documentSummarizer.js +126 -0
- package/dist/agent/src/agent/dummyLLM.js +25 -22
- package/dist/agent/src/agent/imageGenLLM.js +22 -19
- package/dist/agent/src/agent/llm.js +1 -1
- package/dist/agent/src/agent/openAILLM.js +15 -12
- package/dist/agent/src/agent/openAILLMStreaming.js +68 -37
- package/dist/agent/src/agent/repeatLLM.js +16 -7
- package/dist/agent/src/agent/tokenCounter.js +390 -0
- package/dist/agent/src/agent/tokenCounter.test.js +206 -0
- package/dist/agent/src/agent/toolSettings.js +17 -0
- package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
- package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
- package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
- package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
- package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
- package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
- package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
- package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
- package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
- package/dist/agent/src/agent/tools/index.js +64 -0
- package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
- package/dist/agent/src/agent/tools/renderTool.js +89 -0
- package/dist/agent/src/agent/tools/utils.js +61 -0
- package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
- package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
- package/dist/agent/src/chat/client/chatClient.js +28 -0
- package/dist/agent/src/chat/client/index.js +4 -1
- package/dist/agent/src/chat/client/sessionClient.js +28 -2
- package/dist/agent/src/chat/constants.js +8 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
- package/dist/agent/src/chat/protocol/messages.js +5 -0
- package/dist/agent/src/chat/server/chatContextManager.js +45 -25
- package/dist/agent/src/chat/server/conversation.js +3 -0
- package/dist/agent/src/chat/server/imageGeneratorTools.js +20 -8
- package/dist/agent/src/chat/server/openAIRouterLLM.js +0 -3
- package/dist/agent/src/chat/server/openSession.js +218 -55
- package/dist/agent/src/chat/server/promptRefiner.js +86 -0
- package/dist/agent/src/chat/server/server.js +5 -1
- package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
- package/dist/agent/src/chat/server/sessionRegistry.js +87 -0
- package/dist/agent/src/chat/server/titleGenerator.js +112 -0
- package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
- package/dist/agent/src/chat/server/tools.js +63 -287
- package/dist/agent/src/chat/utils/approvalManager.js +6 -3
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
- package/dist/agent/src/test/agent.test.js +16 -17
- package/dist/agent/src/test/chatContextManager.test.js +15 -3
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
- package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
- package/dist/agent/src/test/testTools.js +6 -1
- package/dist/agent/src/test/tools.test.js +27 -9
- package/dist/agent/src/tool/agentChat.js +5 -2
- package/dist/agent/src/tool/chatMain.js +34 -7
- package/dist/agent/src/tool/commandPrompt.js +2 -2
- package/dist/agent/src/tool/files.js +7 -8
- package/package.json +4 -1
- package/scripts/test_chat +195 -176
- package/src/agent/agent.ts +98 -23
- package/src/agent/agentUtils.ts +3 -2
- package/src/agent/documentSummarizer.ts +157 -0
- package/src/agent/dummyLLM.ts +27 -23
- package/src/agent/imageGenLLM.ts +28 -24
- package/src/agent/llm.ts +2 -2
- package/src/agent/openAILLM.ts +17 -13
- package/src/agent/openAILLMStreaming.ts +80 -41
- package/src/agent/repeatLLM.ts +19 -7
- package/src/agent/test_data/harrypotter.txt +6065 -0
- package/src/agent/tokenCounter.test.ts +243 -0
- package/src/agent/tokenCounter.ts +483 -0
- package/src/agent/toolSettings.ts +24 -0
- package/src/agent/tools/calculatorTool.ts +50 -0
- package/src/agent/tools/contentExtractors/pdfToText.ts +60 -0
- package/src/agent/tools/datetimeTool.ts +41 -0
- package/src/agent/tools/fileManager/fileManagerTool.ts +199 -0
- package/src/agent/tools/fileManager/index.ts +50 -0
- package/src/agent/tools/fileManager/memoryFileManager.ts +120 -0
- package/src/{chat/data → agent/tools/fileManager}/mimeTypes.ts +3 -1
- package/src/agent/tools/fileManager/prompt.ts +38 -0
- package/src/{chat/data/dbSessionFileModels.ts → agent/tools/fileManager/types.ts} +76 -0
- package/src/agent/tools/index.ts +49 -0
- package/src/agent/tools/openUrlTool.ts +62 -0
- package/src/agent/tools/renderTool.ts +92 -0
- package/src/agent/tools/utils.ts +74 -0
- package/src/{chat/utils/search.ts → agent/tools/webSearch.ts} +0 -1
- package/src/agent/tools/webSearchTool.ts +44 -0
- package/src/chat/client/chatClient.ts +45 -0
- package/src/chat/client/index.ts +3 -0
- package/src/chat/client/sessionClient.ts +40 -3
- package/src/chat/client/sessionFiles.ts +1 -1
- package/src/chat/constants.ts +6 -0
- package/src/chat/data/dataModels.ts +6 -0
- package/src/chat/data/dbSessionFiles.ts +12 -4
- package/src/chat/protocol/messages.ts +60 -7
- package/src/chat/server/chatContextManager.ts +58 -37
- package/src/chat/server/conversation.ts +3 -0
- package/src/chat/server/imageGeneratorTools.ts +31 -12
- package/src/chat/server/openAIRouterLLM.ts +1 -4
- package/src/chat/server/openSession.ts +323 -67
- package/src/chat/server/promptRefiner.ts +106 -0
- package/src/chat/server/server.ts +4 -1
- package/src/chat/server/sessionFileManager.ts +35 -306
- package/src/chat/server/sessionRegistry.ts +128 -0
- package/src/chat/server/titleGenerator.test.ts +103 -0
- package/src/chat/server/titleGenerator.ts +143 -0
- package/src/chat/server/tools.ts +77 -304
- package/src/chat/utils/approvalManager.ts +9 -3
- package/src/chat/utils/multiAsyncQueue.ts +4 -0
- package/src/test/agent.test.ts +17 -23
- package/src/test/chatContextManager.test.ts +29 -4
- package/src/test/dbMcpServerConfigs.test.ts +4 -4
- package/src/test/dbSessionFiles.test.ts +16 -16
- package/src/test/testTools.ts +8 -3
- package/src/test/tools.test.ts +30 -5
- package/src/tool/agentChat.ts +12 -3
- package/src/tool/chatMain.ts +33 -6
- package/src/tool/commandPrompt.ts +2 -2
- package/src/tool/files.ts +1 -3
- package/dist/agent/src/agent/tools.js +0 -44
- package/src/agent/tools.ts +0 -57
- /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
- /package/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.ts +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
2
|
import { strict as assert } from "assert";
|
|
3
3
|
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
AgentPreferences,
|
|
@@ -23,6 +24,7 @@ import { McpServerInfo } from "../../agent/mcpServerManager";
|
|
|
23
24
|
import { IAgentEventHandler } from "../../agent/iAgentEventHandler";
|
|
24
25
|
import { createSpecializedLLM } from "../../agent/agentUtils";
|
|
25
26
|
import { IPlatform } from "../../agent/iplatform";
|
|
27
|
+
import { ITitleGenerator, createTitleGenerator } from "./titleGenerator";
|
|
26
28
|
|
|
27
29
|
import type {
|
|
28
30
|
ServerToClient,
|
|
@@ -47,6 +49,8 @@ import type {
|
|
|
47
49
|
ClientSessionMessageBase,
|
|
48
50
|
ServerAgentPaused,
|
|
49
51
|
ClientGetMcpResource,
|
|
52
|
+
ClientRaceModeResult,
|
|
53
|
+
ServerRaceModeGetResult,
|
|
50
54
|
} from "../protocol/messages";
|
|
51
55
|
import { AsyncQueue } from "../utils/asyncQueue";
|
|
52
56
|
import { MultiAsyncQueue } from "../utils/multiAsyncQueue";
|
|
@@ -80,20 +84,25 @@ import {
|
|
|
80
84
|
llmUserMessageToUserMessageData,
|
|
81
85
|
MESSAGE_INDEX_START_VALUE,
|
|
82
86
|
} from "./conversation";
|
|
87
|
+
import { ChatSessionFileManager } from "./sessionFileManager";
|
|
83
88
|
import {
|
|
84
|
-
ChatSessionFileManager,
|
|
85
89
|
ISessionFileManager,
|
|
86
90
|
ISessionFileManagerEventHandler,
|
|
87
|
-
|
|
91
|
+
SessionFileEntry,
|
|
92
|
+
ParsedContent,
|
|
93
|
+
getMimeTypeFromDataUrl,
|
|
94
|
+
} from "../../agent/tools/fileManager";
|
|
95
|
+
import { pdfToText } from "../../agent/tools/contentExtractors/pdfToText";
|
|
96
|
+
import { summarizeDocument } from "../../agent/documentSummarizer";
|
|
88
97
|
import { IUserConnectionManager } from "./connectionManager";
|
|
89
98
|
import { getErrorString } from "./errorUtils";
|
|
90
99
|
import { NODE_PLATFORM } from "../../tool/nodePlatform";
|
|
91
100
|
import { DbMcpServerConfigs } from "../data/dbMcpServerConfigs";
|
|
92
|
-
import { SessionFileEntry } from "../data/dbSessionFileModels";
|
|
93
101
|
import { ApiKeyManager } from "../data/apiKeyManager";
|
|
94
102
|
import { DbSessionMessages } from "../data/dbSessionMessages";
|
|
95
103
|
import { ISessionMessageSender } from "./openSessionMessageSender";
|
|
96
104
|
import { OpenAIRouterLLM } from "./openAIRouterLLM";
|
|
105
|
+
import { ResponseAwaiter } from "../utils/responseAwaiter";
|
|
97
106
|
|
|
98
107
|
/**
|
|
99
108
|
* The model to use when the AgentProfile does not specify one.
|
|
@@ -108,6 +117,8 @@ export const DEFAULT_NUM_MESSGAES = 500;
|
|
|
108
117
|
|
|
109
118
|
export const GUEST_TOKEN_PREFIX = "guest";
|
|
110
119
|
|
|
120
|
+
export const RACE_MODE_TIMEOUT_MS = 120_000;
|
|
121
|
+
|
|
111
122
|
const logger = getLogger();
|
|
112
123
|
|
|
113
124
|
type QueuedClientMessage<
|
|
@@ -214,22 +225,29 @@ export class ChatSessionAgentEventHandler implements IAgentEventHandler {
|
|
|
214
225
|
private readonly sessionUUID: string,
|
|
215
226
|
private readonly sender: ISessionMessageSender<ServerToClient>,
|
|
216
227
|
private readonly approvalManager: ToolApprovalManager,
|
|
217
|
-
private readonly contextTx: ChatContextTransaction
|
|
228
|
+
private readonly contextTx: ChatContextTransaction,
|
|
229
|
+
private readonly alt?: string
|
|
218
230
|
) {}
|
|
219
231
|
|
|
220
232
|
onCompletion(result: AssistantMessageParam): void {
|
|
221
|
-
logger.debug(
|
|
233
|
+
logger.debug(
|
|
234
|
+
`[OpenSession.onCompletion] ${this.alt || ""} : ${JSON.stringify(result)}`
|
|
235
|
+
);
|
|
222
236
|
// Nothing to broadcast. Caller will receive this via onAgentMessage.
|
|
223
237
|
this.contextTx.processAgentResponse(result);
|
|
224
238
|
}
|
|
225
239
|
|
|
226
240
|
onImage(image: OpenAI.Chat.Completions.ChatCompletionContentPartImage): void {
|
|
227
|
-
logger.debug(
|
|
241
|
+
logger.debug(
|
|
242
|
+
`[OpenSession.onImage] ${this.alt || ""} : ${image.image_url.url}`
|
|
243
|
+
);
|
|
228
244
|
throw new Error("[OpenSession.onImage] unimplemented");
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
onToolCallResult(result: ToolMessageParam): void {
|
|
232
|
-
logger.debug(
|
|
248
|
+
logger.debug(
|
|
249
|
+
`[onToolCallResult] ${this.alt || ""} : ${JSON.stringify(result)}`
|
|
250
|
+
);
|
|
233
251
|
const toolCallMessage = this.contextTx.processToolCallResult(result);
|
|
234
252
|
this.sender.broadcast(toolCallMessage);
|
|
235
253
|
}
|
|
@@ -245,6 +263,7 @@ export class ChatSessionAgentEventHandler implements IAgentEventHandler {
|
|
|
245
263
|
type: "tool_call",
|
|
246
264
|
tool_call: toolCall,
|
|
247
265
|
session_id: this.sessionUUID,
|
|
266
|
+
...(this.alt ? { alt: this.alt } : {}),
|
|
248
267
|
});
|
|
249
268
|
return true;
|
|
250
269
|
}
|
|
@@ -255,7 +274,8 @@ export class ChatSessionAgentEventHandler implements IAgentEventHandler {
|
|
|
255
274
|
const { approved, requested } = await this.approvalManager.getApproval(
|
|
256
275
|
serverName,
|
|
257
276
|
tool,
|
|
258
|
-
toolCall
|
|
277
|
+
toolCall,
|
|
278
|
+
this.alt
|
|
259
279
|
);
|
|
260
280
|
|
|
261
281
|
// For now, the frontend uses the tool_call data in the
|
|
@@ -267,6 +287,7 @@ export class ChatSessionAgentEventHandler implements IAgentEventHandler {
|
|
|
267
287
|
type: "tool_call",
|
|
268
288
|
tool_call: toolCall,
|
|
269
289
|
session_id: this.sessionUUID,
|
|
290
|
+
...(this.alt ? { alt: this.alt } : {}),
|
|
270
291
|
});
|
|
271
292
|
}
|
|
272
293
|
|
|
@@ -275,23 +296,28 @@ export class ChatSessionAgentEventHandler implements IAgentEventHandler {
|
|
|
275
296
|
|
|
276
297
|
onAgentMessage(msg: string, end: boolean): Promise<void> {
|
|
277
298
|
logger.debug(
|
|
278
|
-
`[OpenSession.onAgentMessage] msg: ${msg},
|
|
299
|
+
`[OpenSession.onAgentMessage] ${this.alt || ""} msg: ${msg}, ` +
|
|
300
|
+
`end: ${String(end)}`
|
|
279
301
|
);
|
|
280
302
|
|
|
281
303
|
// Inform the contextManager and broadcast the ServerAgentMessageChunk
|
|
282
304
|
const agentMsgChunk = this.contextTx.processAgentMessageChunk(msg, end);
|
|
305
|
+
if (this.alt) {
|
|
306
|
+
agentMsgChunk.alt = this.alt;
|
|
307
|
+
}
|
|
283
308
|
this.sender.broadcast(agentMsgChunk);
|
|
284
309
|
return Promise.resolve();
|
|
285
310
|
}
|
|
286
311
|
|
|
287
312
|
onReasoning(reasoning: string): Promise<void> {
|
|
288
313
|
return new Promise<void>((r) => {
|
|
289
|
-
logger.debug(`[OpenSession.onReasoning]${reasoning}`);
|
|
314
|
+
logger.debug(`[OpenSession.onReasoning] ${this.alt || ""} ${reasoning}`);
|
|
290
315
|
if (reasoning.length > 0) {
|
|
291
316
|
this.sender.broadcast({
|
|
292
317
|
type: "agent_reasoning_chunk",
|
|
293
318
|
reasoning,
|
|
294
319
|
session_id: this.sessionUUID,
|
|
320
|
+
...(this.alt ? { alt: this.alt } : {}),
|
|
295
321
|
});
|
|
296
322
|
}
|
|
297
323
|
r();
|
|
@@ -326,11 +352,14 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
326
352
|
private readonly approvalManager: ToolApprovalManager;
|
|
327
353
|
private readonly savedAgentProfile: SavedAgentProfile;
|
|
328
354
|
private readonly sessionFileManager: ISessionFileManager;
|
|
355
|
+
private readonly platform: IPlatform;
|
|
356
|
+
private readonly raceModeAwaiter: ResponseAwaiter<ClientRaceModeResult>;
|
|
329
357
|
private isPersisted: boolean;
|
|
330
358
|
private accessToken: string | undefined;
|
|
331
359
|
private sessionTitle: string;
|
|
332
360
|
private sessionUpdatedAt: string;
|
|
333
361
|
private agentPaused: boolean;
|
|
362
|
+
private readonly titleGenerator: ITitleGenerator;
|
|
334
363
|
|
|
335
364
|
private constructor(
|
|
336
365
|
db: Database,
|
|
@@ -344,7 +373,8 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
344
373
|
contextManager: ChatContextManager,
|
|
345
374
|
sender: ChatSessionMessageSender,
|
|
346
375
|
approvalManager: ToolApprovalManager,
|
|
347
|
-
fileManager: ISessionFileManager
|
|
376
|
+
fileManager: ISessionFileManager,
|
|
377
|
+
platform: IPlatform
|
|
348
378
|
) {
|
|
349
379
|
this.db = db;
|
|
350
380
|
this.agent = agent;
|
|
@@ -368,10 +398,17 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
368
398
|
this.approvalManager = approvalManager;
|
|
369
399
|
this.savedAgentProfile = savedAgentProfile;
|
|
370
400
|
this.sessionFileManager = fileManager;
|
|
401
|
+
this.platform = platform;
|
|
402
|
+
this.raceModeAwaiter = ResponseAwaiter.init(
|
|
403
|
+
undefined,
|
|
404
|
+
(m: ClientRaceModeResult) => m.message_id,
|
|
405
|
+
RACE_MODE_TIMEOUT_MS
|
|
406
|
+
);
|
|
371
407
|
this.isPersisted = isPersisted;
|
|
372
408
|
this.sessionTitle = sessionData.title;
|
|
373
409
|
this.sessionUpdatedAt = sessionData.updated_at;
|
|
374
410
|
this.agentPaused = sessionData.agent_paused;
|
|
411
|
+
this.titleGenerator = createTitleGenerator();
|
|
375
412
|
|
|
376
413
|
fileManager.addEventHandler(this);
|
|
377
414
|
this.updateParticipantsPrompt();
|
|
@@ -419,7 +456,8 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
419
456
|
xmcpUrl,
|
|
420
457
|
fileManager,
|
|
421
458
|
platform,
|
|
422
|
-
checkpointWriter
|
|
459
|
+
checkpointWriter,
|
|
460
|
+
sender
|
|
423
461
|
);
|
|
424
462
|
|
|
425
463
|
const openSession = new OpenSession(
|
|
@@ -434,7 +472,8 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
434
472
|
contextManager,
|
|
435
473
|
sender,
|
|
436
474
|
toolApprovalManager,
|
|
437
|
-
fileManager
|
|
475
|
+
fileManager,
|
|
476
|
+
platform
|
|
438
477
|
);
|
|
439
478
|
|
|
440
479
|
// Note, MCP servers have not been enabled yet
|
|
@@ -582,7 +621,9 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
582
621
|
});
|
|
583
622
|
|
|
584
623
|
// send conversation history
|
|
585
|
-
const conversationMessages = this.contextManager
|
|
624
|
+
const conversationMessages = this.contextManager
|
|
625
|
+
.getConversationMessages()
|
|
626
|
+
.concat(this.userMessageQueue.getEntries());
|
|
586
627
|
conversationMessages.forEach((message) => {
|
|
587
628
|
connMgr.sendToConnection(connectionId, message);
|
|
588
629
|
});
|
|
@@ -680,29 +721,28 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
680
721
|
}
|
|
681
722
|
};
|
|
682
723
|
|
|
724
|
+
let errorMessage: string;
|
|
683
725
|
if (err instanceof ChatFatalError) {
|
|
684
726
|
// ChatFatalError in session context should send error to specific user
|
|
685
727
|
// or broadcast if no specific user context
|
|
686
|
-
|
|
687
|
-
type: "session_error",
|
|
688
|
-
message: err.message,
|
|
689
|
-
session_id: this.sessionUUID,
|
|
690
|
-
});
|
|
728
|
+
errorMessage = err.message;
|
|
691
729
|
} else if (err instanceof ChatErrorMessage) {
|
|
692
|
-
|
|
693
|
-
type: "session_error",
|
|
694
|
-
message: err.message,
|
|
695
|
-
session_id: this.sessionUUID,
|
|
696
|
-
});
|
|
730
|
+
errorMessage = err.message;
|
|
697
731
|
} else {
|
|
698
|
-
|
|
699
|
-
sendError({
|
|
700
|
-
type: "session_error",
|
|
701
|
-
message: errString,
|
|
702
|
-
session_id: this.sessionUUID,
|
|
703
|
-
});
|
|
732
|
+
errorMessage = getErrorString(err);
|
|
704
733
|
}
|
|
705
734
|
|
|
735
|
+
logger.error(
|
|
736
|
+
`[OpenSession.handleError] sessionUUID=${this.sessionUUID} ` +
|
|
737
|
+
`from=${from ?? "broadcast"}: ${errorMessage}`
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
sendError({
|
|
741
|
+
type: "session_error",
|
|
742
|
+
message: errorMessage,
|
|
743
|
+
session_id: this.sessionUUID,
|
|
744
|
+
});
|
|
745
|
+
|
|
706
746
|
return true;
|
|
707
747
|
}
|
|
708
748
|
|
|
@@ -715,6 +755,16 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
715
755
|
});
|
|
716
756
|
}
|
|
717
757
|
|
|
758
|
+
private broadcastContextUsage(): void {
|
|
759
|
+
const { used, max } = this.contextManager.getContextUsage();
|
|
760
|
+
this.sender.broadcast({
|
|
761
|
+
type: "context_usage",
|
|
762
|
+
session_id: this.sessionUUID,
|
|
763
|
+
used_tokens: used,
|
|
764
|
+
max_tokens: max,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
718
768
|
// ISessionFileManagerEventHandler.onFileChanged
|
|
719
769
|
onFileChanged(entry: SessionFileEntry, new_file: boolean): void {
|
|
720
770
|
this.sender.broadcast({
|
|
@@ -812,6 +862,9 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
812
862
|
case "msg":
|
|
813
863
|
broadcastMsg = await this.handleUserMessage(msg, queuedMessage.from);
|
|
814
864
|
break;
|
|
865
|
+
case "stop":
|
|
866
|
+
this.agent.stop();
|
|
867
|
+
break;
|
|
815
868
|
case "add_mcp_server":
|
|
816
869
|
broadcastMsg = await this.handleAddMcpServer(
|
|
817
870
|
msg.server_name,
|
|
@@ -883,6 +936,9 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
883
936
|
case "get_mcp_resource":
|
|
884
937
|
void this.handleGetMcpResource(msg, queuedMessage.from);
|
|
885
938
|
break;
|
|
939
|
+
case "race_mode_result":
|
|
940
|
+
this.raceModeAwaiter.onMessage(msg);
|
|
941
|
+
break;
|
|
886
942
|
default: {
|
|
887
943
|
const exhaustive: never = msg; // Error => non-exhaustive switch-case.
|
|
888
944
|
return exhaustive;
|
|
@@ -896,6 +952,10 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
896
952
|
});
|
|
897
953
|
} else {
|
|
898
954
|
this.sender.broadcast(broadcastMsg);
|
|
955
|
+
// Broadcast context usage after user message
|
|
956
|
+
if (broadcastMsg.type === "user_msg") {
|
|
957
|
+
this.broadcastContextUsage();
|
|
958
|
+
}
|
|
899
959
|
}
|
|
900
960
|
}
|
|
901
961
|
} catch (err: unknown) {
|
|
@@ -978,16 +1038,14 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
978
1038
|
// they are available to the LLM once it is restarted.
|
|
979
1039
|
|
|
980
1040
|
const { contextTx } = await this.contextManager.startAgentResponse(msgs);
|
|
981
|
-
|
|
1041
|
+
const result = this.contextManager.endAgentResponse(contextTx);
|
|
1042
|
+
this.broadcastContextUsage();
|
|
1043
|
+
return result;
|
|
982
1044
|
}
|
|
983
1045
|
|
|
984
1046
|
private async processUserMessagesActive(
|
|
985
1047
|
msgs: ServerUserMessage[]
|
|
986
1048
|
): Promise<SessionMessage[]> {
|
|
987
|
-
// TODO: create the contextTx and store all new messages on this. Event
|
|
988
|
-
// handler should accept the contextTx and forward messages to it, as well
|
|
989
|
-
// as sending the updates.
|
|
990
|
-
|
|
991
1049
|
// All accumulated messages (DB, Protocol and LLM) should be on the
|
|
992
1050
|
// specialized contextTx.
|
|
993
1051
|
|
|
@@ -1017,14 +1075,105 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
1017
1075
|
// DB. Should we keep them?
|
|
1018
1076
|
throw new Error(errMsg);
|
|
1019
1077
|
}
|
|
1020
|
-
|
|
1078
|
+
const result = this.contextManager.endAgentResponse(contextTx);
|
|
1079
|
+
this.broadcastContextUsage();
|
|
1080
|
+
return result;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* `processUserMessage` logic for race-mode.
|
|
1085
|
+
*/
|
|
1086
|
+
private async processUserMessagesRaceMode(
|
|
1087
|
+
msgs: ServerUserMessage[]
|
|
1088
|
+
): Promise<SessionMessage[]> {
|
|
1089
|
+
// Create a second agent, same skillManager
|
|
1090
|
+
|
|
1091
|
+
const modelB = msgs[0].race_mode;
|
|
1092
|
+
assert(typeof modelB === "string");
|
|
1093
|
+
|
|
1094
|
+
const agentB = await (async () => {
|
|
1095
|
+
try {
|
|
1096
|
+
const llmB = await createLLM(modelB, this.platform);
|
|
1097
|
+
return new AgentEx(this.skillManager, llmB);
|
|
1098
|
+
} catch (e) {
|
|
1099
|
+
logger.warn(
|
|
1100
|
+
"[OpenSession.processUserMessages] error creating race agent: " +
|
|
1101
|
+
String(e)
|
|
1102
|
+
);
|
|
1103
|
+
throw new Error(`error creating race: ${String(e)}`);
|
|
1104
|
+
}
|
|
1105
|
+
})();
|
|
1106
|
+
|
|
1107
|
+
const run = async (
|
|
1108
|
+
alt: string,
|
|
1109
|
+
msgs: ServerUserMessage[]
|
|
1110
|
+
): Promise<ChatContextTransaction> => {
|
|
1111
|
+
const { contextTx: contextTx, agentFirstChunk: agentFirstChunk } =
|
|
1112
|
+
await this.contextManager.startAgentResponse(msgs.slice());
|
|
1113
|
+
this.sender.broadcast(agentFirstChunk);
|
|
1114
|
+
|
|
1115
|
+
const eventHandler = new ChatSessionAgentEventHandler(
|
|
1116
|
+
this.sessionUUID,
|
|
1117
|
+
this.sender,
|
|
1118
|
+
this.approvalManager,
|
|
1119
|
+
contextTx,
|
|
1120
|
+
alt
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
try {
|
|
1124
|
+
const agent = alt === "B" ? agentB : this.agent;
|
|
1125
|
+
await agent.userMessagesRaw(contextTx, eventHandler);
|
|
1126
|
+
} catch (e) {
|
|
1127
|
+
logger.warn(
|
|
1128
|
+
`[OpenSession.processUserMessages] agent ${alt} error: ${String(e)}`
|
|
1129
|
+
);
|
|
1130
|
+
const errMsg = `error from LLM: ${String(e)}`;
|
|
1131
|
+
contextTx.revertAgentResponse(errMsg);
|
|
1132
|
+
throw new Error(errMsg);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
return contextTx;
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
const [txA, txB] = await Promise.all([
|
|
1139
|
+
run("A", msgs.slice()),
|
|
1140
|
+
run("B", msgs),
|
|
1141
|
+
]);
|
|
1142
|
+
|
|
1143
|
+
// Ask which fork to commit
|
|
1144
|
+
|
|
1145
|
+
const answer = await (async () => {
|
|
1146
|
+
const message_id = uuidv4();
|
|
1147
|
+
const answerP = this.raceModeAwaiter.waitForResponse(message_id);
|
|
1148
|
+
const getResultMsg: ServerRaceModeGetResult = {
|
|
1149
|
+
type: "race_mode_get_result",
|
|
1150
|
+
message_id,
|
|
1151
|
+
alts: ["A", "B"],
|
|
1152
|
+
session_id: this.sessionUUID,
|
|
1153
|
+
};
|
|
1154
|
+
this.sender.broadcast(getResultMsg);
|
|
1155
|
+
|
|
1156
|
+
return answerP;
|
|
1157
|
+
})();
|
|
1158
|
+
|
|
1159
|
+
if (answer.result === "A") {
|
|
1160
|
+
return this.contextManager.endAgentResponse(txA);
|
|
1161
|
+
} else {
|
|
1162
|
+
return this.contextManager.endAgentResponse(txB);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// UI is responsible for switching model if the user wants to.
|
|
1021
1166
|
}
|
|
1022
1167
|
|
|
1023
1168
|
private async processUserMessages(msgs: ServerUserMessage[]): Promise<void> {
|
|
1169
|
+
logger.debug(`[processUserMessages] msgs: ${JSON.stringify(msgs)}`);
|
|
1170
|
+
|
|
1024
1171
|
try {
|
|
1025
1172
|
const newSessionMessages = this.agentPaused
|
|
1026
1173
|
? await this.processUserMessagePaused(msgs)
|
|
1027
|
-
: await
|
|
1174
|
+
: await (msgs[0].race_mode
|
|
1175
|
+
? this.processUserMessagesRaceMode(msgs)
|
|
1176
|
+
: this.processUserMessagesActive(msgs));
|
|
1028
1177
|
|
|
1029
1178
|
logger.debug(
|
|
1030
1179
|
"[processUserMessages] newSessionMessages: " +
|
|
@@ -1035,6 +1184,10 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
1035
1184
|
const dbsm = this.db.createTypedClient(DbSessionMessages);
|
|
1036
1185
|
await dbsm.append(this.sessionUUID, newSessionMessages);
|
|
1037
1186
|
} catch (e) {
|
|
1187
|
+
logger.error(
|
|
1188
|
+
`[processUserMessages] ERROR session=${this.sessionUUID}: ` +
|
|
1189
|
+
String(e)
|
|
1190
|
+
);
|
|
1038
1191
|
if (!this.handleError(e)) {
|
|
1039
1192
|
throw e;
|
|
1040
1193
|
}
|
|
@@ -1049,11 +1202,22 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
1049
1202
|
// on a queue to be dealt with in another loop. This allows Agent
|
|
1050
1203
|
// processing of user messages to depend on other messages.
|
|
1051
1204
|
|
|
1052
|
-
assert(msg);
|
|
1053
|
-
assert(from);
|
|
1205
|
+
assert(msg, "undefined user message");
|
|
1206
|
+
assert(from, "undefined user message sender");
|
|
1054
1207
|
|
|
1055
|
-
|
|
1208
|
+
logger.info(
|
|
1209
|
+
`[handleUserMessage] msg.attachedFiles: ${JSON.stringify(
|
|
1210
|
+
msg.attachedFiles
|
|
1211
|
+
? msg.attachedFiles.map((f) => ({
|
|
1212
|
+
name: f.name,
|
|
1213
|
+
data_url_length: f.data_url.length,
|
|
1214
|
+
}))
|
|
1215
|
+
: "none"
|
|
1216
|
+
)}`
|
|
1217
|
+
);
|
|
1056
1218
|
|
|
1219
|
+
// Assign the user message_idx first so we can check if this is
|
|
1220
|
+
// the first message
|
|
1057
1221
|
const user = this.sessionParticipants.get(from);
|
|
1058
1222
|
if (!user) {
|
|
1059
1223
|
throw new Error(`unrecognized user ${from}`);
|
|
@@ -1066,8 +1230,87 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
1066
1230
|
if (!userMessage) {
|
|
1067
1231
|
return;
|
|
1068
1232
|
}
|
|
1069
|
-
// Special case for the first message of the session
|
|
1070
1233
|
|
|
1234
|
+
// Handle attached files by adding them to session files
|
|
1235
|
+
if (msg.attachedFiles && msg.attachedFiles.length > 0) {
|
|
1236
|
+
const fileCount = String(msg.attachedFiles.length);
|
|
1237
|
+
logger.info(`[handleUserMessage] Processing ${fileCount} attached files`);
|
|
1238
|
+
|
|
1239
|
+
// If this is the first message, set the session title before
|
|
1240
|
+
// creating the session
|
|
1241
|
+
if (
|
|
1242
|
+
userMessage.message_idx === MESSAGE_INDEX_START_VALUE &&
|
|
1243
|
+
!this.isPersisted
|
|
1244
|
+
) {
|
|
1245
|
+
this.sessionTitle = userMessage.message?.slice(0, 128) || "New Chat";
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// If the session hasn't been persisted, it must be written to
|
|
1249
|
+
// the DB first
|
|
1250
|
+
if (!this.isPersisted) {
|
|
1251
|
+
await this.createSessionInDB();
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Add each attached file to session files
|
|
1255
|
+
for (const file of msg.attachedFiles) {
|
|
1256
|
+
logger.info(
|
|
1257
|
+
`[handleUserMessage] Adding file ${file.name} to session files`
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
// Extract content and generate summary for PDFs
|
|
1261
|
+
let parsed_content: ParsedContent | undefined;
|
|
1262
|
+
let summary: string | undefined;
|
|
1263
|
+
|
|
1264
|
+
const mimeType = getMimeTypeFromDataUrl(file.data_url);
|
|
1265
|
+
if (mimeType === "application/pdf") {
|
|
1266
|
+
try {
|
|
1267
|
+
// Extract base64 data and convert to ArrayBuffer
|
|
1268
|
+
const base64Data = file.data_url.split(",")[1];
|
|
1269
|
+
const binaryString = atob(base64Data);
|
|
1270
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
1271
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
1272
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
1273
|
+
}
|
|
1274
|
+
const arrayBuffer = bytes.buffer;
|
|
1275
|
+
|
|
1276
|
+
// Extract text from PDF
|
|
1277
|
+
const extractedText = await pdfToText(arrayBuffer);
|
|
1278
|
+
parsed_content = {
|
|
1279
|
+
version: 1,
|
|
1280
|
+
text: extractedText,
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
// Generate summary from extracted text
|
|
1284
|
+
if (extractedText.trim().length > 0) {
|
|
1285
|
+
summary = await summarizeDocument(extractedText);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
logger.info(
|
|
1289
|
+
`[handleUserMessage] Extracted ${String(extractedText.length)}` +
|
|
1290
|
+
` chars from PDF ${file.name}`
|
|
1291
|
+
);
|
|
1292
|
+
} catch (err) {
|
|
1293
|
+
logger.warn(
|
|
1294
|
+
`[handleUserMessage] Failed to extract PDF content: ` +
|
|
1295
|
+
String(err)
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
await this.sessionFileManager.putFileContent(
|
|
1301
|
+
file.name,
|
|
1302
|
+
summary,
|
|
1303
|
+
file.data_url,
|
|
1304
|
+
parsed_content
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Remove attachedFiles from the message so they don't get
|
|
1309
|
+
// included in the LLM context
|
|
1310
|
+
msg = { ...msg, attachedFiles: undefined };
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Special case for the first message of the session
|
|
1071
1314
|
if (userMessage.message_idx === MESSAGE_INDEX_START_VALUE) {
|
|
1072
1315
|
// No need to wait for this to complete before broadcasting.
|
|
1073
1316
|
await this.onFirstMessage(userMessage);
|
|
@@ -1118,9 +1361,11 @@ export class OpenSession implements ISessionFileManagerEventHandler {
|
|
|
1118
1361
|
}
|
|
1119
1362
|
|
|
1120
1363
|
private async onFirstMessage(userMsg: ServerUserMessage): Promise<void> {
|
|
1121
|
-
//
|
|
1364
|
+
// Generate title using LLM with automatic fallback
|
|
1122
1365
|
|
|
1123
|
-
this.sessionTitle =
|
|
1366
|
+
this.sessionTitle = await this.titleGenerator.generateTitle(
|
|
1367
|
+
userMsg.message || ""
|
|
1368
|
+
);
|
|
1124
1369
|
|
|
1125
1370
|
// The session may already have been saved (e.g. if the workspace is
|
|
1126
1371
|
// updated before any messages are sent).
|
|
@@ -1626,6 +1871,15 @@ async function loadSessionData(
|
|
|
1626
1871
|
};
|
|
1627
1872
|
}
|
|
1628
1873
|
|
|
1874
|
+
async function createLLM(model: string, platform: IPlatform): Promise<ILLM> {
|
|
1875
|
+
let llm = await createSpecializedLLM(model, platform);
|
|
1876
|
+
if (!llm) {
|
|
1877
|
+
llm = new OpenAIRouterLLM(model);
|
|
1878
|
+
}
|
|
1879
|
+
assert(llm);
|
|
1880
|
+
return llm;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1629
1883
|
async function createContextAndAgent(
|
|
1630
1884
|
sessionUUID: string,
|
|
1631
1885
|
systemPrompt: string,
|
|
@@ -1638,22 +1892,38 @@ async function createContextAndAgent(
|
|
|
1638
1892
|
xmcpUrl: string,
|
|
1639
1893
|
fileManager: ChatSessionFileManager,
|
|
1640
1894
|
platform: IPlatform,
|
|
1641
|
-
checkpointWriter: ICheckpointWriter
|
|
1895
|
+
checkpointWriter: ICheckpointWriter,
|
|
1896
|
+
messageSender: ISessionMessageSender<ServerToClient>
|
|
1642
1897
|
): Promise<{
|
|
1643
1898
|
agent: AgentEx;
|
|
1644
1899
|
skillManager: SkillManager;
|
|
1645
1900
|
contextManager: ChatContextManager;
|
|
1646
1901
|
}> {
|
|
1902
|
+
// Create SkillManager
|
|
1903
|
+
|
|
1904
|
+
const xmcpConfig = Configuration.new(ownerApiKey, xmcpUrl, false);
|
|
1905
|
+
const skillManager = await SkillManager.initialize(
|
|
1906
|
+
(url: string, authResultP: Promise<boolean>, displayName: string) => {
|
|
1907
|
+
platform.openUrl(url, authResultP, displayName);
|
|
1908
|
+
},
|
|
1909
|
+
xmcpConfig.backend_url,
|
|
1910
|
+
xmcpConfig.api_key,
|
|
1911
|
+
undefined /* authorizedUrl */
|
|
1912
|
+
);
|
|
1913
|
+
|
|
1647
1914
|
// Fn to create the llm. One invocation for the compression context, one
|
|
1648
1915
|
// for the Agent.
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1916
|
+
|
|
1917
|
+
const llm = await createLLM(model, platform);
|
|
1918
|
+
const agent = new AgentEx(skillManager, llm);
|
|
1919
|
+
|
|
1920
|
+
await addDefaultChatTools(
|
|
1921
|
+
agent,
|
|
1922
|
+
ownerData.timezone,
|
|
1923
|
+
platform,
|
|
1924
|
+
fileManager,
|
|
1925
|
+
messageSender
|
|
1926
|
+
);
|
|
1657
1927
|
|
|
1658
1928
|
const contextManager = new ChatContextManager(
|
|
1659
1929
|
systemPrompt,
|
|
@@ -1663,7 +1933,7 @@ async function createContextAndAgent(
|
|
|
1663
1933
|
sessionCheckpoint,
|
|
1664
1934
|
checkpointWriter,
|
|
1665
1935
|
fileManager,
|
|
1666
|
-
await createLLM()
|
|
1936
|
+
await createLLM(model, platform)
|
|
1667
1937
|
);
|
|
1668
1938
|
if (workspace) {
|
|
1669
1939
|
contextManager.setWorkspace(
|
|
@@ -1671,19 +1941,5 @@ async function createContextAndAgent(
|
|
|
1671
1941
|
);
|
|
1672
1942
|
}
|
|
1673
1943
|
|
|
1674
|
-
const xmcpConfig = Configuration.new(ownerApiKey, xmcpUrl, false);
|
|
1675
|
-
const skillManager = await SkillManager.initialize(
|
|
1676
|
-
(url: string, authResultP: Promise<boolean>, displayName: string) => {
|
|
1677
|
-
platform.openUrl(url, authResultP, displayName);
|
|
1678
|
-
},
|
|
1679
|
-
xmcpConfig.backend_url,
|
|
1680
|
-
xmcpConfig.api_key,
|
|
1681
|
-
undefined /* authorizedUrl */
|
|
1682
|
-
);
|
|
1683
|
-
const llm = await createLLM();
|
|
1684
|
-
const agent = new AgentEx(skillManager, llm);
|
|
1685
|
-
|
|
1686
|
-
await addDefaultChatTools(agent, ownerData.timezone, platform, fileManager);
|
|
1687
|
-
|
|
1688
1944
|
return { agent, skillManager, contextManager };
|
|
1689
1945
|
}
|