@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
package/src/chat/server/tools.ts
CHANGED
|
@@ -1,307 +1,111 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Chat-specific agent tools.
|
|
3
|
+
*
|
|
4
|
+
* This file contains chat-specific tools like image generation and test tools.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
import { Parser } from "expr-eval";
|
|
6
|
-
|
|
7
|
-
import { getLogger } from "@xalia/xmcp/sdk";
|
|
8
|
-
|
|
9
|
-
import { Agent, IAgentToolProvider, ToolCallResult } from "../../agent/agent";
|
|
10
|
-
import { htmlToText } from "../utils/htmlToText";
|
|
11
|
-
import { webSearch } from "../utils/search";
|
|
12
7
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
Agent,
|
|
9
|
+
AgentEx,
|
|
10
|
+
IAgentToolProvider,
|
|
11
|
+
ToolCallResult,
|
|
12
|
+
} from "../../agent/agent";
|
|
18
13
|
import { ToolDescriptor } from "../../agent/llm";
|
|
14
|
+
import { IPlatform } from "../../agent/iplatform";
|
|
15
|
+
import {
|
|
16
|
+
datetimeTool,
|
|
17
|
+
calculatorTool,
|
|
18
|
+
webSearchTool,
|
|
19
|
+
openURLTool,
|
|
20
|
+
renderTool,
|
|
21
|
+
fileManagerTool,
|
|
22
|
+
ISessionFileManager,
|
|
23
|
+
makeParseArgsFn,
|
|
24
|
+
} from "../../agent/tools";
|
|
25
|
+
import { ISessionMessageSender } from "./openSessionMessageSender";
|
|
26
|
+
import { ServerToClient } from "../protocol/messages";
|
|
19
27
|
|
|
20
|
-
|
|
28
|
+
import { genImageFileTool } from "./imageGeneratorTools";
|
|
21
29
|
|
|
22
30
|
const DEVELOPMENT: boolean = process.env.DEVELOPMENT === "1";
|
|
31
|
+
const TEST: boolean = process.env.TEST === "1";
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
* Returns a function which parses an `args` struct and attempts to extract
|
|
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
|
-
export function makeParseArgsFn<
|
|
47
|
-
T extends readonly string[] & (string extends T[number] ? never : unknown),
|
|
48
|
-
U extends readonly string[] & (string extends U[number] ? never : unknown),
|
|
49
|
-
>(
|
|
50
|
-
names: T,
|
|
51
|
-
optNames?: U
|
|
52
|
-
): (
|
|
53
|
-
args: unknown
|
|
54
|
-
) => { [K in T[number]]: string } & { [K in U[number]]: string | undefined } {
|
|
55
|
-
return (args: unknown) => {
|
|
56
|
-
if (!args || typeof args !== "object") {
|
|
57
|
-
throw new Error(`invalid args: ${typeof args}`);
|
|
58
|
-
}
|
|
59
|
-
const argsObj = args as Record<string, string | undefined>;
|
|
60
|
-
for (const name of names) {
|
|
61
|
-
const val = argsObj[name];
|
|
62
|
-
if (typeof val !== "string") {
|
|
63
|
-
throw new Error(`invalid expr args.${name}: ${typeof val}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (optNames) {
|
|
67
|
-
for (const name of optNames) {
|
|
68
|
-
const val = argsObj[name];
|
|
69
|
-
if (typeof val !== "undefined" && typeof val !== "string") {
|
|
70
|
-
throw new Error(`invalid expr args.${name}: ${typeof val}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return argsObj as { [K in T[number]]: string } & {
|
|
76
|
-
[K in U[number]]: string | undefined;
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const DATETIME_DESC: ToolDescriptor = {
|
|
82
|
-
type: "function",
|
|
83
|
-
function: {
|
|
84
|
-
name: "time_now",
|
|
85
|
-
description: "Current time",
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export function isoWithTimezone(timeZone: string): string {
|
|
90
|
-
return (
|
|
91
|
-
new Intl.DateTimeFormat("sv-SE", {
|
|
92
|
-
timeZone,
|
|
93
|
-
year: "numeric",
|
|
94
|
-
month: "2-digit",
|
|
95
|
-
day: "2-digit",
|
|
96
|
-
hour: "2-digit",
|
|
97
|
-
minute: "2-digit",
|
|
98
|
-
second: "2-digit",
|
|
99
|
-
hour12: false,
|
|
100
|
-
timeZoneName: "short",
|
|
101
|
-
})
|
|
102
|
-
.format(new Date())
|
|
103
|
-
.replace(" ", "T") + ` (${timeZone})`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function datetimeTool(timezone: string): IAgentToolProvider {
|
|
108
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
109
|
-
const toolFn = async () => {
|
|
110
|
-
return { response: isoWithTimezone(timezone) };
|
|
111
|
-
};
|
|
112
|
-
return {
|
|
113
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
114
|
-
setup: async (agent: Agent) => {
|
|
115
|
-
agent.addAgentTool(DATETIME_DESC, toolFn);
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const ARITHMETIC_DESC: ToolDescriptor = {
|
|
121
|
-
type: "function",
|
|
122
|
-
function: {
|
|
123
|
-
name: "arithmetic",
|
|
124
|
-
description: "Evaluate arithmetic expression",
|
|
125
|
-
parameters: {
|
|
126
|
-
type: "object",
|
|
127
|
-
properties: {
|
|
128
|
-
expr: {
|
|
129
|
-
type: "string",
|
|
130
|
-
description: "Expression containing +-*/()",
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
required: ["expr"],
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export function calculatorEval(args: string): string {
|
|
139
|
-
try {
|
|
140
|
-
return String(Parser.evaluate(args));
|
|
141
|
-
} catch (e) {
|
|
142
|
-
if (typeof (e as { message: string }).message === "string") {
|
|
143
|
-
return (e as { message: string }).message;
|
|
144
|
-
}
|
|
145
|
-
return String(e);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export const calculatorTool: IAgentToolProvider = {
|
|
150
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
151
|
-
setup: async (agent: Agent) => {
|
|
152
|
-
const getExpr = makeParseArgsFn(["expr"] as const);
|
|
153
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
154
|
-
const toolFn = async (_: Agent, args: unknown): Promise<ToolCallResult> => {
|
|
155
|
-
const { expr } = getExpr(args);
|
|
156
|
-
return { response: calculatorEval(expr) };
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
agent.addAgentTool(ARITHMETIC_DESC, toolFn);
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const RENDER_DESC: ToolDescriptor = {
|
|
33
|
+
const EDIT_TEXT_DESC: ToolDescriptor = {
|
|
164
34
|
type: "function",
|
|
165
35
|
function: {
|
|
166
|
-
name: "
|
|
167
|
-
description:
|
|
36
|
+
name: "edit_text",
|
|
37
|
+
description:
|
|
38
|
+
"Modify text in the user's editor. Use this to write, update, " +
|
|
39
|
+
"or add content to the editor when the user asks you to create or " +
|
|
40
|
+
"edit text content. If the user quoted text from the editor, use " +
|
|
41
|
+
"'replace_selection' to modify only that quoted text.",
|
|
168
42
|
parameters: {
|
|
169
43
|
type: "object",
|
|
170
44
|
properties: {
|
|
171
|
-
|
|
172
|
-
type: "string",
|
|
173
|
-
description: "Filename to store the html",
|
|
174
|
-
},
|
|
175
|
-
summary: {
|
|
45
|
+
operation: {
|
|
176
46
|
type: "string",
|
|
177
|
-
|
|
47
|
+
enum: ["replace", "append", "prepend", "replace_selection"],
|
|
48
|
+
description:
|
|
49
|
+
"How to modify the editor: 'replace' to overwrite all " +
|
|
50
|
+
"content, 'append' to add at the end, 'prepend' to add at " +
|
|
51
|
+
"the beginning, 'replace_selection' to replace the " +
|
|
52
|
+
"quoted/selected text",
|
|
178
53
|
},
|
|
179
|
-
|
|
54
|
+
content: {
|
|
180
55
|
type: "string",
|
|
181
|
-
description: "
|
|
56
|
+
description: "The markdown content to set or add to the editor",
|
|
182
57
|
},
|
|
183
58
|
},
|
|
184
|
-
required: ["
|
|
59
|
+
required: ["operation", "content"],
|
|
185
60
|
},
|
|
186
61
|
},
|
|
187
62
|
};
|
|
188
63
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
] as const);
|
|
197
|
-
const toolFn = async (
|
|
198
|
-
_: Agent,
|
|
199
|
-
args: unknown
|
|
200
|
-
): Promise<ToolCallResultWithFileRef> => {
|
|
201
|
-
const { name, summary, html } = getNameSummeryHtml(args);
|
|
202
|
-
const mimeType = "text/html";
|
|
203
|
-
const dataURL = `data:${mimeType},${html}`;
|
|
204
|
-
await fileManager.putFileContent(name, summary, dataURL);
|
|
205
|
-
const uri = fileManager.getSessionFileRelativeUrl(name);
|
|
206
|
-
return {
|
|
207
|
-
response: "",
|
|
208
|
-
_meta: { "xalia/fileUri": uri, "xalia/fileMimeType": mimeType },
|
|
209
|
-
};
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
214
|
-
setup: async (agent: Agent) => {
|
|
215
|
-
agent.addAgentTool(RENDER_DESC, toolFn);
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const WEB_SEARCH_DESC: ToolDescriptor = {
|
|
221
|
-
type: "function",
|
|
222
|
-
function: {
|
|
223
|
-
name: "web_search",
|
|
224
|
-
description: "Web search",
|
|
225
|
-
parameters: {
|
|
226
|
-
type: "object",
|
|
227
|
-
properties: {
|
|
228
|
-
query: {
|
|
229
|
-
type: "string",
|
|
230
|
-
description: "Search query text",
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
required: ["query"],
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
export function webSearchTool(): IAgentToolProvider {
|
|
239
|
-
const getQuery = makeParseArgsFn(["query"] as const);
|
|
240
|
-
const toolFn = async (_: Agent, args: unknown): Promise<ToolCallResult> => {
|
|
241
|
-
const { query } = getQuery(args);
|
|
242
|
-
logger.debug(`[web_search]: query: ${query}`);
|
|
243
|
-
const results = await webSearch(query);
|
|
244
|
-
logger.debug(`[web_search]: results: ${results}`);
|
|
245
|
-
return { response: JSON.stringify(results) };
|
|
246
|
-
};
|
|
64
|
+
type EditorOperation = "replace" | "append" | "prepend" | "replace_selection";
|
|
65
|
+
const VALID_OPERATIONS: EditorOperation[] = [
|
|
66
|
+
"replace",
|
|
67
|
+
"append",
|
|
68
|
+
"prepend",
|
|
69
|
+
"replace_selection",
|
|
70
|
+
];
|
|
247
71
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
},
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// open_url
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* For now, this matches the duckduckgo-mcp-server length. Could extend
|
|
260
|
-
* depending on the application / model etc.
|
|
261
|
-
*/
|
|
262
|
-
const _OPEN_URL_MAX_LENGTH_STR = process.env["OPEN_URL_MAX_LENGTH"] || "8000";
|
|
263
|
-
const OPEN_URL_MAX_LENGTH: number = parseInt(_OPEN_URL_MAX_LENGTH_STR, 10);
|
|
72
|
+
export function editTextTool(
|
|
73
|
+
messageSender: ISessionMessageSender<ServerToClient>
|
|
74
|
+
): IAgentToolProvider {
|
|
75
|
+
const parseArgs = makeParseArgsFn(["operation", "content"] as const);
|
|
264
76
|
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
parameters: {
|
|
271
|
-
type: "object",
|
|
272
|
-
properties: {
|
|
273
|
-
url: {
|
|
274
|
-
type: "string",
|
|
275
|
-
description: "URL to download",
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
required: ["url"],
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
};
|
|
77
|
+
const toolFn = (
|
|
78
|
+
_: AgentEx,
|
|
79
|
+
args: unknown
|
|
80
|
+
): Promise<ToolCallResult> => {
|
|
81
|
+
const { operation, content } = parseArgs(args);
|
|
282
82
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
const html = await response.text();
|
|
291
|
-
return htmlToText(html, OPEN_URL_MAX_LENGTH);
|
|
292
|
-
}
|
|
83
|
+
// Validate operation
|
|
84
|
+
if (!VALID_OPERATIONS.includes(operation as EditorOperation)) {
|
|
85
|
+
return Promise.resolve({
|
|
86
|
+
response: `Invalid operation: ${operation}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
293
89
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
90
|
+
// Broadcast editor update to all session participants
|
|
91
|
+
messageSender.broadcast({
|
|
92
|
+
type: "editor_update",
|
|
93
|
+
operation: operation as EditorOperation,
|
|
94
|
+
content,
|
|
95
|
+
format: "markdown",
|
|
96
|
+
session_id: "", // Filled by the message sender
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return Promise.resolve({
|
|
100
|
+
response: `Editor updated with ${operation} operation`,
|
|
101
|
+
overwriteResponse: "", // Don't clutter context with this response
|
|
102
|
+
});
|
|
299
103
|
};
|
|
300
104
|
|
|
301
105
|
return {
|
|
302
106
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
303
|
-
setup: async (agent:
|
|
304
|
-
agent.addAgentTool(
|
|
107
|
+
setup: async (agent: AgentEx) => {
|
|
108
|
+
agent.addAgentTool(EDIT_TEXT_DESC, toolFn);
|
|
305
109
|
},
|
|
306
110
|
};
|
|
307
111
|
}
|
|
@@ -320,8 +124,11 @@ const TEST_DESC: ToolDescriptor = {
|
|
|
320
124
|
};
|
|
321
125
|
|
|
322
126
|
export const testTool: IAgentToolProvider = {
|
|
323
|
-
setup: (agent:
|
|
324
|
-
const toolFn = (
|
|
127
|
+
setup: (agent: AgentEx) => {
|
|
128
|
+
const toolFn = (
|
|
129
|
+
_agent: AgentEx,
|
|
130
|
+
_args: unknown
|
|
131
|
+
): Promise<ToolCallResult> => {
|
|
325
132
|
// Return an object with structuredContent and _meta
|
|
326
133
|
return Promise.resolve({
|
|
327
134
|
response: "Some text",
|
|
@@ -336,13 +143,16 @@ export const testTool: IAgentToolProvider = {
|
|
|
336
143
|
|
|
337
144
|
/**
|
|
338
145
|
* Add a set of agent tools for chat sessions.
|
|
146
|
+
*
|
|
147
|
+
* This includes all general-purpose tools plus chat-specific tools
|
|
148
|
+
* like image generation.
|
|
339
149
|
*/
|
|
340
150
|
export async function addDefaultChatTools(
|
|
341
|
-
agent: Agent,
|
|
151
|
+
agent: AgentEx | Agent,
|
|
342
152
|
timezone: string,
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
153
|
+
platform: IPlatform,
|
|
154
|
+
fileManager: ISessionFileManager,
|
|
155
|
+
messageSender: ISessionMessageSender<ServerToClient>
|
|
346
156
|
): Promise<void> {
|
|
347
157
|
await agent.addAgentToolProvider(datetimeTool(timezone));
|
|
348
158
|
await agent.addAgentToolProvider(calculatorTool);
|
|
@@ -351,9 +161,10 @@ export async function addDefaultChatTools(
|
|
|
351
161
|
await agent.addAgentToolProvider(openURLTool());
|
|
352
162
|
await agent.addAgentToolProvider(fileManagerTool(fileManager));
|
|
353
163
|
await agent.addAgentToolProvider(
|
|
354
|
-
await genImageFileTool(
|
|
164
|
+
await genImageFileTool(platform, fileManager)
|
|
355
165
|
);
|
|
356
|
-
|
|
166
|
+
await agent.addAgentToolProvider(editTextTool(messageSender));
|
|
167
|
+
if (DEVELOPMENT || TEST) {
|
|
357
168
|
await agent.addAgentToolProvider(testTool);
|
|
358
169
|
}
|
|
359
170
|
}
|
|
@@ -76,7 +76,8 @@ export class ToolApprovalManager {
|
|
|
76
76
|
public async getApproval(
|
|
77
77
|
serverName: string,
|
|
78
78
|
tool: string,
|
|
79
|
-
toolCall: MessageToolCall
|
|
79
|
+
toolCall: MessageToolCall,
|
|
80
|
+
alt?: string
|
|
80
81
|
): Promise<{ approved: boolean; requested: boolean }> {
|
|
81
82
|
const autoApproved = prefsGetAutoApprove(
|
|
82
83
|
this.agentProfilePreferences,
|
|
@@ -98,6 +99,7 @@ export class ToolApprovalManager {
|
|
|
98
99
|
id,
|
|
99
100
|
tool_call: toolCall,
|
|
100
101
|
session_id: this.sessionUUID,
|
|
102
|
+
...(alt ? { alt } : {}),
|
|
101
103
|
});
|
|
102
104
|
|
|
103
105
|
logger.debug(`[ApprovalManager.getApproval] awaiting approval ${id}`);
|
|
@@ -113,7 +115,8 @@ export class ToolApprovalManager {
|
|
|
113
115
|
const autoApprovalMsg = await this.setAutoApprove(
|
|
114
116
|
serverName,
|
|
115
117
|
tool,
|
|
116
|
-
true
|
|
118
|
+
true,
|
|
119
|
+
alt
|
|
117
120
|
);
|
|
118
121
|
if (autoApprovalMsg) {
|
|
119
122
|
this.sender.broadcast(autoApprovalMsg);
|
|
@@ -127,6 +130,7 @@ export class ToolApprovalManager {
|
|
|
127
130
|
id: approval.id,
|
|
128
131
|
result: approval.result,
|
|
129
132
|
session_id: this.sessionUUID,
|
|
133
|
+
...(alt ? { alt } : {}),
|
|
130
134
|
});
|
|
131
135
|
|
|
132
136
|
return { approved: approval.result, requested: true };
|
|
@@ -146,7 +150,8 @@ export class ToolApprovalManager {
|
|
|
146
150
|
public async setAutoApprove(
|
|
147
151
|
serverName: string,
|
|
148
152
|
tool: string,
|
|
149
|
-
autoApprove: boolean
|
|
153
|
+
autoApprove: boolean,
|
|
154
|
+
alt?: string
|
|
150
155
|
): Promise<ServerToolAutoApprovalSet | undefined> {
|
|
151
156
|
if (
|
|
152
157
|
prefsSetAutoApprove(
|
|
@@ -166,6 +171,7 @@ export class ToolApprovalManager {
|
|
|
166
171
|
tool,
|
|
167
172
|
auto_approve: autoApprove,
|
|
168
173
|
session_id: this.sessionUUID,
|
|
174
|
+
...(alt ? { alt } : {}),
|
|
169
175
|
};
|
|
170
176
|
}
|
|
171
177
|
}
|
|
@@ -22,6 +22,10 @@ export class MultiAsyncQueue<T> {
|
|
|
22
22
|
return this.maxBacklog;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
public getEntries(): T[] {
|
|
26
|
+
return this.queue.slice();
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
public tryEnqueue(queueEntry: T): boolean {
|
|
26
30
|
if (this.maxBacklog > 0 && this.queue.length >= this.maxBacklog) {
|
|
27
31
|
return false;
|
package/src/test/agent.test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Schema } from "@xalia/xmcp/sdk";
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
Agent,
|
|
8
|
+
AgentEx,
|
|
8
9
|
AgentProfile,
|
|
9
10
|
IAgentToolProvider,
|
|
10
11
|
ToolCallResult,
|
|
@@ -66,9 +67,9 @@ function createCallTestToolScript(
|
|
|
66
67
|
expectAgentMessages: string[];
|
|
67
68
|
expectToolResults: ToolMessageParam[];
|
|
68
69
|
tool0_descriptor: ToolDescriptor;
|
|
69
|
-
tool0_fn: (agent:
|
|
70
|
+
tool0_fn: (agent: AgentEx, args: unknown) => Promise<ToolCallResult>;
|
|
70
71
|
test_tool_descriptor: ToolDescriptor;
|
|
71
|
-
test_tool_fn: (agent:
|
|
72
|
+
test_tool_fn: (agent: AgentEx, args: unknown) => Promise<ToolCallResult>;
|
|
72
73
|
} {
|
|
73
74
|
// A tool with no args
|
|
74
75
|
|
|
@@ -86,7 +87,7 @@ function createCallTestToolScript(
|
|
|
86
87
|
},
|
|
87
88
|
};
|
|
88
89
|
|
|
89
|
-
const tool0_fn = (_:
|
|
90
|
+
const tool0_fn = (_: AgentEx, _args: unknown): Promise<ToolCallResult> => {
|
|
90
91
|
return Promise.resolve({ response: "0" });
|
|
91
92
|
};
|
|
92
93
|
|
|
@@ -115,7 +116,7 @@ function createCallTestToolScript(
|
|
|
115
116
|
};
|
|
116
117
|
|
|
117
118
|
const test_tool_fn = async (
|
|
118
|
-
_:
|
|
119
|
+
_: AgentEx,
|
|
119
120
|
args: unknown
|
|
120
121
|
): Promise<ToolCallResult> => {
|
|
121
122
|
const { param1, param2 } = args as { param1: string; param2: number };
|
|
@@ -278,8 +279,8 @@ describe("Agent", () => {
|
|
|
278
279
|
expect(eventHandler.getAgentMessages()).eql(expectAgentMessages);
|
|
279
280
|
expect(eventHandler.getToolCallResults()).eql(expectToolResults);
|
|
280
281
|
|
|
281
|
-
// Check the ordering
|
|
282
|
-
|
|
282
|
+
// Check the ordering: completion message is broadcast before tool
|
|
283
|
+
// results to match LLM context order for correct DB persistence.
|
|
283
284
|
const allExpect = [
|
|
284
285
|
completionToAssistantMessageParam(script[0].message),
|
|
285
286
|
...expectToolResults,
|
|
@@ -312,7 +313,7 @@ describe("Agent", () => {
|
|
|
312
313
|
await createTestAgent(script);
|
|
313
314
|
|
|
314
315
|
const toolProvider: IAgentToolProvider = {
|
|
315
|
-
setup: (agent:
|
|
316
|
+
setup: (agent: AgentEx) => {
|
|
316
317
|
// Add the tool async to test this mechanism
|
|
317
318
|
return new Promise<void>((r) => {
|
|
318
319
|
setTimeout(() => {
|
|
@@ -387,22 +388,13 @@ describe("Agent", () => {
|
|
|
387
388
|
|
|
388
389
|
await agent.userMessageEx("user message 1");
|
|
389
390
|
|
|
390
|
-
// Check the event handler was called with all completions and tool
|
|
391
|
-
// results, in the correct order.
|
|
392
|
-
|
|
393
391
|
const allExpect = [
|
|
394
392
|
completionToAssistantMessageParam(script[0].message),
|
|
395
393
|
...expectToolResults,
|
|
396
394
|
completionToAssistantMessageParam(script[1].message),
|
|
397
395
|
];
|
|
398
|
-
|
|
399
|
-
expect(
|
|
400
|
-
|
|
401
|
-
// The conversation (context) messages (order and number) should also
|
|
402
|
-
// match this.
|
|
403
|
-
|
|
404
|
-
const conv = agent.getConversation();
|
|
405
|
-
expect(conv.slice(1)).eql(all);
|
|
396
|
+
expect(eventHandler.getAll()).eql(allExpect);
|
|
397
|
+
expect(agent.getConversation().slice(1)).eql(allExpect);
|
|
406
398
|
});
|
|
407
399
|
|
|
408
400
|
it("correctly updates tool call args", async function () {
|
|
@@ -437,7 +429,7 @@ describe("Agent", () => {
|
|
|
437
429
|
return JSON.stringify(transformArgs(args));
|
|
438
430
|
};
|
|
439
431
|
const newToolFn = async (
|
|
440
|
-
agent:
|
|
432
|
+
agent: AgentEx,
|
|
441
433
|
args: unknown
|
|
442
434
|
): Promise<ToolCallResult> => {
|
|
443
435
|
const result = await test_tool_fn(agent, args);
|
|
@@ -478,11 +470,8 @@ describe("Agent", () => {
|
|
|
478
470
|
...expectToolResults,
|
|
479
471
|
completionToAssistantMessageParam(scriptCopy[1].message),
|
|
480
472
|
];
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
expect(all).eql(allExpect);
|
|
485
|
-
expect(conv.slice(1)).eql(all);
|
|
473
|
+
expect(eventHandler.getAll()).eql(allExpect);
|
|
474
|
+
expect(agent.getConversation().slice(1)).eql(allExpect);
|
|
486
475
|
});
|
|
487
476
|
|
|
488
477
|
it("correctly updates tool call results", async function () {
|
|
@@ -507,7 +496,7 @@ describe("Agent", () => {
|
|
|
507
496
|
return result.toUpperCase();
|
|
508
497
|
};
|
|
509
498
|
const newToolFn = async (
|
|
510
|
-
agent:
|
|
499
|
+
agent: AgentEx,
|
|
511
500
|
args: unknown
|
|
512
501
|
): Promise<ToolCallResult> => {
|
|
513
502
|
const result = await test_tool_fn(agent, args);
|
|
@@ -534,16 +523,22 @@ describe("Agent", () => {
|
|
|
534
523
|
}
|
|
535
524
|
);
|
|
536
525
|
|
|
526
|
+
// Due to object mutation, first tool result appears transformed
|
|
537
527
|
const allExpect: MessageParam[] = [
|
|
538
528
|
completionToAssistantMessageParam(script[0].message),
|
|
539
|
-
|
|
529
|
+
expectToolCallResults[0],
|
|
530
|
+
expectToolResults[1],
|
|
531
|
+
expectToolCallResults[0],
|
|
540
532
|
completionToAssistantMessageParam(script[1].message),
|
|
541
533
|
];
|
|
542
|
-
|
|
543
|
-
const conv = agent.getConversation();
|
|
534
|
+
expect(eventHandler.getAll()).eql(allExpect);
|
|
544
535
|
|
|
545
|
-
|
|
546
|
-
|
|
536
|
+
const convExpect: MessageParam[] = [
|
|
537
|
+
completionToAssistantMessageParam(script[0].message),
|
|
538
|
+
...expectToolCallResults,
|
|
539
|
+
completionToAssistantMessageParam(script[1].message),
|
|
540
|
+
];
|
|
541
|
+
expect(agent.getConversation().slice(1)).eql(convExpect);
|
|
547
542
|
});
|
|
548
543
|
|
|
549
544
|
it("correctly handles LLM errors", async function () {
|