@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.
Files changed (152) hide show
  1. package/.env.development +6 -0
  2. package/.env.test +7 -0
  3. package/README.md +11 -0
  4. package/context_system.md +498 -0
  5. package/dist/agent/src/agent/agent.js +169 -87
  6. package/dist/agent/src/agent/agentUtils.js +24 -18
  7. package/dist/agent/src/agent/compressingContextManager.js +10 -14
  8. package/dist/agent/src/agent/context.js +101 -127
  9. package/dist/agent/src/agent/contextWithWorkspace.js +133 -0
  10. package/dist/agent/src/agent/documentSummarizer.js +126 -0
  11. package/dist/agent/src/agent/dummyLLM.js +25 -22
  12. package/dist/agent/src/agent/imageGenLLM.js +22 -25
  13. package/dist/agent/src/agent/imageGenerator.js +2 -10
  14. package/dist/agent/src/agent/llm.js +1 -1
  15. package/dist/agent/src/agent/openAILLM.js +15 -12
  16. package/dist/agent/src/agent/openAILLMStreaming.js +73 -39
  17. package/dist/agent/src/agent/repeatLLM.js +16 -7
  18. package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
  19. package/dist/agent/src/agent/tokenCounter.js +390 -0
  20. package/dist/agent/src/agent/tokenCounter.test.js +206 -0
  21. package/dist/agent/src/agent/toolSettings.js +17 -0
  22. package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
  23. package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
  24. package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
  25. package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
  26. package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
  27. package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
  28. package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
  29. package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
  30. package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
  31. package/dist/agent/src/agent/tools/index.js +64 -0
  32. package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
  33. package/dist/agent/src/agent/tools/renderTool.js +89 -0
  34. package/dist/agent/src/agent/tools/utils.js +61 -0
  35. package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
  36. package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
  37. package/dist/agent/src/chat/client/chatClient.js +63 -2
  38. package/dist/agent/src/chat/client/connection.js +6 -1
  39. package/dist/agent/src/chat/client/index.js +4 -1
  40. package/dist/agent/src/chat/client/sessionClient.js +28 -9
  41. package/dist/agent/src/chat/constants.js +8 -0
  42. package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
  43. package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
  44. package/dist/agent/src/chat/protocol/messages.js +9 -0
  45. package/dist/agent/src/chat/server/chatContextManager.js +186 -156
  46. package/dist/agent/src/chat/server/conversation.js +3 -0
  47. package/dist/agent/src/chat/server/imageGeneratorTools.js +39 -16
  48. package/dist/agent/src/chat/server/openAIRouterLLM.js +111 -0
  49. package/dist/agent/src/chat/server/openSession.js +253 -91
  50. package/dist/agent/src/chat/server/promptRefiner.js +86 -0
  51. package/dist/agent/src/chat/server/server.js +10 -2
  52. package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
  53. package/dist/agent/src/chat/server/sessionRegistry.js +152 -6
  54. package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
  55. package/dist/agent/src/chat/server/titleGenerator.js +112 -0
  56. package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
  57. package/dist/agent/src/chat/server/tools.js +64 -253
  58. package/dist/agent/src/chat/utils/approvalManager.js +6 -3
  59. package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
  60. package/dist/agent/src/test/agent.test.js +16 -17
  61. package/dist/agent/src/test/chatContextManager.test.js +44 -30
  62. package/dist/agent/src/test/clientServerConnection.test.js +1 -2
  63. package/dist/agent/src/test/compressingContextManager.test.js +22 -36
  64. package/dist/agent/src/test/context.test.js +55 -17
  65. package/dist/agent/src/test/contextTestTools.js +87 -0
  66. package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
  67. package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
  68. package/dist/agent/src/test/testTools.js +6 -1
  69. package/dist/agent/src/test/tools.test.js +27 -9
  70. package/dist/agent/src/tool/agentChat.js +5 -2
  71. package/dist/agent/src/tool/chatMain.js +56 -15
  72. package/dist/agent/src/tool/commandPrompt.js +2 -2
  73. package/dist/agent/src/tool/files.js +7 -8
  74. package/package.json +4 -1
  75. package/scripts/test_chat +195 -173
  76. package/src/agent/agent.ts +257 -137
  77. package/src/agent/agentUtils.ts +32 -20
  78. package/src/agent/compressingContextManager.ts +13 -44
  79. package/src/agent/context.ts +165 -159
  80. package/src/agent/contextWithWorkspace.ts +162 -0
  81. package/src/agent/documentSummarizer.ts +157 -0
  82. package/src/agent/dummyLLM.ts +27 -23
  83. package/src/agent/imageGenLLM.ts +28 -32
  84. package/src/agent/imageGenerator.ts +3 -18
  85. package/src/agent/llm.ts +2 -2
  86. package/src/agent/openAILLM.ts +17 -13
  87. package/src/agent/openAILLMStreaming.ts +99 -43
  88. package/src/agent/repeatLLM.ts +19 -7
  89. package/src/agent/sudoMcpServerManager.ts +41 -20
  90. package/src/agent/test_data/harrypotter.txt +6065 -0
  91. package/src/agent/tokenCounter.test.ts +243 -0
  92. package/src/agent/tokenCounter.ts +483 -0
  93. package/src/agent/toolSettings.ts +24 -0
  94. package/src/agent/tools/calculatorTool.ts +50 -0
  95. package/src/agent/tools/contentExtractors/pdfToText.ts +60 -0
  96. package/src/agent/tools/datetimeTool.ts +41 -0
  97. package/src/agent/tools/fileManager/fileManagerTool.ts +199 -0
  98. package/src/agent/tools/fileManager/index.ts +50 -0
  99. package/src/agent/tools/fileManager/memoryFileManager.ts +120 -0
  100. package/src/{chat/data → agent/tools/fileManager}/mimeTypes.ts +3 -1
  101. package/src/agent/tools/fileManager/prompt.ts +38 -0
  102. package/src/{chat/data/dbSessionFileModels.ts → agent/tools/fileManager/types.ts} +76 -0
  103. package/src/agent/tools/index.ts +49 -0
  104. package/src/agent/tools/openUrlTool.ts +62 -0
  105. package/src/agent/tools/renderTool.ts +92 -0
  106. package/src/agent/tools/utils.ts +74 -0
  107. package/src/{chat/utils/search.ts → agent/tools/webSearch.ts} +0 -1
  108. package/src/agent/tools/webSearchTool.ts +44 -0
  109. package/src/chat/client/chatClient.ts +92 -3
  110. package/src/chat/client/connection.ts +11 -1
  111. package/src/chat/client/index.ts +3 -0
  112. package/src/chat/client/sessionClient.ts +40 -11
  113. package/src/chat/client/sessionFiles.ts +1 -1
  114. package/src/chat/constants.ts +6 -0
  115. package/src/chat/data/dataModels.ts +12 -0
  116. package/src/chat/data/dbSessionFiles.ts +12 -4
  117. package/src/chat/data/dbSessionMessages.ts +34 -0
  118. package/src/chat/protocol/messages.ts +94 -14
  119. package/src/chat/server/chatContextManager.ts +255 -221
  120. package/src/chat/server/connectionManager.ts +1 -1
  121. package/src/chat/server/conversation.ts +3 -0
  122. package/src/chat/server/imageGeneratorTools.ts +62 -30
  123. package/src/chat/server/openAIRouterLLM.ts +168 -0
  124. package/src/chat/server/openSession.ts +381 -138
  125. package/src/chat/server/promptRefiner.ts +106 -0
  126. package/src/chat/server/server.ts +9 -2
  127. package/src/chat/server/sessionFileManager.ts +35 -306
  128. package/src/chat/server/sessionRegistry.test.ts +0 -1
  129. package/src/chat/server/sessionRegistry.ts +228 -4
  130. package/src/chat/server/titleGenerator.test.ts +103 -0
  131. package/src/chat/server/titleGenerator.ts +143 -0
  132. package/src/chat/server/tools.ts +92 -281
  133. package/src/chat/utils/approvalManager.ts +9 -3
  134. package/src/chat/utils/multiAsyncQueue.ts +4 -0
  135. package/src/test/agent.test.ts +25 -30
  136. package/src/test/chatContextManager.test.ts +68 -38
  137. package/src/test/clientServerConnection.test.ts +0 -2
  138. package/src/test/compressingContextManager.test.ts +29 -34
  139. package/src/test/context.test.ts +59 -15
  140. package/src/test/contextTestTools.ts +95 -0
  141. package/src/test/dbMcpServerConfigs.test.ts +4 -4
  142. package/src/test/dbSessionFiles.test.ts +16 -16
  143. package/src/test/testTools.ts +8 -3
  144. package/src/test/tools.test.ts +30 -5
  145. package/src/tool/agentChat.ts +12 -3
  146. package/src/tool/chatMain.ts +59 -18
  147. package/src/tool/commandPrompt.ts +2 -2
  148. package/src/tool/files.ts +1 -3
  149. package/dist/agent/src/agent/tools.js +0 -44
  150. package/src/agent/tools.ts +0 -57
  151. /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
  152. /package/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.ts +0 -0
@@ -0,0 +1,106 @@
1
+ import { getOpenAIClient } from "./openAIRouterLLM";
2
+ import { getLogger } from "@xalia/xmcp/sdk";
3
+
4
+ const logger = getLogger();
5
+
6
+ const REFINER_MODEL = "google/gemini-2.5-flash";
7
+ const REFINER_MAX_TOKENS = 2000;
8
+ const REFINER_TEMPERATURE = 0.3;
9
+ const REFINER_TIMEOUT_MS = 30000;
10
+
11
+ // prettier-ignore
12
+ const REFINEMENT_SYSTEM_PROMPT =
13
+ `You are an expert at writing system prompts for AI assistants. Your task is \
14
+ to refine and improve the given system prompt to make it more effective, \
15
+ clear, and well-structured for an LLM to follow.
16
+
17
+ Guidelines for refinement:
18
+ 1. Make instructions clear, specific, and actionable
19
+ 2. Use structured formatting (sections, bullet points) when appropriate
20
+ 3. Remove ambiguity and redundancy
21
+ 4. Add relevant context if missing
22
+ 5. Ensure the tone and style are consistent
23
+ 6. Preserve the original intent and personality
24
+ 7. Keep the prompt concise but comprehensive
25
+
26
+ Return ONLY the refined system prompt, without any explanations or \
27
+ meta-commentary.`;
28
+
29
+ export interface IPromptRefiner {
30
+ refinePrompt(prompt: string): Promise<string>;
31
+ }
32
+
33
+ export class LLMPromptRefiner implements IPromptRefiner {
34
+ private model: string;
35
+
36
+ constructor(model: string = REFINER_MODEL) {
37
+ this.model = model;
38
+ }
39
+
40
+ async refinePrompt(prompt: string): Promise<string> {
41
+ if (!prompt || prompt.trim().length === 0) {
42
+ return prompt;
43
+ }
44
+
45
+ try {
46
+ const refined = await this.refineWithTimeout(prompt);
47
+ return refined.trim();
48
+ } catch (error) {
49
+ const errorMsg =
50
+ error instanceof Error ? error.message : String(error);
51
+ logger.warn(
52
+ `[PromptRefiner] LLM refinement failed: ${errorMsg}, returning original`
53
+ );
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ private async refineWithTimeout(prompt: string): Promise<string> {
59
+ const timeoutPromise = new Promise<never>((_, reject) => {
60
+ setTimeout(() => {
61
+ reject(new Error("Prompt refinement timeout"));
62
+ }, REFINER_TIMEOUT_MS);
63
+ });
64
+
65
+ const refinePromise = this.callLLM(prompt);
66
+
67
+ return Promise.race([refinePromise, timeoutPromise]);
68
+ }
69
+
70
+ private async callLLM(prompt: string): Promise<string> {
71
+ const client = getOpenAIClient(this.model);
72
+
73
+ const response = await client.chat.completions.create({
74
+ model: this.model,
75
+ messages: [
76
+ {
77
+ role: "system",
78
+ content: REFINEMENT_SYSTEM_PROMPT,
79
+ },
80
+ {
81
+ role: "user",
82
+ content: `Please refine this system prompt:\n\n${prompt}`,
83
+ },
84
+ ],
85
+ max_tokens: REFINER_MAX_TOKENS,
86
+ temperature: REFINER_TEMPERATURE,
87
+ });
88
+
89
+ const refined = response.choices[0]?.message?.content?.trim();
90
+
91
+ if (!refined) {
92
+ throw new Error("Empty response from LLM");
93
+ }
94
+
95
+ return refined;
96
+ }
97
+ }
98
+
99
+ let promptRefinerInstance: IPromptRefiner | undefined;
100
+
101
+ export function getPromptRefiner(model?: string): IPromptRefiner {
102
+ if (!promptRefinerInstance) {
103
+ promptRefinerInstance = new LLMPromptRefiner(model);
104
+ }
105
+ return promptRefinerInstance;
106
+ }
@@ -13,7 +13,15 @@ import { ChatFatalError } from "../protocol/errors";
13
13
  import { ServerToClient } from "../protocol/messages";
14
14
  import { SessionRegistry } from "./sessionRegistry";
15
15
 
16
+ const DEVELOPMENT: boolean = process.env.DEVELOPMENT === "1";
17
+ const TEST: boolean = process.env.TEST === "1";
18
+
16
19
  dotenv.config();
20
+ if (TEST) {
21
+ dotenv.config({ path: ".env.test" });
22
+ } else if (DEVELOPMENT) {
23
+ dotenv.config({ path: ".env.development" });
24
+ }
17
25
 
18
26
  const logger = getLogger();
19
27
 
@@ -59,7 +67,6 @@ export async function runServer(
59
67
  port: number,
60
68
  supabaseUrl: string,
61
69
  supabaseKey: string,
62
- llmUrl: string,
63
70
  xmcpUrl: string
64
71
  ): Promise<ws.Server> {
65
72
  return new Promise((r, _e) => {
@@ -68,7 +75,7 @@ export async function runServer(
68
75
  const createSessionRegistry = (
69
76
  connManager: IUserConnectionManager<ServerToClient>
70
77
  ) => {
71
- return new SessionRegistry(db, connManager, llmUrl, xmcpUrl);
78
+ return new SessionRegistry(db, connManager, xmcpUrl);
72
79
  };
73
80
  const connectionManager = ConnectionManager.init(createSessionRegistry);
74
81
 
@@ -1,148 +1,22 @@
1
+ /**
2
+ * Chat-specific file manager implementation backed by the database.
3
+ */
4
+
1
5
  import { v4 as uuidv4 } from "uuid";
2
6
  import { strict as assert } from "assert";
3
7
 
4
- import {
5
- Agent,
6
- IAgentToolProvider,
7
- ToolCallResult,
8
- ToolHandler,
9
- } from "../../agent/agent";
10
- import { makeParseArgsFn } from "./tools";
11
8
  import { Database } from "../data/database";
12
9
  import { DbSessionFiles } from "../data/dbSessionFiles";
13
10
  import {
14
- FileMetaData,
11
+ ISessionFileManager,
12
+ ISessionFileManagerEventHandler,
15
13
  SessionFileDescriptor,
16
- SessionFileEntry,
14
+ ParsedContent,
17
15
  getSessionFileMimeTypeFromDataUrl,
18
- } from "../data/dbSessionFileModels";
19
- import { ToolDescriptor } from "../../agent/llm";
20
-
21
- /**
22
- * Tool call result with specific form of metadata
23
- */
24
- export type ToolCallResultWithFileRef = ToolCallResult<FileMetaData>;
25
-
26
- /**
27
- * Calling code implements this to be informed of file changes.
28
- */
29
- export interface ISessionFileManagerEventHandler {
30
- onFileChanged(entry: SessionFileEntry, new_file: boolean): void;
31
- onFileDeleted(name: string): void;
32
- }
33
-
34
- /**
35
- * Interface to a set of files in a session.
36
- */
37
- export interface ISessionFileManager {
38
- /**
39
- * List file names, types and summaries,
40
- */
41
- listFiles(): SessionFileDescriptor[];
42
-
43
- /**
44
- * Retrieve file contents.
45
- */
46
- getFileContent(name: string): Promise<string>;
47
-
48
- /**
49
- * Delete a file
50
- */
51
- deleteFile(name: string): Promise<void>;
52
-
53
- /**
54
- * Create or update a file with the given name. Returns the name (which is
55
- * created if one is not passed in).
56
- */
57
- putFileContent(
58
- name: string | undefined,
59
- summary: string | undefined,
60
- data_url: string
61
- ): Promise<SessionFileDescriptor>;
62
-
63
- addEventHandler(eventHandler: ISessionFileManagerEventHandler): void;
64
- }
65
-
66
- /**
67
- * In-memory implementation of ISessionFileManager
68
- */
69
- export class MemoryFileManager implements ISessionFileManager {
70
- private readonly files: Map<string, SessionFileEntry>;
71
- private eventHandlers: ISessionFileManagerEventHandler[];
72
-
73
- constructor() {
74
- this.files = new Map();
75
- this.eventHandlers = [];
76
- }
77
-
78
- // ISessionFileManager.listFiles
79
- listFiles(): SessionFileDescriptor[] {
80
- return Array.from(this.files.values());
81
- }
82
-
83
- // ISessionFileManager.getFileContent
84
- getFileContent(name: string): Promise<string> {
85
- return new Promise((r, e) => {
86
- const entry = this.files.get(name);
87
- if (entry) {
88
- r(entry.data_url);
89
- } else {
90
- e(new Error(`no such file ${name}`));
91
- }
92
- });
93
- }
94
-
95
- // ISessionFileManager.deleteFile
96
- deleteFile(name: string): Promise<void> {
97
- return new Promise((r, e) => {
98
- if (this.files.has(name)) {
99
- this.files.delete(name);
100
- this.eventHandlers.forEach((eh) => {
101
- eh.onFileDeleted(name);
102
- });
103
- r();
104
- } else {
105
- e(new Error(`no such file ${name}`));
106
- }
107
- });
108
- }
109
-
110
- // ISessionFileManager.addFile
111
- putFileContent(
112
- name: string | undefined,
113
- summary: string | undefined,
114
- data_url: string
115
- ): Promise<SessionFileDescriptor> {
116
- return new Promise((r, e) => {
117
- if (!name) {
118
- name = uuidv4();
119
- }
120
- if (!summary) {
121
- summary = "";
122
- }
123
- if (!isSingleLine(summary)) {
124
- e(new Error("summary must no contain new-lines"));
125
- } else {
126
- const mime_type = getSessionFileMimeTypeFromDataUrl(data_url);
127
- const is_new = !this.files.has(name);
128
- const entry: SessionFileEntry = { name, mime_type, summary, data_url };
129
- this.eventHandlers.forEach((eh) => {
130
- eh.onFileChanged(entry, is_new);
131
- });
132
- this.files.set(name, entry);
133
- r({ name, mime_type, summary });
134
- }
135
- });
136
- }
137
-
138
- // ISessionFileManager.setEventHandler
139
- addEventHandler(eventHandler: ISessionFileManagerEventHandler) {
140
- this.eventHandlers.push(eventHandler);
141
- }
142
- }
16
+ } from "../../agent/tools/fileManager";
143
17
 
144
18
  /**
145
- * Implementation of ISessionFileManager which can store files in the DB.
19
+ * Implementation of ISessionFileManager which stores files in the DB.
146
20
  */
147
21
  export class ChatSessionFileManager implements ISessionFileManager {
148
22
  private readonly sessionUUID: string;
@@ -197,7 +71,15 @@ export class ChatSessionFileManager implements ISessionFileManager {
197
71
  return fileData;
198
72
  }
199
73
 
200
- // ISessionFileManager.deleteFile
74
+ // eslint-disable-next-line @typescript-eslint/require-await
75
+ async getFileParsedContent(name: string): Promise<ParsedContent | undefined> {
76
+ const descriptor = this.fileMap.get(name);
77
+ if (!descriptor) {
78
+ throw new Error(`no file '${name}' in session '${this.sessionUUID}'`);
79
+ }
80
+ return descriptor.parsed_content;
81
+ }
82
+
201
83
  async deleteFile(name: string): Promise<void> {
202
84
  if (this.fileMap.has(name)) {
203
85
  this.fileMap.delete(name);
@@ -211,41 +93,44 @@ export class ChatSessionFileManager implements ISessionFileManager {
211
93
  }
212
94
  }
213
95
 
214
- // ISessionFileManager.addFile
215
96
  async putFileContent(
216
97
  name: string | undefined,
217
98
  summary: string | undefined,
218
- data_url: string
99
+ data_url: string,
100
+ parsed_content?: ParsedContent
219
101
  ): Promise<SessionFileDescriptor> {
220
102
  if (!name) {
221
- // TODO: have the LLM fill in the name?
222
103
  name = uuidv4();
223
104
  }
224
- // TODO: have the LLM create summary if missing?
225
-
226
- // If we have an existing entry in the cache then update it, otherwise
227
- // create a new one.
228
105
 
229
106
  const mime_type = getSessionFileMimeTypeFromDataUrl(data_url);
230
107
  const existingDesc = this.fileMap.get(name);
108
+ const is_new = !existingDesc;
231
109
  if (existingDesc) {
232
110
  existingDesc.summary = summary;
233
111
  existingDesc.mime_type = mime_type;
112
+ existingDesc.parsed_content = parsed_content;
234
113
  } else {
235
- this.fileMap.set(name, { name, summary, mime_type });
114
+ this.fileMap.set(name, { name, summary, mime_type, parsed_content });
236
115
  }
237
-
238
- const is_new = !this.fileMap.has(name);
239
116
  this.fileDataCache.set(name, data_url);
240
- await this.sfc.setFileContent(this.sessionUUID, name, summary, data_url);
117
+ await this.sfc.setFileContent(
118
+ this.sessionUUID,
119
+ name,
120
+ summary,
121
+ data_url,
122
+ parsed_content
123
+ );
241
124
  this.eventHandlers.forEach((eh) => {
242
125
  assert(typeof name === "string");
243
- eh.onFileChanged({ name, summary, mime_type, data_url }, is_new);
126
+ eh.onFileChanged(
127
+ { name, summary, mime_type, parsed_content, data_url },
128
+ is_new
129
+ );
244
130
  });
245
- return { name, mime_type, summary };
131
+ return { name, mime_type, summary, parsed_content };
246
132
  }
247
133
 
248
- // ISessionFileManager.setEventHandler
249
134
  addEventHandler(eventHandler: ISessionFileManagerEventHandler) {
250
135
  this.eventHandlers.push(eventHandler);
251
136
  }
@@ -264,159 +149,3 @@ export class ChatSessionFileManager implements ISessionFileManager {
264
149
  return `file+session://${this.sessionUUID}/${name}`;
265
150
  }
266
151
  }
267
-
268
- /**
269
- * Return the file list in a form easily parsable by the LLM.
270
- */
271
- export function createSessionFilesManagerPrompt(
272
- fm: ISessionFileManager
273
- ): string {
274
- const files = fm.listFiles();
275
- if (files.length === 0) {
276
- return "";
277
- }
278
- let prompt =
279
- "Files can be read/written as required. Create new files conservatively, " +
280
- "usually when complex content is requested. Use base64 for binary (image," +
281
- "pdf), ascii for text formats (html,markdown). " +
282
- "Available files:\nname,type,summary\n";
283
- for (const f of files) {
284
- prompt += `${f.name},${f.mime_type},${f.summary || ""}\n`;
285
- }
286
- return prompt;
287
- }
288
-
289
- const GET_FILE_CONTENT_TOOL: ToolDescriptor = {
290
- type: "function",
291
- function: {
292
- name: "get_file_content",
293
- description: "Obtain contents of file listed in system prompt, as data-url",
294
- parameters: {
295
- type: "object",
296
- properties: {
297
- name: {
298
- type: "string",
299
- description: "File name",
300
- },
301
- },
302
- required: ["name"],
303
- },
304
- },
305
- };
306
-
307
- const PUT_FILE_CONTENT_TOOL: ToolDescriptor = {
308
- type: "function",
309
- function: {
310
- name: "put_file_content",
311
- description: "Create or update file content",
312
- parameters: {
313
- type: "object",
314
- properties: {
315
- name: {
316
- type: "string",
317
- description: "File name",
318
- },
319
- summary: {
320
- type: "string",
321
- description: "Content summary",
322
- },
323
- data_url: {
324
- type: "string",
325
- description:
326
- "Content data-url: `data:<mime-type>[;<format>],<encoding>`",
327
- },
328
- },
329
- required: ["name", "summary", "data_url"],
330
- },
331
- },
332
- };
333
-
334
- const DELETE_FILE_CONTENT_TOOL: ToolDescriptor = {
335
- type: "function",
336
- function: {
337
- name: "delete_file_content",
338
- description: "Delete file",
339
- parameters: {
340
- type: "object",
341
- properties: {
342
- name: {
343
- type: "string",
344
- description: "File name",
345
- },
346
- },
347
- required: ["name"],
348
- },
349
- },
350
- };
351
-
352
- export function fileManagerTool(
353
- fileManager: ChatSessionFileManager
354
- ): IAgentToolProvider {
355
- // `get_file_content` tool
356
- //
357
- // LLM can read data from the file. To keep the context small, we overwrite
358
- // the data with the session file url after the LLM has seen it.
359
-
360
- const parseName = makeParseArgsFn(["name"] as const);
361
- const getFileContentFn: ToolHandler = async (
362
- _agent: Agent,
363
- args: unknown
364
- ): Promise<ToolCallResult> => {
365
- const { name } = parseName(args);
366
- const response = await fileManager.getFileContent(name);
367
- const overwriteResponse = fileManager.getSessionFileRelativeUrl(name);
368
- return { response, overwriteResponse };
369
- };
370
-
371
- // `set_file_content` tool
372
- //
373
- // Allows LLM to write data (as a data-url) to the session file manager. The
374
- // data is replaced by a session file url after being saved.
375
-
376
- const putArgs = ["name", "summary", "data_url"] as const;
377
- const parseNameSummaryDataUrl = makeParseArgsFn(putArgs);
378
- const putFileContentFn: ToolHandler = async (
379
- _: Agent,
380
- args: unknown
381
- ): Promise<ToolCallResultWithFileRef> => {
382
- const parsed = parseNameSummaryDataUrl(args);
383
- const { name, summary, data_url } = parsed;
384
- const desc = await fileManager.putFileContent(name, summary, data_url);
385
- const mimeType = getSessionFileMimeTypeFromDataUrl(data_url);
386
- parsed.data_url = fileManager.getSessionFileRelativeUrl(name);
387
- return {
388
- response: desc.name,
389
- overwriteArgs: JSON.stringify(parsed),
390
- _meta: {
391
- "xalia/fileUri": parsed.data_url,
392
- "xalia/fileMimeType": mimeType,
393
- },
394
- };
395
- };
396
-
397
- // `delete_file_content` tool
398
- //
399
- // Allows LLM to write data (as a data-url) to the session file manager. The
400
- // data is replaced by a session file url after being saved.
401
-
402
- const deleteFileFn: ToolHandler = async (_: Agent, args: unknown) => {
403
- const { name } = parseName(args);
404
- await fileManager.deleteFile(name);
405
- return { response: "" };
406
- };
407
-
408
- return {
409
- setup: (agent: Agent) => {
410
- agent.addAgentTool(GET_FILE_CONTENT_TOOL, getFileContentFn);
411
- agent.addAgentTool(PUT_FILE_CONTENT_TOOL, putFileContentFn);
412
- agent.addAgentTool(DELETE_FILE_CONTENT_TOOL, deleteFileFn);
413
- return new Promise<void>((r) => {
414
- r();
415
- });
416
- },
417
- };
418
- }
419
-
420
- function isSingleLine(str: string): boolean {
421
- return !/[\r\n]/.test(str);
422
- }
@@ -49,7 +49,6 @@ describe("SessionRegistry", () => {
49
49
  sessionRegistry = new SessionRegistry(
50
50
  mockDatabase.mock,
51
51
  mockUserConnectionManager.mock,
52
- "http://llm-api.test",
53
52
  "http://xmcp-api.test"
54
53
  );
55
54
  });