@xalia/agent 0.6.9 → 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/README.md +11 -0
- package/dist/agent/src/agent/agent.js +77 -18
- package/dist/agent/src/agent/agentUtils.js +3 -2
- package/dist/agent/src/agent/documentSummarizer.js +126 -0
- package/dist/agent/src/agent/dummyLLM.js +25 -22
- package/dist/agent/src/agent/imageGenLLM.js +22 -19
- package/dist/agent/src/agent/llm.js +1 -1
- package/dist/agent/src/agent/openAILLM.js +15 -12
- package/dist/agent/src/agent/openAILLMStreaming.js +68 -37
- package/dist/agent/src/agent/repeatLLM.js +16 -7
- package/dist/agent/src/agent/tokenCounter.js +390 -0
- package/dist/agent/src/agent/tokenCounter.test.js +206 -0
- package/dist/agent/src/agent/toolSettings.js +17 -0
- package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
- package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
- package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
- package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
- package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
- package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
- package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
- package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
- package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
- package/dist/agent/src/agent/tools/index.js +64 -0
- package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
- package/dist/agent/src/agent/tools/renderTool.js +89 -0
- package/dist/agent/src/agent/tools/utils.js +61 -0
- package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
- package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
- package/dist/agent/src/chat/client/chatClient.js +28 -0
- package/dist/agent/src/chat/client/index.js +4 -1
- package/dist/agent/src/chat/client/sessionClient.js +28 -2
- package/dist/agent/src/chat/constants.js +8 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
- package/dist/agent/src/chat/protocol/messages.js +5 -0
- package/dist/agent/src/chat/server/chatContextManager.js +45 -25
- package/dist/agent/src/chat/server/conversation.js +3 -0
- package/dist/agent/src/chat/server/imageGeneratorTools.js +20 -8
- package/dist/agent/src/chat/server/openAIRouterLLM.js +0 -3
- package/dist/agent/src/chat/server/openSession.js +218 -55
- package/dist/agent/src/chat/server/promptRefiner.js +86 -0
- package/dist/agent/src/chat/server/server.js +5 -1
- package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
- package/dist/agent/src/chat/server/sessionRegistry.js +87 -0
- package/dist/agent/src/chat/server/titleGenerator.js +112 -0
- package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
- package/dist/agent/src/chat/server/tools.js +63 -287
- package/dist/agent/src/chat/utils/approvalManager.js +6 -3
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
- package/dist/agent/src/test/agent.test.js +16 -17
- package/dist/agent/src/test/chatContextManager.test.js +15 -3
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
- package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
- package/dist/agent/src/test/testTools.js +6 -1
- package/dist/agent/src/test/tools.test.js +27 -9
- package/dist/agent/src/tool/agentChat.js +5 -2
- package/dist/agent/src/tool/chatMain.js +34 -7
- package/dist/agent/src/tool/commandPrompt.js +2 -2
- package/dist/agent/src/tool/files.js +7 -8
- package/package.json +8 -2
- package/.env.development +0 -1
- package/.prettierrc.json +0 -11
- package/dist/agent/src/agent/tools.js +0 -44
- 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 -308
- package/src/agent/agent.ts +0 -624
- package/src/agent/agentUtils.ts +0 -285
- package/src/agent/compressingContextManager.ts +0 -129
- package/src/agent/context.ts +0 -265
- package/src/agent/contextWithWorkspace.ts +0 -162
- package/src/agent/dummyLLM.ts +0 -126
- package/src/agent/iAgentEventHandler.ts +0 -64
- package/src/agent/imageGenLLM.ts +0 -97
- 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 -95
- package/src/agent/openAILLMStreaming.ts +0 -609
- package/src/agent/promptProvider.ts +0 -87
- package/src/agent/repeatLLM.ts +0 -50
- package/src/agent/sudoMcpServerManager.ts +0 -361
- package/src/agent/tokenAuth.ts +0 -50
- package/src/agent/tools.ts +0 -57
- package/src/chat/client/chatClient.ts +0 -922
- 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 -18
- package/src/chat/client/interfaces.ts +0 -34
- package/src/chat/client/sessionClient.ts +0 -537
- package/src/chat/client/sessionFiles.ts +0 -142
- package/src/chat/client/teamManager.ts +0 -29
- package/src/chat/data/apiKeyManager.ts +0 -76
- package/src/chat/data/dataModels.ts +0 -101
- package/src/chat/data/database.ts +0 -997
- package/src/chat/data/dbMcpServerConfigs.ts +0 -59
- package/src/chat/data/dbSessionFileModels.ts +0 -113
- package/src/chat/data/dbSessionFiles.ts +0 -99
- package/src/chat/data/dbSessionMessages.ts +0 -102
- package/src/chat/data/mimeTypes.ts +0 -58
- 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 -846
- package/src/chat/server/README.md +0 -127
- package/src/chat/server/chatContextManager.ts +0 -639
- package/src/chat/server/connectionManager.test.ts +0 -246
- package/src/chat/server/connectionManager.ts +0 -506
- package/src/chat/server/conversation.ts +0 -316
- package/src/chat/server/errorUtils.ts +0 -28
- package/src/chat/server/imageGeneratorTools.ts +0 -160
- package/src/chat/server/openAIRouterLLM.ts +0 -171
- package/src/chat/server/openSession.ts +0 -1689
- package/src/chat/server/openSessionMessageSender.ts +0 -4
- package/src/chat/server/server.ts +0 -175
- package/src/chat/server/sessionFileManager.ts +0 -422
- package/src/chat/server/sessionRegistry.test.ts +0 -137
- package/src/chat/server/sessionRegistry.ts +0 -1425
- package/src/chat/server/test-utils/mockFactories.ts +0 -422
- package/src/chat/server/tools.ts +0 -397
- package/src/chat/utils/agentSessionMap.ts +0 -76
- package/src/chat/utils/approvalManager.ts +0 -183
- package/src/chat/utils/asyncLock.ts +0 -43
- package/src/chat/utils/asyncQueue.ts +0 -62
- package/src/chat/utils/htmlToText.ts +0 -61
- package/src/chat/utils/multiAsyncQueue.ts +0 -62
- package/src/chat/utils/responseAwaiter.ts +0 -181
- package/src/chat/utils/search.ts +0 -139
- 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 -590
- package/src/test/approvalManager.test.ts +0 -141
- package/src/test/chatContextManager.test.ts +0 -527
- 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 -171
- package/src/test/tools.test.ts +0 -39
- package/src/tool/agentChat.ts +0 -194
- package/src/tool/agentMain.ts +0 -180
- package/src/tool/chatMain.ts +0 -594
- package/src/tool/commandPrompt.ts +0 -264
- package/src/tool/files.ts +0 -84
- 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
- /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
package/README.md
CHANGED
|
@@ -25,6 +25,17 @@ Example of running agent can be found in [test script](../mcppro/scripts/test_sc
|
|
|
25
25
|
|
|
26
26
|
## Architecture
|
|
27
27
|
|
|
28
|
+
For a detailed explanation of the context system architecture, including how context is formed, compressed, and managed during agent execution, see:
|
|
29
|
+
|
|
30
|
+
**📚 [Context System Architecture Documentation](../context_system.md)**
|
|
31
|
+
|
|
32
|
+
This document covers:
|
|
33
|
+
- Context window structure and composition
|
|
34
|
+
- System prompt fragment injection
|
|
35
|
+
- Automatic context compression
|
|
36
|
+
- Session file handling
|
|
37
|
+
- Transaction-based message flow
|
|
38
|
+
|
|
28
39
|
### Core Components
|
|
29
40
|
|
|
30
41
|
#### Agent (`src/agent/`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Agent = exports.AgentEx = exports.DEFAULT_LLM_URL = exports.AgentProfile = void 0;
|
|
3
|
+
exports.Agent = exports.AgentEx = exports.USER_STOP_MESSAGE = exports.DEFAULT_LLM_URL = exports.AgentProfile = void 0;
|
|
4
4
|
exports.createUserMessage = createUserMessage;
|
|
5
5
|
exports.createUserMessageEnsure = createUserMessageEnsure;
|
|
6
6
|
exports.completionToAssistantMessageParam = completionToAssistantMessageParam;
|
|
@@ -9,9 +9,20 @@ var sdk_1 = require("@xalia/xmcp/sdk");
|
|
|
9
9
|
Object.defineProperty(exports, "AgentProfile", { enumerable: true, get: function () { return sdk_1.AgentProfile; } });
|
|
10
10
|
const sdk_2 = require("@xalia/xmcp/sdk");
|
|
11
11
|
const mcpServerManager_1 = require("./mcpServerManager");
|
|
12
|
+
const toolSettings_1 = require("./toolSettings");
|
|
12
13
|
exports.DEFAULT_LLM_URL = "http://localhost:5001/v1";
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The message to append to the agent output if the agent is interrupted by a
|
|
16
|
+
* signal from the user.
|
|
17
|
+
*/
|
|
18
|
+
exports.USER_STOP_MESSAGE = " AGENT INTERRUPTED";
|
|
14
19
|
const logger = (0, sdk_2.getLogger)();
|
|
20
|
+
/**
|
|
21
|
+
* An agent attached to an ILLM which updates a context via an
|
|
22
|
+
* IContextTransaction interface (where IContextTransaction is like a DB tx or
|
|
23
|
+
* DB writer, for staging changes and reading back state as-if those changes
|
|
24
|
+
* were applied).
|
|
25
|
+
*/
|
|
15
26
|
class AgentEx {
|
|
16
27
|
constructor(mcpServerManager, llm) {
|
|
17
28
|
/// The full list of tools, ready to pass to the LLM
|
|
@@ -21,15 +32,25 @@ class AgentEx {
|
|
|
21
32
|
this.agentTools = new Map();
|
|
22
33
|
this.mcpServerManager = mcpServerManager;
|
|
23
34
|
this.llm = llm;
|
|
35
|
+
this.stopFlag = false;
|
|
36
|
+
this.stopFn = undefined;
|
|
24
37
|
}
|
|
25
38
|
async shutdown() {
|
|
39
|
+
this.stop("shutting down");
|
|
26
40
|
return this.mcpServerManager.shutdown();
|
|
27
41
|
}
|
|
42
|
+
stop(msg) {
|
|
43
|
+
this.stopFlag = true;
|
|
44
|
+
if (this.stopFn) {
|
|
45
|
+
this.stopFn(msg || exports.USER_STOP_MESSAGE);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
28
48
|
getMcpServerManager() {
|
|
29
49
|
return this.mcpServerManager;
|
|
30
50
|
}
|
|
31
51
|
// TODO: rename
|
|
32
52
|
async userMessagesRaw(contextTx, eventHandler) {
|
|
53
|
+
this.stopFlag = false;
|
|
33
54
|
// New user messages have already been added to the `contextTx`.
|
|
34
55
|
// Image and audio handling
|
|
35
56
|
//
|
|
@@ -47,6 +68,11 @@ class AgentEx {
|
|
|
47
68
|
contextTx.addMessage(message);
|
|
48
69
|
// While there are tool calls to make, invoke them and loop
|
|
49
70
|
while (message.tool_calls && message.tool_calls.length > 0) {
|
|
71
|
+
// Signal the event handler of the assistant message with tool calls
|
|
72
|
+
// BEFORE processing tool results. This ensures the order of messages
|
|
73
|
+
// in pendingMessages matches the order in the LLM context:
|
|
74
|
+
// [user, assistant(tool_calls), tool_result, assistant(final)]
|
|
75
|
+
eventHandler.onCompletion(message);
|
|
50
76
|
// TODO: Execute all tool calls in parallel
|
|
51
77
|
// [indexInContext, ToolCallResult][]
|
|
52
78
|
const toolCallResults = [];
|
|
@@ -66,6 +92,11 @@ class AgentEx {
|
|
|
66
92
|
};
|
|
67
93
|
const toolResultHandle = contextTx.addMessage(toolResult);
|
|
68
94
|
toolCallResults.push([toolResultHandle, result]);
|
|
95
|
+
// Immediately broadcast the tool result to the frontend for UI
|
|
96
|
+
// feedback. This ensures the frontend knows the tool executed
|
|
97
|
+
// successfully without waiting for the next LLM completion to
|
|
98
|
+
// finish streaming
|
|
99
|
+
eventHandler.onToolCallResult(toolResult);
|
|
69
100
|
// If the tool call requested that its args be redacted, this can be
|
|
70
101
|
// done now - before the next LLM invocation.
|
|
71
102
|
if (result.overwriteArgs) {
|
|
@@ -74,9 +105,6 @@ class AgentEx {
|
|
|
74
105
|
logger.debug(`agent message after update ${JSON.stringify(message)}`);
|
|
75
106
|
}
|
|
76
107
|
}
|
|
77
|
-
// Now that any args have been overwritten, signal the event handler of
|
|
78
|
-
// the prevoius completion.
|
|
79
|
-
eventHandler.onCompletion(message);
|
|
80
108
|
// Get a new completion using the untouched tool call results. Note
|
|
81
109
|
// that, since we are deferring the `onToolCallResult` calls (so they
|
|
82
110
|
// can be redacted), we must take care that the errors in
|
|
@@ -89,17 +117,15 @@ class AgentEx {
|
|
|
89
117
|
}
|
|
90
118
|
finally {
|
|
91
119
|
// Now that the tool call results have been passed to the LLM, perform
|
|
92
|
-
// any updates on them.
|
|
93
|
-
//
|
|
94
|
-
// error occured, so that the caller has an up-to-date picture of the
|
|
95
|
-
// context state when the error occured.
|
|
120
|
+
// any updates on them if overwriteResponse was requested. If so, send
|
|
121
|
+
// the updated tool result to the frontend to replace the original.
|
|
96
122
|
toolCallResults.forEach(([handle, tcr]) => {
|
|
97
|
-
const ctxMsg = contextTx.getMessage(handle);
|
|
98
123
|
if (tcr.overwriteResponse) {
|
|
124
|
+
const ctxMsg = contextTx.getMessage(handle);
|
|
99
125
|
ctxMsg.content = tcr.overwriteResponse;
|
|
126
|
+
(0, assert_1.strict)(ctxMsg.role === "tool");
|
|
127
|
+
eventHandler.onToolCallResult(ctxMsg);
|
|
100
128
|
}
|
|
101
|
-
(0, assert_1.strict)(ctxMsg.role === "tool");
|
|
102
|
-
eventHandler.onToolCallResult(ctxMsg);
|
|
103
129
|
});
|
|
104
130
|
// Note, if an error DID occur, the ContextManager does not see any of
|
|
105
131
|
// the new context.
|
|
@@ -110,6 +136,26 @@ class AgentEx {
|
|
|
110
136
|
return { message, images: images.length === 0 ? undefined : images };
|
|
111
137
|
}
|
|
112
138
|
async chatCompletion(context, eventHandler) {
|
|
139
|
+
if (this.stopFlag) {
|
|
140
|
+
return {
|
|
141
|
+
id: "user_stopped",
|
|
142
|
+
choices: [
|
|
143
|
+
{
|
|
144
|
+
finish_reason: "stop",
|
|
145
|
+
index: 0,
|
|
146
|
+
message: {
|
|
147
|
+
content: exports.USER_STOP_MESSAGE,
|
|
148
|
+
role: "assistant",
|
|
149
|
+
refusal: null,
|
|
150
|
+
},
|
|
151
|
+
logprobs: null,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
created: Date.now(),
|
|
155
|
+
model: this.llm.getModel(),
|
|
156
|
+
object: "chat.completion",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
113
159
|
// Compute the full list of available tools
|
|
114
160
|
let tools;
|
|
115
161
|
const mcpTools = this.mcpServerManager.getOpenAITools();
|
|
@@ -119,7 +165,15 @@ class AgentEx {
|
|
|
119
165
|
tools = enabledTools;
|
|
120
166
|
}
|
|
121
167
|
logger.debug(`[chatCompletion] tools: ${JSON.stringify(tools)}`);
|
|
122
|
-
|
|
168
|
+
// Log system prompt length
|
|
169
|
+
if (context.length > 0 && context[0].role === "system") {
|
|
170
|
+
const systemPrompt = context[0].content;
|
|
171
|
+
logger.info(`[chatCompletion] System prompt length: ${String(systemPrompt.length)}`);
|
|
172
|
+
}
|
|
173
|
+
const { stop, completion: completionP } = await this.llm.getConversationResponse(context, tools, eventHandler.onAgentMessage.bind(eventHandler), eventHandler.onReasoning.bind(eventHandler));
|
|
174
|
+
this.stopFn = stop;
|
|
175
|
+
const completion = await completionP;
|
|
176
|
+
this.stopFn = undefined;
|
|
123
177
|
logger.debug(`Received chat completion ${JSON.stringify(completion)}`);
|
|
124
178
|
return completion;
|
|
125
179
|
}
|
|
@@ -161,6 +215,9 @@ class AgentEx {
|
|
|
161
215
|
* ChatCompletionToolMessageParam to be used in the conversation.
|
|
162
216
|
*/
|
|
163
217
|
async doToolCall(toolCall, eventHandler) {
|
|
218
|
+
if (this.stopFlag) {
|
|
219
|
+
return { response: exports.USER_STOP_MESSAGE };
|
|
220
|
+
}
|
|
164
221
|
// If the tool is and "agent" (internal) tool, we can just execute it.
|
|
165
222
|
// Otherwise, call the event handler to get permission and invoke the
|
|
166
223
|
// external tool handler.
|
|
@@ -210,11 +267,11 @@ class AgentEx {
|
|
|
210
267
|
};
|
|
211
268
|
}
|
|
212
269
|
// Final sanity check on the tool call response length.
|
|
213
|
-
if (result.response.length > MAX_TOOL_CALL_RESPONSE_LENGTH) {
|
|
270
|
+
if (result.response.length > toolSettings_1.MAX_TOOL_CALL_RESPONSE_LENGTH) {
|
|
214
271
|
logger.warn("[Agent.doToolCall]: truncating tool call result.response for call:\n" +
|
|
215
272
|
JSON.stringify(toolCall));
|
|
216
273
|
result.response =
|
|
217
|
-
result.response.slice(0, MAX_TOOL_CALL_RESPONSE_LENGTH) +
|
|
274
|
+
result.response.slice(0, toolSettings_1.MAX_TOOL_CALL_RESPONSE_LENGTH) +
|
|
218
275
|
" ..truncated";
|
|
219
276
|
}
|
|
220
277
|
return result;
|
|
@@ -233,8 +290,10 @@ class AgentEx {
|
|
|
233
290
|
}
|
|
234
291
|
exports.AgentEx = AgentEx;
|
|
235
292
|
/**
|
|
236
|
-
* Higher-level abstraction over AgentEx, which abstracts out the
|
|
237
|
-
*
|
|
293
|
+
* Higher-level abstraction over AgentEx, which abstracts out the context
|
|
294
|
+
* transactions. A single agent is associated with an IContextManager and
|
|
295
|
+
* internally creates and commits transactions during each call to
|
|
296
|
+
* `userMessage*`.
|
|
238
297
|
*/
|
|
239
298
|
class Agent {
|
|
240
299
|
constructor(eventHandler, mcpServerManager, llm, contextManager) {
|
|
@@ -346,7 +405,7 @@ function createUserMessage(msg, imageB64, name) {
|
|
|
346
405
|
}
|
|
347
406
|
function createUserMessageEnsure(msg, imageB64, name) {
|
|
348
407
|
const userMsg = createUserMessage(msg, imageB64, name);
|
|
349
|
-
(0, assert_1.strict)(userMsg);
|
|
408
|
+
(0, assert_1.strict)(userMsg, "createUserMessageEnsure");
|
|
350
409
|
return userMsg;
|
|
351
410
|
}
|
|
352
411
|
function completionToAssistantMessageParam(compMessage) {
|
|
@@ -55,8 +55,9 @@ async function createSpecializedLLM(model, platform) {
|
|
|
55
55
|
if (model && model.startsWith("dummy:")) {
|
|
56
56
|
llm = await dummyLLM_1.DummyLLM.initFromModelUrl(model, platform);
|
|
57
57
|
}
|
|
58
|
-
else if (model
|
|
59
|
-
|
|
58
|
+
else if (model && model.startsWith("repeat")) {
|
|
59
|
+
const prefix = model.startsWith("repeat:") ? model.slice(7) : "";
|
|
60
|
+
llm = new repeatLLM_1.RepeatLLM(prefix);
|
|
60
61
|
}
|
|
61
62
|
return llm;
|
|
62
63
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LLMDocumentSummarizer = void 0;
|
|
4
|
+
exports.createDocumentSummarizer = createDocumentSummarizer;
|
|
5
|
+
exports.summarizeDocument = summarizeDocument;
|
|
6
|
+
const openAIRouterLLM_1 = require("../chat/server/openAIRouterLLM");
|
|
7
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
8
|
+
const logger = (0, sdk_1.getLogger)();
|
|
9
|
+
const SUMMARY_MODEL = "google/gemini-2.5-flash";
|
|
10
|
+
const SUMMARY_MAX_TOKENS = 500;
|
|
11
|
+
const SUMMARY_TEMPERATURE = 0.3;
|
|
12
|
+
const SUMMARY_TIMEOUT_MS = 30000;
|
|
13
|
+
const MAX_CONTENT_LENGTH = 100000;
|
|
14
|
+
/**
|
|
15
|
+
* System prompt for document summarization, optimized for recall.
|
|
16
|
+
*/
|
|
17
|
+
const SUMMARY_SYSTEM_PROMPT = `You are a document summarizer optimizing for RECALL. Create a summary ` +
|
|
18
|
+
`(3-10 sentences) that captures:
|
|
19
|
+
- Main topic and purpose of the document
|
|
20
|
+
- Key entities (names, organizations, places, dates, numbers)
|
|
21
|
+
- Important concepts, terms, and topics mentioned
|
|
22
|
+
- Any conclusions, results, or key findings
|
|
23
|
+
|
|
24
|
+
Include specific details that would help locate this document later.
|
|
25
|
+
Use keywords and phrases from the original text.
|
|
26
|
+
Do NOT include meta-commentary about the document format.
|
|
27
|
+
Output ONLY the summary text.`;
|
|
28
|
+
class LLMDocumentSummarizer {
|
|
29
|
+
constructor(model = SUMMARY_MODEL) {
|
|
30
|
+
this.model = model;
|
|
31
|
+
}
|
|
32
|
+
async summarize(content) {
|
|
33
|
+
if (!content || content.trim().length === 0) {
|
|
34
|
+
return "Empty document";
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const summary = await this.summarizeWithTimeout(content);
|
|
38
|
+
return this.sanitizeSummary(summary);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
42
|
+
logger.warn(`[DocumentSummarizer] LLM summarization failed: ${errorMsg}, ` +
|
|
43
|
+
`using fallback`);
|
|
44
|
+
return this.fallbackSummary(content);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async summarizeWithTimeout(content) {
|
|
48
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
reject(new Error("Summary generation timeout"));
|
|
51
|
+
}, SUMMARY_TIMEOUT_MS);
|
|
52
|
+
});
|
|
53
|
+
const summaryPromise = this.callLLM(content);
|
|
54
|
+
return Promise.race([summaryPromise, timeoutPromise]);
|
|
55
|
+
}
|
|
56
|
+
async callLLM(content) {
|
|
57
|
+
const client = (0, openAIRouterLLM_1.getOpenAIClient)(this.model);
|
|
58
|
+
const truncatedContent = content.length > MAX_CONTENT_LENGTH
|
|
59
|
+
? content.slice(0, MAX_CONTENT_LENGTH) + "\n\n[Content truncated...]"
|
|
60
|
+
: content;
|
|
61
|
+
const response = await client.chat.completions.create({
|
|
62
|
+
model: this.model,
|
|
63
|
+
messages: [
|
|
64
|
+
{
|
|
65
|
+
role: "system",
|
|
66
|
+
content: SUMMARY_SYSTEM_PROMPT,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
role: "user",
|
|
70
|
+
content: `Please summarize this document:\n\n${truncatedContent}`,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
max_tokens: SUMMARY_MAX_TOKENS,
|
|
74
|
+
temperature: SUMMARY_TEMPERATURE,
|
|
75
|
+
});
|
|
76
|
+
const summary = response.choices[0]?.message?.content?.trim();
|
|
77
|
+
if (!summary) {
|
|
78
|
+
throw new Error("Empty response from LLM");
|
|
79
|
+
}
|
|
80
|
+
return summary;
|
|
81
|
+
}
|
|
82
|
+
sanitizeSummary(summary) {
|
|
83
|
+
return summary.replace(/\s+/g, " ").trim();
|
|
84
|
+
}
|
|
85
|
+
fallbackSummary(content) {
|
|
86
|
+
const cleaned = content.trim();
|
|
87
|
+
if (cleaned.length === 0) {
|
|
88
|
+
return "Empty document";
|
|
89
|
+
}
|
|
90
|
+
const firstParagraph = cleaned.split(/\n\n/)[0];
|
|
91
|
+
const maxLength = 500;
|
|
92
|
+
if (firstParagraph.length <= maxLength) {
|
|
93
|
+
return firstParagraph;
|
|
94
|
+
}
|
|
95
|
+
return cleaned.slice(0, maxLength).trim() + "...";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.LLMDocumentSummarizer = LLMDocumentSummarizer;
|
|
99
|
+
class FallbackDocumentSummarizer {
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
101
|
+
async summarize(content) {
|
|
102
|
+
const cleaned = content.trim();
|
|
103
|
+
if (cleaned.length === 0) {
|
|
104
|
+
return "Empty document";
|
|
105
|
+
}
|
|
106
|
+
const firstParagraph = cleaned.split(/\n\n/)[0];
|
|
107
|
+
const maxLength = 500;
|
|
108
|
+
if (firstParagraph.length <= maxLength) {
|
|
109
|
+
return firstParagraph;
|
|
110
|
+
}
|
|
111
|
+
return cleaned.slice(0, maxLength).trim() + "...";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function createDocumentSummarizer(model) {
|
|
115
|
+
if (process.env.DISABLE_LLM_SUMMARIES === "true") {
|
|
116
|
+
return new FallbackDocumentSummarizer();
|
|
117
|
+
}
|
|
118
|
+
return new LLMDocumentSummarizer(model);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Convenience function for one-off summarization.
|
|
122
|
+
*/
|
|
123
|
+
async function summarizeDocument(content) {
|
|
124
|
+
const summarizer = createDocumentSummarizer();
|
|
125
|
+
return summarizer.summarize(content);
|
|
126
|
+
}
|
|
@@ -50,31 +50,34 @@ class DummyLLM {
|
|
|
50
50
|
}
|
|
51
51
|
async getConversationResponse(messages, _tools, onMessage, onReasoning) {
|
|
52
52
|
await new Promise((r) => setTimeout(r, 0));
|
|
53
|
-
(0, assert_1.strict)(this.idx < this.responses.length);
|
|
54
53
|
this.lastRequest = messages;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (onReasoning) {
|
|
62
|
-
await onReasoning(response.message);
|
|
54
|
+
const completion = (async () => {
|
|
55
|
+
for (;;) {
|
|
56
|
+
const idx = this.idx++;
|
|
57
|
+
const response = this.responses[idx % this.responses.length];
|
|
58
|
+
if (response.finish_reason === "error") {
|
|
59
|
+
throw new Error(response.message);
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
if (response.finish_reason === "reasoning") {
|
|
62
|
+
if (onReasoning) {
|
|
63
|
+
await onReasoning(response.message);
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (onMessage) {
|
|
68
|
+
const message = response.message;
|
|
69
|
+
void onMessage(message.content || "", true);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
id: String(idx),
|
|
73
|
+
choices: [response],
|
|
74
|
+
created: Date.now(),
|
|
75
|
+
model: "dummyLlmModel",
|
|
76
|
+
object: "chat.completion",
|
|
77
|
+
};
|
|
69
78
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
choices: [response],
|
|
73
|
-
created: Date.now(),
|
|
74
|
-
model: "dummyLlmModel",
|
|
75
|
-
object: "chat.completion",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
79
|
+
})();
|
|
80
|
+
return { stop: () => { }, completion };
|
|
78
81
|
}
|
|
79
82
|
setModel(_model) {
|
|
80
83
|
(0, assert_1.strict)(false, "unexpected call to setModel");
|
|
@@ -27,7 +27,7 @@ class ImageGenLLM {
|
|
|
27
27
|
getUrl() {
|
|
28
28
|
return this.openai.baseURL;
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
getConversationResponse(messages, tools, onMessage) {
|
|
31
31
|
(0, assert_1.strict)(!tools || tools.length === 0, "tools not supported in ImageGenLLM");
|
|
32
32
|
// Designed for image generation using openrouter, which tweaks the Create
|
|
33
33
|
const params = {
|
|
@@ -37,25 +37,28 @@ class ImageGenLLM {
|
|
|
37
37
|
modalities: ["image", "text"],
|
|
38
38
|
};
|
|
39
39
|
logger.info(`[ImageGenLLM] params; ${JSON.stringify(params)}`);
|
|
40
|
-
const completion = (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
const completion = (async () => {
|
|
41
|
+
const completion = (await this.openai.chat.completions.create(params));
|
|
42
|
+
// logger.debug(
|
|
43
|
+
// `[ImageGenLLM.getConversationResponse] completion:
|
|
44
|
+
// ${JSON.stringify(completion)}`
|
|
45
|
+
// );
|
|
46
|
+
if (onMessage) {
|
|
47
|
+
const message = completion.choices[0].message;
|
|
48
|
+
if (message.content) {
|
|
49
|
+
await onMessage(message.content, true);
|
|
50
|
+
}
|
|
51
|
+
if (message.images) {
|
|
52
|
+
message.images.forEach((image, index) => {
|
|
53
|
+
const imageUrl = image.image_url.url; // Base64 data URL
|
|
54
|
+
const truncated = imageUrl.substring(0, 50);
|
|
55
|
+
logger.info(`[ImageGenLLM] ${String(index + 1)}: ${truncated}...`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
49
58
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const truncated = imageUrl.substring(0, 50);
|
|
54
|
-
logger.info(`[ImageGenLLM] ${String(index + 1)}: ${truncated}...`);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return completion;
|
|
59
|
+
return completion;
|
|
60
|
+
})();
|
|
61
|
+
return Promise.resolve({ stop: () => { }, completion });
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
exports.ImageGenLLM = ImageGenLLM;
|
|
@@ -46,19 +46,22 @@ class OpenAILLM {
|
|
|
46
46
|
getUrl() {
|
|
47
47
|
return this.openai.baseURL;
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
const completion =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
getConversationResponse(messages, tools, onMessage) {
|
|
50
|
+
const completion = (async () => {
|
|
51
|
+
const completion = await this.openai.chat.completions.create({
|
|
52
|
+
model: this.model,
|
|
53
|
+
messages,
|
|
54
|
+
tools,
|
|
55
|
+
});
|
|
56
|
+
if (onMessage) {
|
|
57
|
+
const message = completion.choices[0].message;
|
|
58
|
+
if (message.content) {
|
|
59
|
+
await onMessage(message.content, true);
|
|
60
|
+
}
|
|
59
61
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
return completionFromOpenAI(completion);
|
|
63
|
+
})();
|
|
64
|
+
return Promise.resolve({ stop: () => { }, completion });
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
exports.OpenAILLM = OpenAILLM;
|
|
@@ -439,47 +439,78 @@ class OpenAILLMStreaming {
|
|
|
439
439
|
if (!chunks.iterator) {
|
|
440
440
|
throw new Error("not a stream");
|
|
441
441
|
}
|
|
442
|
-
let
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (!aggregatedMessage) {
|
|
451
|
-
logger.debug(`[stream] first}`);
|
|
452
|
-
const { initMessage } = initializeCompletion(chunk);
|
|
453
|
-
aggregatedMessage = initMessage;
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
updateCompletion(aggregatedMessage, chunk);
|
|
457
|
-
}
|
|
458
|
-
if (onMessage) {
|
|
459
|
-
// Inform the call of a message fragment if it contains any text.
|
|
460
|
-
// Note: chunks may have zero choices (e.g., usage-only chunks), so
|
|
461
|
-
// we safely access the first choice.
|
|
462
|
-
const delta = chunk.choices[0]?.delta;
|
|
442
|
+
let stopMsg = undefined;
|
|
443
|
+
const stop = (msg) => {
|
|
444
|
+
stopMsg = msg;
|
|
445
|
+
};
|
|
446
|
+
const completion = (async () => {
|
|
447
|
+
// Completion built up over successive calls to processChunk.
|
|
448
|
+
let aggregatedMessage;
|
|
449
|
+
const processChunk = async (chunk) => {
|
|
463
450
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
464
|
-
if (
|
|
465
|
-
|
|
451
|
+
if (chunk.object !== "chat.completion.chunk") {
|
|
452
|
+
// logger.warn("[stream]: unexpected message");
|
|
453
|
+
return;
|
|
466
454
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
455
|
+
if (!aggregatedMessage) {
|
|
456
|
+
logger.debug(`[stream] first}`);
|
|
457
|
+
const { initMessage } = initializeCompletion(chunk);
|
|
458
|
+
aggregatedMessage = initMessage;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
updateCompletion(aggregatedMessage, chunk);
|
|
474
462
|
}
|
|
463
|
+
if (onMessage) {
|
|
464
|
+
// Inform the call of a message fragment if it contains any text.
|
|
465
|
+
// Note: chunks may have zero choices (e.g., usage-only chunks), so
|
|
466
|
+
// we safely access the first choice.
|
|
467
|
+
const delta = chunk.choices[0]?.delta;
|
|
468
|
+
// eslint-disable-next-line
|
|
469
|
+
if (delta?.content) {
|
|
470
|
+
await onMessage(delta.content, false);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (onReasoning) {
|
|
474
|
+
const delta = chunk.choices[0]
|
|
475
|
+
?.delta;
|
|
476
|
+
const reasoning = (0, openAI_1.choiceDeltaExtractReasoning)(delta);
|
|
477
|
+
if (reasoning) {
|
|
478
|
+
await onReasoning(reasoning);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
// Process each chunk, checking for a stop signal.
|
|
483
|
+
for await (const chunk of chunks) {
|
|
484
|
+
logger.debug(`[stream] chunk: ${JSON.stringify(chunk)}`);
|
|
485
|
+
await processChunk(chunk);
|
|
486
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
487
|
+
if (stopMsg) {
|
|
488
|
+
const choice = {
|
|
489
|
+
delta: { content: stopMsg },
|
|
490
|
+
finish_reason: aggregatedMessage && aggregatedMessage.choices[0].finish_reason
|
|
491
|
+
? null
|
|
492
|
+
: "stop",
|
|
493
|
+
index: 0,
|
|
494
|
+
};
|
|
495
|
+
await processChunk({
|
|
496
|
+
id: aggregatedMessage?.id || "user_stop_chunk",
|
|
497
|
+
created: aggregatedMessage?.created || Date.now(),
|
|
498
|
+
model: aggregatedMessage?.model || model,
|
|
499
|
+
object: "chat.completion.chunk",
|
|
500
|
+
choices: [choice],
|
|
501
|
+
});
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
475
505
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
506
|
+
if (onMessage) {
|
|
507
|
+
await onMessage("", true);
|
|
508
|
+
}
|
|
509
|
+
logger.debug(`[stream] final message: ${JSON.stringify(aggregatedMessage)}`);
|
|
510
|
+
(0, assert_1.strict)(aggregatedMessage);
|
|
511
|
+
return aggregatedMessage;
|
|
512
|
+
})();
|
|
513
|
+
return { stop, completion };
|
|
483
514
|
}
|
|
484
515
|
}
|
|
485
516
|
exports.OpenAILLMStreaming = OpenAILLMStreaming;
|