@xalia/agent 0.5.8 → 0.6.1
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 +23 -8
- package/dist/agent/src/agent/agent.js +173 -96
- package/dist/agent/src/agent/agentUtils.js +82 -53
- package/dist/agent/src/agent/compressingContextManager.js +102 -0
- package/dist/agent/src/agent/context.js +189 -0
- package/dist/agent/src/agent/dummyLLM.js +46 -5
- package/dist/agent/src/agent/iAgentEventHandler.js +2 -0
- package/dist/agent/src/agent/mcpServerManager.js +22 -23
- package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
- package/dist/agent/src/agent/nullPlatform.js +14 -0
- package/dist/agent/src/agent/openAILLMStreaming.js +12 -7
- package/dist/agent/src/agent/promptProvider.js +63 -0
- package/dist/agent/src/agent/repeatLLM.js +5 -5
- package/dist/agent/src/agent/sudoMcpServerManager.js +11 -9
- package/dist/agent/src/agent/tokenAuth.js +7 -7
- package/dist/agent/src/agent/tools.js +1 -1
- package/dist/agent/src/chat/client/chatClient.js +733 -0
- package/dist/agent/src/chat/client/connection.js +209 -0
- package/dist/agent/src/chat/client/connection.test.js +188 -0
- package/dist/agent/src/chat/client/constants.js +5 -0
- package/dist/agent/src/chat/client/index.js +15 -0
- package/dist/agent/src/chat/client/interfaces.js +2 -0
- package/dist/agent/src/chat/client/responseHandler.js +105 -0
- package/dist/agent/src/chat/client/sessionClient.js +331 -0
- package/dist/agent/src/chat/client/teamManager.js +2 -0
- package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
- package/dist/agent/src/chat/data/dataModels.js +2 -0
- package/dist/agent/src/chat/data/database.js +749 -0
- package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
- package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
- package/dist/agent/src/chat/protocol/constants.js +50 -0
- package/dist/agent/src/chat/protocol/errors.js +22 -0
- package/dist/agent/src/chat/protocol/messages.js +110 -0
- package/dist/agent/src/chat/server/chatContextManager.js +405 -0
- package/dist/agent/src/chat/server/connectionManager.js +352 -0
- package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
- package/dist/agent/src/chat/server/conversation.js +198 -0
- package/dist/agent/src/chat/server/errorUtils.js +23 -0
- package/dist/agent/src/chat/server/openSession.js +869 -0
- package/dist/agent/src/chat/server/server.js +177 -0
- package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
- package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
- package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
- package/dist/agent/src/chat/server/tools.js +243 -0
- package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
- package/dist/agent/src/chat/utils/approvalManager.js +85 -0
- package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
- package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
- package/dist/agent/src/chat/utils/htmlToText.js +84 -0
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
- package/dist/agent/src/chat/utils/search.js +145 -0
- package/dist/agent/src/chat/utils/userResolver.js +46 -0
- package/dist/agent/src/chat/{websocket.js → utils/websocket.js} +2 -0
- package/dist/agent/src/test/agent.test.js +332 -0
- package/dist/agent/src/test/approvalManager.test.js +58 -0
- package/dist/agent/src/test/chatContextManager.test.js +392 -0
- package/dist/agent/src/test/clientServerConnection.test.js +158 -0
- package/dist/agent/src/test/compressingContextManager.test.js +65 -0
- package/dist/agent/src/test/context.test.js +83 -0
- package/dist/agent/src/test/conversation.test.js +89 -0
- package/dist/agent/src/test/db.test.js +262 -90
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
- package/dist/agent/src/test/dbTestTools.js +99 -0
- package/dist/agent/src/test/imageLoad.test.js +8 -7
- package/dist/agent/src/test/mcpServerManager.test.js +21 -18
- package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
- package/dist/agent/src/test/openaiStreaming.test.js +12 -11
- package/dist/agent/src/test/prompt.test.js +5 -4
- package/dist/agent/src/test/promptProvider.test.js +28 -0
- package/dist/agent/src/test/responseHandler.test.js +61 -0
- package/dist/agent/src/test/sudoMcpServerManager.test.js +14 -12
- package/dist/agent/src/test/testTools.js +109 -0
- package/dist/agent/src/test/tools.test.js +31 -0
- package/dist/agent/src/tool/agentChat.js +21 -10
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +235 -58
- package/dist/agent/src/tool/commandPrompt.js +15 -9
- package/dist/agent/src/tool/files.js +20 -16
- package/dist/agent/src/tool/nodePlatform.js +47 -3
- package/dist/agent/src/tool/options.js +4 -4
- package/dist/agent/src/tool/prompt.js +19 -13
- package/eslint.config.mjs +14 -1
- package/package.json +14 -6
- package/scripts/chat_server +8 -0
- package/scripts/setup_chat +7 -2
- package/scripts/shutdown_chat_server +3 -0
- package/scripts/test_chat +135 -17
- package/src/agent/agent.ts +270 -135
- package/src/agent/agentUtils.ts +136 -95
- package/src/agent/compressingContextManager.ts +164 -0
- package/src/agent/context.ts +268 -0
- package/src/agent/dummyLLM.ts +76 -8
- package/src/agent/iAgentEventHandler.ts +54 -0
- package/src/agent/iplatform.ts +1 -0
- package/src/agent/mcpServerManager.ts +32 -30
- package/src/agent/nullAgentEventHandler.ts +20 -0
- package/src/agent/nullPlatform.ts +13 -0
- package/src/agent/openAILLMStreaming.ts +12 -6
- package/src/agent/promptProvider.ts +87 -0
- package/src/agent/repeatLLM.ts +5 -5
- package/src/agent/sudoMcpServerManager.ts +13 -11
- package/src/agent/tokenAuth.ts +7 -7
- package/src/agent/tools.ts +3 -1
- package/src/chat/client/chatClient.ts +900 -0
- package/src/chat/client/connection.test.ts +241 -0
- package/src/chat/client/connection.ts +276 -0
- package/src/chat/client/constants.ts +3 -0
- package/src/chat/client/index.ts +18 -0
- package/src/chat/client/interfaces.ts +34 -0
- package/src/chat/client/responseHandler.ts +131 -0
- package/src/chat/client/sessionClient.ts +443 -0
- package/src/chat/client/teamManager.ts +29 -0
- package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
- package/src/chat/data/dataModels.ts +85 -0
- package/src/chat/data/database.ts +982 -0
- package/src/chat/data/dbMcpServerConfigs.ts +59 -0
- package/src/chat/protocol/connectionMessages.ts +49 -0
- package/src/chat/protocol/constants.ts +55 -0
- package/src/chat/protocol/errors.ts +16 -0
- package/src/chat/protocol/messages.ts +682 -0
- package/src/chat/server/README.md +127 -0
- package/src/chat/server/chatContextManager.ts +612 -0
- package/src/chat/server/connectionManager.test.ts +266 -0
- package/src/chat/server/connectionManager.ts +541 -0
- package/src/chat/server/conversation.ts +269 -0
- package/src/chat/server/errorUtils.ts +28 -0
- package/src/chat/server/openSession.ts +1332 -0
- package/src/chat/server/server.ts +177 -0
- package/src/chat/server/sessionFileManager.ts +239 -0
- package/src/chat/server/sessionRegistry.test.ts +138 -0
- package/src/chat/server/sessionRegistry.ts +1064 -0
- package/src/chat/server/test-utils/mockFactories.ts +422 -0
- package/src/chat/server/tools.ts +265 -0
- package/src/chat/utils/agentSessionMap.ts +76 -0
- package/src/chat/utils/approvalManager.ts +111 -0
- package/src/{utils → chat/utils}/asyncLock.ts +3 -3
- package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
- package/src/chat/utils/htmlToText.ts +61 -0
- package/src/chat/utils/multiAsyncQueue.ts +52 -0
- package/src/chat/utils/search.ts +139 -0
- package/src/chat/utils/userResolver.ts +48 -0
- package/src/chat/{websocket.ts → utils/websocket.ts} +2 -0
- package/src/test/agent.test.ts +487 -0
- package/src/test/approvalManager.test.ts +73 -0
- package/src/test/chatContextManager.test.ts +521 -0
- package/src/test/clientServerConnection.test.ts +207 -0
- package/src/test/compressingContextManager.test.ts +82 -0
- package/src/test/context.test.ts +105 -0
- package/src/test/conversation.test.ts +109 -0
- package/src/test/db.test.ts +351 -103
- package/src/test/dbMcpServerConfigs.test.ts +112 -0
- package/src/test/dbTestTools.ts +153 -0
- package/src/test/imageLoad.test.ts +7 -6
- package/src/test/mcpServerManager.test.ts +19 -14
- package/src/test/multiAsyncQueue.test.ts +125 -0
- package/src/test/openaiStreaming.test.ts +11 -10
- package/src/test/prompt.test.ts +4 -3
- package/src/test/promptProvider.test.ts +33 -0
- package/src/test/responseHandler.test.ts +78 -0
- package/src/test/sudoMcpServerManager.test.ts +22 -15
- package/src/test/testTools.ts +146 -0
- package/src/test/tools.test.ts +39 -0
- package/src/tool/agentChat.ts +26 -12
- package/src/tool/agentMain.ts +1 -1
- package/src/tool/chatMain.ts +283 -100
- package/src/tool/commandPrompt.ts +25 -9
- package/src/tool/files.ts +25 -19
- package/src/tool/nodePlatform.ts +52 -3
- package/src/tool/options.ts +4 -2
- package/src/tool/prompt.ts +22 -15
- package/test_data/dummyllm_script_crash.json +32 -0
- package/test_data/frog.png.b64 +1 -0
- package/vitest.config.ts +39 -0
- package/dist/agent/src/chat/client.js +0 -310
- package/dist/agent/src/chat/conversationManager.js +0 -502
- package/dist/agent/src/chat/db.js +0 -218
- package/dist/agent/src/chat/messages.js +0 -29
- package/dist/agent/src/chat/server.js +0 -158
- package/src/chat/client.ts +0 -445
- package/src/chat/conversationManager.ts +0 -730
- package/src/chat/db.ts +0 -304
- package/src/chat/messages.ts +0 -266
- package/src/chat/server.ts +0 -177
- /package/{frog.png → test_data/frog.png} +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.XALIA_APP_HEADER = exports.DEFAULT_LLM_MODEL = exports.DEFAULT_LLM_URL = void 0;
|
|
4
|
+
exports.createAgentWithoutSkills = createAgentWithoutSkills;
|
|
4
5
|
exports.createAgentWithSkills = createAgentWithSkills;
|
|
5
6
|
exports.createAgentFromSkillManager = createAgentFromSkillManager;
|
|
7
|
+
exports.createLLM = createLLM;
|
|
6
8
|
exports.createNonInteractiveAgent = createNonInteractiveAgent;
|
|
7
9
|
exports.runOneShot = runOneShot;
|
|
8
10
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
@@ -13,72 +15,70 @@ const openAILLMStreaming_1 = require("./openAILLMStreaming");
|
|
|
13
15
|
const dummyLLM_1 = require("./dummyLLM");
|
|
14
16
|
const assert_1 = require("assert");
|
|
15
17
|
const repeatLLM_1 = require("./repeatLLM");
|
|
18
|
+
const context_1 = require("./context");
|
|
16
19
|
const logger = (0, sdk_1.getLogger)();
|
|
17
20
|
exports.DEFAULT_LLM_URL = "http://localhost:5001/v1";
|
|
18
|
-
|
|
21
|
+
// uses openrouter
|
|
22
|
+
exports.DEFAULT_LLM_MODEL = process.env["DEFAULT_LLM_MODEL"] || "openai/gpt-4o";
|
|
19
23
|
exports.XALIA_APP_HEADER = {
|
|
20
24
|
"HTTP-Referer": "xalia.ai",
|
|
21
25
|
"X-Title": "Xalia",
|
|
22
26
|
};
|
|
27
|
+
async function createAgentWithoutSkills(llmUrl, agentProfile, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream = false) {
|
|
28
|
+
// Init SudoMcpServerManager
|
|
29
|
+
logger.debug("[createAgentWithSkills] creating SudoMcpServerManager.");
|
|
30
|
+
const sudoMcpServerManager = await sudoMcpServerManager_1.SkillManager.initialize((url, authResultP, displayName) => {
|
|
31
|
+
platform.openUrl(url, authResultP, displayName);
|
|
32
|
+
}, sudomcpConfig.backend_url, sudomcpConfig.api_key, authorizedUrl);
|
|
33
|
+
logger.debug("[createAgentWithoutSkills] restore mcp settings:" +
|
|
34
|
+
JSON.stringify(agentProfile.mcp_settings));
|
|
35
|
+
// Create agent using the event handler
|
|
36
|
+
const agent = await createAgentFromSkillManager(llmUrl, agentProfile, eventHandler, platform, contextManager, llmApiKey, sudoMcpServerManager, stream);
|
|
37
|
+
return [agent, sudoMcpServerManager];
|
|
38
|
+
}
|
|
23
39
|
/**
|
|
24
|
-
*
|
|
40
|
+
* Create and initialize an Agent given an AgentProfile using the
|
|
41
|
+
* IAgentEventHandler interface. This is the preferred way to create
|
|
42
|
+
* agents.
|
|
25
43
|
*/
|
|
26
|
-
async function
|
|
44
|
+
async function createAgentWithSkills(llmUrl, agentProfile, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream = false) {
|
|
45
|
+
const [agent, sudoMcpServerManager] = await createAgentWithoutSkills(llmUrl, agentProfile, eventHandler, platform, contextManager, llmApiKey, sudomcpConfig, authorizedUrl, stream);
|
|
46
|
+
logger.debug("[createAgentWithSkills] restoring skills");
|
|
47
|
+
await sudoMcpServerManager.restoreMcpSettings(agentProfile.mcp_settings);
|
|
48
|
+
return [agent, sudoMcpServerManager];
|
|
49
|
+
}
|
|
50
|
+
async function createAgentFromSkillManager(llmUrl, agentProfile, eventHandler, platform, contextManager, llmApiKey, skillManager, stream = false) {
|
|
51
|
+
// Create agent
|
|
52
|
+
logger.debug("[createAgentFromSkillManager] creating agent ...");
|
|
53
|
+
const llm = await createLLM(llmUrl, llmApiKey, agentProfile.model, stream, platform);
|
|
54
|
+
contextManager.setAgentPrompt(agentProfile.system_prompt);
|
|
55
|
+
const agent = agent_1.Agent.initializeWithLLM(eventHandler, llm, contextManager, skillManager);
|
|
56
|
+
logger.debug("[createAgentFromSkillManager] done");
|
|
57
|
+
return agent;
|
|
58
|
+
}
|
|
59
|
+
async function createLLM(llmUrl, llmApiKey, model, stream = false, platform) {
|
|
27
60
|
let llm;
|
|
28
61
|
if (model && model.startsWith("dummy:")) {
|
|
29
|
-
|
|
30
|
-
const llmUrl = model.slice(6);
|
|
31
|
-
if (llmUrl.length === 0) {
|
|
32
|
-
throw "malformed dummy:<script>";
|
|
33
|
-
}
|
|
34
|
-
const script = await platform.load(llmUrl);
|
|
35
|
-
logger.debug(` script: ${script}`);
|
|
36
|
-
const responses = JSON.parse(script);
|
|
37
|
-
logger.debug(`Initializing Dummy Agent: ${llmUrl}`);
|
|
38
|
-
llm = new dummyLLM_1.DummyLLM(responses);
|
|
62
|
+
llm = await dummyLLM_1.DummyLLM.initFromModelUrl(model, platform);
|
|
39
63
|
}
|
|
40
64
|
else if (model === "repeat") {
|
|
41
65
|
llm = new repeatLLM_1.RepeatLLM();
|
|
42
66
|
}
|
|
43
67
|
else {
|
|
44
68
|
// Regular Agent
|
|
45
|
-
if (!
|
|
46
|
-
throw "Missing OpenAI API Key";
|
|
69
|
+
if (!llmApiKey) {
|
|
70
|
+
throw new Error("Missing OpenAI API Key");
|
|
47
71
|
}
|
|
48
|
-
logger.debug(`Initializing Agent: ${llmUrl} - ${model}`);
|
|
72
|
+
logger.debug(`Initializing Agent: ${llmUrl ?? "unknown"} - ${model ?? "unknown"}`);
|
|
49
73
|
if (stream) {
|
|
50
|
-
llm = new openAILLMStreaming_1.OpenAILLMStreaming(
|
|
74
|
+
llm = new openAILLMStreaming_1.OpenAILLMStreaming(llmApiKey, llmUrl, model);
|
|
51
75
|
}
|
|
52
76
|
else {
|
|
53
|
-
llm = new openAILLM_1.OpenAILLM(
|
|
77
|
+
llm = new openAILLM_1.OpenAILLM(llmApiKey, llmUrl, model);
|
|
54
78
|
}
|
|
55
79
|
}
|
|
56
80
|
(0, assert_1.strict)(llm);
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Util function to create and initialize an Agent given an AgentProfile.
|
|
61
|
-
*/
|
|
62
|
-
async function createAgentWithSkills(llmUrl, agentProfile, onMessage, onToolCall, platform, llmApiKey, sudomcpConfig, authorizedUrl, conversation, stream = false) {
|
|
63
|
-
// Init SudoMcpServerManager
|
|
64
|
-
logger.debug("[createAgentWithSkills] creating SudoMcpServerManager.");
|
|
65
|
-
const sudoMcpServerManager = await sudoMcpServerManager_1.SkillManager.initialize(platform.openUrl, sudomcpConfig.backend_url, sudomcpConfig.api_key, authorizedUrl);
|
|
66
|
-
logger.debug("[createAgentWithSkills] restore mcp settings:" +
|
|
67
|
-
JSON.stringify(agentProfile.mcp_settings));
|
|
68
|
-
await sudoMcpServerManager.restoreMcpSettings(agentProfile.mcp_settings);
|
|
69
|
-
// Create agent using the McpServerManager just created
|
|
70
|
-
const agent = await createAgentFromSkillManager(llmUrl, agentProfile, onMessage, onToolCall, platform, llmApiKey, sudoMcpServerManager, conversation, stream);
|
|
71
|
-
return [agent, sudoMcpServerManager];
|
|
72
|
-
}
|
|
73
|
-
async function createAgentFromSkillManager(llmUrl, agentProfile, onMessage, onToolCall, platform, llmApiKey, skillManager, conversation, stream = false) {
|
|
74
|
-
// Create agent
|
|
75
|
-
logger.debug("[createAgentFromSkillManager] creating agent ...");
|
|
76
|
-
const agent = await createAgent(llmUrl, agentProfile.model, agentProfile.system_prompt, onMessage, onToolCall, platform, llmApiKey, stream, skillManager);
|
|
77
|
-
if (conversation) {
|
|
78
|
-
agent.setConversation(conversation);
|
|
79
|
-
}
|
|
80
|
-
logger.debug("[createAgentFromSkillManager] done");
|
|
81
|
-
return agent;
|
|
81
|
+
return llm;
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
84
|
* An "non-interactive" agent is one which is not intended to be used
|
|
@@ -88,15 +88,21 @@ async function createAgentFromSkillManager(llmUrl, agentProfile, onMessage, onTo
|
|
|
88
88
|
*/
|
|
89
89
|
async function createNonInteractiveAgent(url, agentProfile, conversation, platform, openaiApiKey, sudomcpConfig, approveToolsUpTo) {
|
|
90
90
|
let remainingToolCalls = approveToolsUpTo;
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
const eventHandler = {
|
|
92
|
+
onCompletion: () => { },
|
|
93
|
+
onAgentMessage: async () => { },
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
95
|
+
onToolCall: async () => {
|
|
96
|
+
if (remainingToolCalls !== 0) {
|
|
97
|
+
--remainingToolCalls;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
},
|
|
102
|
+
onToolCallResult: () => { },
|
|
98
103
|
};
|
|
99
|
-
const
|
|
104
|
+
const contextManager = new context_1.ContextManager(agentProfile.system_prompt, conversation || []);
|
|
105
|
+
const [agent, _] = await createAgentWithSkills(url, agentProfile, eventHandler, platform, contextManager, openaiApiKey, sudomcpConfig, undefined);
|
|
100
106
|
return agent;
|
|
101
107
|
}
|
|
102
108
|
/**
|
|
@@ -112,10 +118,33 @@ async function runOneShot(url, agentProfile, conversation, platform, prompt, ima
|
|
|
112
118
|
await agent.shutdown();
|
|
113
119
|
logger.debug("[runOneShot]: shutdown done");
|
|
114
120
|
if (!response) {
|
|
115
|
-
throw "No message returned from agent";
|
|
121
|
+
throw new Error("No message returned from agent");
|
|
122
|
+
}
|
|
123
|
+
// Handle different content types
|
|
124
|
+
let responseText = "";
|
|
125
|
+
if (typeof response.content === "string") {
|
|
126
|
+
responseText = response.content;
|
|
127
|
+
}
|
|
128
|
+
else if (response.content === null || response.content === undefined) {
|
|
129
|
+
responseText = "";
|
|
130
|
+
}
|
|
131
|
+
else if (Array.isArray(response.content)) {
|
|
132
|
+
// Handle array of content parts
|
|
133
|
+
responseText = response.content
|
|
134
|
+
.map((part) => {
|
|
135
|
+
if ("text" in part) {
|
|
136
|
+
return part.text;
|
|
137
|
+
}
|
|
138
|
+
return "";
|
|
139
|
+
})
|
|
140
|
+
.join("");
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Fallback for other types
|
|
144
|
+
responseText = String(response.content);
|
|
116
145
|
}
|
|
117
146
|
return {
|
|
118
|
-
response:
|
|
147
|
+
response: responseText,
|
|
119
148
|
conversation: agent.getConversation(),
|
|
120
149
|
};
|
|
121
150
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompressingContextManager = void 0;
|
|
4
|
+
exports.createCheckpointMessage = createCheckpointMessage;
|
|
5
|
+
exports.createCompressionAgent = createCompressionAgent;
|
|
6
|
+
exports.createSummary = createSummary;
|
|
7
|
+
const assert_1 = require("assert");
|
|
8
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
9
|
+
const agent_1 = require("./agent");
|
|
10
|
+
const nullPlatform_1 = require("./nullPlatform");
|
|
11
|
+
const agentUtils_1 = require("./agentUtils");
|
|
12
|
+
const context_1 = require("./context");
|
|
13
|
+
const nullAgentEventHandler_1 = require("./nullAgentEventHandler");
|
|
14
|
+
const logger = (0, sdk_1.getLogger)();
|
|
15
|
+
/**
|
|
16
|
+
* System prompt used to generate a conversation summary.
|
|
17
|
+
*/
|
|
18
|
+
const COMPRESSION_SYSTEM_PROMPT =
|
|
19
|
+
// eslint-disable-next-line max-len
|
|
20
|
+
"You are a context summarizer, creating MINIMAL conversation digests which can be used to CONTINUE the conversation at a later date. TOKEN EFFICIENCY is a HIGH PRIORITY. Summaries will only be seen by LLMs and should USE ANY AND ALL ABBREVIATIONS to keep them as CONCISE as possible, while PRESERVING IMPORTANT DETAILS of the CONVERSATION.";
|
|
21
|
+
/**
|
|
22
|
+
* Text prepended to a summary to create the checkpoint message
|
|
23
|
+
*/
|
|
24
|
+
const CHECKPOINT_MESSAGE_PREFIX =
|
|
25
|
+
// eslint-disable-next-line max-len
|
|
26
|
+
"We are continuing an earlier conversation. The remainder of this message is a summary of the conversation so far: ";
|
|
27
|
+
function createCheckpointMessage(summary) {
|
|
28
|
+
return {
|
|
29
|
+
role: "user",
|
|
30
|
+
content: CHECKPOINT_MESSAGE_PREFIX + summary,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function createCompressionAgent(compressionAgentUrl, compressionAgentModel, compressionAgentApiKey) {
|
|
34
|
+
const llm = await (0, agentUtils_1.createLLM)(compressionAgentUrl, compressionAgentApiKey, compressionAgentModel, false /* stream */, nullPlatform_1.NULL_PLATFORM);
|
|
35
|
+
return agent_1.Agent.initializeWithLLM(nullAgentEventHandler_1.NULL_AGENT_EVENT_HANDLER, llm, new context_1.ContextManager(COMPRESSION_SYSTEM_PROMPT, []));
|
|
36
|
+
}
|
|
37
|
+
async function createSummary(compressionAgentUrl, compressionAgentModel, compressionAgentApiKey, conversation) {
|
|
38
|
+
const agent = await createCompressionAgent(compressionAgentUrl, compressionAgentModel, compressionAgentApiKey);
|
|
39
|
+
const resp = await agent.userMessageEx(JSON.stringify(conversation));
|
|
40
|
+
if (!resp) {
|
|
41
|
+
throw new Error("compression agent returned null");
|
|
42
|
+
}
|
|
43
|
+
(0, assert_1.strict)(resp.role === "assistant");
|
|
44
|
+
(0, assert_1.strict)(typeof resp.content === "string", "expected string content from compression agent");
|
|
45
|
+
return resp.content;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Can perform compression on the committed part of the context. Caller (not
|
|
49
|
+
* the Agent) is responsible for committing the conversation and triggering
|
|
50
|
+
* compression.
|
|
51
|
+
*/
|
|
52
|
+
class CompressingContextManager extends context_1.ContextManagerWithCommit {
|
|
53
|
+
constructor(systemPrompt, messages, compressionAgentUrl, compressionAgentModel, compressionAgentApiKey) {
|
|
54
|
+
super(systemPrompt, messages);
|
|
55
|
+
this.compressionAgentUrl = compressionAgentUrl;
|
|
56
|
+
this.compressionAgentModel = compressionAgentModel;
|
|
57
|
+
this.compressionAgentApiKey = compressionAgentApiKey;
|
|
58
|
+
this.compressingMessages = undefined;
|
|
59
|
+
// Sanity check the conversation form.
|
|
60
|
+
//
|
|
61
|
+
// Ordinarily, the committed context should end with an "assistant"
|
|
62
|
+
// message (i.e. user messages, an agent loop terminating in a final agent
|
|
63
|
+
// response). However, if a conversation has been compressed, we may have
|
|
64
|
+
// only the summary, which is a "user" message. In this case, this should
|
|
65
|
+
// be the only message.
|
|
66
|
+
const numMessages = this.numMessages();
|
|
67
|
+
const lastMessage = this.lastMessage();
|
|
68
|
+
if (lastMessage) {
|
|
69
|
+
const finalRole = lastMessage.role;
|
|
70
|
+
if (finalRole === "user") {
|
|
71
|
+
(0, assert_1.strict)(numMessages === 1);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
(0, assert_1.strict)(finalRole === "assistant", `unexpected final role ${finalRole}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async compress() {
|
|
79
|
+
// Only select messages for compression if they have been committed.
|
|
80
|
+
const numToCompress = this.getCommittedLength();
|
|
81
|
+
const messagesToCompress = this.leadingMessages(numToCompress);
|
|
82
|
+
(0, assert_1.strict)(messagesToCompress.length === numToCompress);
|
|
83
|
+
this.compressingMessages = numToCompress;
|
|
84
|
+
(0, assert_1.strict)(this.compressingMessages > 1, "<2 messages commited in the context");
|
|
85
|
+
logger.debug(`[CompressingContextManager] start (${String(this.compressingMessages)})`);
|
|
86
|
+
try {
|
|
87
|
+
const summary = await createSummary(this.compressionAgentUrl, this.compressionAgentModel, this.compressionAgentApiKey, messagesToCompress);
|
|
88
|
+
logger.debug(`[CompressingContextManager] summary: ${summary}`);
|
|
89
|
+
// Replace the context `messages` and update `lastCommittedMessage`
|
|
90
|
+
// index.
|
|
91
|
+
const checkpointMessage = createCheckpointMessage(summary);
|
|
92
|
+
(0, assert_1.strict)(typeof checkpointMessage.content === "string");
|
|
93
|
+
this.replaceLeadingMessages(numToCompress, checkpointMessage);
|
|
94
|
+
return summary;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
this.compressingMessages = undefined;
|
|
98
|
+
logger.debug(`[CompressingContextManager] compression done`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.CompressingContextManager = CompressingContextManager;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContextManagerWithCommit = exports.ContextManagerWithWorkspace = exports.ContextManager = void 0;
|
|
4
|
+
const assert_1 = require("assert");
|
|
5
|
+
const promptProvider_1 = require("./promptProvider");
|
|
6
|
+
/**
|
|
7
|
+
* Trivial implementation of IContextManage which just manages the context as
|
|
8
|
+
* an array of `ChatCompletionMessageParam[]`
|
|
9
|
+
*/
|
|
10
|
+
class ContextManager {
|
|
11
|
+
constructor(systemPrompt, messages) {
|
|
12
|
+
(0, assert_1.strict)(messages.length === 0 || messages[0].role !== "system");
|
|
13
|
+
// Insert system message at the beginning if not present
|
|
14
|
+
if (messages.length === 0 || messages[0].role !== "system") {
|
|
15
|
+
this.messages = [
|
|
16
|
+
{
|
|
17
|
+
role: "system",
|
|
18
|
+
content: "",
|
|
19
|
+
},
|
|
20
|
+
...messages,
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
(0, assert_1.strict)(false);
|
|
25
|
+
// this.messages = [...conversationMessages];
|
|
26
|
+
// this.messages[0] = {systemPrompt;
|
|
27
|
+
}
|
|
28
|
+
this.systemPromptProvider = new promptProvider_1.SystemPromptProvider(systemPrompt);
|
|
29
|
+
}
|
|
30
|
+
numMessages() {
|
|
31
|
+
return this.messages.length - 1;
|
|
32
|
+
}
|
|
33
|
+
lastMessage() {
|
|
34
|
+
if (this.messages.length > 1) {
|
|
35
|
+
return this.messages[this.messages.length - 1];
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
leadingMessages(numMessages) {
|
|
40
|
+
return this.messages.slice(1, numMessages + 1);
|
|
41
|
+
}
|
|
42
|
+
trailingMessagesFrom(idx) {
|
|
43
|
+
return this.messages.slice(1 + idx);
|
|
44
|
+
}
|
|
45
|
+
addMessages(messages) {
|
|
46
|
+
for (const message of messages) {
|
|
47
|
+
this.messages.push(message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
addMessage(message) {
|
|
51
|
+
this.messages.push(message);
|
|
52
|
+
}
|
|
53
|
+
getLLMContext() {
|
|
54
|
+
return this.getLLMContextRaw().slice();
|
|
55
|
+
}
|
|
56
|
+
replaceLeadingMessages(numMessagesToReplace, replacement) {
|
|
57
|
+
(0, assert_1.strict)(this.messages.length >= 1 + numMessagesToReplace);
|
|
58
|
+
const remainingMsgs = this.messages.slice(1 + numMessagesToReplace);
|
|
59
|
+
this.messages = [this.messages[0], replacement, ...remainingMsgs];
|
|
60
|
+
}
|
|
61
|
+
popMessage() {
|
|
62
|
+
if (this.messages.length > 1) {
|
|
63
|
+
return this.messages.pop();
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
getAgentPrompt() {
|
|
68
|
+
return this.systemPromptProvider.getAgentPrompt();
|
|
69
|
+
}
|
|
70
|
+
setAgentPrompt(prompt) {
|
|
71
|
+
this.systemPromptProvider.setAgentPrompt(prompt);
|
|
72
|
+
}
|
|
73
|
+
setPromptFragment(fragmentID, prompt) {
|
|
74
|
+
this.systemPromptProvider.setFragment(fragmentID, prompt);
|
|
75
|
+
}
|
|
76
|
+
removePromptFragment(fragmentID) {
|
|
77
|
+
this.systemPromptProvider.removeFragment(fragmentID);
|
|
78
|
+
}
|
|
79
|
+
/// Returns the actual array (so that child classes can avoid an unnecessary
|
|
80
|
+
/// copy)
|
|
81
|
+
getLLMContextRaw() {
|
|
82
|
+
(0, assert_1.strict)(this.messages[0].role === "system");
|
|
83
|
+
this.messages[0].content = this.systemPromptProvider.getSystemPrompt();
|
|
84
|
+
return this.messages;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.ContextManager = ContextManager;
|
|
88
|
+
/**
|
|
89
|
+
* Maintain a trailing "workspace" message at the end of the context.
|
|
90
|
+
*/
|
|
91
|
+
class ContextManagerWithWorkspace extends ContextManager {
|
|
92
|
+
constructor(systemPrompt, conversationMessages) {
|
|
93
|
+
super(systemPrompt, conversationMessages);
|
|
94
|
+
super.addMessage({ role: "user", content: "" });
|
|
95
|
+
}
|
|
96
|
+
numMessages() {
|
|
97
|
+
return super.numMessages() - 1;
|
|
98
|
+
}
|
|
99
|
+
lastMessage() {
|
|
100
|
+
const totalNum = super.numMessages();
|
|
101
|
+
if (totalNum < 2) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const trailing = super.trailingMessagesFrom(totalNum - 2);
|
|
105
|
+
return trailing[0];
|
|
106
|
+
}
|
|
107
|
+
trailingMessagesFrom(idx) {
|
|
108
|
+
const trailing = super.trailingMessagesFrom(idx);
|
|
109
|
+
return trailing.slice(0, trailing.length - 1);
|
|
110
|
+
}
|
|
111
|
+
addMessages(messages) {
|
|
112
|
+
const workspaceMsg = super.popMessage();
|
|
113
|
+
(0, assert_1.strict)(workspaceMsg);
|
|
114
|
+
super.addMessages(messages);
|
|
115
|
+
super.addMessage(workspaceMsg);
|
|
116
|
+
}
|
|
117
|
+
addMessage(message) {
|
|
118
|
+
const workspaceMsg = super.popMessage();
|
|
119
|
+
(0, assert_1.strict)(workspaceMsg);
|
|
120
|
+
super.addMessages([message, workspaceMsg]);
|
|
121
|
+
}
|
|
122
|
+
getLLMContext() {
|
|
123
|
+
const context = super.getLLMContextRaw();
|
|
124
|
+
const workspace = super.lastMessage();
|
|
125
|
+
(0, assert_1.strict)(workspace);
|
|
126
|
+
if (workspace.content === "") {
|
|
127
|
+
return context.slice(0, context.length - 1);
|
|
128
|
+
}
|
|
129
|
+
return context.slice();
|
|
130
|
+
}
|
|
131
|
+
popMessage() {
|
|
132
|
+
const workspaceMsg = super.popMessage();
|
|
133
|
+
(0, assert_1.strict)(workspaceMsg);
|
|
134
|
+
const msg = super.popMessage();
|
|
135
|
+
super.addMessage(workspaceMsg);
|
|
136
|
+
return msg;
|
|
137
|
+
}
|
|
138
|
+
getWorkspace() {
|
|
139
|
+
const workspace = super.lastMessage();
|
|
140
|
+
(0, assert_1.strict)(workspace);
|
|
141
|
+
(0, assert_1.strict)(workspace.role === "user");
|
|
142
|
+
if (workspace.content === "") {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return workspace;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Replace (or remove) the workspace message
|
|
149
|
+
*/
|
|
150
|
+
setWorkspace(userMessage) {
|
|
151
|
+
if (!userMessage) {
|
|
152
|
+
userMessage = { role: "user", content: "" };
|
|
153
|
+
}
|
|
154
|
+
// Replace the workspace message.
|
|
155
|
+
super.popMessage();
|
|
156
|
+
super.addMessage(userMessage);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.ContextManagerWithWorkspace = ContextManagerWithWorkspace;
|
|
160
|
+
/**
|
|
161
|
+
* ContextManagerWithCommit
|
|
162
|
+
*
|
|
163
|
+
* Allows the user to distinguish between messages that are still part of an
|
|
164
|
+
* on-going agent loop.
|
|
165
|
+
*/
|
|
166
|
+
class ContextManagerWithCommit extends ContextManagerWithWorkspace {
|
|
167
|
+
constructor(systemPrompt, messages) {
|
|
168
|
+
super(systemPrompt, messages);
|
|
169
|
+
// Use `this.messages` to initialize the index since this is well-formed
|
|
170
|
+
// (starts with "system")
|
|
171
|
+
this.numCommitted = this.numMessages();
|
|
172
|
+
}
|
|
173
|
+
getPending() {
|
|
174
|
+
(0, assert_1.strict)(this.numCommitted <= this.numMessages());
|
|
175
|
+
return this.trailingMessagesFrom(this.numCommitted);
|
|
176
|
+
}
|
|
177
|
+
getCommittedLength() {
|
|
178
|
+
// this.lastCommittedMessage + 1 - 1 (system message)
|
|
179
|
+
return this.numCommitted;
|
|
180
|
+
}
|
|
181
|
+
commit() {
|
|
182
|
+
this.numCommitted = this.numMessages();
|
|
183
|
+
}
|
|
184
|
+
replaceLeadingMessages(numMessages, replacement) {
|
|
185
|
+
super.replaceLeadingMessages(numMessages, replacement);
|
|
186
|
+
this.numCommitted -= numMessages - 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
exports.ContextManagerWithCommit = ContextManagerWithCommit;
|
|
@@ -2,29 +2,67 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DummyLLM = void 0;
|
|
4
4
|
const assert_1 = require("assert");
|
|
5
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
6
|
+
const logger = (0, sdk_1.getLogger)();
|
|
7
|
+
/// Supports model-urls:
|
|
8
|
+
///
|
|
9
|
+
/// dummy:[{"index":0,"message":{"role":"assistant","content":"message 1"},
|
|
10
|
+
/// "finish_reason":"stop"}]
|
|
11
|
+
/// (OpenAI.Chat.Completions.ChatCompletion.Choice[] as JSON)
|
|
12
|
+
/// dummy:<filename>
|
|
13
|
+
///
|
|
5
14
|
class DummyLLM {
|
|
6
15
|
constructor(responses) {
|
|
7
16
|
this.responses = responses;
|
|
8
17
|
this.idx = 0;
|
|
9
18
|
}
|
|
19
|
+
static async initFromModelUrl(modelUrl, platform) {
|
|
20
|
+
// Dummy LLM
|
|
21
|
+
(0, assert_1.strict)(modelUrl.startsWith("dummy:"));
|
|
22
|
+
const scriptUrl = modelUrl.slice(6);
|
|
23
|
+
let script;
|
|
24
|
+
try {
|
|
25
|
+
script = JSON.parse(scriptUrl);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
logger.debug(`[DummyLLM.init] failed to parse model url as json: ${String(e)}`);
|
|
29
|
+
}
|
|
30
|
+
if (!script) {
|
|
31
|
+
try {
|
|
32
|
+
const scriptTxt = await platform.load(scriptUrl);
|
|
33
|
+
script = JSON.parse(scriptTxt);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
throw new Error("model URL error: invalid JSON and load (as filename) failed: " +
|
|
37
|
+
String(e));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
logger.debug(`Initialized Dummy Agent: ${modelUrl}`);
|
|
41
|
+
return new DummyLLM(script);
|
|
42
|
+
}
|
|
10
43
|
getModel() {
|
|
11
|
-
|
|
44
|
+
// Returns as a data-url
|
|
45
|
+
return "dummy:" + JSON.stringify(this.responses);
|
|
12
46
|
}
|
|
13
47
|
getUrl() {
|
|
14
|
-
|
|
48
|
+
return "";
|
|
15
49
|
}
|
|
16
|
-
async getConversationResponse(
|
|
50
|
+
async getConversationResponse(messages, _tools, onMessage) {
|
|
17
51
|
await new Promise((r) => setTimeout(r, 0));
|
|
18
52
|
(0, assert_1.strict)(this.idx < this.responses.length);
|
|
53
|
+
this.lastRequest = messages;
|
|
19
54
|
const response = this.responses[this.idx++];
|
|
55
|
+
if (response.finish_reason === "error") {
|
|
56
|
+
throw new Error(response.message);
|
|
57
|
+
}
|
|
20
58
|
if (onMessage) {
|
|
21
59
|
const message = response.message;
|
|
22
60
|
if (message.content) {
|
|
23
|
-
onMessage(message.content, true);
|
|
61
|
+
void onMessage(message.content, true);
|
|
24
62
|
}
|
|
25
63
|
}
|
|
26
64
|
return {
|
|
27
|
-
id:
|
|
65
|
+
id: String(this.idx),
|
|
28
66
|
choices: [response],
|
|
29
67
|
created: Date.now(),
|
|
30
68
|
model: "dummyLlmModel",
|
|
@@ -34,5 +72,8 @@ class DummyLLM {
|
|
|
34
72
|
setModel(_model) {
|
|
35
73
|
(0, assert_1.strict)(false, "unexpected call to setModel");
|
|
36
74
|
}
|
|
75
|
+
getLastRequest() {
|
|
76
|
+
return this.lastRequest;
|
|
77
|
+
}
|
|
37
78
|
}
|
|
38
79
|
exports.DummyLLM = DummyLLM;
|