@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.
Files changed (161) hide show
  1. package/package.json +5 -2
  2. package/.env.development +0 -6
  3. package/.env.test +0 -7
  4. package/.prettierrc.json +0 -11
  5. package/context_system.md +0 -498
  6. package/eslint.config.mjs +0 -38
  7. package/scripts/chat_server +0 -8
  8. package/scripts/git_message +0 -31
  9. package/scripts/git_wip +0 -21
  10. package/scripts/pr_message +0 -18
  11. package/scripts/pr_review +0 -16
  12. package/scripts/setup_chat +0 -90
  13. package/scripts/shutdown_chat_server +0 -42
  14. package/scripts/start_chat_server +0 -24
  15. package/scripts/sudomcp_import +0 -23
  16. package/scripts/test_chat +0 -327
  17. package/src/agent/agent.ts +0 -699
  18. package/src/agent/agentUtils.ts +0 -286
  19. package/src/agent/compressingContextManager.ts +0 -129
  20. package/src/agent/context.ts +0 -265
  21. package/src/agent/contextWithWorkspace.ts +0 -162
  22. package/src/agent/documentSummarizer.ts +0 -157
  23. package/src/agent/dummyLLM.ts +0 -130
  24. package/src/agent/iAgentEventHandler.ts +0 -64
  25. package/src/agent/imageGenLLM.ts +0 -101
  26. package/src/agent/imageGenerator.ts +0 -45
  27. package/src/agent/iplatform.ts +0 -18
  28. package/src/agent/llm.ts +0 -74
  29. package/src/agent/mcpServerManager.ts +0 -541
  30. package/src/agent/nullAgentEventHandler.ts +0 -26
  31. package/src/agent/nullPlatform.ts +0 -13
  32. package/src/agent/openAI.ts +0 -123
  33. package/src/agent/openAILLM.ts +0 -99
  34. package/src/agent/openAILLMStreaming.ts +0 -648
  35. package/src/agent/promptProvider.ts +0 -87
  36. package/src/agent/repeatLLM.ts +0 -62
  37. package/src/agent/sudoMcpServerManager.ts +0 -361
  38. package/src/agent/test_data/harrypotter.txt +0 -6065
  39. package/src/agent/tokenAuth.ts +0 -50
  40. package/src/agent/tokenCounter.test.ts +0 -243
  41. package/src/agent/tokenCounter.ts +0 -483
  42. package/src/agent/toolSettings.ts +0 -24
  43. package/src/agent/tools/calculatorTool.ts +0 -50
  44. package/src/agent/tools/contentExtractors/htmlToText.ts +0 -61
  45. package/src/agent/tools/contentExtractors/pdfToText.ts +0 -60
  46. package/src/agent/tools/datetimeTool.ts +0 -41
  47. package/src/agent/tools/fileManager/fileManagerTool.ts +0 -199
  48. package/src/agent/tools/fileManager/index.ts +0 -50
  49. package/src/agent/tools/fileManager/memoryFileManager.ts +0 -120
  50. package/src/agent/tools/fileManager/mimeTypes.ts +0 -60
  51. package/src/agent/tools/fileManager/prompt.ts +0 -38
  52. package/src/agent/tools/fileManager/types.ts +0 -189
  53. package/src/agent/tools/index.ts +0 -49
  54. package/src/agent/tools/openUrlTool.ts +0 -62
  55. package/src/agent/tools/renderTool.ts +0 -92
  56. package/src/agent/tools/utils.ts +0 -74
  57. package/src/agent/tools/webSearch.ts +0 -138
  58. package/src/agent/tools/webSearchTool.ts +0 -44
  59. package/src/chat/client/chatClient.ts +0 -967
  60. package/src/chat/client/connection.test.ts +0 -241
  61. package/src/chat/client/connection.ts +0 -286
  62. package/src/chat/client/constants.ts +0 -1
  63. package/src/chat/client/index.ts +0 -21
  64. package/src/chat/client/interfaces.ts +0 -34
  65. package/src/chat/client/sessionClient.ts +0 -574
  66. package/src/chat/client/sessionFiles.ts +0 -142
  67. package/src/chat/client/teamManager.ts +0 -29
  68. package/src/chat/constants.ts +0 -6
  69. package/src/chat/data/apiKeyManager.ts +0 -76
  70. package/src/chat/data/dataModels.ts +0 -107
  71. package/src/chat/data/database.ts +0 -997
  72. package/src/chat/data/dbMcpServerConfigs.ts +0 -59
  73. package/src/chat/data/dbSessionFiles.ts +0 -107
  74. package/src/chat/data/dbSessionMessages.ts +0 -102
  75. package/src/chat/protocol/connectionMessages.ts +0 -49
  76. package/src/chat/protocol/constants.ts +0 -55
  77. package/src/chat/protocol/errors.ts +0 -16
  78. package/src/chat/protocol/messages.ts +0 -899
  79. package/src/chat/server/README.md +0 -127
  80. package/src/chat/server/chatContextManager.ts +0 -660
  81. package/src/chat/server/connectionManager.test.ts +0 -246
  82. package/src/chat/server/connectionManager.ts +0 -506
  83. package/src/chat/server/conversation.ts +0 -319
  84. package/src/chat/server/errorUtils.ts +0 -28
  85. package/src/chat/server/imageGeneratorTools.ts +0 -179
  86. package/src/chat/server/openAIRouterLLM.ts +0 -168
  87. package/src/chat/server/openSession.ts +0 -1945
  88. package/src/chat/server/openSessionMessageSender.ts +0 -4
  89. package/src/chat/server/promptRefiner.ts +0 -106
  90. package/src/chat/server/server.ts +0 -178
  91. package/src/chat/server/sessionFileManager.ts +0 -151
  92. package/src/chat/server/sessionRegistry.test.ts +0 -137
  93. package/src/chat/server/sessionRegistry.ts +0 -1553
  94. package/src/chat/server/test-utils/mockFactories.ts +0 -422
  95. package/src/chat/server/titleGenerator.test.ts +0 -103
  96. package/src/chat/server/titleGenerator.ts +0 -143
  97. package/src/chat/server/tools.ts +0 -170
  98. package/src/chat/utils/agentSessionMap.ts +0 -76
  99. package/src/chat/utils/approvalManager.ts +0 -189
  100. package/src/chat/utils/asyncLock.ts +0 -43
  101. package/src/chat/utils/asyncQueue.ts +0 -62
  102. package/src/chat/utils/multiAsyncQueue.ts +0 -66
  103. package/src/chat/utils/responseAwaiter.ts +0 -181
  104. package/src/chat/utils/userResolver.ts +0 -48
  105. package/src/chat/utils/websocket.ts +0 -16
  106. package/src/index.ts +0 -0
  107. package/src/test/agent.test.ts +0 -584
  108. package/src/test/approvalManager.test.ts +0 -141
  109. package/src/test/chatContextManager.test.ts +0 -552
  110. package/src/test/clientServerConnection.test.ts +0 -205
  111. package/src/test/compressingContextManager.test.ts +0 -77
  112. package/src/test/context.test.ts +0 -150
  113. package/src/test/contextTestTools.ts +0 -95
  114. package/src/test/conversation.test.ts +0 -109
  115. package/src/test/db.test.ts +0 -363
  116. package/src/test/dbMcpServerConfigs.test.ts +0 -112
  117. package/src/test/dbSessionFiles.test.ts +0 -258
  118. package/src/test/dbSessionMessages.test.ts +0 -85
  119. package/src/test/dbTestTools.ts +0 -157
  120. package/src/test/imageLoad.test.ts +0 -15
  121. package/src/test/mcpServerManager.test.ts +0 -114
  122. package/src/test/multiAsyncQueue.test.ts +0 -183
  123. package/src/test/openaiStreaming.test.ts +0 -177
  124. package/src/test/prompt.test.ts +0 -27
  125. package/src/test/promptProvider.test.ts +0 -33
  126. package/src/test/responseAwaiter.test.ts +0 -103
  127. package/src/test/sudoMcpServerManager.test.ts +0 -63
  128. package/src/test/testTools.ts +0 -176
  129. package/src/test/tools.test.ts +0 -64
  130. package/src/tool/agentChat.ts +0 -203
  131. package/src/tool/agentMain.ts +0 -180
  132. package/src/tool/chatMain.ts +0 -621
  133. package/src/tool/commandPrompt.ts +0 -264
  134. package/src/tool/files.ts +0 -82
  135. package/src/tool/main.ts +0 -25
  136. package/src/tool/nodePlatform.ts +0 -73
  137. package/src/tool/options.ts +0 -144
  138. package/src/tool/prompt.ts +0 -101
  139. package/test_data/background_test_profile.json +0 -6
  140. package/test_data/background_test_script.json +0 -11
  141. package/test_data/dummyllm_script_crash.json +0 -32
  142. package/test_data/dummyllm_script_image_gen.json +0 -19
  143. package/test_data/dummyllm_script_image_gen_fe.json +0 -29
  144. package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
  145. package/test_data/dummyllm_script_render_tool.json +0 -29
  146. package/test_data/dummyllm_script_simplecalc.json +0 -28
  147. package/test_data/dummyllm_script_test_auto_approve.json +0 -81
  148. package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
  149. package/test_data/frog.png +0 -0
  150. package/test_data/frog.png.b64 +0 -1
  151. package/test_data/git_message_profile.json +0 -4
  152. package/test_data/git_wip_system.txt +0 -5
  153. package/test_data/image_gen_test_profile.json +0 -5
  154. package/test_data/pr_message_profile.json +0 -4
  155. package/test_data/pr_review_profile.json +0 -4
  156. package/test_data/prompt_simplecalc.txt +0 -1
  157. package/test_data/simplecalc_profile.json +0 -4
  158. package/test_data/sudomcp_import_profile.json +0 -4
  159. package/test_data/test_script_profile.json +0 -8
  160. package/tsconfig.json +0 -13
  161. 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
- }