@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.
- package/.env.development +1 -0
- package/dist/agent/src/agent/agent.js +100 -77
- package/dist/agent/src/agent/agentUtils.js +21 -16
- package/dist/agent/src/agent/compressingContextManager.js +10 -14
- package/dist/agent/src/agent/context.js +101 -127
- package/dist/agent/src/agent/contextWithWorkspace.js +133 -0
- package/dist/agent/src/agent/imageGenLLM.js +0 -6
- package/dist/agent/src/agent/imageGenerator.js +2 -10
- package/dist/agent/src/agent/openAILLMStreaming.js +5 -2
- package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
- package/dist/agent/src/chat/client/chatClient.js +35 -2
- package/dist/agent/src/chat/client/connection.js +6 -1
- package/dist/agent/src/chat/client/sessionClient.js +0 -7
- package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
- package/dist/agent/src/chat/protocol/messages.js +4 -0
- package/dist/agent/src/chat/server/chatContextManager.js +149 -139
- package/dist/agent/src/chat/server/imageGeneratorTools.js +19 -8
- package/dist/agent/src/chat/server/openAIRouterLLM.js +114 -0
- package/dist/agent/src/chat/server/openSession.js +57 -58
- package/dist/agent/src/chat/server/server.js +6 -2
- package/dist/agent/src/chat/server/sessionRegistry.js +65 -6
- package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
- package/dist/agent/src/chat/server/tools.js +52 -17
- package/dist/agent/src/test/chatContextManager.test.js +31 -29
- package/dist/agent/src/test/clientServerConnection.test.js +1 -2
- package/dist/agent/src/test/compressingContextManager.test.js +22 -36
- package/dist/agent/src/test/context.test.js +55 -17
- package/dist/agent/src/test/contextTestTools.js +87 -0
- package/dist/agent/src/tool/chatMain.js +22 -8
- package/package.json +1 -1
- package/scripts/test_chat +3 -0
- package/src/agent/agent.ts +170 -125
- package/src/agent/agentUtils.ts +31 -20
- package/src/agent/compressingContextManager.ts +13 -44
- package/src/agent/context.ts +165 -159
- package/src/agent/contextWithWorkspace.ts +162 -0
- package/src/agent/imageGenLLM.ts +0 -8
- package/src/agent/imageGenerator.ts +3 -18
- package/src/agent/openAILLMStreaming.ts +20 -3
- package/src/agent/sudoMcpServerManager.ts +41 -20
- package/src/chat/client/chatClient.ts +47 -3
- package/src/chat/client/connection.ts +11 -1
- package/src/chat/client/sessionClient.ts +0 -8
- package/src/chat/data/dataModels.ts +6 -0
- package/src/chat/data/dbSessionMessages.ts +34 -0
- package/src/chat/protocol/messages.ts +35 -8
- package/src/chat/server/chatContextManager.ts +210 -197
- package/src/chat/server/connectionManager.ts +1 -1
- package/src/chat/server/imageGeneratorTools.ts +31 -18
- package/src/chat/server/openAIRouterLLM.ts +171 -0
- package/src/chat/server/openSession.ts +87 -100
- package/src/chat/server/server.ts +6 -2
- package/src/chat/server/sessionFileManager.ts +5 -5
- package/src/chat/server/sessionRegistry.test.ts +0 -1
- package/src/chat/server/sessionRegistry.ts +100 -4
- package/src/chat/server/tools.ts +73 -35
- package/src/test/agent.test.ts +8 -7
- package/src/test/chatContextManager.test.ts +42 -37
- package/src/test/clientServerConnection.test.ts +0 -2
- package/src/test/compressingContextManager.test.ts +29 -34
- package/src/test/context.test.ts +59 -15
- package/src/test/contextTestTools.ts +95 -0
- package/src/tool/chatMain.ts +26 -12
- package/test_data/dummyllm_script_image_gen.json +13 -23
- 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 {
|
|
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
|
|
78
|
-
private llmContext: CompressingContextManager;
|
|
79
|
-
private nextMessageIdx: number;
|
|
214
|
+
private readonly sessionUUID: string;
|
|
215
|
+
private readonly conversationMessages: ConversationMessage[];
|
|
216
|
+
private readonly llmContext: CompressingContextManager;
|
|
80
217
|
|
|
81
|
-
|
|
82
|
-
private
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(...
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
* This function checks that nothing has been entered into the LLM context,
|
|
399
|
-
* and drops any new user messages or responses before the error.
|
|
400
|
-
*/
|
|
401
|
-
revertAgentResponse(errMsg: string): void {
|
|
402
|
-
logger.warn(`[ChatContextManager.revertAgentResponse] error: ${errMsg}`);
|
|
403
|
-
|
|
404
|
-
assert(typeof this.startingLLMContextLength !== "undefined");
|
|
405
|
-
assert(typeof this.pendingMessages !== "undefined");
|
|
406
|
-
assert(typeof this.curAgentMsgIdx !== "undefined");
|
|
407
|
-
|
|
408
|
-
// Sanity check that no new messages were put into the context (The Agent
|
|
409
|
-
// is expected to only call `addMessage(s)` at the end of the Agent loop.
|
|
410
|
-
// (Note, we don't check for equality here, just in case the context was
|
|
411
|
-
// compressed while the Agent was executing).
|
|
412
|
-
|
|
413
|
-
const contextLength = this.llmContext.getCommittedLength();
|
|
414
|
-
if (contextLength > this.startingLLMContextLength) {
|
|
415
|
-
logger.error(
|
|
416
|
-
"[ChatContextManager.revertAgentResponse] llmContext has grown " +
|
|
417
|
-
`despite Agent error (${String(contextLength)}, ` +
|
|
418
|
-
`${String(this.startingLLMContextLength)})`
|
|
419
|
-
);
|
|
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.
|
|
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: ${
|
|
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
|
|
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 {
|
|
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
|
-
|
|
18
|
-
llmApiKey: string
|
|
19
|
+
async function createImageGenerator(
|
|
20
|
+
platform: IPlatform
|
|
19
21
|
): Promise<ImageGenerator> {
|
|
20
|
-
const imageGenModel =
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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(
|
|
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 (_:
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
_:
|
|
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:
|
|
156
|
+
setup: async (agent: AgentEx) => {
|
|
144
157
|
agent.addAgentTool(GEN_IMAGE_FILE_DESC, toolFn);
|
|
145
158
|
},
|
|
146
159
|
};
|