@xalia/agent 0.6.9 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +11 -0
  2. package/dist/agent/src/agent/agent.js +77 -18
  3. package/dist/agent/src/agent/agentUtils.js +3 -2
  4. package/dist/agent/src/agent/documentSummarizer.js +126 -0
  5. package/dist/agent/src/agent/dummyLLM.js +25 -22
  6. package/dist/agent/src/agent/imageGenLLM.js +22 -19
  7. package/dist/agent/src/agent/llm.js +1 -1
  8. package/dist/agent/src/agent/openAILLM.js +15 -12
  9. package/dist/agent/src/agent/openAILLMStreaming.js +68 -37
  10. package/dist/agent/src/agent/repeatLLM.js +16 -7
  11. package/dist/agent/src/agent/tokenCounter.js +390 -0
  12. package/dist/agent/src/agent/tokenCounter.test.js +206 -0
  13. package/dist/agent/src/agent/toolSettings.js +17 -0
  14. package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
  15. package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
  16. package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
  17. package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
  18. package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
  19. package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
  20. package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
  21. package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
  22. package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
  23. package/dist/agent/src/agent/tools/index.js +64 -0
  24. package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
  25. package/dist/agent/src/agent/tools/renderTool.js +89 -0
  26. package/dist/agent/src/agent/tools/utils.js +61 -0
  27. package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
  28. package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
  29. package/dist/agent/src/chat/client/chatClient.js +28 -0
  30. package/dist/agent/src/chat/client/index.js +4 -1
  31. package/dist/agent/src/chat/client/sessionClient.js +28 -2
  32. package/dist/agent/src/chat/constants.js +8 -0
  33. package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
  34. package/dist/agent/src/chat/protocol/messages.js +5 -0
  35. package/dist/agent/src/chat/server/chatContextManager.js +45 -25
  36. package/dist/agent/src/chat/server/conversation.js +3 -0
  37. package/dist/agent/src/chat/server/imageGeneratorTools.js +20 -8
  38. package/dist/agent/src/chat/server/openAIRouterLLM.js +0 -3
  39. package/dist/agent/src/chat/server/openSession.js +218 -55
  40. package/dist/agent/src/chat/server/promptRefiner.js +86 -0
  41. package/dist/agent/src/chat/server/server.js +5 -1
  42. package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
  43. package/dist/agent/src/chat/server/sessionRegistry.js +87 -0
  44. package/dist/agent/src/chat/server/titleGenerator.js +112 -0
  45. package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
  46. package/dist/agent/src/chat/server/tools.js +63 -287
  47. package/dist/agent/src/chat/utils/approvalManager.js +6 -3
  48. package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
  49. package/dist/agent/src/test/agent.test.js +16 -17
  50. package/dist/agent/src/test/chatContextManager.test.js +15 -3
  51. package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
  52. package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
  53. package/dist/agent/src/test/testTools.js +6 -1
  54. package/dist/agent/src/test/tools.test.js +27 -9
  55. package/dist/agent/src/tool/agentChat.js +5 -2
  56. package/dist/agent/src/tool/chatMain.js +34 -7
  57. package/dist/agent/src/tool/commandPrompt.js +2 -2
  58. package/dist/agent/src/tool/files.js +7 -8
  59. package/package.json +8 -2
  60. package/.env.development +0 -1
  61. package/.prettierrc.json +0 -11
  62. package/dist/agent/src/agent/tools.js +0 -44
  63. package/eslint.config.mjs +0 -38
  64. package/scripts/chat_server +0 -8
  65. package/scripts/git_message +0 -31
  66. package/scripts/git_wip +0 -21
  67. package/scripts/pr_message +0 -18
  68. package/scripts/pr_review +0 -16
  69. package/scripts/setup_chat +0 -90
  70. package/scripts/shutdown_chat_server +0 -42
  71. package/scripts/start_chat_server +0 -24
  72. package/scripts/sudomcp_import +0 -23
  73. package/scripts/test_chat +0 -308
  74. package/src/agent/agent.ts +0 -624
  75. package/src/agent/agentUtils.ts +0 -285
  76. package/src/agent/compressingContextManager.ts +0 -129
  77. package/src/agent/context.ts +0 -265
  78. package/src/agent/contextWithWorkspace.ts +0 -162
  79. package/src/agent/dummyLLM.ts +0 -126
  80. package/src/agent/iAgentEventHandler.ts +0 -64
  81. package/src/agent/imageGenLLM.ts +0 -97
  82. package/src/agent/imageGenerator.ts +0 -45
  83. package/src/agent/iplatform.ts +0 -18
  84. package/src/agent/llm.ts +0 -74
  85. package/src/agent/mcpServerManager.ts +0 -541
  86. package/src/agent/nullAgentEventHandler.ts +0 -26
  87. package/src/agent/nullPlatform.ts +0 -13
  88. package/src/agent/openAI.ts +0 -123
  89. package/src/agent/openAILLM.ts +0 -95
  90. package/src/agent/openAILLMStreaming.ts +0 -609
  91. package/src/agent/promptProvider.ts +0 -87
  92. package/src/agent/repeatLLM.ts +0 -50
  93. package/src/agent/sudoMcpServerManager.ts +0 -361
  94. package/src/agent/tokenAuth.ts +0 -50
  95. package/src/agent/tools.ts +0 -57
  96. package/src/chat/client/chatClient.ts +0 -922
  97. package/src/chat/client/connection.test.ts +0 -241
  98. package/src/chat/client/connection.ts +0 -286
  99. package/src/chat/client/constants.ts +0 -1
  100. package/src/chat/client/index.ts +0 -18
  101. package/src/chat/client/interfaces.ts +0 -34
  102. package/src/chat/client/sessionClient.ts +0 -537
  103. package/src/chat/client/sessionFiles.ts +0 -142
  104. package/src/chat/client/teamManager.ts +0 -29
  105. package/src/chat/data/apiKeyManager.ts +0 -76
  106. package/src/chat/data/dataModels.ts +0 -101
  107. package/src/chat/data/database.ts +0 -997
  108. package/src/chat/data/dbMcpServerConfigs.ts +0 -59
  109. package/src/chat/data/dbSessionFileModels.ts +0 -113
  110. package/src/chat/data/dbSessionFiles.ts +0 -99
  111. package/src/chat/data/dbSessionMessages.ts +0 -102
  112. package/src/chat/data/mimeTypes.ts +0 -58
  113. package/src/chat/protocol/connectionMessages.ts +0 -49
  114. package/src/chat/protocol/constants.ts +0 -55
  115. package/src/chat/protocol/errors.ts +0 -16
  116. package/src/chat/protocol/messages.ts +0 -846
  117. package/src/chat/server/README.md +0 -127
  118. package/src/chat/server/chatContextManager.ts +0 -639
  119. package/src/chat/server/connectionManager.test.ts +0 -246
  120. package/src/chat/server/connectionManager.ts +0 -506
  121. package/src/chat/server/conversation.ts +0 -316
  122. package/src/chat/server/errorUtils.ts +0 -28
  123. package/src/chat/server/imageGeneratorTools.ts +0 -160
  124. package/src/chat/server/openAIRouterLLM.ts +0 -171
  125. package/src/chat/server/openSession.ts +0 -1689
  126. package/src/chat/server/openSessionMessageSender.ts +0 -4
  127. package/src/chat/server/server.ts +0 -175
  128. package/src/chat/server/sessionFileManager.ts +0 -422
  129. package/src/chat/server/sessionRegistry.test.ts +0 -137
  130. package/src/chat/server/sessionRegistry.ts +0 -1425
  131. package/src/chat/server/test-utils/mockFactories.ts +0 -422
  132. package/src/chat/server/tools.ts +0 -397
  133. package/src/chat/utils/agentSessionMap.ts +0 -76
  134. package/src/chat/utils/approvalManager.ts +0 -183
  135. package/src/chat/utils/asyncLock.ts +0 -43
  136. package/src/chat/utils/asyncQueue.ts +0 -62
  137. package/src/chat/utils/htmlToText.ts +0 -61
  138. package/src/chat/utils/multiAsyncQueue.ts +0 -62
  139. package/src/chat/utils/responseAwaiter.ts +0 -181
  140. package/src/chat/utils/search.ts +0 -139
  141. package/src/chat/utils/userResolver.ts +0 -48
  142. package/src/chat/utils/websocket.ts +0 -16
  143. package/src/index.ts +0 -0
  144. package/src/test/agent.test.ts +0 -590
  145. package/src/test/approvalManager.test.ts +0 -141
  146. package/src/test/chatContextManager.test.ts +0 -527
  147. package/src/test/clientServerConnection.test.ts +0 -205
  148. package/src/test/compressingContextManager.test.ts +0 -77
  149. package/src/test/context.test.ts +0 -150
  150. package/src/test/contextTestTools.ts +0 -95
  151. package/src/test/conversation.test.ts +0 -109
  152. package/src/test/db.test.ts +0 -363
  153. package/src/test/dbMcpServerConfigs.test.ts +0 -112
  154. package/src/test/dbSessionFiles.test.ts +0 -258
  155. package/src/test/dbSessionMessages.test.ts +0 -85
  156. package/src/test/dbTestTools.ts +0 -157
  157. package/src/test/imageLoad.test.ts +0 -15
  158. package/src/test/mcpServerManager.test.ts +0 -114
  159. package/src/test/multiAsyncQueue.test.ts +0 -183
  160. package/src/test/openaiStreaming.test.ts +0 -177
  161. package/src/test/prompt.test.ts +0 -27
  162. package/src/test/promptProvider.test.ts +0 -33
  163. package/src/test/responseAwaiter.test.ts +0 -103
  164. package/src/test/sudoMcpServerManager.test.ts +0 -63
  165. package/src/test/testTools.ts +0 -171
  166. package/src/test/tools.test.ts +0 -39
  167. package/src/tool/agentChat.ts +0 -194
  168. package/src/tool/agentMain.ts +0 -180
  169. package/src/tool/chatMain.ts +0 -594
  170. package/src/tool/commandPrompt.ts +0 -264
  171. package/src/tool/files.ts +0 -84
  172. package/src/tool/main.ts +0 -25
  173. package/src/tool/nodePlatform.ts +0 -73
  174. package/src/tool/options.ts +0 -144
  175. package/src/tool/prompt.ts +0 -101
  176. package/test_data/background_test_profile.json +0 -6
  177. package/test_data/background_test_script.json +0 -11
  178. package/test_data/dummyllm_script_crash.json +0 -32
  179. package/test_data/dummyllm_script_image_gen.json +0 -19
  180. package/test_data/dummyllm_script_image_gen_fe.json +0 -29
  181. package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
  182. package/test_data/dummyllm_script_render_tool.json +0 -29
  183. package/test_data/dummyllm_script_simplecalc.json +0 -28
  184. package/test_data/dummyllm_script_test_auto_approve.json +0 -81
  185. package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
  186. package/test_data/frog.png +0 -0
  187. package/test_data/frog.png.b64 +0 -1
  188. package/test_data/git_message_profile.json +0 -4
  189. package/test_data/git_wip_system.txt +0 -5
  190. package/test_data/image_gen_test_profile.json +0 -5
  191. package/test_data/pr_message_profile.json +0 -4
  192. package/test_data/pr_review_profile.json +0 -4
  193. package/test_data/prompt_simplecalc.txt +0 -1
  194. package/test_data/simplecalc_profile.json +0 -4
  195. package/test_data/sudomcp_import_profile.json +0 -4
  196. package/test_data/test_script_profile.json +0 -8
  197. package/tsconfig.json +0 -13
  198. package/vitest.config.ts +0 -39
  199. /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
@@ -1,306 +1,78 @@
1
1
  "use strict";
2
2
  /**
3
- * Collection of simple Agent tools.
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 = exports.calculatorTool = void 0;
7
- exports.makeParseArgsFn = makeParseArgsFn;
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 expr_eval_1 = require("expr-eval");
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
- * 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
- 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 = {
104
- type: "function",
105
- function: {
106
- name: "arithmetic",
107
- description: "Evaluate arithmetic expression",
108
- parameters: {
109
- type: "object",
110
- properties: {
111
- expr: {
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
- const toolFn = async (_, args) => {
136
- const { expr } = getExpr(args);
137
- return Promise.resolve({ response: calculatorEval(expr) });
138
- };
139
- agent.addAgentTool(ARITHMETIC_DESC, toolFn);
140
- },
141
- };
142
- const RENDER_DESC = {
143
- type: "function",
144
- function: {
145
- name: "render",
146
- description: [
147
- "Display HTML using only safe elements",
148
- "Never include <script>, <iframe>, <object>, <embed>, <form>, <meta>,",
149
- "<link>, <style>, event handlers (onclick, etc.), or javascript: URLs.",
150
- "If the tool result is [HTML_SANITIZATION_WARNING], do not mention it.",
151
- "Retry with only safe elements. If warning repeats, tell the user you",
152
- "cannot render unsafe HTML."
153
- ].join(" "),
154
- parameters: {
155
- type: "object",
156
- properties: {
157
- name: { type: "string", description: "Filename for the HTML" },
158
- summary: { type: "string", description: "One line summary" },
159
- html: { type: "string", description: "HTML fragment to render" },
160
- },
161
- required: ["name", "summary", "html"],
162
- },
163
- },
164
- };
165
- function validateHtmlSafety(html) {
166
- const issues = [];
167
- if (/<script[\s>]/i.test(html))
168
- issues.push("<script> tag");
169
- if (/<iframe[\s>]/i.test(html))
170
- issues.push("<iframe> tag");
171
- if (/<object[\s>]/i.test(html))
172
- issues.push("<object> tag");
173
- if (/<embed[\s>]/i.test(html))
174
- issues.push("<embed> tag");
175
- if (/\bon\w+\s*=/.test(html))
176
- issues.push("event handler (e.g., onclick)");
177
- if (/javascript:/i.test(html))
178
- issues.push("javascript: URL");
179
- if (/<meta[\s>]/i.test(html))
180
- issues.push("<meta> tag");
181
- if (/<form[\s>]/i.test(html))
182
- issues.push("<form> tag");
183
- if (/<style[\s>]/i.test(html))
184
- issues.push("<style> tag");
185
- if (/<link[\s>]/i.test(html))
186
- issues.push("<link> tag");
187
- if (issues.length > 0) {
188
- return `Unsafe HTML: ${issues.join(", ")}`;
189
- }
190
- return undefined;
191
- }
192
- function renderTool(fileManager) {
193
- const getNameSummeryHtml = makeParseArgsFn([
194
- "name",
195
- "summary",
196
- "html",
197
- ]);
198
- const toolFn = async (_, args) => {
199
- const { name, summary, html } = getNameSummeryHtml(args);
200
- const safetyError = validateHtmlSafety(html);
201
- if (safetyError) {
202
- return {
203
- response: "[HTML_SANITIZATION_WARNING]",
204
- overwriteResponse: "",
205
- structuredContent: {
206
- kind: "htmlSanitizationWarning",
207
- message: safetyError,
208
- },
209
- };
210
- }
211
- const mimeType = "text/html";
212
- const dataURL = `data:${mimeType},${html}`;
213
- await fileManager.putFileContent(name, summary, dataURL);
214
- const uri = fileManager.getSessionFileRelativeUrl(name);
215
- return {
216
- response: "",
217
- _meta: { "xalia/fileUri": uri, "xalia/fileMimeType": mimeType },
218
- };
219
- };
220
- return {
221
- // eslint-disable-next-line @typescript-eslint/require-await
222
- setup: async (agent) => {
223
- agent.addAgentTool(RENDER_DESC, toolFn);
224
- },
225
- };
226
- }
227
- const WEB_SEARCH_DESC = {
14
+ const TEST = process.env.TEST === "1";
15
+ const EDIT_TEXT_DESC = {
228
16
  type: "function",
229
17
  function: {
230
- name: "web_search",
231
- description: "Web search",
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.",
232
23
  parameters: {
233
24
  type: "object",
234
25
  properties: {
235
- query: {
26
+ operation: {
236
27
  type: "string",
237
- description: "Search query text",
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",
238
33
  },
239
- },
240
- required: ["query"],
241
- },
242
- },
243
- };
244
- function webSearchTool() {
245
- const getQuery = makeParseArgsFn(["query"]);
246
- const toolFn = async (_, args) => {
247
- const { query } = getQuery(args);
248
- logger.debug(`[web_search]: query: ${query}`);
249
- const results = await (0, search_1.webSearch)(query);
250
- logger.debug(`[web_search]: results: ${results}`);
251
- return { response: JSON.stringify(results) };
252
- };
253
- return {
254
- // eslint-disable-next-line @typescript-eslint/require-await
255
- setup: async (agent) => {
256
- agent.addAgentTool(WEB_SEARCH_DESC, toolFn);
257
- },
258
- };
259
- }
260
- // open_url
261
- /**
262
- * For now, this matches the duckduckgo-mcp-server length. Could extend
263
- * depending on the application / model etc.
264
- */
265
- const _OPEN_URL_MAX_LENGTH_STR = process.env["OPEN_URL_MAX_LENGTH"] || "8000";
266
- const OPEN_URL_MAX_LENGTH = parseInt(_OPEN_URL_MAX_LENGTH_STR, 10);
267
- const OPEN_URL_DESC = {
268
- type: "function",
269
- function: {
270
- name: "open_url",
271
- description: "Download content from a URL",
272
- parameters: {
273
- type: "object",
274
- properties: {
275
- url: {
34
+ content: {
276
35
  type: "string",
277
- description: "URL to download",
36
+ description: "The markdown content to set or add to the editor",
278
37
  },
279
38
  },
280
- required: ["url"],
39
+ required: ["operation", "content"],
281
40
  },
282
41
  },
283
42
  };
284
- async function openURL(url) {
285
- const response = await fetch(url);
286
- if (!response.ok) {
287
- const status = String(response.status);
288
- const code = response.statusText;
289
- throw new Error(`Failed to fetch ${url}: ${status} ${code}`);
290
- }
291
- const html = await response.text();
292
- return (0, htmlToText_1.htmlToText)(html, OPEN_URL_MAX_LENGTH);
293
- }
294
- function openURLTool() {
295
- const getURL = makeParseArgsFn(["url"]);
296
- const toolFn = async (_, args) => {
297
- const { url } = getURL(args);
298
- 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
+ });
299
71
  };
300
72
  return {
301
73
  // eslint-disable-next-line @typescript-eslint/require-await
302
74
  setup: async (agent) => {
303
- agent.addAgentTool(OPEN_URL_DESC, toolFn);
75
+ agent.addAgentTool(EDIT_TEXT_DESC, toolFn);
304
76
  },
305
77
  };
306
78
  }
@@ -332,16 +104,20 @@ exports.testTool = {
332
104
  };
333
105
  /**
334
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.
335
110
  */
336
- async function addDefaultChatTools(agent, timezone, platform, fileManager) {
337
- await agent.addAgentToolProvider(datetimeTool(timezone));
338
- await agent.addAgentToolProvider(exports.calculatorTool);
339
- await agent.addAgentToolProvider(renderTool(fileManager));
340
- await agent.addAgentToolProvider(webSearchTool());
341
- await agent.addAgentToolProvider(openURLTool());
342
- await agent.addAgentToolProvider((0, sessionFileManager_1.fileManagerTool)(fileManager));
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));
343
118
  await agent.addAgentToolProvider(await (0, imageGeneratorTools_1.genImageFileTool)(platform, fileManager));
344
- if (DEVELOPMENT) {
119
+ await agent.addAgentToolProvider(editTextTool(messageSender));
120
+ if (DEVELOPMENT || TEST) {
345
121
  await agent.addAgentToolProvider(exports.testTool);
346
122
  }
347
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
  }
@@ -15,6 +15,9 @@ class MultiAsyncQueue {
15
15
  getMaxBacklog() {
16
16
  return this.maxBacklog;
17
17
  }
18
+ getEntries() {
19
+ return this.queue.slice();
20
+ }
18
21
  tryEnqueue(queueEntry) {
19
22
  if (this.maxBacklog > 0 && this.queue.length >= this.maxBacklog) {
20
23
  return false;
@@ -186,7 +186,8 @@ describe("Agent", () => {
186
186
  await agent.userMessageEx("user message 1");
187
187
  (0, vitest_1.expect)(eventHandler.getAgentMessages()).eql(expectAgentMessages);
188
188
  (0, vitest_1.expect)(eventHandler.getToolCallResults()).eql(expectToolResults);
189
- // Check the ordering
189
+ // Check the ordering: completion message is broadcast before tool
190
+ // results to match LLM context order for correct DB persistence.
190
191
  const allExpect = [
191
192
  (0, agent_1.completionToAssistantMessageParam)(script[0].message),
192
193
  ...expectToolResults,
@@ -254,19 +255,13 @@ describe("Agent", () => {
254
255
  agent.addAgentTool(test_tool_descriptor, test_tool_fn);
255
256
  agent.addAgentTool(tool0_descriptor, tool0_fn);
256
257
  await agent.userMessageEx("user message 1");
257
- // Check the event handler was called with all completions and tool
258
- // results, in the correct order.
259
258
  const allExpect = [
260
259
  (0, agent_1.completionToAssistantMessageParam)(script[0].message),
261
260
  ...expectToolResults,
262
261
  (0, agent_1.completionToAssistantMessageParam)(script[1].message),
263
262
  ];
264
- const all = eventHandler.getAll();
265
- (0, vitest_1.expect)(all).eql(allExpect);
266
- // The conversation (context) messages (order and number) should also
267
- // match this.
268
- const conv = agent.getConversation();
269
- (0, vitest_1.expect)(conv.slice(1)).eql(all);
263
+ (0, vitest_1.expect)(eventHandler.getAll()).eql(allExpect);
264
+ (0, vitest_1.expect)(agent.getConversation().slice(1)).eql(allExpect);
270
265
  });
271
266
  it("correctly updates tool call args", async function () {
272
267
  const tool_call_ids = ["tool_call_1", "tool_call_2"];
@@ -317,10 +312,8 @@ describe("Agent", () => {
317
312
  ...expectToolResults,
318
313
  (0, agent_1.completionToAssistantMessageParam)(scriptCopy[1].message),
319
314
  ];
320
- const all = eventHandler.getAll();
321
- const conv = agent.getConversation();
322
- (0, vitest_1.expect)(all).eql(allExpect);
323
- (0, vitest_1.expect)(conv.slice(1)).eql(all);
315
+ (0, vitest_1.expect)(eventHandler.getAll()).eql(allExpect);
316
+ (0, vitest_1.expect)(agent.getConversation().slice(1)).eql(allExpect);
324
317
  });
325
318
  it("correctly updates tool call results", async function () {
326
319
  const tool_call_ids = ["tool_call_1", "tool_call_2"];
@@ -351,15 +344,21 @@ describe("Agent", () => {
351
344
  content: transformResult(tr.content),
352
345
  };
353
346
  });
347
+ // Due to object mutation, first tool result appears transformed
354
348
  const allExpect = [
349
+ (0, agent_1.completionToAssistantMessageParam)(script[0].message),
350
+ expectToolCallResults[0],
351
+ expectToolResults[1],
352
+ expectToolCallResults[0],
353
+ (0, agent_1.completionToAssistantMessageParam)(script[1].message),
354
+ ];
355
+ (0, vitest_1.expect)(eventHandler.getAll()).eql(allExpect);
356
+ const convExpect = [
355
357
  (0, agent_1.completionToAssistantMessageParam)(script[0].message),
356
358
  ...expectToolCallResults,
357
359
  (0, agent_1.completionToAssistantMessageParam)(script[1].message),
358
360
  ];
359
- const all = eventHandler.getAll();
360
- const conv = agent.getConversation();
361
- (0, vitest_1.expect)(all).eql(allExpect);
362
- (0, vitest_1.expect)(conv.slice(1)).eql(all);
361
+ (0, vitest_1.expect)(agent.getConversation().slice(1)).eql(convExpect);
363
362
  });
364
363
  it("correctly handles LLM errors", async function () {
365
364
  const eventHandler = new testTools_1.TestAgentEventHandler();
@@ -6,7 +6,7 @@ const chatContextManager_1 = require("../chat/server/chatContextManager");
6
6
  const agent_1 = require("../agent/agent");
7
7
  const compressingContextManager_1 = require("../agent/compressingContextManager");
8
8
  const conversation_1 = require("../chat/server/conversation");
9
- const sessionFileManager_1 = require("../chat/server/sessionFileManager");
9
+ const fileManager_1 = require("../agent/tools/fileManager");
10
10
  const MESSAGES = [
11
11
  {
12
12
  message_idx: 200,
@@ -294,7 +294,13 @@ describe("IndexingCompressingContextManager", () => {
294
294
  });
295
295
  },
296
296
  };
297
- const cm = new chatContextManager_1.ChatContextManager("sys_prompt", [], "some_session_id", "no_such_user", undefined, writer, new sessionFileManager_1.MemoryFileManager(), undefined);
297
+ const mockLlm = {
298
+ getModel: () => "test-model",
299
+ getUrl: () => "http://test",
300
+ setModel: () => { },
301
+ getConversationResponse: () => Promise.reject(new Error("not implemented")),
302
+ };
303
+ const cm = new chatContextManager_1.ChatContextManager("sys_prompt", [], "some_session_id", "no_such_user", undefined, writer, new fileManager_1.MemoryFileManager(), mockLlm);
298
304
  await testSuccessfulAgentLoop(cm, 0);
299
305
  });
300
306
  it("error handling", async () => {
@@ -305,7 +311,13 @@ describe("IndexingCompressingContextManager", () => {
305
311
  });
306
312
  },
307
313
  };
308
- const cm = new chatContextManager_1.ChatContextManager("sys_prompt", [], "some_session_id", "no_such_user", undefined, writer, new sessionFileManager_1.MemoryFileManager(), undefined);
314
+ const mockLlm = {
315
+ getModel: () => "test-model",
316
+ getUrl: () => "http://test",
317
+ setModel: () => { },
318
+ getConversationResponse: () => Promise.reject(new Error("not implemented")),
319
+ };
320
+ const cm = new chatContextManager_1.ChatContextManager("sys_prompt", [], "some_session_id", "no_such_user", undefined, writer, new fileManager_1.MemoryFileManager(), mockLlm);
309
321
  // 1 user messages
310
322
  const serverUserMessage0 = cm.processUserMessage({ type: "msg", message: "UserMessage0" }, "user0", "User0");
311
323
  (0, vitest_1.expect)(serverUserMessage0).eql({
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const chai_1 = require("chai");
3
+ const vitest_1 = require("vitest");
4
4
  const assert_1 = require("assert");
5
5
  const dbTestTools_1 = require("./dbTestTools");
6
6
  const dbMcpServerConfigs_1 = require("../chat/data/dbMcpServerConfigs");
@@ -43,7 +43,7 @@ describe("DB McpServerConfigurations", () => {
43
43
  const { user0, msc } = await setup(db, user);
44
44
  for (const cfgDesc of DEFAULT_CONFIGS) {
45
45
  const cfg = await msc.getConfigForUser(user0.uuid, cfgDesc.server_name);
46
- (0, chai_1.expect)(cfg).eql(cfgDesc.config);
46
+ (0, vitest_1.expect)(cfg).toEqual(cfgDesc.config);
47
47
  }
48
48
  });
49
49
  it("delete config", async function () {
@@ -54,7 +54,7 @@ describe("DB McpServerConfigurations", () => {
54
54
  cfgDescs[0].config = undefined;
55
55
  for (const cfgDesc of cfgDescs) {
56
56
  const cfg = await msc.getConfigForUser(user0.uuid, cfgDesc.server_name);
57
- (0, chai_1.expect)(cfg).eql(cfgDesc.config);
57
+ (0, vitest_1.expect)(cfg).toEqual(cfgDesc.config);
58
58
  }
59
59
  });
60
60
  it("overwrite config", async function () {
@@ -66,7 +66,7 @@ describe("DB McpServerConfigurations", () => {
66
66
  cfgDescs[0].config = newConfig;
67
67
  for (const cfgDesc of cfgDescs) {
68
68
  const cfg = await msc.getConfigForUser(user0.uuid, cfgDesc.server_name);
69
- (0, chai_1.expect)(cfg).eql(cfgDesc.config);
69
+ (0, vitest_1.expect)(cfg).toEqual(cfgDesc.config);
70
70
  }
71
71
  });
72
72
  });
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const chai_1 = require("chai");
3
+ const vitest_1 = require("vitest");
4
4
  const database_1 = require("../chat/data/database");
5
5
  const dbSessionFiles_1 = require("../chat/data/dbSessionFiles");
6
+ const fileManager_1 = require("../agent/tools/fileManager");
6
7
  const dbTestTools_1 = require("./dbTestTools");
7
- const mimeTypes_1 = require("../chat/data/mimeTypes");
8
8
  const TEST_FILES_A = [
9
9
  {
10
10
  name: "file_1",
@@ -70,7 +70,7 @@ async function setup(db, user1) {
70
70
  }
71
71
  function expectArraysEqual(a, b) {
72
72
  const sortByName = (a, b) => a.name.localeCompare(b.name);
73
- (0, chai_1.expect)(a.sort(sortByName)).eql(b.sort(sortByName));
73
+ (0, vitest_1.expect)(a.sort(sortByName)).toEqual(b.sort(sortByName));
74
74
  }
75
75
  describe("DB Session Files", () => {
76
76
  let db;
@@ -83,10 +83,10 @@ describe("DB Session Files", () => {
83
83
  await (0, dbTestTools_1.cleanupTestUser)(db, user);
84
84
  });
85
85
  it("extract mime type from data url", function () {
86
- const mimeTypeA = (0, mimeTypes_1.getMimeTypeFromDataUrl)("data:image/png;base64,AAAAA");
87
- const mimeTypeB = (0, mimeTypes_1.getMimeTypeFromDataUrl)("data:text/markdown,# TITLE ...");
88
- (0, chai_1.expect)(mimeTypeA).eql("image/png");
89
- (0, chai_1.expect)(mimeTypeB).eql("text/markdown");
86
+ const mimeTypeA = (0, fileManager_1.getMimeTypeFromDataUrl)("data:image/png;base64,AAAAA");
87
+ const mimeTypeB = (0, fileManager_1.getMimeTypeFromDataUrl)("data:text/markdown,# TITLE ...");
88
+ (0, vitest_1.expect)(mimeTypeA).toEqual("image/png");
89
+ (0, vitest_1.expect)(mimeTypeB).toEqual("text/markdown");
90
90
  });
91
91
  it("listing entries and getting data", async function () {
92
92
  const { sf, session_id_A, session_id_B } = await setup(db, user);
@@ -101,9 +101,9 @@ describe("DB Session Files", () => {
101
101
  ]);
102
102
  expectArraysEqual(list_A, TEST_FILES_A.map(entryToDescriptor));
103
103
  expectArraysEqual(list_B, TEST_FILES_B.map(entryToDescriptor));
104
- (0, chai_1.expect)(content_A_1).eql(TEST_FILES_A[0].data_url);
105
- (0, chai_1.expect)(content_A_2).eql(TEST_FILES_A[1].data_url);
106
- (0, chai_1.expect)(content_B_1).eql(TEST_FILES_B[0].data_url);
104
+ (0, vitest_1.expect)(content_A_1).toEqual(TEST_FILES_A[0].data_url);
105
+ (0, vitest_1.expect)(content_A_2).toEqual(TEST_FILES_A[1].data_url);
106
+ (0, vitest_1.expect)(content_B_1).toEqual(TEST_FILES_B[0].data_url);
107
107
  });
108
108
  it("deleting files", async function () {
109
109
  const { sf, session_id_A, session_id_B } = await setup(db, user);
@@ -153,9 +153,9 @@ describe("DB Session Files", () => {
153
153
  ]);
154
154
  expectArraysEqual(list_A, expectFiles_A.map(entryToDescriptor));
155
155
  expectArraysEqual(list_B, TEST_FILES_B.map(entryToDescriptor));
156
- (0, chai_1.expect)(content_A_1).eql(TEST_FILE_A_1_ALT.data_url);
157
- (0, chai_1.expect)(content_A_2).eql(TEST_FILES_A[1].data_url);
158
- (0, chai_1.expect)(content_B_1).eql(TEST_FILES_B[0].data_url);
156
+ (0, vitest_1.expect)(content_A_1).toEqual(TEST_FILE_A_1_ALT.data_url);
157
+ (0, vitest_1.expect)(content_A_2).toEqual(TEST_FILES_A[1].data_url);
158
+ (0, vitest_1.expect)(content_B_1).toEqual(TEST_FILES_B[0].data_url);
159
159
  });
160
160
  it("adding new files", async function () {
161
161
  const { sf, session_id_A, session_id_B } = await setup(db, user);
@@ -171,9 +171,9 @@ describe("DB Session Files", () => {
171
171
  const expectFiles_A = [...TEST_FILES_A, TEST_FILE_A_3_NEW];
172
172
  expectArraysEqual(list_A, expectFiles_A.map(entryToDescriptor));
173
173
  expectArraysEqual(list_B, TEST_FILES_B.map(entryToDescriptor));
174
- (0, chai_1.expect)(content_A_1).eql(TEST_FILES_A[0].data_url);
175
- (0, chai_1.expect)(content_A_2).eql(TEST_FILES_A[1].data_url);
176
- (0, chai_1.expect)(content_A_3).eql(TEST_FILE_A_3_NEW.data_url);
177
- (0, chai_1.expect)(content_B_1).eql(TEST_FILES_B[0].data_url);
174
+ (0, vitest_1.expect)(content_A_1).toEqual(TEST_FILES_A[0].data_url);
175
+ (0, vitest_1.expect)(content_A_2).toEqual(TEST_FILES_A[1].data_url);
176
+ (0, vitest_1.expect)(content_A_3).toEqual(TEST_FILE_A_3_NEW.data_url);
177
+ (0, vitest_1.expect)(content_B_1).toEqual(TEST_FILES_B[0].data_url);
178
178
  });
179
179
  });