@xalia/agent 0.6.10 → 0.6.11
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/package.json +5 -2
- package/.env.development +0 -6
- package/.env.test +0 -7
- package/.prettierrc.json +0 -11
- package/context_system.md +0 -498
- package/eslint.config.mjs +0 -38
- package/scripts/chat_server +0 -8
- package/scripts/git_message +0 -31
- package/scripts/git_wip +0 -21
- package/scripts/pr_message +0 -18
- package/scripts/pr_review +0 -16
- package/scripts/setup_chat +0 -90
- package/scripts/shutdown_chat_server +0 -42
- package/scripts/start_chat_server +0 -24
- package/scripts/sudomcp_import +0 -23
- package/scripts/test_chat +0 -327
- package/src/agent/agent.ts +0 -699
- package/src/agent/agentUtils.ts +0 -286
- package/src/agent/compressingContextManager.ts +0 -129
- package/src/agent/context.ts +0 -265
- package/src/agent/contextWithWorkspace.ts +0 -162
- package/src/agent/documentSummarizer.ts +0 -157
- package/src/agent/dummyLLM.ts +0 -130
- package/src/agent/iAgentEventHandler.ts +0 -64
- package/src/agent/imageGenLLM.ts +0 -101
- package/src/agent/imageGenerator.ts +0 -45
- package/src/agent/iplatform.ts +0 -18
- package/src/agent/llm.ts +0 -74
- package/src/agent/mcpServerManager.ts +0 -541
- package/src/agent/nullAgentEventHandler.ts +0 -26
- package/src/agent/nullPlatform.ts +0 -13
- package/src/agent/openAI.ts +0 -123
- package/src/agent/openAILLM.ts +0 -99
- package/src/agent/openAILLMStreaming.ts +0 -648
- package/src/agent/promptProvider.ts +0 -87
- package/src/agent/repeatLLM.ts +0 -62
- package/src/agent/sudoMcpServerManager.ts +0 -361
- package/src/agent/test_data/harrypotter.txt +0 -6065
- package/src/agent/tokenAuth.ts +0 -50
- package/src/agent/tokenCounter.test.ts +0 -243
- package/src/agent/tokenCounter.ts +0 -483
- package/src/agent/toolSettings.ts +0 -24
- package/src/agent/tools/calculatorTool.ts +0 -50
- package/src/agent/tools/contentExtractors/htmlToText.ts +0 -61
- package/src/agent/tools/contentExtractors/pdfToText.ts +0 -60
- package/src/agent/tools/datetimeTool.ts +0 -41
- package/src/agent/tools/fileManager/fileManagerTool.ts +0 -199
- package/src/agent/tools/fileManager/index.ts +0 -50
- package/src/agent/tools/fileManager/memoryFileManager.ts +0 -120
- package/src/agent/tools/fileManager/mimeTypes.ts +0 -60
- package/src/agent/tools/fileManager/prompt.ts +0 -38
- package/src/agent/tools/fileManager/types.ts +0 -189
- package/src/agent/tools/index.ts +0 -49
- package/src/agent/tools/openUrlTool.ts +0 -62
- package/src/agent/tools/renderTool.ts +0 -92
- package/src/agent/tools/utils.ts +0 -74
- package/src/agent/tools/webSearch.ts +0 -138
- package/src/agent/tools/webSearchTool.ts +0 -44
- package/src/chat/client/chatClient.ts +0 -967
- package/src/chat/client/connection.test.ts +0 -241
- package/src/chat/client/connection.ts +0 -286
- package/src/chat/client/constants.ts +0 -1
- package/src/chat/client/index.ts +0 -21
- package/src/chat/client/interfaces.ts +0 -34
- package/src/chat/client/sessionClient.ts +0 -574
- package/src/chat/client/sessionFiles.ts +0 -142
- package/src/chat/client/teamManager.ts +0 -29
- package/src/chat/constants.ts +0 -6
- package/src/chat/data/apiKeyManager.ts +0 -76
- package/src/chat/data/dataModels.ts +0 -107
- package/src/chat/data/database.ts +0 -997
- package/src/chat/data/dbMcpServerConfigs.ts +0 -59
- package/src/chat/data/dbSessionFiles.ts +0 -107
- package/src/chat/data/dbSessionMessages.ts +0 -102
- package/src/chat/protocol/connectionMessages.ts +0 -49
- package/src/chat/protocol/constants.ts +0 -55
- package/src/chat/protocol/errors.ts +0 -16
- package/src/chat/protocol/messages.ts +0 -899
- package/src/chat/server/README.md +0 -127
- package/src/chat/server/chatContextManager.ts +0 -660
- package/src/chat/server/connectionManager.test.ts +0 -246
- package/src/chat/server/connectionManager.ts +0 -506
- package/src/chat/server/conversation.ts +0 -319
- package/src/chat/server/errorUtils.ts +0 -28
- package/src/chat/server/imageGeneratorTools.ts +0 -179
- package/src/chat/server/openAIRouterLLM.ts +0 -168
- package/src/chat/server/openSession.ts +0 -1945
- package/src/chat/server/openSessionMessageSender.ts +0 -4
- package/src/chat/server/promptRefiner.ts +0 -106
- package/src/chat/server/server.ts +0 -178
- package/src/chat/server/sessionFileManager.ts +0 -151
- package/src/chat/server/sessionRegistry.test.ts +0 -137
- package/src/chat/server/sessionRegistry.ts +0 -1553
- package/src/chat/server/test-utils/mockFactories.ts +0 -422
- package/src/chat/server/titleGenerator.test.ts +0 -103
- package/src/chat/server/titleGenerator.ts +0 -143
- package/src/chat/server/tools.ts +0 -170
- package/src/chat/utils/agentSessionMap.ts +0 -76
- package/src/chat/utils/approvalManager.ts +0 -189
- package/src/chat/utils/asyncLock.ts +0 -43
- package/src/chat/utils/asyncQueue.ts +0 -62
- package/src/chat/utils/multiAsyncQueue.ts +0 -66
- package/src/chat/utils/responseAwaiter.ts +0 -181
- package/src/chat/utils/userResolver.ts +0 -48
- package/src/chat/utils/websocket.ts +0 -16
- package/src/index.ts +0 -0
- package/src/test/agent.test.ts +0 -584
- package/src/test/approvalManager.test.ts +0 -141
- package/src/test/chatContextManager.test.ts +0 -552
- package/src/test/clientServerConnection.test.ts +0 -205
- package/src/test/compressingContextManager.test.ts +0 -77
- package/src/test/context.test.ts +0 -150
- package/src/test/contextTestTools.ts +0 -95
- package/src/test/conversation.test.ts +0 -109
- package/src/test/db.test.ts +0 -363
- package/src/test/dbMcpServerConfigs.test.ts +0 -112
- package/src/test/dbSessionFiles.test.ts +0 -258
- package/src/test/dbSessionMessages.test.ts +0 -85
- package/src/test/dbTestTools.ts +0 -157
- package/src/test/imageLoad.test.ts +0 -15
- package/src/test/mcpServerManager.test.ts +0 -114
- package/src/test/multiAsyncQueue.test.ts +0 -183
- package/src/test/openaiStreaming.test.ts +0 -177
- package/src/test/prompt.test.ts +0 -27
- package/src/test/promptProvider.test.ts +0 -33
- package/src/test/responseAwaiter.test.ts +0 -103
- package/src/test/sudoMcpServerManager.test.ts +0 -63
- package/src/test/testTools.ts +0 -176
- package/src/test/tools.test.ts +0 -64
- package/src/tool/agentChat.ts +0 -203
- package/src/tool/agentMain.ts +0 -180
- package/src/tool/chatMain.ts +0 -621
- package/src/tool/commandPrompt.ts +0 -264
- package/src/tool/files.ts +0 -82
- package/src/tool/main.ts +0 -25
- package/src/tool/nodePlatform.ts +0 -73
- package/src/tool/options.ts +0 -144
- package/src/tool/prompt.ts +0 -101
- package/test_data/background_test_profile.json +0 -6
- package/test_data/background_test_script.json +0 -11
- package/test_data/dummyllm_script_crash.json +0 -32
- package/test_data/dummyllm_script_image_gen.json +0 -19
- package/test_data/dummyllm_script_image_gen_fe.json +0 -29
- package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
- package/test_data/dummyllm_script_render_tool.json +0 -29
- package/test_data/dummyllm_script_simplecalc.json +0 -28
- package/test_data/dummyllm_script_test_auto_approve.json +0 -81
- package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
- package/test_data/frog.png +0 -0
- package/test_data/frog.png.b64 +0 -1
- package/test_data/git_message_profile.json +0 -4
- package/test_data/git_wip_system.txt +0 -5
- package/test_data/image_gen_test_profile.json +0 -5
- package/test_data/pr_message_profile.json +0 -4
- package/test_data/pr_review_profile.json +0 -4
- package/test_data/prompt_simplecalc.txt +0 -1
- package/test_data/simplecalc_profile.json +0 -4
- package/test_data/sudomcp_import_profile.json +0 -4
- package/test_data/test_script_profile.json +0 -8
- package/tsconfig.json +0 -13
- package/vitest.config.ts +0 -39
|
@@ -1,660 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from "assert";
|
|
2
|
-
|
|
3
|
-
import { getLogger } from "@xalia/xmcp/sdk";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
AssistantMessageParam,
|
|
7
|
-
ILLM,
|
|
8
|
-
MessageParam,
|
|
9
|
-
ToolMessageParam,
|
|
10
|
-
UserMessageParam,
|
|
11
|
-
} from "../../agent/llm";
|
|
12
|
-
import { IContextTransaction } from "../../agent/context";
|
|
13
|
-
import {
|
|
14
|
-
CompressingContextManager,
|
|
15
|
-
createCheckpointMessage,
|
|
16
|
-
} from "../../agent/compressingContextManager";
|
|
17
|
-
import { SessionCheckpoint, SessionMessage } from "../data/dataModels";
|
|
18
|
-
import {
|
|
19
|
-
ClientUserMessage,
|
|
20
|
-
ServerAgentMessage,
|
|
21
|
-
ServerAgentMessageChunk,
|
|
22
|
-
ServerToolCallResult,
|
|
23
|
-
ServerUserMessage,
|
|
24
|
-
} from "../protocol/messages";
|
|
25
|
-
import {
|
|
26
|
-
ConversationMessage,
|
|
27
|
-
MESSAGE_INDEX_FULL_INCREMENT,
|
|
28
|
-
MESSAGE_INDEX_SUB_INCREMENT,
|
|
29
|
-
chatMessagesToSessionMessages,
|
|
30
|
-
sessionMessagesToConversationMessages,
|
|
31
|
-
sessionMessagesToNextIndex,
|
|
32
|
-
} from "./conversation";
|
|
33
|
-
import {
|
|
34
|
-
ISessionFileManager,
|
|
35
|
-
ISessionFileManagerEventHandler,
|
|
36
|
-
createSessionFilesManagerPrompt,
|
|
37
|
-
SessionFileDescriptor,
|
|
38
|
-
SessionFileEntry,
|
|
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";
|
|
45
|
-
|
|
46
|
-
const logger = getLogger();
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* TODO: Until token-tracking is in place.
|
|
50
|
-
*/
|
|
51
|
-
const COMPRESSION_TRIGGER_NUM_MESSAGES: number = parseInt(
|
|
52
|
-
process.env["COMPRESSION_TRIGGER_NUM_MESSAGES"] || "80",
|
|
53
|
-
10
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Interface to store checkpoints for a specific session.
|
|
58
|
-
*/
|
|
59
|
-
export interface ICheckpointWriter {
|
|
60
|
-
writeCheckpoint(checkpoint: SessionCheckpoint): Promise<void>;
|
|
61
|
-
}
|
|
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
|
-
|
|
201
|
-
/**
|
|
202
|
-
* A context manager for Agents interacting with the (potentially multi-user)
|
|
203
|
-
* chat conversations.
|
|
204
|
-
*
|
|
205
|
-
* - Recreate an LLM context from message history + checkpoints
|
|
206
|
-
*
|
|
207
|
-
* - Maintain a compressing LLM context, and generate new checkpoints
|
|
208
|
-
*
|
|
209
|
-
* - Maintain pending user messages, agent loop messages and messages to be
|
|
210
|
-
* committed to the DB
|
|
211
|
-
*/
|
|
212
|
-
export class ChatContextManager implements ISessionFileManagerEventHandler {
|
|
213
|
-
// Including any pending user messages
|
|
214
|
-
private readonly sessionUUID: string;
|
|
215
|
-
private readonly conversationMessages: ConversationMessage[];
|
|
216
|
-
private readonly llmContext: CompressingContextManager;
|
|
217
|
-
|
|
218
|
-
private nextMessageIdx: number;
|
|
219
|
-
|
|
220
|
-
// Compression state
|
|
221
|
-
private readonly checkpointWriter: ICheckpointWriter;
|
|
222
|
-
private pendingCompression: boolean;
|
|
223
|
-
|
|
224
|
-
// FileManager
|
|
225
|
-
private readonly fileManager: ISessionFileManager;
|
|
226
|
-
private fileManagerDescriptionsDirty: boolean;
|
|
227
|
-
|
|
228
|
-
// LLM and token counting
|
|
229
|
-
private readonly llm: ILLM;
|
|
230
|
-
private tokenCounter: TokenCounter;
|
|
231
|
-
|
|
232
|
-
constructor(
|
|
233
|
-
systemPrompt: string,
|
|
234
|
-
sessionMessages: SessionMessage[],
|
|
235
|
-
sessionUUID: string,
|
|
236
|
-
defaultUserName: string,
|
|
237
|
-
checkpoint: SessionCheckpoint | undefined = undefined,
|
|
238
|
-
checkpointWriter: ICheckpointWriter,
|
|
239
|
-
fileManager: ISessionFileManager,
|
|
240
|
-
llm: ILLM
|
|
241
|
-
) {
|
|
242
|
-
const nextMessageIdx = sessionMessagesToNextIndex(sessionMessages);
|
|
243
|
-
const { messages: llmMessages } = resolveConversationWithCheckpoint(
|
|
244
|
-
sessionMessages,
|
|
245
|
-
checkpoint
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
logger.debug(
|
|
249
|
-
`[ChatContextManager]: llm messages: ${JSON.stringify(llmMessages)}`
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
const getLLM = () => Promise.resolve(llm);
|
|
253
|
-
|
|
254
|
-
this.sessionUUID = sessionUUID;
|
|
255
|
-
this.conversationMessages = sessionMessagesToConversationMessages(
|
|
256
|
-
sessionMessages,
|
|
257
|
-
defaultUserName,
|
|
258
|
-
sessionUUID
|
|
259
|
-
);
|
|
260
|
-
this.llmContext = new CompressingContextManager(
|
|
261
|
-
systemPrompt,
|
|
262
|
-
llmMessages,
|
|
263
|
-
getLLM
|
|
264
|
-
);
|
|
265
|
-
this.nextMessageIdx = nextMessageIdx;
|
|
266
|
-
this.pendingCompression = false;
|
|
267
|
-
this.checkpointWriter = checkpointWriter;
|
|
268
|
-
this.fileManager = fileManager;
|
|
269
|
-
fileManager.addEventHandler(this);
|
|
270
|
-
this.fileManagerDescriptionsDirty = true;
|
|
271
|
-
this.llm = llm;
|
|
272
|
-
this.tokenCounter = new TokenCounter(llm.getModel());
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// IContextManager.getLLMContext
|
|
276
|
-
getLLMContext(): MessageParam[] {
|
|
277
|
-
if (this.fileManagerDescriptionsDirty) {
|
|
278
|
-
const prompt = createSessionFilesManagerPrompt(this.fileManager);
|
|
279
|
-
this.llmContext.setPromptFragment("file_manager", prompt);
|
|
280
|
-
this.fileManagerDescriptionsDirty = false;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return this.llmContext.getLLMContext();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// IContextManager.getAgentPrompt
|
|
287
|
-
getAgentPrompt(): string {
|
|
288
|
-
return this.llmContext.getAgentPrompt();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// IContextManager.setAgentPrompt
|
|
292
|
-
setAgentPrompt(prompt: string): void {
|
|
293
|
-
this.llmContext.setAgentPrompt(prompt);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// IContextManager.setPromptFragment
|
|
297
|
-
setPromptFragment(fragmentID: string, prompt: string): void {
|
|
298
|
-
this.llmContext.setPromptFragment(fragmentID, prompt);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// IContextManager.removePromptFragment
|
|
302
|
-
removePromptFragment(fragmentID: string): void {
|
|
303
|
-
this.llmContext.removePromptFragment(fragmentID);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// ISessionFileManagerEventHandler.onFileDescriptorChange
|
|
307
|
-
onFileDescriptorChange(_desc: SessionFileDescriptor): void {
|
|
308
|
-
this.fileManagerDescriptionsDirty = true;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// ISessionFileManagerEventHandler.onFileChange
|
|
312
|
-
onFileChanged(_entry: SessionFileEntry): void {
|
|
313
|
-
this.fileManagerDescriptionsDirty = true;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
onFileDeleted(_name: string): void {
|
|
317
|
-
this.fileManagerDescriptionsDirty = true;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
setWorkspace(userMessage: UserMessageParam | undefined): void {
|
|
321
|
-
this.llmContext.setWorkspace(userMessage);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
getWorkspace(): UserMessageParam | undefined {
|
|
325
|
-
return this.llmContext.getWorkspace();
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Get the conversation (to send to clients)
|
|
329
|
-
getConversationMessages(): ConversationMessage[] {
|
|
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 };
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
processUserMessage(
|
|
349
|
-
msg: ClientUserMessage,
|
|
350
|
-
from_uuid: string,
|
|
351
|
-
from_nickname: string
|
|
352
|
-
): ServerUserMessage | undefined {
|
|
353
|
-
// TODO: maintain a queue internally instead of relying on the caller to
|
|
354
|
-
// pass in our generated messages back into `startAgentResponse`.
|
|
355
|
-
|
|
356
|
-
// Filter out null messages immediately.
|
|
357
|
-
if (
|
|
358
|
-
!msg.imageB64 &&
|
|
359
|
-
!msg.message &&
|
|
360
|
-
(!msg.attachedFiles || msg.attachedFiles.length === 0)
|
|
361
|
-
) {
|
|
362
|
-
return undefined;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const message_idx = this.getNextMessageIdx();
|
|
366
|
-
const userMessage: ServerUserMessage = {
|
|
367
|
-
type: "user_msg",
|
|
368
|
-
session_id: this.sessionUUID,
|
|
369
|
-
message_idx,
|
|
370
|
-
message: msg.message,
|
|
371
|
-
user_uuid: from_uuid,
|
|
372
|
-
user_nickname: from_nickname,
|
|
373
|
-
};
|
|
374
|
-
if (msg.imageB64) {
|
|
375
|
-
userMessage.imageB64 = msg.imageB64;
|
|
376
|
-
}
|
|
377
|
-
if (msg.attachedFiles) {
|
|
378
|
-
userMessage.attachedFiles = msg.attachedFiles;
|
|
379
|
-
}
|
|
380
|
-
if (msg.race_mode) {
|
|
381
|
-
userMessage.race_mode = msg.race_mode;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return userMessage;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
unprocessUserMessage(userMsg: ServerUserMessage): void {
|
|
388
|
-
// TODO: when we maintain a queue, remove the entry from the queue.
|
|
389
|
-
this.freeMessageIdx(userMsg.message_idx);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// TODO: Don't take the set of messages. Instead, have OpenSession or this
|
|
393
|
-
// class manage the interaction with the agent and ensure this process only
|
|
394
|
-
// happens one-at-a-time.
|
|
395
|
-
|
|
396
|
-
async startAgentResponse(msgs: ServerUserMessage[]): Promise<{
|
|
397
|
-
contextTx: ChatContextTransaction;
|
|
398
|
-
agentFirstChunk: ServerAgentMessageChunk;
|
|
399
|
-
}> {
|
|
400
|
-
// Sanity check the state - the incoming user messages should match the
|
|
401
|
-
// pending user messages.
|
|
402
|
-
|
|
403
|
-
const numMessages = msgs.length;
|
|
404
|
-
assert(numMessages > 0, "no messages");
|
|
405
|
-
// Collect the pending user messages and allocate a starting index for
|
|
406
|
-
// agent messages and tool calls.
|
|
407
|
-
|
|
408
|
-
const baseMsgIdx = this.lastCommittedMessageIdx();
|
|
409
|
-
const curAgentMsgIdx = this.getNextMessageIdx();
|
|
410
|
-
|
|
411
|
-
// Compute the new llm messages
|
|
412
|
-
|
|
413
|
-
const llmUserMessages: UserMessageParam[] = [];
|
|
414
|
-
for (const msg of msgs) {
|
|
415
|
-
const userMsg = createUserMessage(
|
|
416
|
-
msg.message,
|
|
417
|
-
msg.imageB64,
|
|
418
|
-
msg.user_uuid
|
|
419
|
-
);
|
|
420
|
-
if (userMsg) {
|
|
421
|
-
llmUserMessages.push(userMsg);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
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,
|
|
433
|
-
};
|
|
434
|
-
|
|
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
|
|
449
|
-
);
|
|
450
|
-
return { agentFirstChunk, contextTx };
|
|
451
|
-
}
|
|
452
|
-
|
|
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;
|
|
464
|
-
assert(numPending > 0, "no pending"); // at least 1 user message
|
|
465
|
-
|
|
466
|
-
// Compute DB messages
|
|
467
|
-
|
|
468
|
-
const newSessionMessages = chatMessagesToSessionMessages(pending);
|
|
469
|
-
const newLLMMessages = tx.newMessages();
|
|
470
|
-
|
|
471
|
-
const messageListError = (error: string) => {
|
|
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);
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
if (newSessionMessages.length !== numPending) {
|
|
482
|
-
messageListError("newSessionMessages.length !== numPending");
|
|
483
|
-
}
|
|
484
|
-
if (newLLMMessages.length !== numPending) {
|
|
485
|
-
messageListError("newLLMMessages.length !== numPending");
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// The SessionMessages should satisfy:
|
|
489
|
-
// - sMsg.message_idx === pMsg.message_idx
|
|
490
|
-
// - sMsg.content === llmMsg
|
|
491
|
-
// this ensures all representations are aligned.
|
|
492
|
-
|
|
493
|
-
for (let i = 0; i < numPending; ++i) {
|
|
494
|
-
const sMsg = newSessionMessages[i];
|
|
495
|
-
const pMsg = pending[i];
|
|
496
|
-
const lMsg = newLLMMessages[i];
|
|
497
|
-
|
|
498
|
-
if (sMsg.content.role !== lMsg.role) {
|
|
499
|
-
messageListError(
|
|
500
|
-
`newSessionMessages[${String(i)}].role !== ` +
|
|
501
|
-
`newLLMMessages[${String(i)}].role`
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
if (JSON.stringify(sMsg.content) !== JSON.stringify(lMsg)) {
|
|
505
|
-
messageListError(
|
|
506
|
-
`newSessionMessages[${String(i)}].content !== ` +
|
|
507
|
-
`newLLMMessages[${String(i)}]`
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
if (sMsg.message_idx !== pMsg.message_idx) {
|
|
511
|
-
messageListError(
|
|
512
|
-
`newSessionMessages[${String(i)}].message_idx !== ` +
|
|
513
|
-
`pendingMessages[${String(i)}].message_idx`
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Update our internal state and return the SessionMessages to write to
|
|
519
|
-
// the DB
|
|
520
|
-
|
|
521
|
-
await this.llmContext.commit(tx.getBaseTx());
|
|
522
|
-
this.conversationMessages.push(...pending);
|
|
523
|
-
|
|
524
|
-
// Kick off a compression?
|
|
525
|
-
this.checkCompression();
|
|
526
|
-
|
|
527
|
-
return newSessionMessages;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
private lastCommittedMessageIdx(): number | undefined {
|
|
531
|
-
const numMsgs = this.conversationMessages.length;
|
|
532
|
-
if (numMsgs > 0) {
|
|
533
|
-
return this.conversationMessages[numMsgs - 1].message_idx;
|
|
534
|
-
}
|
|
535
|
-
return undefined;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
private getNextMessageIdx(): number {
|
|
539
|
-
const idx = this.nextMessageIdx;
|
|
540
|
-
this.nextMessageIdx += MESSAGE_INDEX_FULL_INCREMENT;
|
|
541
|
-
return idx;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Only reuse it if no other indices have been allocated. It's the callers
|
|
546
|
-
* responsibility to ensure this.
|
|
547
|
-
*/
|
|
548
|
-
private freeMessageIdx(messageIdx: number) {
|
|
549
|
-
assert(
|
|
550
|
-
messageIdx === this.nextMessageIdx - MESSAGE_INDEX_FULL_INCREMENT,
|
|
551
|
-
"message idx cannot be free-ed"
|
|
552
|
-
);
|
|
553
|
-
this.nextMessageIdx = messageIdx;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
private checkCompression(): void {
|
|
557
|
-
if (this.pendingCompression) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// TODO: track tokens and use that to trigger compression
|
|
562
|
-
|
|
563
|
-
const numCommitted = this.llmContext.numMessages();
|
|
564
|
-
if (numCommitted < COMPRESSION_TRIGGER_NUM_MESSAGES) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
void this.runCompression();
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
private async runCompression() {
|
|
572
|
-
assert(!this.pendingCompression);
|
|
573
|
-
|
|
574
|
-
this.pendingCompression = true;
|
|
575
|
-
const checkpointIndex =
|
|
576
|
-
this.conversationMessages[this.conversationMessages.length - 1]
|
|
577
|
-
.message_idx;
|
|
578
|
-
|
|
579
|
-
try {
|
|
580
|
-
const summary = await this.llmContext.compress();
|
|
581
|
-
const checkpoint: SessionCheckpoint = {
|
|
582
|
-
message_idx: checkpointIndex,
|
|
583
|
-
summary,
|
|
584
|
-
};
|
|
585
|
-
await this.checkpointWriter.writeCheckpoint(checkpoint);
|
|
586
|
-
} catch (err: unknown) {
|
|
587
|
-
logger.warn(
|
|
588
|
-
`[runCompression] error during compression: ${getErrorString(err)}`
|
|
589
|
-
);
|
|
590
|
-
} finally {
|
|
591
|
-
this.pendingCompression = false;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
export function resolveConversationWithCheckpoint(
|
|
597
|
-
messages: SessionMessage[],
|
|
598
|
-
checkpoint: SessionCheckpoint | undefined
|
|
599
|
-
): {
|
|
600
|
-
messages: MessageParam[];
|
|
601
|
-
lastEntryIdx: number;
|
|
602
|
-
} {
|
|
603
|
-
const numMessages = messages.length;
|
|
604
|
-
let lastEntryIdx = 0;
|
|
605
|
-
if (numMessages !== 0) {
|
|
606
|
-
lastEntryIdx = messages[numMessages - 1].message_idx;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Only keep the messages we care about
|
|
610
|
-
|
|
611
|
-
messages = messages.filter((msg) => msg.is_for_llm);
|
|
612
|
-
|
|
613
|
-
// If no checkpoint, return all messages
|
|
614
|
-
|
|
615
|
-
if (!checkpoint) {
|
|
616
|
-
return {
|
|
617
|
-
messages: messages.map((msg) => msg.content),
|
|
618
|
-
lastEntryIdx,
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Find the first entry in messages s.t. entry.message_idx >
|
|
623
|
-
// checkpoint.message_idx
|
|
624
|
-
const checkpointIdx = checkpoint.message_idx;
|
|
625
|
-
const keepIdx = (() => {
|
|
626
|
-
let idx = numMessages - 1;
|
|
627
|
-
while (idx >= 0) {
|
|
628
|
-
if (messages[idx].message_idx <= checkpointIdx) {
|
|
629
|
-
break;
|
|
630
|
-
}
|
|
631
|
-
idx--;
|
|
632
|
-
}
|
|
633
|
-
// Idx points to the first entry (from the end) with message_idx <=
|
|
634
|
-
// checkpointIdx, so the first entry with message_idx > checkpointIdx is
|
|
635
|
-
// idx+1 and We can then extract the required messages with
|
|
636
|
-
//
|
|
637
|
-
// message.slice(idx+1)
|
|
638
|
-
//
|
|
639
|
-
// Edge-cases:
|
|
640
|
-
//
|
|
641
|
-
// - loop exhausted, idx = -1, we want slice(0 = idx+1) (all messages)
|
|
642
|
-
//
|
|
643
|
-
// - loop exited on first entry (all entries are contained in the
|
|
644
|
-
// checkpoint), idx = numMessages - 1, we want
|
|
645
|
-
// slice(numMessages = idx + 1)
|
|
646
|
-
|
|
647
|
-
return idx + 1;
|
|
648
|
-
})();
|
|
649
|
-
|
|
650
|
-
const checkpointMessage = createCheckpointMessage(checkpoint.summary);
|
|
651
|
-
const llmMessages: MessageParam[] = [
|
|
652
|
-
checkpointMessage,
|
|
653
|
-
...messages.slice(keepIdx).map((msg) => msg.content),
|
|
654
|
-
];
|
|
655
|
-
|
|
656
|
-
return {
|
|
657
|
-
messages: llmMessages,
|
|
658
|
-
lastEntryIdx: Math.max(lastEntryIdx, checkpoint.message_idx),
|
|
659
|
-
};
|
|
660
|
-
}
|