@xalia/agent 0.6.7 → 0.6.9

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 (65) hide show
  1. package/.env.development +1 -0
  2. package/dist/agent/src/agent/agent.js +100 -77
  3. package/dist/agent/src/agent/agentUtils.js +21 -16
  4. package/dist/agent/src/agent/compressingContextManager.js +10 -14
  5. package/dist/agent/src/agent/context.js +101 -127
  6. package/dist/agent/src/agent/contextWithWorkspace.js +133 -0
  7. package/dist/agent/src/agent/imageGenLLM.js +0 -6
  8. package/dist/agent/src/agent/imageGenerator.js +2 -10
  9. package/dist/agent/src/agent/openAILLMStreaming.js +5 -2
  10. package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
  11. package/dist/agent/src/chat/client/chatClient.js +35 -2
  12. package/dist/agent/src/chat/client/connection.js +6 -1
  13. package/dist/agent/src/chat/client/sessionClient.js +0 -7
  14. package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
  15. package/dist/agent/src/chat/protocol/messages.js +4 -0
  16. package/dist/agent/src/chat/server/chatContextManager.js +149 -139
  17. package/dist/agent/src/chat/server/imageGeneratorTools.js +19 -8
  18. package/dist/agent/src/chat/server/openAIRouterLLM.js +114 -0
  19. package/dist/agent/src/chat/server/openSession.js +57 -58
  20. package/dist/agent/src/chat/server/server.js +6 -2
  21. package/dist/agent/src/chat/server/sessionRegistry.js +65 -6
  22. package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
  23. package/dist/agent/src/chat/server/tools.js +52 -17
  24. package/dist/agent/src/test/chatContextManager.test.js +31 -29
  25. package/dist/agent/src/test/clientServerConnection.test.js +1 -2
  26. package/dist/agent/src/test/compressingContextManager.test.js +22 -36
  27. package/dist/agent/src/test/context.test.js +55 -17
  28. package/dist/agent/src/test/contextTestTools.js +87 -0
  29. package/dist/agent/src/tool/chatMain.js +22 -8
  30. package/package.json +1 -1
  31. package/scripts/test_chat +3 -0
  32. package/src/agent/agent.ts +170 -125
  33. package/src/agent/agentUtils.ts +31 -20
  34. package/src/agent/compressingContextManager.ts +13 -44
  35. package/src/agent/context.ts +165 -159
  36. package/src/agent/contextWithWorkspace.ts +162 -0
  37. package/src/agent/imageGenLLM.ts +0 -8
  38. package/src/agent/imageGenerator.ts +3 -18
  39. package/src/agent/openAILLMStreaming.ts +20 -3
  40. package/src/agent/sudoMcpServerManager.ts +41 -20
  41. package/src/chat/client/chatClient.ts +47 -3
  42. package/src/chat/client/connection.ts +11 -1
  43. package/src/chat/client/sessionClient.ts +0 -8
  44. package/src/chat/data/dataModels.ts +6 -0
  45. package/src/chat/data/dbSessionMessages.ts +34 -0
  46. package/src/chat/protocol/messages.ts +35 -8
  47. package/src/chat/server/chatContextManager.ts +210 -197
  48. package/src/chat/server/connectionManager.ts +1 -1
  49. package/src/chat/server/imageGeneratorTools.ts +31 -18
  50. package/src/chat/server/openAIRouterLLM.ts +171 -0
  51. package/src/chat/server/openSession.ts +87 -100
  52. package/src/chat/server/server.ts +6 -2
  53. package/src/chat/server/sessionFileManager.ts +5 -5
  54. package/src/chat/server/sessionRegistry.test.ts +0 -1
  55. package/src/chat/server/sessionRegistry.ts +100 -4
  56. package/src/chat/server/tools.ts +73 -35
  57. package/src/test/agent.test.ts +8 -7
  58. package/src/test/chatContextManager.test.ts +42 -37
  59. package/src/test/clientServerConnection.test.ts +0 -2
  60. package/src/test/compressingContextManager.test.ts +29 -34
  61. package/src/test/context.test.ts +59 -15
  62. package/src/test/contextTestTools.ts +95 -0
  63. package/src/tool/chatMain.ts +26 -12
  64. package/test_data/dummyllm_script_image_gen.json +13 -23
  65. package/test_data/dummyllm_script_image_gen_fe.json +29 -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 { IContextManager } from "../../agent/context";
12
+ import { IContextTransaction } from "../../agent/context";
13
13
  import {
14
14
  CompressingContextManager,
15
15
  createCheckpointMessage,
@@ -39,6 +39,10 @@ import {
39
39
  SessionFileDescriptor,
40
40
  SessionFileEntry,
41
41
  } from "../data/dbSessionFileModels";
42
+ // eslint-disable-next-line max-len
43
+ import { ContextTransactionWithWorkspace } from "../../agent/contextWithWorkspace";
44
+ import { getErrorString } from "./errorUtils";
45
+ import { createUserMessage } from "../../agent/agent";
42
46
 
43
47
  const logger = getLogger();
44
48
 
@@ -57,6 +61,143 @@ export interface ICheckpointWriter {
57
61
  writeCheckpoint(checkpoint: SessionCheckpoint): Promise<void>;
58
62
  }
59
63
 
64
+ export class ChatContextTransaction implements IContextTransaction {
65
+ private readonly baseTx: ContextTransactionWithWorkspace;
66
+ private readonly sessionUUID: string;
67
+ /// Index of final message in the committed context
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,27 +209,21 @@ 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 pendingUserMessages: ServerUserMessage[];
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
- // State while processing agent messages
82
- private startingLLMContextLength: number | undefined;
83
- private curAgentMsgIdx: number | undefined; // active agent message
84
- private pendingMessages: ConversationMessage[] | undefined;
218
+ private nextMessageIdx: number;
219
+ private pendingUserMessages: ServerUserMessage[];
85
220
 
86
221
  // Compression state
87
- private checkpointWriter: ICheckpointWriter;
222
+ private readonly checkpointWriter: ICheckpointWriter;
88
223
  private pendingCompression: boolean;
89
224
 
90
225
  // FileManager
91
- private fileManager: ISessionFileManager;
226
+ private readonly fileManager: ISessionFileManager;
92
227
  private fileManagerDescriptionsDirty: boolean;
93
228
 
94
229
  constructor(
@@ -97,11 +232,9 @@ export class ChatContextManager
97
232
  sessionUUID: string,
98
233
  defaultUserName: string,
99
234
  checkpoint: SessionCheckpoint | undefined = undefined,
100
- compressionAgentUrl: string,
101
- compressionAgentModel: string,
102
- compressionAgentApiKey: string,
103
235
  checkpointWriter: ICheckpointWriter,
104
- fileManager: ISessionFileManager
236
+ fileManager: ISessionFileManager,
237
+ llm: ILLM
105
238
  ) {
106
239
  const nextMessageIdx = sessionMessagesToNextIndex(sessionMessages);
107
240
  const { messages: llmMessages } = resolveConversationWithCheckpoint(
@@ -113,7 +246,7 @@ export class ChatContextManager
113
246
  `[ChatContextManager]: llm messages: ${JSON.stringify(llmMessages)}`
114
247
  );
115
248
 
116
- // Insert a system message placeholder into the context
249
+ const getLLM = () => Promise.resolve(llm);
117
250
 
118
251
  this.sessionUUID = sessionUUID;
119
252
  this.conversationMessages = sessionMessagesToConversationMessages(
@@ -125,14 +258,9 @@ export class ChatContextManager
125
258
  this.llmContext = new CompressingContextManager(
126
259
  systemPrompt,
127
260
  llmMessages,
128
- compressionAgentUrl,
129
- compressionAgentModel,
130
- compressionAgentApiKey
261
+ getLLM
131
262
  );
132
263
  this.nextMessageIdx = nextMessageIdx;
133
- this.startingLLMContextLength = undefined;
134
- this.curAgentMsgIdx = undefined;
135
- this.pendingMessages = undefined;
136
264
  this.pendingCompression = false;
137
265
  this.checkpointWriter = checkpointWriter;
138
266
  this.fileManager = fileManager;
@@ -140,16 +268,6 @@ export class ChatContextManager
140
268
  this.fileManagerDescriptionsDirty = true;
141
269
  }
142
270
 
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);
151
- }
152
-
153
271
  // IContextManager.getLLMContext
154
272
  getLLMContext(): MessageParam[] {
155
273
  if (this.fileManagerDescriptionsDirty) {
@@ -166,6 +284,7 @@ export class ChatContextManager
166
284
  getAgentPrompt(): string {
167
285
  return this.llmContext.getAgentPrompt();
168
286
  }
287
+
169
288
  // IContextManager.setAgentPrompt
170
289
  setAgentPrompt(prompt: string): void {
171
290
  this.llmContext.setAgentPrompt(prompt);
@@ -247,24 +366,21 @@ export class ChatContextManager
247
366
  // class manage the interaction with the agent and ensure this process only
248
367
  // happens one-at-a-time.
249
368
 
250
- startAgentResponse(msgs: ServerUserMessage[]): {
251
- llmUserMessages: UserMessageParam[];
369
+ async startAgentResponse(msgs: ServerUserMessage[]): Promise<{
370
+ contextTx: ChatContextTransaction;
252
371
  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
372
+ }> {
373
+ // Sanity check the state - the incoming user messages should match the
374
+ // pending user messages.
264
375
 
265
376
  const numMessages = this.pendingUserMessages.length;
266
377
  assert(numMessages > 0);
267
- assert(msgs.length === this.pendingUserMessages.length);
378
+ if (msgs.length !== this.pendingUserMessages.length) {
379
+ throw new Error(
380
+ `length mismatch: msgs: ${JSON.stringify(msgs)}, ` +
381
+ `this.pendingUserMsgs: ${JSON.stringify(this.pendingUserMessages)}`
382
+ );
383
+ }
268
384
  assert(msgs[0].message_idx === this.pendingUserMessages[0].message_idx);
269
385
  assert(
270
386
  msgs[numMessages - 1].message_idx ===
@@ -275,10 +391,9 @@ export class ChatContextManager
275
391
  // agent messages and tool calls.
276
392
 
277
393
  const pendingUserMessages = this.pendingUserMessages;
394
+ const baseMsgIdx = this.lastMessageIdx();
395
+ const curAgentMsgIdx = this.getNextMessageIdx();
278
396
  this.pendingUserMessages = [];
279
- this.startingLLMContextLength = this.llmContext.getCommittedLength();
280
- this.curAgentMsgIdx = this.getNextMessageIdx();
281
- this.pendingMessages = pendingUserMessages as ConversationMessage[];
282
397
 
283
398
  // Compute the new llm messages
284
399
 
@@ -294,47 +409,50 @@ export class ChatContextManager
294
409
  }
295
410
  }
296
411
 
297
- return {
298
- llmUserMessages,
299
- agentFirstChunk: {
300
- type: "agent_msg_chunk",
301
- session_id: this.sessionUUID,
302
- message_idx: this.curAgentMsgIdx,
303
- message: "",
304
- end: false,
305
- },
412
+ // Return the context tx and first ServerAgentMessageChunk
413
+
414
+ const agentFirstChunk: ServerAgentMessageChunk = {
415
+ type: "agent_msg_chunk",
416
+ session_id: this.sessionUUID,
417
+ message_idx: curAgentMsgIdx,
418
+ message: "",
419
+ end: false,
306
420
  };
307
- }
308
421
 
309
- endAgentResponse(): SessionMessage[] {
310
- assert(
311
- typeof this.startingLLMContextLength !== "undefined",
312
- "agent response not started (startingLLMContextLength)"
313
- );
314
- assert(
315
- typeof this.pendingMessages !== "undefined",
316
- "agent response not started (pendingMessages)"
317
- );
318
- assert(
319
- typeof this.curAgentMsgIdx !== "undefined",
320
- "agent response not started (curAgentMsgIdx)"
422
+ const baseTx = await this.llmContext.startTx(llmUserMessages);
423
+ const contextTx = new ChatContextTransaction(
424
+ baseTx,
425
+ this.sessionUUID,
426
+ baseMsgIdx,
427
+ pendingUserMessages,
428
+ curAgentMsgIdx
321
429
  );
430
+ return { agentFirstChunk, contextTx };
431
+ }
322
432
 
323
- const numPending = this.pendingMessages.length;
433
+ async endAgentResponse(tx: IContextTransaction): Promise<SessionMessage[]> {
434
+ assert(tx instanceof ChatContextTransaction);
435
+ if (tx.baseMessageIdx() !== this.lastMessageIdx()) {
436
+ throw new Error(
437
+ `Tx stale? tx.baseMessageIdx=${String(tx.baseMessageIdx())}, ` +
438
+ `this.conv: ${JSON.stringify(this.conversationMessages)}`
439
+ );
440
+ }
441
+
442
+ const pending = tx.getPending();
443
+ const numPending = pending.length;
324
444
  assert(numPending > 0, "no pending"); // at least 1 user message
325
445
 
326
446
  // Compute DB messages
327
447
 
328
- const newSessionMessages = chatMessagesToSessionMessages(
329
- this.pendingMessages
330
- );
331
- const newLLMMessages = this.llmContext.getPending();
448
+ const newSessionMessages = chatMessagesToSessionMessages(pending);
449
+ const newLLMMessages = tx.newMessages();
332
450
 
333
451
  const messageListError = (error: string) => {
334
452
  throw new Error(
335
453
  `${error}:` +
336
454
  `\n newSessionMessages: ${JSON.stringify(newSessionMessages)}` +
337
- `\n this.pendingMessages: ${JSON.stringify(this.pendingMessages)}` +
455
+ `\n pending: ${JSON.stringify(pending)}` +
338
456
  `\n newLLMMessages: ${JSON.stringify(newLLMMessages)}`
339
457
  );
340
458
  };
@@ -353,7 +471,7 @@ export class ChatContextManager
353
471
 
354
472
  for (let i = 0; i < numPending; ++i) {
355
473
  const sMsg = newSessionMessages[i];
356
- const pMsg = this.pendingMessages[i];
474
+ const pMsg = pending[i];
357
475
  const lMsg = newLLMMessages[i];
358
476
 
359
477
  if (sMsg.content.role !== lMsg.role) {
@@ -379,11 +497,8 @@ export class ChatContextManager
379
497
  // Update our internal state and return the SessionMessages to write to
380
498
  // the DB
381
499
 
382
- this.llmContext.commit();
383
- this.conversationMessages.push(...this.pendingMessages);
384
- this.startingLLMContextLength = undefined;
385
- this.pendingMessages = undefined;
386
- this.curAgentMsgIdx = undefined;
500
+ await this.llmContext.commit(tx.getBaseTx());
501
+ this.conversationMessages.push(...pending);
387
502
 
388
503
  // Kick off a compression?
389
504
  this.checkCompression();
@@ -391,98 +506,12 @@ export class ChatContextManager
391
506
  return newSessionMessages;
392
507
  }
393
508
 
394
- /**
395
- * End the Agent message session with an error. Caller should not call
396
- * `endAgentResponse` after calling this function.
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
- );
509
+ private lastMessageIdx(): number | undefined {
510
+ const numMsgs = this.conversationMessages.length;
511
+ if (numMsgs > 0) {
512
+ return this.conversationMessages[numMsgs - 1].message_idx;
420
513
  }
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;
514
+ return undefined;
486
515
  }
487
516
 
488
517
  private getNextMessageIdx(): number {
@@ -503,22 +532,6 @@ export class ChatContextManager
503
532
  this.nextMessageIdx = messageIdx;
504
533
  }
505
534
 
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
535
  private checkCompression(): void {
523
536
  if (this.pendingCompression) {
524
537
  return;
@@ -526,7 +539,7 @@ export class ChatContextManager
526
539
 
527
540
  // TODO: track tokens and use that to trigger compression
528
541
 
529
- const numCommitted = this.llmContext.getCommittedLength();
542
+ const numCommitted = this.llmContext.numMessages();
530
543
  if (numCommitted < COMPRESSION_TRIGGER_NUM_MESSAGES) {
531
544
  return;
532
545
  }
@@ -551,7 +564,7 @@ export class ChatContextManager
551
564
  await this.checkpointWriter.writeCheckpoint(checkpoint);
552
565
  } catch (err: unknown) {
553
566
  logger.warn(
554
- `[runCompression] error during compression: ${JSON.stringify(err)}`
567
+ `[runCompression] error during compression: ${getErrorString(err)}`
555
568
  );
556
569
  } finally {
557
570
  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>, message: ServerMsgT): void;
28
+ sendToUsers(userIds: Set<string> | string[], message: ServerMsgT): void;
29
29
 
30
30
  /**
31
31
  * Send message to a specific connection.
@@ -1,25 +1,40 @@
1
1
  import { getLogger } from "@xalia/xmcp/sdk";
2
2
 
3
- import { Agent, IAgentToolProvider, ToolCallResult } from "../../agent/agent";
3
+ import { AgentEx, IAgentToolProvider, ToolCallResult } from "../../agent/agent";
4
4
  import { ToolDescriptor } from "../../agent/llm";
5
5
  import { ImageGenerator } from "../../agent/imageGenerator";
6
-
7
6
  import {
8
7
  ChatSessionFileManager,
9
8
  ToolCallResultWithFileRef,
10
9
  } from "./sessionFileManager";
11
10
  import { makeParseArgsFn } from "./tools";
12
11
  import { getSessionFileMimeTypeFromDataUrl } from "../data/dbSessionFileModels";
12
+ import { getOpenAIClientParams } from "./openAIRouterLLM";
13
+ import { DEFAULT_IMAGE_GEN_MODEL, ImageGenLLM } from "../../agent/imageGenLLM";
14
+ import { createSpecializedLLM } from "../../agent/agentUtils";
15
+ import { IPlatform } from "../../agent/iplatform";
13
16
 
14
17
  const logger = getLogger();
15
18
 
16
- function createImageGenerator(
17
- llmUrl: string,
18
- llmApiKey: string
19
+ async function createImageGenerator(
20
+ platform: IPlatform
19
21
  ): Promise<ImageGenerator> {
20
- const imageGenModel = process.env["GEN_IMAGE_MODEL"];
21
- logger.debug(`[genImageFileTool] model: ${imageGenModel || "(default)"}`);
22
- return ImageGenerator.init(llmUrl, llmApiKey, imageGenModel);
22
+ const imageGenModel =
23
+ process.env["GEN_IMAGE_MODEL"] || DEFAULT_IMAGE_GEN_MODEL;
24
+
25
+ // Allow the image generator to use a "specialized" or "debug" LLM if
26
+ // requested. Otherwise query the router maps for the url and api key and
27
+ // instantiate an ImageGenLLM.
28
+
29
+ let llm = await createSpecializedLLM(imageGenModel, platform);
30
+ if (!llm) {
31
+ const { apiKey, baseURL } = getOpenAIClientParams(imageGenModel);
32
+ logger.debug(
33
+ `[genImageFileTool] model: ${imageGenModel}, url: ${baseURL}"}`
34
+ );
35
+ llm = new ImageGenLLM(apiKey, baseURL, imageGenModel);
36
+ }
37
+ return ImageGenerator.init(llm);
23
38
  }
24
39
 
25
40
  // gen_image
@@ -28,8 +43,7 @@ function createImageGenerator(
28
43
  // decide how/whether to use the image.
29
44
 
30
45
  export async function genImageTool(
31
- llmUrl: string,
32
- llmApiKey: string
46
+ platform: IPlatform
33
47
  ): Promise<IAgentToolProvider> {
34
48
  const GEN_IMAGE_DESC: ToolDescriptor = {
35
49
  type: "function",
@@ -52,12 +66,12 @@ export async function genImageTool(
52
66
  },
53
67
  },
54
68
  } as const;
55
- const imageGenerator = await createImageGenerator(llmUrl, llmApiKey);
69
+ const imageGenerator = await createImageGenerator(platform);
56
70
  const getPromptInputImg = makeParseArgsFn(
57
71
  ["prompt"] as const,
58
72
  ["input_img"] as const
59
73
  );
60
- const toolFn = async (_: Agent, args: unknown): Promise<ToolCallResult> => {
74
+ const toolFn = async (_: AgentEx, args: unknown): Promise<ToolCallResult> => {
61
75
  const { prompt, input_img } = getPromptInputImg(args);
62
76
  const image = await imageGenerator.generate(prompt, input_img);
63
77
  return {
@@ -71,7 +85,7 @@ export async function genImageTool(
71
85
 
72
86
  return {
73
87
  // eslint-disable-next-line @typescript-eslint/require-await
74
- setup: async (agent: Agent) => {
88
+ setup: async (agent: AgentEx) => {
75
89
  agent.addAgentTool(GEN_IMAGE_DESC, toolFn);
76
90
  },
77
91
  };
@@ -80,8 +94,7 @@ export async function genImageTool(
80
94
  // gen_image_file
81
95
 
82
96
  export async function genImageFileTool(
83
- llmUrl: string,
84
- llmApiKey: string,
97
+ platform: IPlatform,
85
98
  fileManager: ChatSessionFileManager
86
99
  ): Promise<IAgentToolProvider> {
87
100
  const GEN_IMAGE_FILE_DESC: ToolDescriptor = {
@@ -114,13 +127,13 @@ export async function genImageFileTool(
114
127
  },
115
128
  } as const;
116
129
 
117
- const imageGenerator = await createImageGenerator(llmUrl, llmApiKey);
130
+ const imageGenerator = await createImageGenerator(platform);
118
131
  const getPromptNameSummary = makeParseArgsFn(
119
132
  ["prompt", "name", "summary"] as const,
120
133
  ["input_name"] as const
121
134
  );
122
135
  const toolFn = async (
123
- _: Agent,
136
+ _: AgentEx,
124
137
  args: unknown
125
138
  ): Promise<ToolCallResultWithFileRef> => {
126
139
  const { prompt, name, summary, input_name } = getPromptNameSummary(args);
@@ -140,7 +153,7 @@ export async function genImageFileTool(
140
153
 
141
154
  return {
142
155
  // eslint-disable-next-line @typescript-eslint/require-await
143
- setup: async (agent: Agent) => {
156
+ setup: async (agent: AgentEx) => {
144
157
  agent.addAgentTool(GEN_IMAGE_FILE_DESC, toolFn);
145
158
  },
146
159
  };