@xalia/agent 0.6.8 → 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 -0
- package/.env.test +7 -0
- package/README.md +11 -0
- package/context_system.md +498 -0
- package/dist/agent/src/agent/agent.js +169 -87
- package/dist/agent/src/agent/agentUtils.js +24 -18
- package/dist/agent/src/agent/compressingContextManager.js +10 -14
- package/dist/agent/src/agent/context.js +101 -127
- package/dist/agent/src/agent/contextWithWorkspace.js +133 -0
- 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 -25
- package/dist/agent/src/agent/imageGenerator.js +2 -10
- 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 +73 -39
- package/dist/agent/src/agent/repeatLLM.js +16 -7
- package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
- 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 +63 -2
- package/dist/agent/src/chat/client/connection.js +6 -1
- package/dist/agent/src/chat/client/index.js +4 -1
- package/dist/agent/src/chat/client/sessionClient.js +28 -9
- package/dist/agent/src/chat/constants.js +8 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
- package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
- package/dist/agent/src/chat/protocol/messages.js +9 -0
- package/dist/agent/src/chat/server/chatContextManager.js +186 -156
- package/dist/agent/src/chat/server/conversation.js +3 -0
- package/dist/agent/src/chat/server/imageGeneratorTools.js +39 -16
- package/dist/agent/src/chat/server/openAIRouterLLM.js +111 -0
- package/dist/agent/src/chat/server/openSession.js +253 -91
- package/dist/agent/src/chat/server/promptRefiner.js +86 -0
- package/dist/agent/src/chat/server/server.js +10 -2
- package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
- package/dist/agent/src/chat/server/sessionRegistry.js +152 -6
- package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
- 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 +64 -253
- 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 +44 -30
- package/dist/agent/src/test/clientServerConnection.test.js +1 -2
- package/dist/agent/src/test/compressingContextManager.test.js +22 -36
- package/dist/agent/src/test/context.test.js +55 -17
- package/dist/agent/src/test/contextTestTools.js +87 -0
- 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 +56 -15
- 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 -173
- package/src/agent/agent.ts +257 -137
- package/src/agent/agentUtils.ts +32 -20
- package/src/agent/compressingContextManager.ts +13 -44
- package/src/agent/context.ts +165 -159
- package/src/agent/contextWithWorkspace.ts +162 -0
- package/src/agent/documentSummarizer.ts +157 -0
- package/src/agent/dummyLLM.ts +27 -23
- package/src/agent/imageGenLLM.ts +28 -32
- package/src/agent/imageGenerator.ts +3 -18
- package/src/agent/llm.ts +2 -2
- package/src/agent/openAILLM.ts +17 -13
- package/src/agent/openAILLMStreaming.ts +99 -43
- package/src/agent/repeatLLM.ts +19 -7
- package/src/agent/sudoMcpServerManager.ts +41 -20
- 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 +92 -3
- package/src/chat/client/connection.ts +11 -1
- package/src/chat/client/index.ts +3 -0
- package/src/chat/client/sessionClient.ts +40 -11
- package/src/chat/client/sessionFiles.ts +1 -1
- package/src/chat/constants.ts +6 -0
- package/src/chat/data/dataModels.ts +12 -0
- package/src/chat/data/dbSessionFiles.ts +12 -4
- package/src/chat/data/dbSessionMessages.ts +34 -0
- package/src/chat/protocol/messages.ts +94 -14
- package/src/chat/server/chatContextManager.ts +255 -221
- package/src/chat/server/connectionManager.ts +1 -1
- package/src/chat/server/conversation.ts +3 -0
- package/src/chat/server/imageGeneratorTools.ts +62 -30
- package/src/chat/server/openAIRouterLLM.ts +168 -0
- package/src/chat/server/openSession.ts +381 -138
- package/src/chat/server/promptRefiner.ts +106 -0
- package/src/chat/server/server.ts +9 -2
- package/src/chat/server/sessionFileManager.ts +35 -306
- package/src/chat/server/sessionRegistry.test.ts +0 -1
- package/src/chat/server/sessionRegistry.ts +228 -4
- package/src/chat/server/titleGenerator.test.ts +103 -0
- package/src/chat/server/titleGenerator.ts +143 -0
- package/src/chat/server/tools.ts +92 -281
- package/src/chat/utils/approvalManager.ts +9 -3
- package/src/chat/utils/multiAsyncQueue.ts +4 -0
- package/src/test/agent.test.ts +25 -30
- package/src/test/chatContextManager.test.ts +68 -38
- package/src/test/clientServerConnection.test.ts +0 -2
- package/src/test/compressingContextManager.test.ts +29 -34
- package/src/test/context.test.ts +59 -15
- package/src/test/contextTestTools.ts +95 -0
- 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 +59 -18
- 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,18 +1,123 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ChatContextManager = void 0;
|
|
3
|
+
exports.ChatContextManager = exports.ChatContextTransaction = void 0;
|
|
4
4
|
exports.resolveConversationWithCheckpoint = resolveConversationWithCheckpoint;
|
|
5
5
|
const assert_1 = require("assert");
|
|
6
6
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
7
|
-
const agent_1 = require("../../agent/agent");
|
|
8
7
|
const compressingContextManager_1 = require("../../agent/compressingContextManager");
|
|
9
8
|
const conversation_1 = require("./conversation");
|
|
10
|
-
const
|
|
9
|
+
const fileManager_1 = require("../../agent/tools/fileManager");
|
|
10
|
+
const errorUtils_1 = require("./errorUtils");
|
|
11
|
+
const agent_1 = require("../../agent/agent");
|
|
12
|
+
const tokenCounter_1 = require("../../agent/tokenCounter");
|
|
11
13
|
const logger = (0, sdk_1.getLogger)();
|
|
12
14
|
/**
|
|
13
15
|
* TODO: Until token-tracking is in place.
|
|
14
16
|
*/
|
|
15
17
|
const COMPRESSION_TRIGGER_NUM_MESSAGES = parseInt(process.env["COMPRESSION_TRIGGER_NUM_MESSAGES"] || "80", 10);
|
|
18
|
+
class ChatContextTransaction {
|
|
19
|
+
constructor(baseTx, sessionUUID, baseMsgIdx, pendingUserMessages, curAgentMsgIdx) {
|
|
20
|
+
(0, assert_1.strict)(typeof curAgentMsgIdx !== "undefined");
|
|
21
|
+
this.sessionUUID = sessionUUID;
|
|
22
|
+
this.baseTx = baseTx;
|
|
23
|
+
this.baseMsgIdx = baseMsgIdx;
|
|
24
|
+
this.startingLLMContextLength = baseTx.getLLMContextLength();
|
|
25
|
+
this.pendingMessages = pendingUserMessages;
|
|
26
|
+
this.curAgentMsgIdx = curAgentMsgIdx;
|
|
27
|
+
}
|
|
28
|
+
// IContextTransaction.addMessages
|
|
29
|
+
addMessages(messages) {
|
|
30
|
+
return this.baseTx.addMessages(messages);
|
|
31
|
+
}
|
|
32
|
+
// IContextTransaction.addMessage
|
|
33
|
+
addMessage(message) {
|
|
34
|
+
return this.baseTx.addMessage(message);
|
|
35
|
+
}
|
|
36
|
+
// IContextTransaction.getMessage
|
|
37
|
+
getMessage(handle) {
|
|
38
|
+
return this.baseTx.getMessage(handle);
|
|
39
|
+
}
|
|
40
|
+
// IContextTransaction.getLLMContext
|
|
41
|
+
getLLMContext() {
|
|
42
|
+
return this.baseTx.getLLMContext();
|
|
43
|
+
}
|
|
44
|
+
// IContextTransaction.getLLMContextLength
|
|
45
|
+
getLLMContextLength() {
|
|
46
|
+
return this.baseTx.getLLMContextLength();
|
|
47
|
+
}
|
|
48
|
+
getPending() {
|
|
49
|
+
return this.pendingMessages;
|
|
50
|
+
}
|
|
51
|
+
baseMessageIdx() {
|
|
52
|
+
return this.baseMsgIdx;
|
|
53
|
+
}
|
|
54
|
+
getBaseTx() {
|
|
55
|
+
return this.baseTx;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Process a FULL Agent message (not chunks from stream). No message is
|
|
59
|
+
* required for broadcast as the calling code is expected to broadcast this
|
|
60
|
+
* as chunks.
|
|
61
|
+
*/
|
|
62
|
+
processAgentResponse(result) {
|
|
63
|
+
// Insert this (full) agent response into the list of agent messages
|
|
64
|
+
const msg = {
|
|
65
|
+
type: "agent_msg",
|
|
66
|
+
session_id: this.sessionUUID,
|
|
67
|
+
message_idx: this.getNextMessageSubIdx(),
|
|
68
|
+
message: result,
|
|
69
|
+
};
|
|
70
|
+
this.pendingMessages.push(msg);
|
|
71
|
+
}
|
|
72
|
+
processAgentMessageChunk(msg, end) {
|
|
73
|
+
const message = {
|
|
74
|
+
type: "agent_msg_chunk",
|
|
75
|
+
session_id: this.sessionUUID,
|
|
76
|
+
message_idx: this.getCurrentAgentMessageIdx(),
|
|
77
|
+
message: msg,
|
|
78
|
+
end,
|
|
79
|
+
};
|
|
80
|
+
return message;
|
|
81
|
+
}
|
|
82
|
+
processToolCallResult(result) {
|
|
83
|
+
// Allocate the sub-index for this tool call result. It should not
|
|
84
|
+
// have been used already.
|
|
85
|
+
const message_idx = this.getNextMessageSubIdx();
|
|
86
|
+
const numPending = this.pendingMessages.length;
|
|
87
|
+
(0, assert_1.strict)(numPending > 0);
|
|
88
|
+
(0, assert_1.strict)(this.pendingMessages[numPending - 1].message_idx < message_idx);
|
|
89
|
+
const msg = {
|
|
90
|
+
type: "tool_call_result",
|
|
91
|
+
session_id: this.sessionUUID,
|
|
92
|
+
message_idx,
|
|
93
|
+
result,
|
|
94
|
+
};
|
|
95
|
+
this.pendingMessages.push(msg);
|
|
96
|
+
return msg;
|
|
97
|
+
}
|
|
98
|
+
revertAgentResponse(errMsg) {
|
|
99
|
+
logger.warn(`[ChatContextManager.revertAgentResponse] error: ${errMsg}`);
|
|
100
|
+
// Remove all messages since the user messages were placed on.
|
|
101
|
+
while (this.baseTx.getLLMContextLength() > this.startingLLMContextLength) {
|
|
102
|
+
const last = this.baseTx.popMessage();
|
|
103
|
+
(0, assert_1.strict)(last);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
newMessages() {
|
|
107
|
+
return this.baseTx.newMessages();
|
|
108
|
+
}
|
|
109
|
+
getNextMessageSubIdx() {
|
|
110
|
+
const idx = this.curAgentMsgIdx;
|
|
111
|
+
this.curAgentMsgIdx += conversation_1.MESSAGE_INDEX_SUB_INCREMENT;
|
|
112
|
+
return idx;
|
|
113
|
+
}
|
|
114
|
+
/// Get the current index to use for streaming Agent chunks
|
|
115
|
+
getCurrentAgentMessageIdx() {
|
|
116
|
+
(0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
|
|
117
|
+
return this.curAgentMsgIdx;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.ChatContextTransaction = ChatContextTransaction;
|
|
16
121
|
/**
|
|
17
122
|
* A context manager for Agents interacting with the (potentially multi-user)
|
|
18
123
|
* chat conversations.
|
|
@@ -25,38 +130,27 @@ const COMPRESSION_TRIGGER_NUM_MESSAGES = parseInt(process.env["COMPRESSION_TRIGG
|
|
|
25
130
|
* committed to the DB
|
|
26
131
|
*/
|
|
27
132
|
class ChatContextManager {
|
|
28
|
-
constructor(systemPrompt, sessionMessages, sessionUUID, defaultUserName, checkpoint = undefined,
|
|
133
|
+
constructor(systemPrompt, sessionMessages, sessionUUID, defaultUserName, checkpoint = undefined, checkpointWriter, fileManager, llm) {
|
|
29
134
|
const nextMessageIdx = (0, conversation_1.sessionMessagesToNextIndex)(sessionMessages);
|
|
30
135
|
const { messages: llmMessages } = resolveConversationWithCheckpoint(sessionMessages, checkpoint);
|
|
31
136
|
logger.debug(`[ChatContextManager]: llm messages: ${JSON.stringify(llmMessages)}`);
|
|
32
|
-
|
|
137
|
+
const getLLM = () => Promise.resolve(llm);
|
|
33
138
|
this.sessionUUID = sessionUUID;
|
|
34
139
|
this.conversationMessages = (0, conversation_1.sessionMessagesToConversationMessages)(sessionMessages, defaultUserName, sessionUUID);
|
|
35
|
-
this.
|
|
36
|
-
this.llmContext = new compressingContextManager_1.CompressingContextManager(systemPrompt, llmMessages, compressionAgentUrl, compressionAgentModel, compressionAgentApiKey);
|
|
140
|
+
this.llmContext = new compressingContextManager_1.CompressingContextManager(systemPrompt, llmMessages, getLLM);
|
|
37
141
|
this.nextMessageIdx = nextMessageIdx;
|
|
38
|
-
this.startingLLMContextLength = undefined;
|
|
39
|
-
this.curAgentMsgIdx = undefined;
|
|
40
|
-
this.pendingMessages = undefined;
|
|
41
142
|
this.pendingCompression = false;
|
|
42
143
|
this.checkpointWriter = checkpointWriter;
|
|
43
144
|
this.fileManager = fileManager;
|
|
44
145
|
fileManager.addEventHandler(this);
|
|
45
146
|
this.fileManagerDescriptionsDirty = true;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
addMessages(messages) {
|
|
49
|
-
this.llmContext.addMessages(messages);
|
|
50
|
-
}
|
|
51
|
-
// IContextManager.addMessage
|
|
52
|
-
addMessage(message) {
|
|
53
|
-
this.llmContext.addMessage(message);
|
|
147
|
+
this.llm = llm;
|
|
148
|
+
this.tokenCounter = new tokenCounter_1.TokenCounter(llm.getModel());
|
|
54
149
|
}
|
|
55
150
|
// IContextManager.getLLMContext
|
|
56
151
|
getLLMContext() {
|
|
57
152
|
if (this.fileManagerDescriptionsDirty) {
|
|
58
|
-
const prompt = (0,
|
|
59
|
-
logger.debug(`[ChatContextManager] filemanager prompt:\n${prompt}`);
|
|
153
|
+
const prompt = (0, fileManager_1.createSessionFilesManagerPrompt)(this.fileManager);
|
|
60
154
|
this.llmContext.setPromptFragment("file_manager", prompt);
|
|
61
155
|
this.fileManagerDescriptionsDirty = false;
|
|
62
156
|
}
|
|
@@ -97,13 +191,28 @@ class ChatContextManager {
|
|
|
97
191
|
}
|
|
98
192
|
// Get the conversation (to send to clients)
|
|
99
193
|
getConversationMessages() {
|
|
100
|
-
return this.conversationMessages
|
|
194
|
+
return this.conversationMessages;
|
|
195
|
+
}
|
|
196
|
+
// Get current context usage (tokens)
|
|
197
|
+
getContextUsage() {
|
|
198
|
+
// Update tokenCounter if model changed
|
|
199
|
+
const currentModel = this.llm.getModel();
|
|
200
|
+
if (this.tokenCounter.getModel() !== currentModel) {
|
|
201
|
+
this.tokenCounter.free();
|
|
202
|
+
this.tokenCounter = new tokenCounter_1.TokenCounter(currentModel);
|
|
203
|
+
}
|
|
204
|
+
const messages = this.getLLMContext();
|
|
205
|
+
const used = this.tokenCounter.countMessagesTokens(messages);
|
|
206
|
+
const max = this.tokenCounter.getContextWindow();
|
|
207
|
+
return { used, max };
|
|
101
208
|
}
|
|
102
209
|
processUserMessage(msg, from_uuid, from_nickname) {
|
|
103
210
|
// TODO: maintain a queue internally instead of relying on the caller to
|
|
104
211
|
// pass in our generated messages back into `startAgentResponse`.
|
|
105
212
|
// Filter out null messages immediately.
|
|
106
|
-
if (!msg.imageB64 &&
|
|
213
|
+
if (!msg.imageB64 &&
|
|
214
|
+
!msg.message &&
|
|
215
|
+
(!msg.attachedFiles || msg.attachedFiles.length === 0)) {
|
|
107
216
|
return undefined;
|
|
108
217
|
}
|
|
109
218
|
const message_idx = this.getNextMessageIdx();
|
|
@@ -118,7 +227,12 @@ class ChatContextManager {
|
|
|
118
227
|
if (msg.imageB64) {
|
|
119
228
|
userMessage.imageB64 = msg.imageB64;
|
|
120
229
|
}
|
|
121
|
-
|
|
230
|
+
if (msg.attachedFiles) {
|
|
231
|
+
userMessage.attachedFiles = msg.attachedFiles;
|
|
232
|
+
}
|
|
233
|
+
if (msg.race_mode) {
|
|
234
|
+
userMessage.race_mode = msg.race_mode;
|
|
235
|
+
}
|
|
122
236
|
return userMessage;
|
|
123
237
|
}
|
|
124
238
|
unprocessUserMessage(userMsg) {
|
|
@@ -128,58 +242,60 @@ class ChatContextManager {
|
|
|
128
242
|
// TODO: Don't take the set of messages. Instead, have OpenSession or this
|
|
129
243
|
// class manage the interaction with the agent and ensure this process only
|
|
130
244
|
// happens one-at-a-time.
|
|
131
|
-
startAgentResponse(msgs) {
|
|
132
|
-
// Sanity check the state
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
(0, assert_1.strict)(
|
|
136
|
-
// Sanity check the messages
|
|
137
|
-
const numMessages = this.pendingUserMessages.length;
|
|
138
|
-
(0, assert_1.strict)(numMessages > 0);
|
|
139
|
-
(0, assert_1.strict)(msgs.length === this.pendingUserMessages.length);
|
|
140
|
-
(0, assert_1.strict)(msgs[0].message_idx === this.pendingUserMessages[0].message_idx);
|
|
141
|
-
(0, assert_1.strict)(msgs[numMessages - 1].message_idx ===
|
|
142
|
-
this.pendingUserMessages[numMessages - 1].message_idx);
|
|
245
|
+
async startAgentResponse(msgs) {
|
|
246
|
+
// Sanity check the state - the incoming user messages should match the
|
|
247
|
+
// pending user messages.
|
|
248
|
+
const numMessages = msgs.length;
|
|
249
|
+
(0, assert_1.strict)(numMessages > 0, "no messages");
|
|
143
250
|
// Collect the pending user messages and allocate a starting index for
|
|
144
251
|
// agent messages and tool calls.
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
this.startingLLMContextLength = this.llmContext.getCommittedLength();
|
|
148
|
-
this.curAgentMsgIdx = this.getNextMessageIdx();
|
|
149
|
-
this.pendingMessages = pendingUserMessages;
|
|
252
|
+
const baseMsgIdx = this.lastCommittedMessageIdx();
|
|
253
|
+
const curAgentMsgIdx = this.getNextMessageIdx();
|
|
150
254
|
// Compute the new llm messages
|
|
151
255
|
const llmUserMessages = [];
|
|
152
|
-
for (const msg of
|
|
256
|
+
for (const msg of msgs) {
|
|
153
257
|
const userMsg = (0, agent_1.createUserMessage)(msg.message, msg.imageB64, msg.user_uuid);
|
|
154
258
|
if (userMsg) {
|
|
155
259
|
llmUserMessages.push(userMsg);
|
|
156
260
|
}
|
|
157
261
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
end: false,
|
|
166
|
-
},
|
|
262
|
+
// Return the context tx and first ServerAgentMessageChunk
|
|
263
|
+
const agentFirstChunk = {
|
|
264
|
+
type: "agent_msg_chunk",
|
|
265
|
+
session_id: this.sessionUUID,
|
|
266
|
+
message_idx: curAgentMsgIdx,
|
|
267
|
+
message: "",
|
|
268
|
+
end: false,
|
|
167
269
|
};
|
|
270
|
+
// Update file manager fragment BEFORE starting the transaction
|
|
271
|
+
if (this.fileManagerDescriptionsDirty) {
|
|
272
|
+
const prompt = (0, fileManager_1.createSessionFilesManagerPrompt)(this.fileManager);
|
|
273
|
+
this.llmContext.setPromptFragment("file_manager", prompt);
|
|
274
|
+
this.fileManagerDescriptionsDirty = false;
|
|
275
|
+
}
|
|
276
|
+
const baseTx = await this.llmContext.startTx(llmUserMessages);
|
|
277
|
+
const contextTx = new ChatContextTransaction(baseTx, this.sessionUUID, baseMsgIdx, msgs, curAgentMsgIdx);
|
|
278
|
+
return { agentFirstChunk, contextTx };
|
|
168
279
|
}
|
|
169
|
-
endAgentResponse() {
|
|
170
|
-
(0, assert_1.strict)(
|
|
171
|
-
(
|
|
172
|
-
|
|
173
|
-
|
|
280
|
+
async endAgentResponse(tx) {
|
|
281
|
+
(0, assert_1.strict)(tx instanceof ChatContextTransaction);
|
|
282
|
+
if (tx.baseMessageIdx() !== this.lastCommittedMessageIdx()) {
|
|
283
|
+
throw new Error(`Tx stale? tx.baseMessageIdx=${String(tx.baseMessageIdx())}, ` +
|
|
284
|
+
`this.conv: ${JSON.stringify(this.conversationMessages)}`);
|
|
285
|
+
}
|
|
286
|
+
const pending = tx.getPending();
|
|
287
|
+
const numPending = pending.length;
|
|
174
288
|
(0, assert_1.strict)(numPending > 0, "no pending"); // at least 1 user message
|
|
175
289
|
// Compute DB messages
|
|
176
|
-
const newSessionMessages = (0, conversation_1.chatMessagesToSessionMessages)(
|
|
177
|
-
const newLLMMessages =
|
|
290
|
+
const newSessionMessages = (0, conversation_1.chatMessagesToSessionMessages)(pending);
|
|
291
|
+
const newLLMMessages = tx.newMessages();
|
|
178
292
|
const messageListError = (error) => {
|
|
179
|
-
|
|
293
|
+
const fullError = `[endAgentResponse] Message list validation failed - ${error}:` +
|
|
180
294
|
`\n newSessionMessages: ${JSON.stringify(newSessionMessages)}` +
|
|
181
|
-
`\n
|
|
182
|
-
`\n newLLMMessages: ${JSON.stringify(newLLMMessages)}
|
|
295
|
+
`\n pending: ${JSON.stringify(pending)}` +
|
|
296
|
+
`\n newLLMMessages: ${JSON.stringify(newLLMMessages)}`;
|
|
297
|
+
logger.error(fullError);
|
|
298
|
+
throw new Error(fullError);
|
|
183
299
|
};
|
|
184
300
|
if (newSessionMessages.length !== numPending) {
|
|
185
301
|
messageListError("newSessionMessages.length !== numPending");
|
|
@@ -193,7 +309,7 @@ class ChatContextManager {
|
|
|
193
309
|
// this ensures all representations are aligned.
|
|
194
310
|
for (let i = 0; i < numPending; ++i) {
|
|
195
311
|
const sMsg = newSessionMessages[i];
|
|
196
|
-
const pMsg =
|
|
312
|
+
const pMsg = pending[i];
|
|
197
313
|
const lMsg = newLLMMessages[i];
|
|
198
314
|
if (sMsg.content.role !== lMsg.role) {
|
|
199
315
|
messageListError(`newSessionMessages[${String(i)}].role !== ` +
|
|
@@ -210,91 +326,18 @@ class ChatContextManager {
|
|
|
210
326
|
}
|
|
211
327
|
// Update our internal state and return the SessionMessages to write to
|
|
212
328
|
// the DB
|
|
213
|
-
this.llmContext.commit();
|
|
214
|
-
this.conversationMessages.push(...
|
|
215
|
-
this.startingLLMContextLength = undefined;
|
|
216
|
-
this.pendingMessages = undefined;
|
|
217
|
-
this.curAgentMsgIdx = undefined;
|
|
329
|
+
await this.llmContext.commit(tx.getBaseTx());
|
|
330
|
+
this.conversationMessages.push(...pending);
|
|
218
331
|
// Kick off a compression?
|
|
219
332
|
this.checkCompression();
|
|
220
333
|
return newSessionMessages;
|
|
221
334
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
* This function checks that nothing has been entered into the LLM context,
|
|
227
|
-
* and drops any new user messages or responses before the error.
|
|
228
|
-
*/
|
|
229
|
-
revertAgentResponse(errMsg) {
|
|
230
|
-
logger.warn(`[ChatContextManager.revertAgentResponse] error: ${errMsg}`);
|
|
231
|
-
(0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
|
|
232
|
-
(0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
|
|
233
|
-
(0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
|
|
234
|
-
// Sanity check that no new messages were put into the context (The Agent
|
|
235
|
-
// is expected to only call `addMessage(s)` at the end of the Agent loop.
|
|
236
|
-
// (Note, we don't check for equality here, just in case the context was
|
|
237
|
-
// compressed while the Agent was executing).
|
|
238
|
-
const contextLength = this.llmContext.getCommittedLength();
|
|
239
|
-
if (contextLength > this.startingLLMContextLength) {
|
|
240
|
-
logger.error("[ChatContextManager.revertAgentResponse] llmContext has grown " +
|
|
241
|
-
`despite Agent error (${String(contextLength)}, ` +
|
|
242
|
-
`${String(this.startingLLMContextLength)})`);
|
|
335
|
+
lastCommittedMessageIdx() {
|
|
336
|
+
const numMsgs = this.conversationMessages.length;
|
|
337
|
+
if (numMsgs > 0) {
|
|
338
|
+
return this.conversationMessages[numMsgs - 1].message_idx;
|
|
243
339
|
}
|
|
244
|
-
|
|
245
|
-
this.startingLLMContextLength = undefined;
|
|
246
|
-
this.pendingMessages = undefined;
|
|
247
|
-
this.curAgentMsgIdx = undefined;
|
|
248
|
-
}
|
|
249
|
-
processAgentMessage(msg, end) {
|
|
250
|
-
(0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
|
|
251
|
-
(0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
|
|
252
|
-
(0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
|
|
253
|
-
const message = {
|
|
254
|
-
type: "agent_msg_chunk",
|
|
255
|
-
session_id: this.sessionUUID,
|
|
256
|
-
message_idx: this.getCurrentAgentMessageIdx(),
|
|
257
|
-
message: msg,
|
|
258
|
-
end,
|
|
259
|
-
};
|
|
260
|
-
return message;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Process a FULL Agent message (not chunks from stream). No message is
|
|
264
|
-
* required for broadcast as the calling code is expected to broadcast this
|
|
265
|
-
* as chunks.
|
|
266
|
-
*/
|
|
267
|
-
processAgentResponse(result) {
|
|
268
|
-
(0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
|
|
269
|
-
(0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
|
|
270
|
-
(0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
|
|
271
|
-
// Insert this (full) agent response into the list of agent messages
|
|
272
|
-
const msg = {
|
|
273
|
-
type: "agent_msg",
|
|
274
|
-
session_id: this.sessionUUID,
|
|
275
|
-
message_idx: this.getNextMessageSubIdx(),
|
|
276
|
-
message: result,
|
|
277
|
-
};
|
|
278
|
-
this.pendingMessages.push(msg);
|
|
279
|
-
}
|
|
280
|
-
processToolCallResult(result) {
|
|
281
|
-
(0, assert_1.strict)(typeof this.startingLLMContextLength !== "undefined");
|
|
282
|
-
(0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
|
|
283
|
-
(0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
|
|
284
|
-
// Allocate the sub-index for this tool call result. It should not
|
|
285
|
-
// have been used already.
|
|
286
|
-
const message_idx = this.getNextMessageSubIdx();
|
|
287
|
-
const numPending = this.pendingMessages.length;
|
|
288
|
-
(0, assert_1.strict)(numPending > 0);
|
|
289
|
-
(0, assert_1.strict)(this.pendingMessages[numPending - 1].message_idx < message_idx);
|
|
290
|
-
const msg = {
|
|
291
|
-
type: "tool_call_result",
|
|
292
|
-
session_id: this.sessionUUID,
|
|
293
|
-
message_idx,
|
|
294
|
-
result,
|
|
295
|
-
};
|
|
296
|
-
this.pendingMessages.push(msg);
|
|
297
|
-
return msg;
|
|
340
|
+
return undefined;
|
|
298
341
|
}
|
|
299
342
|
getNextMessageIdx() {
|
|
300
343
|
const idx = this.nextMessageIdx;
|
|
@@ -309,25 +352,12 @@ class ChatContextManager {
|
|
|
309
352
|
(0, assert_1.strict)(messageIdx === this.nextMessageIdx - conversation_1.MESSAGE_INDEX_FULL_INCREMENT, "message idx cannot be free-ed");
|
|
310
353
|
this.nextMessageIdx = messageIdx;
|
|
311
354
|
}
|
|
312
|
-
/// Get the current index to use for streaming Agent chunks
|
|
313
|
-
getCurrentAgentMessageIdx() {
|
|
314
|
-
(0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
|
|
315
|
-
(0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
|
|
316
|
-
return this.curAgentMsgIdx;
|
|
317
|
-
}
|
|
318
|
-
getNextMessageSubIdx() {
|
|
319
|
-
(0, assert_1.strict)(typeof this.pendingMessages !== "undefined");
|
|
320
|
-
(0, assert_1.strict)(typeof this.curAgentMsgIdx !== "undefined");
|
|
321
|
-
const idx = this.curAgentMsgIdx;
|
|
322
|
-
this.curAgentMsgIdx += conversation_1.MESSAGE_INDEX_SUB_INCREMENT;
|
|
323
|
-
return idx;
|
|
324
|
-
}
|
|
325
355
|
checkCompression() {
|
|
326
356
|
if (this.pendingCompression) {
|
|
327
357
|
return;
|
|
328
358
|
}
|
|
329
359
|
// TODO: track tokens and use that to trigger compression
|
|
330
|
-
const numCommitted = this.llmContext.
|
|
360
|
+
const numCommitted = this.llmContext.numMessages();
|
|
331
361
|
if (numCommitted < COMPRESSION_TRIGGER_NUM_MESSAGES) {
|
|
332
362
|
return;
|
|
333
363
|
}
|
|
@@ -347,7 +377,7 @@ class ChatContextManager {
|
|
|
347
377
|
await this.checkpointWriter.writeCheckpoint(checkpoint);
|
|
348
378
|
}
|
|
349
379
|
catch (err) {
|
|
350
|
-
logger.warn(`[runCompression] error during compression: ${
|
|
380
|
+
logger.warn(`[runCompression] error during compression: ${(0, errorUtils_1.getErrorString)(err)}`);
|
|
351
381
|
}
|
|
352
382
|
finally {
|
|
353
383
|
this.pendingCompression = false;
|
|
@@ -92,6 +92,9 @@ function userMessageToConversationMessage(userMessage, user_uuid, message_idx, s
|
|
|
92
92
|
if (userMsgData.imageB64) {
|
|
93
93
|
msg.imageB64 = userMsgData.imageB64;
|
|
94
94
|
}
|
|
95
|
+
if (userMsgData.attachedFiles) {
|
|
96
|
+
msg.attachedFiles = userMsgData.attachedFiles;
|
|
97
|
+
}
|
|
95
98
|
return msg;
|
|
96
99
|
}
|
|
97
100
|
/**
|
|
@@ -4,19 +4,30 @@ exports.genImageTool = genImageTool;
|
|
|
4
4
|
exports.genImageFileTool = genImageFileTool;
|
|
5
5
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
6
6
|
const imageGenerator_1 = require("../../agent/imageGenerator");
|
|
7
|
-
const tools_1 = require("
|
|
8
|
-
const
|
|
7
|
+
const tools_1 = require("../../agent/tools");
|
|
8
|
+
const openAIRouterLLM_1 = require("./openAIRouterLLM");
|
|
9
|
+
const imageGenLLM_1 = require("../../agent/imageGenLLM");
|
|
10
|
+
const agentUtils_1 = require("../../agent/agentUtils");
|
|
11
|
+
const constants_1 = require("../constants");
|
|
9
12
|
const logger = (0, sdk_1.getLogger)();
|
|
10
|
-
function createImageGenerator(
|
|
11
|
-
const imageGenModel = process.env["GEN_IMAGE_MODEL"];
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
async function createImageGenerator(platform) {
|
|
14
|
+
const imageGenModel = process.env["GEN_IMAGE_MODEL"] || imageGenLLM_1.DEFAULT_IMAGE_GEN_MODEL;
|
|
15
|
+
// Allow the image generator to use a "specialized" or "debug" LLM if
|
|
16
|
+
// requested. Otherwise query the router maps for the url and api key and
|
|
17
|
+
// instantiate an ImageGenLLM.
|
|
18
|
+
let llm = await (0, agentUtils_1.createSpecializedLLM)(imageGenModel, platform);
|
|
19
|
+
if (!llm) {
|
|
20
|
+
const { apiKey, baseURL } = (0, openAIRouterLLM_1.getOpenAIClientParams)(imageGenModel);
|
|
21
|
+
logger.debug(`[genImageFileTool] model: ${imageGenModel}, url: ${baseURL}"}`);
|
|
22
|
+
llm = new imageGenLLM_1.ImageGenLLM(apiKey, baseURL, imageGenModel);
|
|
23
|
+
}
|
|
24
|
+
return imageGenerator_1.ImageGenerator.init(llm);
|
|
14
25
|
}
|
|
15
26
|
// gen_image
|
|
16
27
|
//
|
|
17
28
|
// Simple tool to generate image output as tool output. The user can then
|
|
18
29
|
// decide how/whether to use the image.
|
|
19
|
-
async function genImageTool(
|
|
30
|
+
async function genImageTool(platform) {
|
|
20
31
|
const GEN_IMAGE_DESC = {
|
|
21
32
|
type: "function",
|
|
22
33
|
function: {
|
|
@@ -38,7 +49,7 @@ async function genImageTool(llmUrl, llmApiKey) {
|
|
|
38
49
|
},
|
|
39
50
|
},
|
|
40
51
|
};
|
|
41
|
-
const imageGenerator = await createImageGenerator(
|
|
52
|
+
const imageGenerator = await createImageGenerator(platform);
|
|
42
53
|
const getPromptInputImg = (0, tools_1.makeParseArgsFn)(["prompt"], ["input_img"]);
|
|
43
54
|
const toolFn = async (_, args) => {
|
|
44
55
|
const { prompt, input_img } = getPromptInputImg(args);
|
|
@@ -59,12 +70,15 @@ async function genImageTool(llmUrl, llmApiKey) {
|
|
|
59
70
|
};
|
|
60
71
|
}
|
|
61
72
|
// gen_image_file
|
|
62
|
-
async function genImageFileTool(
|
|
73
|
+
async function genImageFileTool(platform, fileManager) {
|
|
63
74
|
const GEN_IMAGE_FILE_DESC = {
|
|
64
75
|
type: "function",
|
|
65
76
|
function: {
|
|
66
77
|
name: "gen_image_file",
|
|
67
|
-
description: "Generate image
|
|
78
|
+
description: "Generate or edit an image. Saves result to file manager. " +
|
|
79
|
+
`Canvas workspace is '${constants_1.CANVAS_WORKSPACE_FILENAME}'. ` +
|
|
80
|
+
"When editing canvas images, set add_to_canvas=true to " +
|
|
81
|
+
"place the result back onto the canvas.",
|
|
68
82
|
parameters: {
|
|
69
83
|
type: "object",
|
|
70
84
|
properties: {
|
|
@@ -82,27 +96,36 @@ async function genImageFileTool(llmUrl, llmApiKey, fileManager) {
|
|
|
82
96
|
},
|
|
83
97
|
input_name: {
|
|
84
98
|
type: "string",
|
|
85
|
-
description: "(Optional) input image filename"
|
|
99
|
+
description: "(Optional) input image filename. " +
|
|
100
|
+
`Use '${constants_1.CANVAS_WORKSPACE_FILENAME}' for canvas edits.`,
|
|
101
|
+
},
|
|
102
|
+
add_to_canvas: {
|
|
103
|
+
type: "boolean",
|
|
104
|
+
description: "Set true to place result on canvas (use when editing canvas)",
|
|
86
105
|
},
|
|
87
106
|
},
|
|
88
107
|
required: ["prompt", "name", "summary"],
|
|
89
108
|
},
|
|
90
109
|
},
|
|
91
110
|
};
|
|
92
|
-
const imageGenerator = await createImageGenerator(
|
|
93
|
-
const
|
|
111
|
+
const imageGenerator = await createImageGenerator(platform);
|
|
112
|
+
const getArgs = (0, tools_1.makeParseArgsFn)(["prompt", "name", "summary"], ["input_name"], ["add_to_canvas"]);
|
|
94
113
|
const toolFn = async (_, args) => {
|
|
95
|
-
const { prompt, name, summary, input_name } =
|
|
114
|
+
const { prompt, name, summary, input_name, add_to_canvas } = getArgs(args);
|
|
115
|
+
const addToCanvas = add_to_canvas ?? false;
|
|
96
116
|
const input_image = input_name
|
|
97
117
|
? await fileManager.getFileContent(input_name)
|
|
98
118
|
: undefined;
|
|
99
119
|
const image = await imageGenerator.generate(prompt, input_image);
|
|
100
|
-
const mimeType = (0, dbSessionFileModels_1.getSessionFileMimeTypeFromDataUrl)(image);
|
|
101
120
|
await fileManager.putFileContent(name, summary, image);
|
|
102
121
|
const uri = fileManager.getSessionFileRelativeUrl(name);
|
|
103
122
|
return {
|
|
104
123
|
response: uri,
|
|
105
|
-
_meta: {
|
|
124
|
+
_meta: {
|
|
125
|
+
"xalia/fileUri": uri,
|
|
126
|
+
"xalia/fileMimeType": (0, tools_1.getSessionFileMimeTypeFromDataUrl)(image),
|
|
127
|
+
...(addToCanvas && { "xalia/addToCanvas": "true" }),
|
|
128
|
+
},
|
|
106
129
|
};
|
|
107
130
|
};
|
|
108
131
|
return {
|