@xalia/agent 0.6.8 → 0.6.10
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 +6 -0
- package/.env.test +7 -0
- package/README.md +11 -0
- package/context_system.md +498 -0
- package/dist/agent/src/agent/agent.js +169 -87
- package/dist/agent/src/agent/agentUtils.js +24 -18
- 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/documentSummarizer.js +126 -0
- package/dist/agent/src/agent/dummyLLM.js +25 -22
- package/dist/agent/src/agent/imageGenLLM.js +22 -25
- package/dist/agent/src/agent/imageGenerator.js +2 -10
- 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 +73 -39
- package/dist/agent/src/agent/repeatLLM.js +16 -7
- package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
- 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 +63 -2
- package/dist/agent/src/chat/client/connection.js +6 -1
- package/dist/agent/src/chat/client/index.js +4 -1
- package/dist/agent/src/chat/client/sessionClient.js +28 -9
- package/dist/agent/src/chat/constants.js +8 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
- package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
- package/dist/agent/src/chat/protocol/messages.js +9 -0
- package/dist/agent/src/chat/server/chatContextManager.js +186 -156
- package/dist/agent/src/chat/server/conversation.js +3 -0
- package/dist/agent/src/chat/server/imageGeneratorTools.js +39 -16
- package/dist/agent/src/chat/server/openAIRouterLLM.js +111 -0
- package/dist/agent/src/chat/server/openSession.js +253 -91
- package/dist/agent/src/chat/server/promptRefiner.js +86 -0
- package/dist/agent/src/chat/server/server.js +10 -2
- package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
- package/dist/agent/src/chat/server/sessionRegistry.js +152 -6
- package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
- 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 +64 -253
- 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 +44 -30
- 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/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 +56 -15
- package/dist/agent/src/tool/commandPrompt.js +2 -2
- package/dist/agent/src/tool/files.js +7 -8
- package/package.json +4 -1
- package/scripts/test_chat +195 -173
- package/src/agent/agent.ts +257 -137
- package/src/agent/agentUtils.ts +32 -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/documentSummarizer.ts +157 -0
- package/src/agent/dummyLLM.ts +27 -23
- package/src/agent/imageGenLLM.ts +28 -32
- package/src/agent/imageGenerator.ts +3 -18
- package/src/agent/llm.ts +2 -2
- package/src/agent/openAILLM.ts +17 -13
- package/src/agent/openAILLMStreaming.ts +99 -43
- package/src/agent/repeatLLM.ts +19 -7
- package/src/agent/sudoMcpServerManager.ts +41 -20
- package/src/agent/test_data/harrypotter.txt +6065 -0
- package/src/agent/tokenCounter.test.ts +243 -0
- package/src/agent/tokenCounter.ts +483 -0
- package/src/agent/toolSettings.ts +24 -0
- package/src/agent/tools/calculatorTool.ts +50 -0
- package/src/agent/tools/contentExtractors/pdfToText.ts +60 -0
- package/src/agent/tools/datetimeTool.ts +41 -0
- package/src/agent/tools/fileManager/fileManagerTool.ts +199 -0
- package/src/agent/tools/fileManager/index.ts +50 -0
- package/src/agent/tools/fileManager/memoryFileManager.ts +120 -0
- package/src/{chat/data → agent/tools/fileManager}/mimeTypes.ts +3 -1
- package/src/agent/tools/fileManager/prompt.ts +38 -0
- package/src/{chat/data/dbSessionFileModels.ts → agent/tools/fileManager/types.ts} +76 -0
- package/src/agent/tools/index.ts +49 -0
- package/src/agent/tools/openUrlTool.ts +62 -0
- package/src/agent/tools/renderTool.ts +92 -0
- package/src/agent/tools/utils.ts +74 -0
- package/src/{chat/utils/search.ts → agent/tools/webSearch.ts} +0 -1
- package/src/agent/tools/webSearchTool.ts +44 -0
- package/src/chat/client/chatClient.ts +92 -3
- package/src/chat/client/connection.ts +11 -1
- package/src/chat/client/index.ts +3 -0
- package/src/chat/client/sessionClient.ts +40 -11
- package/src/chat/client/sessionFiles.ts +1 -1
- package/src/chat/constants.ts +6 -0
- package/src/chat/data/dataModels.ts +12 -0
- package/src/chat/data/dbSessionFiles.ts +12 -4
- package/src/chat/data/dbSessionMessages.ts +34 -0
- package/src/chat/protocol/messages.ts +94 -14
- package/src/chat/server/chatContextManager.ts +255 -221
- package/src/chat/server/connectionManager.ts +1 -1
- package/src/chat/server/conversation.ts +3 -0
- package/src/chat/server/imageGeneratorTools.ts +62 -30
- package/src/chat/server/openAIRouterLLM.ts +168 -0
- package/src/chat/server/openSession.ts +381 -138
- package/src/chat/server/promptRefiner.ts +106 -0
- package/src/chat/server/server.ts +9 -2
- package/src/chat/server/sessionFileManager.ts +35 -306
- package/src/chat/server/sessionRegistry.test.ts +0 -1
- package/src/chat/server/sessionRegistry.ts +228 -4
- package/src/chat/server/titleGenerator.test.ts +103 -0
- package/src/chat/server/titleGenerator.ts +143 -0
- package/src/chat/server/tools.ts +92 -281
- package/src/chat/utils/approvalManager.ts +9 -3
- package/src/chat/utils/multiAsyncQueue.ts +4 -0
- package/src/test/agent.test.ts +25 -30
- package/src/test/chatContextManager.test.ts +68 -38
- 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/test/dbMcpServerConfigs.test.ts +4 -4
- package/src/test/dbSessionFiles.test.ts +16 -16
- package/src/test/testTools.ts +8 -3
- package/src/test/tools.test.ts +30 -5
- package/src/tool/agentChat.ts +12 -3
- package/src/tool/chatMain.ts +59 -18
- package/src/tool/commandPrompt.ts +2 -2
- package/src/tool/files.ts +1 -3
- package/dist/agent/src/agent/tools.js +0 -44
- package/src/agent/tools.ts +0 -57
- /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
- /package/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.ts +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LLMTitleGenerator = void 0;
|
|
4
|
+
exports.createTitleGenerator = createTitleGenerator;
|
|
5
|
+
const openAIRouterLLM_1 = require("./openAIRouterLLM");
|
|
6
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
7
|
+
const logger = (0, sdk_1.getLogger)();
|
|
8
|
+
const TITLE_MODEL = "google/gemini-2.5-flash";
|
|
9
|
+
const TITLE_MAX_TOKENS = 20;
|
|
10
|
+
const TITLE_TEMPERATURE = 0.2;
|
|
11
|
+
const TITLE_TIMEOUT_MS = 5000;
|
|
12
|
+
const MAX_TITLE_LENGTH = 120;
|
|
13
|
+
const TITLE_SYSTEM_PROMPT = `You create ultra-short, descriptive titles for user prompts.
|
|
14
|
+
Rules:
|
|
15
|
+
- Maximum 8 words
|
|
16
|
+
- No punctuation except spaces and hyphens
|
|
17
|
+
- No quotes or special characters
|
|
18
|
+
- Use Title Case
|
|
19
|
+
- Output ONLY the title text
|
|
20
|
+
- Be descriptive and capture the main intent`;
|
|
21
|
+
class LLMTitleGenerator {
|
|
22
|
+
constructor(model = TITLE_MODEL) {
|
|
23
|
+
this.model = model;
|
|
24
|
+
}
|
|
25
|
+
async generateTitle(firstUserMessage) {
|
|
26
|
+
if (!firstUserMessage || firstUserMessage.trim().length === 0) {
|
|
27
|
+
return "New Chat";
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const title = await this.generateTitleWithTimeout(firstUserMessage);
|
|
31
|
+
return this.sanitizeTitle(title);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
35
|
+
logger.warn(`[TitleGenerator] LLM title generation failed: ${errorMsg}, ` +
|
|
36
|
+
`using fallback`);
|
|
37
|
+
return this.fallbackTitle(firstUserMessage);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async generateTitleWithTimeout(message) {
|
|
41
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
reject(new Error("Title generation timeout"));
|
|
44
|
+
}, TITLE_TIMEOUT_MS);
|
|
45
|
+
});
|
|
46
|
+
const titlePromise = this.callLLM(message);
|
|
47
|
+
return Promise.race([titlePromise, timeoutPromise]);
|
|
48
|
+
}
|
|
49
|
+
async callLLM(message) {
|
|
50
|
+
const client = (0, openAIRouterLLM_1.getOpenAIClient)(this.model);
|
|
51
|
+
const truncatedMessage = message.slice(0, 500);
|
|
52
|
+
const response = await client.chat.completions.create({
|
|
53
|
+
model: this.model,
|
|
54
|
+
messages: [
|
|
55
|
+
{
|
|
56
|
+
role: "system",
|
|
57
|
+
content: TITLE_SYSTEM_PROMPT,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
role: "user",
|
|
61
|
+
content: `User prompt: ${truncatedMessage}`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
max_tokens: TITLE_MAX_TOKENS,
|
|
65
|
+
temperature: TITLE_TEMPERATURE,
|
|
66
|
+
});
|
|
67
|
+
const title = response.choices[0]?.message?.content?.trim();
|
|
68
|
+
if (!title) {
|
|
69
|
+
throw new Error("Empty response from LLM");
|
|
70
|
+
}
|
|
71
|
+
return title;
|
|
72
|
+
}
|
|
73
|
+
sanitizeTitle(title) {
|
|
74
|
+
let sanitized = title
|
|
75
|
+
.replace(/["'`]/g, "")
|
|
76
|
+
.replace(/\n/g, " ")
|
|
77
|
+
.replace(/\s+/g, " ")
|
|
78
|
+
.trim();
|
|
79
|
+
if (sanitized.length > MAX_TITLE_LENGTH) {
|
|
80
|
+
sanitized = sanitized.slice(0, MAX_TITLE_LENGTH).trim();
|
|
81
|
+
}
|
|
82
|
+
return sanitized || "New Chat";
|
|
83
|
+
}
|
|
84
|
+
fallbackTitle(message) {
|
|
85
|
+
const cleaned = message.trim();
|
|
86
|
+
if (cleaned.length === 0) {
|
|
87
|
+
return "New Chat";
|
|
88
|
+
}
|
|
89
|
+
const firstSentence = cleaned.split(/[.!?]\s/)[0];
|
|
90
|
+
const titleCandidate = firstSentence.length <= MAX_TITLE_LENGTH
|
|
91
|
+
? firstSentence
|
|
92
|
+
: cleaned.slice(0, MAX_TITLE_LENGTH);
|
|
93
|
+
return titleCandidate.trim() || "New Chat";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.LLMTitleGenerator = LLMTitleGenerator;
|
|
97
|
+
class FallbackTitleGenerator {
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
99
|
+
async generateTitle(firstUserMessage) {
|
|
100
|
+
const cleaned = firstUserMessage.trim();
|
|
101
|
+
if (cleaned.length === 0) {
|
|
102
|
+
return "New Chat";
|
|
103
|
+
}
|
|
104
|
+
return cleaned.slice(0, 120) || "New Chat";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function createTitleGenerator(model) {
|
|
108
|
+
if (process.env.DISABLE_LLM_TITLES === "true") {
|
|
109
|
+
return new FallbackTitleGenerator();
|
|
110
|
+
}
|
|
111
|
+
return new LLMTitleGenerator(model);
|
|
112
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
const titleGenerator_1 = require("./titleGenerator");
|
|
38
|
+
const openAIRouterLLM = __importStar(require("./openAIRouterLLM"));
|
|
39
|
+
vitest_1.vi.mock("./openAIRouterLLM", async (importOriginal) => {
|
|
40
|
+
const actual = await importOriginal();
|
|
41
|
+
return {
|
|
42
|
+
...actual,
|
|
43
|
+
getOpenAIClient: vitest_1.vi.fn(),
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
vitest_1.vi.mock("@xalia/xmcp/sdk", () => ({
|
|
47
|
+
getLogger: () => ({
|
|
48
|
+
info: vitest_1.vi.fn(),
|
|
49
|
+
warn: vitest_1.vi.fn(),
|
|
50
|
+
error: vitest_1.vi.fn(),
|
|
51
|
+
debug: vitest_1.vi.fn(),
|
|
52
|
+
}),
|
|
53
|
+
}));
|
|
54
|
+
(0, vitest_1.describe)("LLMTitleGenerator", () => {
|
|
55
|
+
let generator;
|
|
56
|
+
let mockCreate;
|
|
57
|
+
(0, vitest_1.beforeEach)(() => {
|
|
58
|
+
vitest_1.vi.resetAllMocks();
|
|
59
|
+
mockCreate = vitest_1.vi.fn();
|
|
60
|
+
vitest_1.vi.mocked(openAIRouterLLM.getOpenAIClient).mockReturnValue({
|
|
61
|
+
chat: {
|
|
62
|
+
completions: {
|
|
63
|
+
create: mockCreate,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
generator = new titleGenerator_1.LLMTitleGenerator();
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.afterEach)(() => {
|
|
70
|
+
vitest_1.vi.restoreAllMocks();
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)("should generate and sanitize title from LLM", async () => {
|
|
73
|
+
mockCreate.mockResolvedValue({
|
|
74
|
+
choices: [{ message: { content: '"Title\nWith Issues"' } }],
|
|
75
|
+
});
|
|
76
|
+
const title = await generator.generateTitle("Test message");
|
|
77
|
+
(0, vitest_1.expect)(title).toBe("Title With Issues");
|
|
78
|
+
(0, vitest_1.expect)(mockCreate).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
79
|
+
max_tokens: 20,
|
|
80
|
+
temperature: 0.2,
|
|
81
|
+
}));
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)("should fallback on LLM failure", async () => {
|
|
84
|
+
mockCreate.mockRejectedValue(new Error("API error"));
|
|
85
|
+
const title = await generator.generateTitle("First sentence. Second sentence.");
|
|
86
|
+
(0, vitest_1.expect)(title).toBe("First sentence");
|
|
87
|
+
});
|
|
88
|
+
(0, vitest_1.it)("should handle empty messages", async () => {
|
|
89
|
+
(0, vitest_1.expect)(await generator.generateTitle("")).toBe("New Chat");
|
|
90
|
+
(0, vitest_1.expect)(await generator.generateTitle(" ")).toBe("New Chat");
|
|
91
|
+
(0, vitest_1.expect)(mockCreate).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.it)("should enforce 120 char limit on both LLM and fallback", async () => {
|
|
94
|
+
const longText = "A".repeat(150);
|
|
95
|
+
mockCreate.mockResolvedValue({
|
|
96
|
+
choices: [{ message: { content: longText } }],
|
|
97
|
+
});
|
|
98
|
+
const llmTitle = await generator.generateTitle("Test");
|
|
99
|
+
(0, vitest_1.expect)(llmTitle.length).toBe(120);
|
|
100
|
+
mockCreate.mockRejectedValue(new Error("fail"));
|
|
101
|
+
const fallbackTitle = await generator.generateTitle(longText);
|
|
102
|
+
(0, vitest_1.expect)(fallbackTitle.length).toBe(120);
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.it)("should use fallback when DISABLE_LLM_TITLES is true", async () => {
|
|
105
|
+
const originalEnv = process.env.DISABLE_LLM_TITLES;
|
|
106
|
+
process.env.DISABLE_LLM_TITLES = "true";
|
|
107
|
+
const fallbackGenerator = (0, titleGenerator_1.createTitleGenerator)();
|
|
108
|
+
const title = await fallbackGenerator.generateTitle("Hello from User 1");
|
|
109
|
+
(0, vitest_1.expect)(title).toBe("Hello from User 1");
|
|
110
|
+
(0, vitest_1.expect)(mockCreate).not.toHaveBeenCalled();
|
|
111
|
+
process.env.DISABLE_LLM_TITLES = originalEnv;
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -1,271 +1,78 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Chat-specific agent tools.
|
|
4
|
+
*
|
|
5
|
+
* This file contains chat-specific tools like image generation and test tools.
|
|
4
6
|
*/
|
|
5
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.testTool =
|
|
7
|
-
exports.
|
|
8
|
-
exports.isoWithTimezone = isoWithTimezone;
|
|
9
|
-
exports.datetimeTool = datetimeTool;
|
|
10
|
-
exports.calculatorEval = calculatorEval;
|
|
11
|
-
exports.renderTool = renderTool;
|
|
12
|
-
exports.webSearchTool = webSearchTool;
|
|
13
|
-
exports.openURL = openURL;
|
|
14
|
-
exports.openURLTool = openURLTool;
|
|
8
|
+
exports.testTool = void 0;
|
|
9
|
+
exports.editTextTool = editTextTool;
|
|
15
10
|
exports.addDefaultChatTools = addDefaultChatTools;
|
|
16
|
-
const
|
|
17
|
-
const sdk_1 = require("@xalia/xmcp/sdk");
|
|
18
|
-
const htmlToText_1 = require("../utils/htmlToText");
|
|
19
|
-
const search_1 = require("../utils/search");
|
|
20
|
-
const sessionFileManager_1 = require("./sessionFileManager");
|
|
11
|
+
const tools_1 = require("../../agent/tools");
|
|
21
12
|
const imageGeneratorTools_1 = require("./imageGeneratorTools");
|
|
22
|
-
const logger = (0, sdk_1.getLogger)();
|
|
23
13
|
const DEVELOPMENT = process.env.DEVELOPMENT === "1";
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* multiple string parameters with the given names. e.g.
|
|
27
|
-
*
|
|
28
|
-
* const parseFn = makeParseArgsFn(
|
|
29
|
-
* ["arg0", "arg1"] as const,
|
|
30
|
-
* ["opt0"] as const)
|
|
31
|
-
*
|
|
32
|
-
* creates
|
|
33
|
-
*
|
|
34
|
-
* parseFn: (args: unknown) => {
|
|
35
|
-
* arg0: string,
|
|
36
|
-
* arg1: string,
|
|
37
|
-
* opt0: string|undefined
|
|
38
|
-
* }
|
|
39
|
-
*
|
|
40
|
-
* which can be used to parse tool arguments.
|
|
41
|
-
*
|
|
42
|
-
* NOTE, the complex type parameters ensures that the name list is a
|
|
43
|
-
* compile-time value, which in turn ensures that the return value of this
|
|
44
|
-
* function is well-typed.
|
|
45
|
-
*/
|
|
46
|
-
function makeParseArgsFn(names, optNames) {
|
|
47
|
-
return (args) => {
|
|
48
|
-
if (!args || typeof args !== "object") {
|
|
49
|
-
throw new Error(`invalid args: ${typeof args}`);
|
|
50
|
-
}
|
|
51
|
-
const argsObj = args;
|
|
52
|
-
for (const name of names) {
|
|
53
|
-
const val = argsObj[name];
|
|
54
|
-
if (typeof val !== "string") {
|
|
55
|
-
throw new Error(`invalid expr args.${name}: ${typeof val}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (optNames) {
|
|
59
|
-
for (const name of optNames) {
|
|
60
|
-
const val = argsObj[name];
|
|
61
|
-
if (typeof val !== "undefined" && typeof val !== "string") {
|
|
62
|
-
throw new Error(`invalid expr args.${name}: ${typeof val}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return argsObj;
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
const DATETIME_DESC = {
|
|
70
|
-
type: "function",
|
|
71
|
-
function: {
|
|
72
|
-
name: "time_now",
|
|
73
|
-
description: "Current time",
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
function isoWithTimezone(timeZone) {
|
|
77
|
-
return (new Intl.DateTimeFormat("sv-SE", {
|
|
78
|
-
timeZone,
|
|
79
|
-
year: "numeric",
|
|
80
|
-
month: "2-digit",
|
|
81
|
-
day: "2-digit",
|
|
82
|
-
hour: "2-digit",
|
|
83
|
-
minute: "2-digit",
|
|
84
|
-
second: "2-digit",
|
|
85
|
-
hour12: false,
|
|
86
|
-
timeZoneName: "short",
|
|
87
|
-
})
|
|
88
|
-
.format(new Date())
|
|
89
|
-
.replace(" ", "T") + ` (${timeZone})`);
|
|
90
|
-
}
|
|
91
|
-
function datetimeTool(timezone) {
|
|
92
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
93
|
-
const toolFn = async () => {
|
|
94
|
-
return { response: isoWithTimezone(timezone) };
|
|
95
|
-
};
|
|
96
|
-
return {
|
|
97
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
98
|
-
setup: async (agent) => {
|
|
99
|
-
agent.addAgentTool(DATETIME_DESC, toolFn);
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const ARITHMETIC_DESC = {
|
|
14
|
+
const TEST = process.env.TEST === "1";
|
|
15
|
+
const EDIT_TEXT_DESC = {
|
|
104
16
|
type: "function",
|
|
105
17
|
function: {
|
|
106
|
-
name: "
|
|
107
|
-
description: "
|
|
18
|
+
name: "edit_text",
|
|
19
|
+
description: "Modify text in the user's editor. Use this to write, update, " +
|
|
20
|
+
"or add content to the editor when the user asks you to create or " +
|
|
21
|
+
"edit text content. If the user quoted text from the editor, use " +
|
|
22
|
+
"'replace_selection' to modify only that quoted text.",
|
|
108
23
|
parameters: {
|
|
109
24
|
type: "object",
|
|
110
25
|
properties: {
|
|
111
|
-
|
|
112
|
-
type: "string",
|
|
113
|
-
description: "Expression containing +-*/()",
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
required: ["expr"],
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
function calculatorEval(args) {
|
|
121
|
-
try {
|
|
122
|
-
return String(expr_eval_1.Parser.evaluate(args));
|
|
123
|
-
}
|
|
124
|
-
catch (e) {
|
|
125
|
-
if (typeof e.message === "string") {
|
|
126
|
-
return e.message;
|
|
127
|
-
}
|
|
128
|
-
return String(e);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
exports.calculatorTool = {
|
|
132
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
133
|
-
setup: async (agent) => {
|
|
134
|
-
const getExpr = makeParseArgsFn(["expr"]);
|
|
135
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
136
|
-
const toolFn = async (_, args) => {
|
|
137
|
-
const { expr } = getExpr(args);
|
|
138
|
-
return { response: calculatorEval(expr) };
|
|
139
|
-
};
|
|
140
|
-
agent.addAgentTool(ARITHMETIC_DESC, toolFn);
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
const RENDER_DESC = {
|
|
144
|
-
type: "function",
|
|
145
|
-
function: {
|
|
146
|
-
name: "render",
|
|
147
|
-
description: "Display the given html fragment inside a div element",
|
|
148
|
-
parameters: {
|
|
149
|
-
type: "object",
|
|
150
|
-
properties: {
|
|
151
|
-
name: {
|
|
152
|
-
type: "string",
|
|
153
|
-
description: "Filename to store the html",
|
|
154
|
-
},
|
|
155
|
-
summary: {
|
|
26
|
+
operation: {
|
|
156
27
|
type: "string",
|
|
157
|
-
|
|
28
|
+
enum: ["replace", "append", "prepend", "replace_selection"],
|
|
29
|
+
description: "How to modify the editor: 'replace' to overwrite all " +
|
|
30
|
+
"content, 'append' to add at the end, 'prepend' to add at " +
|
|
31
|
+
"the beginning, 'replace_selection' to replace the " +
|
|
32
|
+
"quoted/selected text",
|
|
158
33
|
},
|
|
159
|
-
|
|
34
|
+
content: {
|
|
160
35
|
type: "string",
|
|
161
|
-
description: "
|
|
36
|
+
description: "The markdown content to set or add to the editor",
|
|
162
37
|
},
|
|
163
38
|
},
|
|
164
|
-
required: ["
|
|
39
|
+
required: ["operation", "content"],
|
|
165
40
|
},
|
|
166
41
|
},
|
|
167
42
|
};
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
description: "Web search",
|
|
197
|
-
parameters: {
|
|
198
|
-
type: "object",
|
|
199
|
-
properties: {
|
|
200
|
-
query: {
|
|
201
|
-
type: "string",
|
|
202
|
-
description: "Search query text",
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
required: ["query"],
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
function webSearchTool() {
|
|
210
|
-
const getQuery = makeParseArgsFn(["query"]);
|
|
211
|
-
const toolFn = async (_, args) => {
|
|
212
|
-
const { query } = getQuery(args);
|
|
213
|
-
logger.debug(`[web_search]: query: ${query}`);
|
|
214
|
-
const results = await (0, search_1.webSearch)(query);
|
|
215
|
-
logger.debug(`[web_search]: results: ${results}`);
|
|
216
|
-
return { response: JSON.stringify(results) };
|
|
217
|
-
};
|
|
218
|
-
return {
|
|
219
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
220
|
-
setup: async (agent) => {
|
|
221
|
-
agent.addAgentTool(WEB_SEARCH_DESC, toolFn);
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
// open_url
|
|
226
|
-
/**
|
|
227
|
-
* For now, this matches the duckduckgo-mcp-server length. Could extend
|
|
228
|
-
* depending on the application / model etc.
|
|
229
|
-
*/
|
|
230
|
-
const _OPEN_URL_MAX_LENGTH_STR = process.env["OPEN_URL_MAX_LENGTH"] || "8000";
|
|
231
|
-
const OPEN_URL_MAX_LENGTH = parseInt(_OPEN_URL_MAX_LENGTH_STR, 10);
|
|
232
|
-
const OPEN_URL_DESC = {
|
|
233
|
-
type: "function",
|
|
234
|
-
function: {
|
|
235
|
-
name: "open_url",
|
|
236
|
-
description: "Download content from a URL",
|
|
237
|
-
parameters: {
|
|
238
|
-
type: "object",
|
|
239
|
-
properties: {
|
|
240
|
-
url: {
|
|
241
|
-
type: "string",
|
|
242
|
-
description: "URL to download",
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
required: ["url"],
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
async function openURL(url) {
|
|
250
|
-
const response = await fetch(url);
|
|
251
|
-
if (!response.ok) {
|
|
252
|
-
const status = String(response.status);
|
|
253
|
-
const code = response.statusText;
|
|
254
|
-
throw new Error(`Failed to fetch ${url}: ${status} ${code}`);
|
|
255
|
-
}
|
|
256
|
-
const html = await response.text();
|
|
257
|
-
return (0, htmlToText_1.htmlToText)(html, OPEN_URL_MAX_LENGTH);
|
|
258
|
-
}
|
|
259
|
-
function openURLTool() {
|
|
260
|
-
const getURL = makeParseArgsFn(["url"]);
|
|
261
|
-
const toolFn = async (_, args) => {
|
|
262
|
-
const { url } = getURL(args);
|
|
263
|
-
return { response: await openURL(url) };
|
|
43
|
+
const VALID_OPERATIONS = [
|
|
44
|
+
"replace",
|
|
45
|
+
"append",
|
|
46
|
+
"prepend",
|
|
47
|
+
"replace_selection",
|
|
48
|
+
];
|
|
49
|
+
function editTextTool(messageSender) {
|
|
50
|
+
const parseArgs = (0, tools_1.makeParseArgsFn)(["operation", "content"]);
|
|
51
|
+
const toolFn = (_, args) => {
|
|
52
|
+
const { operation, content } = parseArgs(args);
|
|
53
|
+
// Validate operation
|
|
54
|
+
if (!VALID_OPERATIONS.includes(operation)) {
|
|
55
|
+
return Promise.resolve({
|
|
56
|
+
response: `Invalid operation: ${operation}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Broadcast editor update to all session participants
|
|
60
|
+
messageSender.broadcast({
|
|
61
|
+
type: "editor_update",
|
|
62
|
+
operation: operation,
|
|
63
|
+
content,
|
|
64
|
+
format: "markdown",
|
|
65
|
+
session_id: "", // Filled by the message sender
|
|
66
|
+
});
|
|
67
|
+
return Promise.resolve({
|
|
68
|
+
response: `Editor updated with ${operation} operation`,
|
|
69
|
+
overwriteResponse: "", // Don't clutter context with this response
|
|
70
|
+
});
|
|
264
71
|
};
|
|
265
72
|
return {
|
|
266
73
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
267
74
|
setup: async (agent) => {
|
|
268
|
-
agent.addAgentTool(
|
|
75
|
+
agent.addAgentTool(EDIT_TEXT_DESC, toolFn);
|
|
269
76
|
},
|
|
270
77
|
};
|
|
271
78
|
}
|
|
@@ -297,16 +104,20 @@ exports.testTool = {
|
|
|
297
104
|
};
|
|
298
105
|
/**
|
|
299
106
|
* Add a set of agent tools for chat sessions.
|
|
107
|
+
*
|
|
108
|
+
* This includes all general-purpose tools plus chat-specific tools
|
|
109
|
+
* like image generation.
|
|
300
110
|
*/
|
|
301
|
-
async function addDefaultChatTools(agent, timezone,
|
|
302
|
-
await agent.addAgentToolProvider(datetimeTool(timezone));
|
|
303
|
-
await agent.addAgentToolProvider(
|
|
304
|
-
await agent.addAgentToolProvider(renderTool(fileManager));
|
|
305
|
-
await agent.addAgentToolProvider(webSearchTool());
|
|
306
|
-
await agent.addAgentToolProvider(openURLTool());
|
|
307
|
-
await agent.addAgentToolProvider((0,
|
|
308
|
-
await agent.addAgentToolProvider(await (0, imageGeneratorTools_1.genImageFileTool)(
|
|
309
|
-
|
|
111
|
+
async function addDefaultChatTools(agent, timezone, platform, fileManager, messageSender) {
|
|
112
|
+
await agent.addAgentToolProvider((0, tools_1.datetimeTool)(timezone));
|
|
113
|
+
await agent.addAgentToolProvider(tools_1.calculatorTool);
|
|
114
|
+
await agent.addAgentToolProvider((0, tools_1.renderTool)(fileManager));
|
|
115
|
+
await agent.addAgentToolProvider((0, tools_1.webSearchTool)());
|
|
116
|
+
await agent.addAgentToolProvider((0, tools_1.openURLTool)());
|
|
117
|
+
await agent.addAgentToolProvider((0, tools_1.fileManagerTool)(fileManager));
|
|
118
|
+
await agent.addAgentToolProvider(await (0, imageGeneratorTools_1.genImageFileTool)(platform, fileManager));
|
|
119
|
+
await agent.addAgentToolProvider(editTextTool(messageSender));
|
|
120
|
+
if (DEVELOPMENT || TEST) {
|
|
310
121
|
await agent.addAgentToolProvider(exports.testTool);
|
|
311
122
|
}
|
|
312
123
|
}
|
|
@@ -33,7 +33,7 @@ class ToolApprovalManager {
|
|
|
33
33
|
*
|
|
34
34
|
* The returned `requested` value indicates whether approval was requested.
|
|
35
35
|
*/
|
|
36
|
-
async getApproval(serverName, tool, toolCall) {
|
|
36
|
+
async getApproval(serverName, tool, toolCall, alt) {
|
|
37
37
|
const autoApproved = (0, sdk_1.prefsGetAutoApprove)(this.agentProfilePreferences, serverName, tool);
|
|
38
38
|
if (autoApproved) {
|
|
39
39
|
return { approved: true, requested: false };
|
|
@@ -47,6 +47,7 @@ class ToolApprovalManager {
|
|
|
47
47
|
id,
|
|
48
48
|
tool_call: toolCall,
|
|
49
49
|
session_id: this.sessionUUID,
|
|
50
|
+
...(alt ? { alt } : {}),
|
|
50
51
|
});
|
|
51
52
|
logger.debug(`[ApprovalManager.getApproval] awaiting approval ${id}`);
|
|
52
53
|
const approval = await approvalP;
|
|
@@ -54,7 +55,7 @@ class ToolApprovalManager {
|
|
|
54
55
|
// Handle any auto-approve update, informing other clients.
|
|
55
56
|
if (approval.auto_approve) {
|
|
56
57
|
logger.debug("[ApprovalManager.getApproval] updated preferences");
|
|
57
|
-
const autoApprovalMsg = await this.setAutoApprove(serverName, tool, true);
|
|
58
|
+
const autoApprovalMsg = await this.setAutoApprove(serverName, tool, true, alt);
|
|
58
59
|
if (autoApprovalMsg) {
|
|
59
60
|
this.sender.broadcast(autoApprovalMsg);
|
|
60
61
|
}
|
|
@@ -65,6 +66,7 @@ class ToolApprovalManager {
|
|
|
65
66
|
id: approval.id,
|
|
66
67
|
result: approval.result,
|
|
67
68
|
session_id: this.sessionUUID,
|
|
69
|
+
...(alt ? { alt } : {}),
|
|
68
70
|
});
|
|
69
71
|
return { approved: approval.result, requested: true };
|
|
70
72
|
}
|
|
@@ -78,7 +80,7 @@ class ToolApprovalManager {
|
|
|
78
80
|
* Handle a request to set auto-approval for a given tool. If there was a
|
|
79
81
|
* change, return the message to be broadcast.
|
|
80
82
|
*/
|
|
81
|
-
async setAutoApprove(serverName, tool, autoApprove) {
|
|
83
|
+
async setAutoApprove(serverName, tool, autoApprove, alt) {
|
|
82
84
|
if ((0, sdk_1.prefsSetAutoApprove)(this.agentProfilePreferences, serverName, tool, autoApprove)) {
|
|
83
85
|
await this.writer.updatePreferences(this.agentProfileUUID, this.agentProfilePreferences);
|
|
84
86
|
return {
|
|
@@ -87,6 +89,7 @@ class ToolApprovalManager {
|
|
|
87
89
|
tool,
|
|
88
90
|
auto_approve: autoApprove,
|
|
89
91
|
session_id: this.sessionUUID,
|
|
92
|
+
...(alt ? { alt } : {}),
|
|
90
93
|
};
|
|
91
94
|
}
|
|
92
95
|
}
|