@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
|
@@ -2,14 +2,14 @@ import { strict as assert } from "assert";
|
|
|
2
2
|
|
|
3
3
|
import { getLogger } from "@xalia/xmcp/sdk";
|
|
4
4
|
|
|
5
|
-
import { createUserMessage } from "../../agent/agent";
|
|
6
5
|
import {
|
|
7
6
|
AssistantMessageParam,
|
|
7
|
+
ILLM,
|
|
8
8
|
MessageParam,
|
|
9
9
|
ToolMessageParam,
|
|
10
10
|
UserMessageParam,
|
|
11
11
|
} from "../../agent/llm";
|
|
12
|
-
import {
|
|
12
|
+
import { IContextTransaction } from "../../agent/context";
|
|
13
13
|
import {
|
|
14
14
|
CompressingContextManager,
|
|
15
15
|
createCheckpointMessage,
|
|
@@ -34,11 +34,14 @@ import {
|
|
|
34
34
|
ISessionFileManager,
|
|
35
35
|
ISessionFileManagerEventHandler,
|
|
36
36
|
createSessionFilesManagerPrompt,
|
|
37
|
-
} from "./sessionFileManager";
|
|
38
|
-
import {
|
|
39
37
|
SessionFileDescriptor,
|
|
40
38
|
SessionFileEntry,
|
|
41
|
-
} from "
|
|
39
|
+
} from "../../agent/tools/fileManager";
|
|
40
|
+
// eslint-disable-next-line max-len
|
|
41
|
+
import { ContextTransactionWithWorkspace } from "../../agent/contextWithWorkspace";
|
|
42
|
+
import { getErrorString } from "./errorUtils";
|
|
43
|
+
import { createUserMessage } from "../../agent/agent";
|
|
44
|
+
import { TokenCounter } from "../../agent/tokenCounter";
|
|
42
45
|
|
|
43
46
|
const logger = getLogger();
|
|
44
47
|
|
|
@@ -57,6 +60,144 @@ export interface ICheckpointWriter {
|
|
|
57
60
|
writeCheckpoint(checkpoint: SessionCheckpoint): Promise<void>;
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
export class ChatContextTransaction implements IContextTransaction {
|
|
64
|
+
private readonly baseTx: ContextTransactionWithWorkspace;
|
|
65
|
+
private readonly sessionUUID: string;
|
|
66
|
+
/// Index of final message in the committed context. If this has changed
|
|
67
|
+
/// before we try to commit this tx, the commit will fail.
|
|
68
|
+
private readonly baseMsgIdx: number | undefined;
|
|
69
|
+
private readonly startingLLMContextLength: number;
|
|
70
|
+
private readonly pendingMessages: ConversationMessage[];
|
|
71
|
+
private curAgentMsgIdx: number;
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
baseTx: ContextTransactionWithWorkspace,
|
|
75
|
+
sessionUUID: string,
|
|
76
|
+
baseMsgIdx: number | undefined,
|
|
77
|
+
pendingUserMessages: ServerUserMessage[],
|
|
78
|
+
curAgentMsgIdx: number
|
|
79
|
+
) {
|
|
80
|
+
assert(typeof curAgentMsgIdx !== "undefined");
|
|
81
|
+
|
|
82
|
+
this.sessionUUID = sessionUUID;
|
|
83
|
+
this.baseTx = baseTx;
|
|
84
|
+
this.baseMsgIdx = baseMsgIdx;
|
|
85
|
+
this.startingLLMContextLength = baseTx.getLLMContextLength();
|
|
86
|
+
this.pendingMessages = pendingUserMessages;
|
|
87
|
+
this.curAgentMsgIdx = curAgentMsgIdx;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// IContextTransaction.addMessages
|
|
91
|
+
addMessages(messages: MessageParam[]): number {
|
|
92
|
+
return this.baseTx.addMessages(messages);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// IContextTransaction.addMessage
|
|
96
|
+
addMessage(message: MessageParam): number {
|
|
97
|
+
return this.baseTx.addMessage(message);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// IContextTransaction.getMessage
|
|
101
|
+
getMessage(handle: number): MessageParam {
|
|
102
|
+
return this.baseTx.getMessage(handle);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// IContextTransaction.getLLMContext
|
|
106
|
+
getLLMContext(): MessageParam[] {
|
|
107
|
+
return this.baseTx.getLLMContext();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// IContextTransaction.getLLMContextLength
|
|
111
|
+
getLLMContextLength(): number {
|
|
112
|
+
return this.baseTx.getLLMContextLength();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getPending(): ConversationMessage[] {
|
|
116
|
+
return this.pendingMessages;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
baseMessageIdx(): number | undefined {
|
|
120
|
+
return this.baseMsgIdx;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getBaseTx(): ContextTransactionWithWorkspace {
|
|
124
|
+
return this.baseTx;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Process a FULL Agent message (not chunks from stream). No message is
|
|
129
|
+
* required for broadcast as the calling code is expected to broadcast this
|
|
130
|
+
* as chunks.
|
|
131
|
+
*/
|
|
132
|
+
processAgentResponse(result: AssistantMessageParam) {
|
|
133
|
+
// Insert this (full) agent response into the list of agent messages
|
|
134
|
+
const msg: ServerAgentMessage = {
|
|
135
|
+
type: "agent_msg",
|
|
136
|
+
session_id: this.sessionUUID,
|
|
137
|
+
message_idx: this.getNextMessageSubIdx(),
|
|
138
|
+
message: result,
|
|
139
|
+
};
|
|
140
|
+
this.pendingMessages.push(msg);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
processAgentMessageChunk(msg: string, end: boolean): ServerAgentMessageChunk {
|
|
144
|
+
const message: ServerAgentMessageChunk = {
|
|
145
|
+
type: "agent_msg_chunk",
|
|
146
|
+
session_id: this.sessionUUID,
|
|
147
|
+
message_idx: this.getCurrentAgentMessageIdx(),
|
|
148
|
+
message: msg,
|
|
149
|
+
end,
|
|
150
|
+
};
|
|
151
|
+
return message;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
processToolCallResult(result: ToolMessageParam): ServerToolCallResult {
|
|
155
|
+
// Allocate the sub-index for this tool call result. It should not
|
|
156
|
+
// have been used already.
|
|
157
|
+
|
|
158
|
+
const message_idx = this.getNextMessageSubIdx();
|
|
159
|
+
const numPending = this.pendingMessages.length;
|
|
160
|
+
assert(numPending > 0);
|
|
161
|
+
assert(this.pendingMessages[numPending - 1].message_idx < message_idx);
|
|
162
|
+
|
|
163
|
+
const msg: ServerToolCallResult = {
|
|
164
|
+
type: "tool_call_result",
|
|
165
|
+
session_id: this.sessionUUID,
|
|
166
|
+
message_idx,
|
|
167
|
+
result,
|
|
168
|
+
};
|
|
169
|
+
this.pendingMessages.push(msg);
|
|
170
|
+
return msg;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
revertAgentResponse(errMsg: string): void {
|
|
174
|
+
logger.warn(`[ChatContextManager.revertAgentResponse] error: ${errMsg}`);
|
|
175
|
+
|
|
176
|
+
// Remove all messages since the user messages were placed on.
|
|
177
|
+
|
|
178
|
+
while (this.baseTx.getLLMContextLength() > this.startingLLMContextLength) {
|
|
179
|
+
const last = this.baseTx.popMessage();
|
|
180
|
+
assert(last);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
newMessages(): MessageParam[] {
|
|
185
|
+
return this.baseTx.newMessages();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private getNextMessageSubIdx(): number {
|
|
189
|
+
const idx = this.curAgentMsgIdx;
|
|
190
|
+
this.curAgentMsgIdx += MESSAGE_INDEX_SUB_INCREMENT;
|
|
191
|
+
return idx;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Get the current index to use for streaming Agent chunks
|
|
195
|
+
private getCurrentAgentMessageIdx(): number {
|
|
196
|
+
assert(typeof this.curAgentMsgIdx !== "undefined");
|
|
197
|
+
return this.curAgentMsgIdx;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
60
201
|
/**
|
|
61
202
|
* A context manager for Agents interacting with the (potentially multi-user)
|
|
62
203
|
* chat conversations.
|
|
@@ -68,40 +209,35 @@ export interface ICheckpointWriter {
|
|
|
68
209
|
* - Maintain pending user messages, agent loop messages and messages to be
|
|
69
210
|
* committed to the DB
|
|
70
211
|
*/
|
|
71
|
-
export class ChatContextManager
|
|
72
|
-
implements IContextManager, ISessionFileManagerEventHandler
|
|
73
|
-
{
|
|
212
|
+
export class ChatContextManager implements ISessionFileManagerEventHandler {
|
|
74
213
|
// Including any pending user messages
|
|
75
|
-
private sessionUUID: string;
|
|
76
|
-
private conversationMessages: ConversationMessage[];
|
|
77
|
-
private
|
|
78
|
-
private llmContext: CompressingContextManager;
|
|
79
|
-
private nextMessageIdx: number;
|
|
214
|
+
private readonly sessionUUID: string;
|
|
215
|
+
private readonly conversationMessages: ConversationMessage[];
|
|
216
|
+
private readonly llmContext: CompressingContextManager;
|
|
80
217
|
|
|
81
|
-
|
|
82
|
-
private startingLLMContextLength: number | undefined;
|
|
83
|
-
private curAgentMsgIdx: number | undefined; // active agent message
|
|
84
|
-
private pendingMessages: ConversationMessage[] | undefined;
|
|
218
|
+
private nextMessageIdx: number;
|
|
85
219
|
|
|
86
220
|
// Compression state
|
|
87
|
-
private checkpointWriter: ICheckpointWriter;
|
|
221
|
+
private readonly checkpointWriter: ICheckpointWriter;
|
|
88
222
|
private pendingCompression: boolean;
|
|
89
223
|
|
|
90
224
|
// FileManager
|
|
91
|
-
private fileManager: ISessionFileManager;
|
|
225
|
+
private readonly fileManager: ISessionFileManager;
|
|
92
226
|
private fileManagerDescriptionsDirty: boolean;
|
|
93
227
|
|
|
228
|
+
// LLM and token counting
|
|
229
|
+
private readonly llm: ILLM;
|
|
230
|
+
private tokenCounter: TokenCounter;
|
|
231
|
+
|
|
94
232
|
constructor(
|
|
95
233
|
systemPrompt: string,
|
|
96
234
|
sessionMessages: SessionMessage[],
|
|
97
235
|
sessionUUID: string,
|
|
98
236
|
defaultUserName: string,
|
|
99
237
|
checkpoint: SessionCheckpoint | undefined = undefined,
|
|
100
|
-
compressionAgentUrl: string,
|
|
101
|
-
compressionAgentModel: string,
|
|
102
|
-
compressionAgentApiKey: string,
|
|
103
238
|
checkpointWriter: ICheckpointWriter,
|
|
104
|
-
fileManager: ISessionFileManager
|
|
239
|
+
fileManager: ISessionFileManager,
|
|
240
|
+
llm: ILLM
|
|
105
241
|
) {
|
|
106
242
|
const nextMessageIdx = sessionMessagesToNextIndex(sessionMessages);
|
|
107
243
|
const { messages: llmMessages } = resolveConversationWithCheckpoint(
|
|
@@ -113,7 +249,7 @@ export class ChatContextManager
|
|
|
113
249
|
`[ChatContextManager]: llm messages: ${JSON.stringify(llmMessages)}`
|
|
114
250
|
);
|
|
115
251
|
|
|
116
|
-
|
|
252
|
+
const getLLM = () => Promise.resolve(llm);
|
|
117
253
|
|
|
118
254
|
this.sessionUUID = sessionUUID;
|
|
119
255
|
this.conversationMessages = sessionMessagesToConversationMessages(
|
|
@@ -121,40 +257,25 @@ export class ChatContextManager
|
|
|
121
257
|
defaultUserName,
|
|
122
258
|
sessionUUID
|
|
123
259
|
);
|
|
124
|
-
this.pendingUserMessages = [];
|
|
125
260
|
this.llmContext = new CompressingContextManager(
|
|
126
261
|
systemPrompt,
|
|
127
262
|
llmMessages,
|
|
128
|
-
|
|
129
|
-
compressionAgentModel,
|
|
130
|
-
compressionAgentApiKey
|
|
263
|
+
getLLM
|
|
131
264
|
);
|
|
132
265
|
this.nextMessageIdx = nextMessageIdx;
|
|
133
|
-
this.startingLLMContextLength = undefined;
|
|
134
|
-
this.curAgentMsgIdx = undefined;
|
|
135
|
-
this.pendingMessages = undefined;
|
|
136
266
|
this.pendingCompression = false;
|
|
137
267
|
this.checkpointWriter = checkpointWriter;
|
|
138
268
|
this.fileManager = fileManager;
|
|
139
269
|
fileManager.addEventHandler(this);
|
|
140
270
|
this.fileManagerDescriptionsDirty = true;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// IContextManager.addMessages
|
|
144
|
-
addMessages(messages: MessageParam[]): void {
|
|
145
|
-
this.llmContext.addMessages(messages);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// IContextManager.addMessage
|
|
149
|
-
addMessage(message: MessageParam): void {
|
|
150
|
-
this.llmContext.addMessage(message);
|
|
271
|
+
this.llm = llm;
|
|
272
|
+
this.tokenCounter = new TokenCounter(llm.getModel());
|
|
151
273
|
}
|
|
152
274
|
|
|
153
275
|
// IContextManager.getLLMContext
|
|
154
276
|
getLLMContext(): MessageParam[] {
|
|
155
277
|
if (this.fileManagerDescriptionsDirty) {
|
|
156
278
|
const prompt = createSessionFilesManagerPrompt(this.fileManager);
|
|
157
|
-
logger.debug(`[ChatContextManager] filemanager prompt:\n${prompt}`);
|
|
158
279
|
this.llmContext.setPromptFragment("file_manager", prompt);
|
|
159
280
|
this.fileManagerDescriptionsDirty = false;
|
|
160
281
|
}
|
|
@@ -166,6 +287,7 @@ export class ChatContextManager
|
|
|
166
287
|
getAgentPrompt(): string {
|
|
167
288
|
return this.llmContext.getAgentPrompt();
|
|
168
289
|
}
|
|
290
|
+
|
|
169
291
|
// IContextManager.setAgentPrompt
|
|
170
292
|
setAgentPrompt(prompt: string): void {
|
|
171
293
|
this.llmContext.setAgentPrompt(prompt);
|
|
@@ -205,7 +327,22 @@ export class ChatContextManager
|
|
|
205
327
|
|
|
206
328
|
// Get the conversation (to send to clients)
|
|
207
329
|
getConversationMessages(): ConversationMessage[] {
|
|
208
|
-
return this.conversationMessages
|
|
330
|
+
return this.conversationMessages;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Get current context usage (tokens)
|
|
334
|
+
getContextUsage(): { used: number; max: number } {
|
|
335
|
+
// Update tokenCounter if model changed
|
|
336
|
+
const currentModel = this.llm.getModel();
|
|
337
|
+
if (this.tokenCounter.getModel() !== currentModel) {
|
|
338
|
+
this.tokenCounter.free();
|
|
339
|
+
this.tokenCounter = new TokenCounter(currentModel);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const messages = this.getLLMContext();
|
|
343
|
+
const used = this.tokenCounter.countMessagesTokens(messages);
|
|
344
|
+
const max = this.tokenCounter.getContextWindow();
|
|
345
|
+
return { used, max };
|
|
209
346
|
}
|
|
210
347
|
|
|
211
348
|
processUserMessage(
|
|
@@ -217,7 +354,11 @@ export class ChatContextManager
|
|
|
217
354
|
// pass in our generated messages back into `startAgentResponse`.
|
|
218
355
|
|
|
219
356
|
// Filter out null messages immediately.
|
|
220
|
-
if (
|
|
357
|
+
if (
|
|
358
|
+
!msg.imageB64 &&
|
|
359
|
+
!msg.message &&
|
|
360
|
+
(!msg.attachedFiles || msg.attachedFiles.length === 0)
|
|
361
|
+
) {
|
|
221
362
|
return undefined;
|
|
222
363
|
}
|
|
223
364
|
|
|
@@ -233,8 +374,13 @@ export class ChatContextManager
|
|
|
233
374
|
if (msg.imageB64) {
|
|
234
375
|
userMessage.imageB64 = msg.imageB64;
|
|
235
376
|
}
|
|
377
|
+
if (msg.attachedFiles) {
|
|
378
|
+
userMessage.attachedFiles = msg.attachedFiles;
|
|
379
|
+
}
|
|
380
|
+
if (msg.race_mode) {
|
|
381
|
+
userMessage.race_mode = msg.race_mode;
|
|
382
|
+
}
|
|
236
383
|
|
|
237
|
-
this.pendingUserMessages.push(userMessage);
|
|
238
384
|
return userMessage;
|
|
239
385
|
}
|
|
240
386
|
|
|
@@ -247,43 +393,25 @@ export class ChatContextManager
|
|
|
247
393
|
// class manage the interaction with the agent and ensure this process only
|
|
248
394
|
// happens one-at-a-time.
|
|
249
395
|
|
|
250
|
-
startAgentResponse(msgs: ServerUserMessage[]): {
|
|
251
|
-
|
|
396
|
+
async startAgentResponse(msgs: ServerUserMessage[]): Promise<{
|
|
397
|
+
contextTx: ChatContextTransaction;
|
|
252
398
|
agentFirstChunk: ServerAgentMessageChunk;
|
|
253
|
-
} {
|
|
254
|
-
// Sanity check the state
|
|
255
|
-
|
|
256
|
-
assert(
|
|
257
|
-
typeof this.startingLLMContextLength === "undefined",
|
|
258
|
-
"already processing"
|
|
259
|
-
);
|
|
260
|
-
assert(typeof this.pendingMessages === "undefined", "already processing");
|
|
261
|
-
assert(typeof this.curAgentMsgIdx === "undefined", "already processing");
|
|
262
|
-
|
|
263
|
-
// Sanity check the messages
|
|
264
|
-
|
|
265
|
-
const numMessages = this.pendingUserMessages.length;
|
|
266
|
-
assert(numMessages > 0);
|
|
267
|
-
assert(msgs.length === this.pendingUserMessages.length);
|
|
268
|
-
assert(msgs[0].message_idx === this.pendingUserMessages[0].message_idx);
|
|
269
|
-
assert(
|
|
270
|
-
msgs[numMessages - 1].message_idx ===
|
|
271
|
-
this.pendingUserMessages[numMessages - 1].message_idx
|
|
272
|
-
);
|
|
399
|
+
}> {
|
|
400
|
+
// Sanity check the state - the incoming user messages should match the
|
|
401
|
+
// pending user messages.
|
|
273
402
|
|
|
403
|
+
const numMessages = msgs.length;
|
|
404
|
+
assert(numMessages > 0, "no messages");
|
|
274
405
|
// Collect the pending user messages and allocate a starting index for
|
|
275
406
|
// agent messages and tool calls.
|
|
276
407
|
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
this.startingLLMContextLength = this.llmContext.getCommittedLength();
|
|
280
|
-
this.curAgentMsgIdx = this.getNextMessageIdx();
|
|
281
|
-
this.pendingMessages = pendingUserMessages as ConversationMessage[];
|
|
408
|
+
const baseMsgIdx = this.lastCommittedMessageIdx();
|
|
409
|
+
const curAgentMsgIdx = this.getNextMessageIdx();
|
|
282
410
|
|
|
283
411
|
// Compute the new llm messages
|
|
284
412
|
|
|
285
413
|
const llmUserMessages: UserMessageParam[] = [];
|
|
286
|
-
for (const msg of
|
|
414
|
+
for (const msg of msgs) {
|
|
287
415
|
const userMsg = createUserMessage(
|
|
288
416
|
msg.message,
|
|
289
417
|
msg.imageB64,
|
|
@@ -294,49 +422,60 @@ export class ChatContextManager
|
|
|
294
422
|
}
|
|
295
423
|
}
|
|
296
424
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
},
|
|
425
|
+
// Return the context tx and first ServerAgentMessageChunk
|
|
426
|
+
|
|
427
|
+
const agentFirstChunk: ServerAgentMessageChunk = {
|
|
428
|
+
type: "agent_msg_chunk",
|
|
429
|
+
session_id: this.sessionUUID,
|
|
430
|
+
message_idx: curAgentMsgIdx,
|
|
431
|
+
message: "",
|
|
432
|
+
end: false,
|
|
306
433
|
};
|
|
307
|
-
}
|
|
308
434
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
"
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
435
|
+
// Update file manager fragment BEFORE starting the transaction
|
|
436
|
+
if (this.fileManagerDescriptionsDirty) {
|
|
437
|
+
const prompt = createSessionFilesManagerPrompt(this.fileManager);
|
|
438
|
+
this.llmContext.setPromptFragment("file_manager", prompt);
|
|
439
|
+
this.fileManagerDescriptionsDirty = false;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const baseTx = await this.llmContext.startTx(llmUserMessages);
|
|
443
|
+
const contextTx = new ChatContextTransaction(
|
|
444
|
+
baseTx,
|
|
445
|
+
this.sessionUUID,
|
|
446
|
+
baseMsgIdx,
|
|
447
|
+
msgs,
|
|
448
|
+
curAgentMsgIdx
|
|
321
449
|
);
|
|
450
|
+
return { agentFirstChunk, contextTx };
|
|
451
|
+
}
|
|
322
452
|
|
|
323
|
-
|
|
453
|
+
async endAgentResponse(tx: IContextTransaction): Promise<SessionMessage[]> {
|
|
454
|
+
assert(tx instanceof ChatContextTransaction);
|
|
455
|
+
if (tx.baseMessageIdx() !== this.lastCommittedMessageIdx()) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
`Tx stale? tx.baseMessageIdx=${String(tx.baseMessageIdx())}, ` +
|
|
458
|
+
`this.conv: ${JSON.stringify(this.conversationMessages)}`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const pending = tx.getPending();
|
|
463
|
+
const numPending = pending.length;
|
|
324
464
|
assert(numPending > 0, "no pending"); // at least 1 user message
|
|
325
465
|
|
|
326
466
|
// Compute DB messages
|
|
327
467
|
|
|
328
|
-
const newSessionMessages = chatMessagesToSessionMessages(
|
|
329
|
-
|
|
330
|
-
);
|
|
331
|
-
const newLLMMessages = this.llmContext.getPending();
|
|
468
|
+
const newSessionMessages = chatMessagesToSessionMessages(pending);
|
|
469
|
+
const newLLMMessages = tx.newMessages();
|
|
332
470
|
|
|
333
471
|
const messageListError = (error: string) => {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
);
|
|
472
|
+
const fullError =
|
|
473
|
+
`[endAgentResponse] Message list validation failed - ${error}:` +
|
|
474
|
+
`\n newSessionMessages: ${JSON.stringify(newSessionMessages)}` +
|
|
475
|
+
`\n pending: ${JSON.stringify(pending)}` +
|
|
476
|
+
`\n newLLMMessages: ${JSON.stringify(newLLMMessages)}`;
|
|
477
|
+
logger.error(fullError);
|
|
478
|
+
throw new Error(fullError);
|
|
340
479
|
};
|
|
341
480
|
|
|
342
481
|
if (newSessionMessages.length !== numPending) {
|
|
@@ -353,7 +492,7 @@ export class ChatContextManager
|
|
|
353
492
|
|
|
354
493
|
for (let i = 0; i < numPending; ++i) {
|
|
355
494
|
const sMsg = newSessionMessages[i];
|
|
356
|
-
const pMsg =
|
|
495
|
+
const pMsg = pending[i];
|
|
357
496
|
const lMsg = newLLMMessages[i];
|
|
358
497
|
|
|
359
498
|
if (sMsg.content.role !== lMsg.role) {
|
|
@@ -379,11 +518,8 @@ export class ChatContextManager
|
|
|
379
518
|
// Update our internal state and return the SessionMessages to write to
|
|
380
519
|
// the DB
|
|
381
520
|
|
|
382
|
-
this.llmContext.commit();
|
|
383
|
-
this.conversationMessages.push(...
|
|
384
|
-
this.startingLLMContextLength = undefined;
|
|
385
|
-
this.pendingMessages = undefined;
|
|
386
|
-
this.curAgentMsgIdx = undefined;
|
|
521
|
+
await this.llmContext.commit(tx.getBaseTx());
|
|
522
|
+
this.conversationMessages.push(...pending);
|
|
387
523
|
|
|
388
524
|
// Kick off a compression?
|
|
389
525
|
this.checkCompression();
|
|
@@ -391,98 +527,12 @@ export class ChatContextManager
|
|
|
391
527
|
return newSessionMessages;
|
|
392
528
|
}
|
|
393
529
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
* This function checks that nothing has been entered into the LLM context,
|
|
399
|
-
* and drops any new user messages or responses before the error.
|
|
400
|
-
*/
|
|
401
|
-
revertAgentResponse(errMsg: string): void {
|
|
402
|
-
logger.warn(`[ChatContextManager.revertAgentResponse] error: ${errMsg}`);
|
|
403
|
-
|
|
404
|
-
assert(typeof this.startingLLMContextLength !== "undefined");
|
|
405
|
-
assert(typeof this.pendingMessages !== "undefined");
|
|
406
|
-
assert(typeof this.curAgentMsgIdx !== "undefined");
|
|
407
|
-
|
|
408
|
-
// Sanity check that no new messages were put into the context (The Agent
|
|
409
|
-
// is expected to only call `addMessage(s)` at the end of the Agent loop.
|
|
410
|
-
// (Note, we don't check for equality here, just in case the context was
|
|
411
|
-
// compressed while the Agent was executing).
|
|
412
|
-
|
|
413
|
-
const contextLength = this.llmContext.getCommittedLength();
|
|
414
|
-
if (contextLength > this.startingLLMContextLength) {
|
|
415
|
-
logger.error(
|
|
416
|
-
"[ChatContextManager.revertAgentResponse] llmContext has grown " +
|
|
417
|
-
`despite Agent error (${String(contextLength)}, ` +
|
|
418
|
-
`${String(this.startingLLMContextLength)})`
|
|
419
|
-
);
|
|
530
|
+
private lastCommittedMessageIdx(): number | undefined {
|
|
531
|
+
const numMsgs = this.conversationMessages.length;
|
|
532
|
+
if (numMsgs > 0) {
|
|
533
|
+
return this.conversationMessages[numMsgs - 1].message_idx;
|
|
420
534
|
}
|
|
421
|
-
|
|
422
|
-
// We simply reset the state, dropping any pending messages.
|
|
423
|
-
|
|
424
|
-
this.startingLLMContextLength = undefined;
|
|
425
|
-
this.pendingMessages = undefined;
|
|
426
|
-
this.curAgentMsgIdx = undefined;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
processAgentMessage(msg: string, end: boolean): ServerAgentMessageChunk {
|
|
430
|
-
assert(typeof this.startingLLMContextLength !== "undefined");
|
|
431
|
-
assert(typeof this.pendingMessages !== "undefined");
|
|
432
|
-
assert(typeof this.curAgentMsgIdx !== "undefined");
|
|
433
|
-
|
|
434
|
-
const message: ServerAgentMessageChunk = {
|
|
435
|
-
type: "agent_msg_chunk",
|
|
436
|
-
session_id: this.sessionUUID,
|
|
437
|
-
message_idx: this.getCurrentAgentMessageIdx(),
|
|
438
|
-
message: msg,
|
|
439
|
-
end,
|
|
440
|
-
};
|
|
441
|
-
return message;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Process a FULL Agent message (not chunks from stream). No message is
|
|
446
|
-
* required for broadcast as the calling code is expected to broadcast this
|
|
447
|
-
* as chunks.
|
|
448
|
-
*/
|
|
449
|
-
processAgentResponse(result: AssistantMessageParam) {
|
|
450
|
-
assert(typeof this.startingLLMContextLength !== "undefined");
|
|
451
|
-
assert(typeof this.pendingMessages !== "undefined");
|
|
452
|
-
assert(typeof this.curAgentMsgIdx !== "undefined");
|
|
453
|
-
|
|
454
|
-
// Insert this (full) agent response into the list of agent messages
|
|
455
|
-
|
|
456
|
-
const msg: ServerAgentMessage = {
|
|
457
|
-
type: "agent_msg",
|
|
458
|
-
session_id: this.sessionUUID,
|
|
459
|
-
message_idx: this.getNextMessageSubIdx(),
|
|
460
|
-
message: result,
|
|
461
|
-
};
|
|
462
|
-
this.pendingMessages.push(msg);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
processToolCallResult(result: ToolMessageParam): ServerToolCallResult {
|
|
466
|
-
assert(typeof this.startingLLMContextLength !== "undefined");
|
|
467
|
-
assert(typeof this.pendingMessages !== "undefined");
|
|
468
|
-
assert(typeof this.curAgentMsgIdx !== "undefined");
|
|
469
|
-
|
|
470
|
-
// Allocate the sub-index for this tool call result. It should not
|
|
471
|
-
// have been used already.
|
|
472
|
-
|
|
473
|
-
const message_idx = this.getNextMessageSubIdx();
|
|
474
|
-
const numPending = this.pendingMessages.length;
|
|
475
|
-
assert(numPending > 0);
|
|
476
|
-
assert(this.pendingMessages[numPending - 1].message_idx < message_idx);
|
|
477
|
-
|
|
478
|
-
const msg: ServerToolCallResult = {
|
|
479
|
-
type: "tool_call_result",
|
|
480
|
-
session_id: this.sessionUUID,
|
|
481
|
-
message_idx,
|
|
482
|
-
result,
|
|
483
|
-
};
|
|
484
|
-
this.pendingMessages.push(msg);
|
|
485
|
-
return msg;
|
|
535
|
+
return undefined;
|
|
486
536
|
}
|
|
487
537
|
|
|
488
538
|
private getNextMessageIdx(): number {
|
|
@@ -503,22 +553,6 @@ export class ChatContextManager
|
|
|
503
553
|
this.nextMessageIdx = messageIdx;
|
|
504
554
|
}
|
|
505
555
|
|
|
506
|
-
/// Get the current index to use for streaming Agent chunks
|
|
507
|
-
private getCurrentAgentMessageIdx(): number {
|
|
508
|
-
assert(typeof this.pendingMessages !== "undefined");
|
|
509
|
-
assert(typeof this.curAgentMsgIdx !== "undefined");
|
|
510
|
-
return this.curAgentMsgIdx;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
private getNextMessageSubIdx(): number {
|
|
514
|
-
assert(typeof this.pendingMessages !== "undefined");
|
|
515
|
-
assert(typeof this.curAgentMsgIdx !== "undefined");
|
|
516
|
-
|
|
517
|
-
const idx = this.curAgentMsgIdx;
|
|
518
|
-
this.curAgentMsgIdx += MESSAGE_INDEX_SUB_INCREMENT;
|
|
519
|
-
return idx;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
556
|
private checkCompression(): void {
|
|
523
557
|
if (this.pendingCompression) {
|
|
524
558
|
return;
|
|
@@ -526,7 +560,7 @@ export class ChatContextManager
|
|
|
526
560
|
|
|
527
561
|
// TODO: track tokens and use that to trigger compression
|
|
528
562
|
|
|
529
|
-
const numCommitted = this.llmContext.
|
|
563
|
+
const numCommitted = this.llmContext.numMessages();
|
|
530
564
|
if (numCommitted < COMPRESSION_TRIGGER_NUM_MESSAGES) {
|
|
531
565
|
return;
|
|
532
566
|
}
|
|
@@ -551,7 +585,7 @@ export class ChatContextManager
|
|
|
551
585
|
await this.checkpointWriter.writeCheckpoint(checkpoint);
|
|
552
586
|
} catch (err: unknown) {
|
|
553
587
|
logger.warn(
|
|
554
|
-
`[runCompression] error during compression: ${
|
|
588
|
+
`[runCompression] error during compression: ${getErrorString(err)}`
|
|
555
589
|
);
|
|
556
590
|
} finally {
|
|
557
591
|
this.pendingCompression = false;
|
|
@@ -25,7 +25,7 @@ export interface IUserConnectionManager<ServerMsgT> {
|
|
|
25
25
|
* Send message to all active connections of specific users.
|
|
26
26
|
* Handles user-to-connection routing internally.
|
|
27
27
|
*/
|
|
28
|
-
sendToUsers(userIds: Set<string
|
|
28
|
+
sendToUsers(userIds: Set<string> | string[], message: ServerMsgT): void;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Send message to a specific connection.
|