@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,199 @@
1
+ import {
2
+ AgentEx,
3
+ IAgentToolProvider,
4
+ ToolCallResult,
5
+ ToolHandler,
6
+ } from "../../agent";
7
+ import { ToolDescriptor } from "../../llm";
8
+ import { makeParseArgsFn } from "../utils";
9
+ import {
10
+ ISessionFileManager,
11
+ FileMetaData,
12
+ getSessionFileMimeTypeFromDataUrl,
13
+ } from "./types";
14
+
15
+ /**
16
+ * Tool call result with specific form of metadata
17
+ */
18
+ export type ToolCallResultWithFileRef = ToolCallResult<FileMetaData>;
19
+
20
+ const GET_FILE_CONTENT_TOOL: ToolDescriptor = {
21
+ type: "function",
22
+ function: {
23
+ name: "get_file_content",
24
+ description:
25
+ "Get raw file content as data-url. Use this for images or when you " +
26
+ "need the original binary data. For PDFs and documents where you need " +
27
+ "the text content, use get_file_parsed_content instead.",
28
+ parameters: {
29
+ type: "object",
30
+ properties: {
31
+ name: {
32
+ type: "string",
33
+ description: "File name",
34
+ },
35
+ },
36
+ required: ["name"],
37
+ },
38
+ },
39
+ };
40
+
41
+ const PUT_FILE_CONTENT_TOOL: ToolDescriptor = {
42
+ type: "function",
43
+ function: {
44
+ name: "put_file_content",
45
+ description: "Create or update file content",
46
+ parameters: {
47
+ type: "object",
48
+ properties: {
49
+ name: {
50
+ type: "string",
51
+ description: "File name",
52
+ },
53
+ summary: {
54
+ type: "string",
55
+ description: "Content summary",
56
+ },
57
+ data_url: {
58
+ type: "string",
59
+ description:
60
+ "Content data-url: `data:<mime-type>[;<format>],<encoding>`",
61
+ },
62
+ },
63
+ required: ["name", "summary", "data_url"],
64
+ },
65
+ },
66
+ };
67
+
68
+ const DELETE_FILE_CONTENT_TOOL: ToolDescriptor = {
69
+ type: "function",
70
+ function: {
71
+ name: "delete_file_content",
72
+ description: "Delete file",
73
+ parameters: {
74
+ type: "object",
75
+ properties: {
76
+ name: {
77
+ type: "string",
78
+ description: "File name",
79
+ },
80
+ },
81
+ required: ["name"],
82
+ },
83
+ },
84
+ };
85
+
86
+ const GET_FILE_PARSED_CONTENT_TOOL: ToolDescriptor = {
87
+ type: "function",
88
+ function: {
89
+ name: "get_file_parsed_content",
90
+ description:
91
+ "Get extracted text content from a file (e.g., PDF). " +
92
+ "Returns the parsed text if available, or an error if the file " +
93
+ "has no parsed content.",
94
+ parameters: {
95
+ type: "object",
96
+ properties: {
97
+ name: {
98
+ type: "string",
99
+ description: "File name",
100
+ },
101
+ },
102
+ required: ["name"],
103
+ },
104
+ },
105
+ };
106
+
107
+ export function fileManagerTool(
108
+ fileManager: ISessionFileManager
109
+ ): IAgentToolProvider {
110
+ // `get_file_content` tool
111
+ //
112
+ // LLM can read data from the file. To keep the context small, we overwrite
113
+ // the data with the session file url after the LLM has seen it.
114
+
115
+ const parseName = makeParseArgsFn(["name"] as const);
116
+ const getFileContentFn: ToolHandler = async (
117
+ _agent: AgentEx,
118
+ args: unknown
119
+ ): Promise<ToolCallResult> => {
120
+ const { name } = parseName(args);
121
+ const response = await fileManager.getFileContent(name);
122
+ const overwriteResponse = fileManager.getSessionFileRelativeUrl(name);
123
+ return { response, overwriteResponse };
124
+ };
125
+
126
+ // `set_file_content` tool
127
+ //
128
+ // Allows LLM to write data (as a data-url) to the session file manager. The
129
+ // data is replaced by a session file url after being saved.
130
+
131
+ const putArgs = ["name", "summary", "data_url"] as const;
132
+ const parseNameSummaryDataUrl = makeParseArgsFn(putArgs);
133
+ const putFileContentFn: ToolHandler = async (
134
+ _: AgentEx,
135
+ args: unknown
136
+ ): Promise<ToolCallResultWithFileRef> => {
137
+ const parsed = parseNameSummaryDataUrl(args);
138
+ const { name, summary, data_url } = parsed;
139
+ const existingFile = fileManager.listFiles().find((f) => f.name === name);
140
+ const desc = await fileManager.putFileContent(name, summary, data_url);
141
+ const mimeType = getSessionFileMimeTypeFromDataUrl(data_url);
142
+
143
+ // Calculate content size for response message
144
+ const contentSize = data_url.split(",")[1]?.length || 0;
145
+ const sizeKB = (contentSize / 1024).toFixed(1);
146
+
147
+ parsed.data_url = fileManager.getSessionFileRelativeUrl(name);
148
+ const action = existingFile ? "updated" : "created";
149
+ const responseMsg =
150
+ `Successfully ${action} file '${desc.name}' with ${sizeKB}KB of content`;
151
+ return {
152
+ response: responseMsg,
153
+ overwriteArgs: JSON.stringify(parsed),
154
+ _meta: {
155
+ "xalia/fileUri": parsed.data_url,
156
+ "xalia/fileMimeType": mimeType,
157
+ },
158
+ };
159
+ };
160
+
161
+ // `delete_file_content` tool
162
+ //
163
+ // Allows LLM to write data (as a data-url) to the session file manager. The
164
+ // data is replaced by a session file url after being saved.
165
+
166
+ const deleteFileFn: ToolHandler = async (_: AgentEx, args: unknown) => {
167
+ const { name } = parseName(args);
168
+ await fileManager.deleteFile(name);
169
+ return { response: "" };
170
+ };
171
+
172
+ // `get_file_parsed_content` tool
173
+ //
174
+ // Returns extracted text content from files like PDFs.
175
+
176
+ const getFileParsedContentFn: ToolHandler = async (
177
+ _: AgentEx,
178
+ args: unknown
179
+ ): Promise<ToolCallResult> => {
180
+ const { name } = parseName(args);
181
+ const parsedContent = await fileManager.getFileParsedContent(name);
182
+ if (!parsedContent) {
183
+ return { response: `File '${name}' has no parsed content available.` };
184
+ }
185
+ return { response: parsedContent.text };
186
+ };
187
+
188
+ return {
189
+ setup: (agent: AgentEx) => {
190
+ agent.addAgentTool(GET_FILE_CONTENT_TOOL, getFileContentFn);
191
+ agent.addAgentTool(PUT_FILE_CONTENT_TOOL, putFileContentFn);
192
+ agent.addAgentTool(DELETE_FILE_CONTENT_TOOL, deleteFileFn);
193
+ agent.addAgentTool(GET_FILE_PARSED_CONTENT_TOOL, getFileParsedContentFn);
194
+ return new Promise<void>((r) => {
195
+ r();
196
+ });
197
+ },
198
+ };
199
+ }
@@ -0,0 +1,50 @@
1
+ // Mime types - values
2
+ export {
3
+ IMAGE_MIME_TYPES,
4
+ isImageMimeType,
5
+ EXTENSION_TO_IMAGE_MIME_TYPE,
6
+ getMimeTypeFromDataUrl,
7
+ createDataUrlFromBuffer,
8
+ createDataUrlFromText,
9
+ } from "./mimeTypes";
10
+
11
+ // Mime types - types
12
+ export type { ImageMimeType } from "./mimeTypes";
13
+
14
+ // Session file types - values
15
+ export {
16
+ SESSION_FILE_MIME_TYPES,
17
+ isSessionFileMimeType,
18
+ SESSION_FILE_TEXT_MIME_TYPES,
19
+ isSessionFileTextMimeType,
20
+ EXTENSION_TO_SESSION_FILE_MIME_TYPE,
21
+ isFileMetaData,
22
+ isParsedContent,
23
+ getSessionFileMimeTypeFromDataUrl,
24
+ createSessionFileDataUrl,
25
+ } from "./types";
26
+
27
+ // Session file types - types
28
+ export type {
29
+ SessionFileMimeType,
30
+ SessionFileTextMimeType,
31
+ SessionFileDescriptor,
32
+ SessionFileEntry,
33
+ FileMetaData,
34
+ ParsedContent,
35
+ ParsedContentV1,
36
+ ISessionFileManagerEventHandler,
37
+ ISessionFileManager,
38
+ } from "./types";
39
+
40
+ // Implementations
41
+ export { MemoryFileManager } from "./memoryFileManager";
42
+
43
+ // Tool - values
44
+ export { fileManagerTool } from "./fileManagerTool";
45
+
46
+ // Tool - types
47
+ export type { ToolCallResultWithFileRef } from "./fileManagerTool";
48
+
49
+ // Prompt
50
+ export { createSessionFilesManagerPrompt } from "./prompt";
@@ -0,0 +1,120 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+
3
+ import {
4
+ ISessionFileManager,
5
+ ISessionFileManagerEventHandler,
6
+ SessionFileDescriptor,
7
+ SessionFileEntry,
8
+ ParsedContent,
9
+ getSessionFileMimeTypeFromDataUrl,
10
+ } from "./types";
11
+
12
+ function isSingleLine(str: string): boolean {
13
+ return !/[\r\n]/.test(str);
14
+ }
15
+
16
+ /**
17
+ * In-memory implementation of ISessionFileManager
18
+ */
19
+ export class MemoryFileManager implements ISessionFileManager {
20
+ private readonly files: Map<string, SessionFileEntry>;
21
+ private eventHandlers: ISessionFileManagerEventHandler[];
22
+
23
+ constructor() {
24
+ this.files = new Map();
25
+ this.eventHandlers = [];
26
+ }
27
+
28
+ // ISessionFileManager.listFiles
29
+ listFiles(): SessionFileDescriptor[] {
30
+ return Array.from(this.files.values());
31
+ }
32
+
33
+ // ISessionFileManager.getFileContent
34
+ getFileContent(name: string): Promise<string> {
35
+ return new Promise((r, e) => {
36
+ const entry = this.files.get(name);
37
+ if (entry) {
38
+ r(entry.data_url);
39
+ } else {
40
+ e(new Error(`no such file ${name}`));
41
+ }
42
+ });
43
+ }
44
+
45
+ // ISessionFileManager.getFileParsedContent
46
+ getFileParsedContent(name: string): Promise<ParsedContent | undefined> {
47
+ return new Promise((r, e) => {
48
+ const entry = this.files.get(name);
49
+ if (entry) {
50
+ r(entry.parsed_content);
51
+ } else {
52
+ e(new Error(`no such file ${name}`));
53
+ }
54
+ });
55
+ }
56
+
57
+ // ISessionFileManager.deleteFile
58
+ deleteFile(name: string): Promise<void> {
59
+ return new Promise((r, e) => {
60
+ if (this.files.has(name)) {
61
+ this.files.delete(name);
62
+ this.eventHandlers.forEach((eh) => {
63
+ eh.onFileDeleted(name);
64
+ });
65
+ r();
66
+ } else {
67
+ e(new Error(`no such file ${name}`));
68
+ }
69
+ });
70
+ }
71
+
72
+ // ISessionFileManager.addFile
73
+ putFileContent(
74
+ name: string | undefined,
75
+ summary: string | undefined,
76
+ data_url: string,
77
+ parsed_content?: ParsedContent
78
+ ): Promise<SessionFileDescriptor> {
79
+ return new Promise((r, e) => {
80
+ if (!name) {
81
+ name = uuidv4();
82
+ }
83
+ if (!summary) {
84
+ summary = "";
85
+ }
86
+ if (!isSingleLine(summary)) {
87
+ e(new Error("summary must no contain new-lines"));
88
+ } else {
89
+ const mime_type = getSessionFileMimeTypeFromDataUrl(data_url);
90
+ const is_new = !this.files.has(name);
91
+ const entry: SessionFileEntry = {
92
+ name,
93
+ mime_type,
94
+ summary,
95
+ parsed_content,
96
+ data_url,
97
+ };
98
+ this.eventHandlers.forEach((eh) => {
99
+ eh.onFileChanged(entry, is_new);
100
+ });
101
+ this.files.set(name, entry);
102
+ r({ name, mime_type, summary, parsed_content });
103
+ }
104
+ });
105
+ }
106
+
107
+ // ISessionFileManager.setEventHandler
108
+ addEventHandler(eventHandler: ISessionFileManagerEventHandler) {
109
+ this.eventHandlers.push(eventHandler);
110
+ }
111
+
112
+ getSessionFileRelativeUrl(name: string): string {
113
+ return `file+session:/./${name}`;
114
+ }
115
+
116
+ getSessionFileAbsoluteUrl(name: string): string {
117
+ // MemoryFileManager doesn't have a session ID, use relative URL
118
+ return this.getSessionFileRelativeUrl(name);
119
+ }
120
+ }
@@ -54,5 +54,7 @@ export function createDataUrlFromBuffer(
54
54
  }
55
55
 
56
56
  export function createDataUrlFromText(data: string, mime_type: string): string {
57
- return `data:${mime_type},${data}`;
57
+ // Properly URL-encode the content to handle special characters
58
+ const encodedData = encodeURIComponent(data);
59
+ return `data:${mime_type};charset=utf-8,${encodedData}`;
58
60
  }
@@ -0,0 +1,38 @@
1
+ import { ISessionFileManager } from "./types";
2
+
3
+ /**
4
+ * Return the file list in a form easily parsable by the LLM.
5
+ */
6
+ export function createSessionFilesManagerPrompt(
7
+ fm: ISessionFileManager
8
+ ): string {
9
+ const files = fm.listFiles();
10
+
11
+ let prompt =
12
+ "Files can be read/written as required. Create new files " +
13
+ "conservatively, usually when complex content is requested. " +
14
+ "\n\nDATA URL FORMAT: For text files (plain, markdown, html), use " +
15
+ "URL-encoded format: 'data:<mime-type>;charset=utf-8," +
16
+ "<URL-encoded-content>'. For binary files (images, PDF), use " +
17
+ "base64: 'data:<mime-type>;base64,<base64-data>'. URL encoding " +
18
+ "means using encodeURIComponent() or percent-encoding where " +
19
+ "special characters become %XX codes." +
20
+ "\n\n⚠️ CRITICAL: After you call put_file_content, the system " +
21
+ "AUTOMATICALLY replaces your data_url argument with " +
22
+ "'file+session://...' in the conversation history. This is NORMAL " +
23
+ "behavior to save context space. The replacement happens AFTER the " +
24
+ "file is successfully written. When you see 'file+session://...' in " +
25
+ "your previous tool call, this means SUCCESS - the file was created " +
26
+ "with your full content. NEVER retry or attempt to 'fix' this. The " +
27
+ "file+session:// reference is NOT an error - it confirms the " +
28
+ "operation succeeded.";
29
+
30
+ if (files.length > 0) {
31
+ prompt += "\n\nAvailable files:\nname,type,summary\n";
32
+ for (const f of files) {
33
+ prompt += `${f.name},${f.mime_type},${f.summary || ""}\n`;
34
+ }
35
+ }
36
+
37
+ return prompt;
38
+ }
@@ -54,10 +54,28 @@ export const EXTENSION_TO_SESSION_FILE_MIME_TYPE: Record<
54
54
  md: "text/markdown",
55
55
  };
56
56
 
57
+ /**
58
+ * Parsed content from files like PDFs.
59
+ * Stored as JSON to allow future expansion (pages, tables, metadata).
60
+ */
61
+ export type ParsedContentV1 = {
62
+ version: 1;
63
+ text: string;
64
+ };
65
+
66
+ export type ParsedContent = ParsedContentV1;
67
+
68
+ export function isParsedContent(obj: unknown): obj is ParsedContent {
69
+ if (typeof obj !== "object" || obj === null) return false;
70
+ const pc = obj as Record<string, unknown>;
71
+ return pc.version === 1 && typeof pc.text === "string";
72
+ }
73
+
57
74
  export type SessionFileDescriptor = {
58
75
  name: string;
59
76
  mime_type: SessionFileMimeType;
60
77
  summary?: string;
78
+ parsed_content?: ParsedContent;
61
79
  };
62
80
 
63
81
  export type SessionFileEntry = SessionFileDescriptor & { data_url: string };
@@ -111,3 +129,61 @@ export function createSessionFileDataUrl(
111
129
  return createDataUrlFromBuffer(data, mime_type);
112
130
  }
113
131
  }
132
+
133
+ /**
134
+ * Calling code implements this to be informed of file changes.
135
+ */
136
+ export interface ISessionFileManagerEventHandler {
137
+ onFileChanged(entry: SessionFileEntry, new_file: boolean): void;
138
+ onFileDeleted(name: string): void;
139
+ }
140
+
141
+ /**
142
+ * Interface to a set of files in a session.
143
+ */
144
+ export interface ISessionFileManager {
145
+ /**
146
+ * List file names, types and summaries,
147
+ */
148
+ listFiles(): SessionFileDescriptor[];
149
+
150
+ /**
151
+ * Retrieve file contents as data URL.
152
+ */
153
+ getFileContent(name: string): Promise<string>;
154
+
155
+ /**
156
+ * Retrieve parsed content for a file (e.g., extracted text from PDF).
157
+ * Returns undefined if the file has no parsed content.
158
+ */
159
+ getFileParsedContent(name: string): Promise<ParsedContent | undefined>;
160
+
161
+ /**
162
+ * Delete a file
163
+ */
164
+ deleteFile(name: string): Promise<void>;
165
+
166
+ /**
167
+ * Create or update a file with the given name. Returns the name (which is
168
+ * created if one is not passed in).
169
+ */
170
+ putFileContent(
171
+ name: string | undefined,
172
+ summary: string | undefined,
173
+ data_url: string,
174
+ parsed_content?: ParsedContent
175
+ ): Promise<SessionFileDescriptor>;
176
+
177
+ addEventHandler(eventHandler: ISessionFileManagerEventHandler): void;
178
+
179
+ /**
180
+ * Get a relative URL for a session file (e.g., "file+session:/./{name}")
181
+ */
182
+ getSessionFileRelativeUrl(name: string): string;
183
+
184
+ /**
185
+ * Get an absolute URL for a session file
186
+ * (e.g., "file+session://{sessionId}/{name}")
187
+ */
188
+ getSessionFileAbsoluteUrl(name: string): string;
189
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * General-purpose agent tools.
3
+ *
4
+ * These tools are available to all agents, not just chat-based ones.
5
+ */
6
+
7
+ import { AgentEx, Agent } from "../agent";
8
+ import { IPlatform } from "../iplatform";
9
+ import { ISessionFileManager } from "./fileManager";
10
+
11
+ // Tool implementations
12
+ export { datetimeTool, isoWithTimezone } from "./datetimeTool";
13
+ export { calculatorTool, calculatorEval } from "./calculatorTool";
14
+ export { webSearchTool } from "./webSearchTool";
15
+ export { openURLTool, openURL } from "./openUrlTool";
16
+ export { renderTool } from "./renderTool";
17
+
18
+ // Utilities
19
+ export { makeParseArgsFn } from "./utils";
20
+ export { webSearch } from "./webSearch";
21
+ export { htmlToText } from "./contentExtractors/htmlToText";
22
+
23
+ // File manager (re-export everything)
24
+ export * from "./fileManager";
25
+
26
+ // Import tools for addDefaultTools
27
+ import { datetimeTool } from "./datetimeTool";
28
+ import { calculatorTool } from "./calculatorTool";
29
+ import { webSearchTool } from "./webSearchTool";
30
+ import { openURLTool } from "./openUrlTool";
31
+ import { renderTool } from "./renderTool";
32
+ import { fileManagerTool } from "./fileManager";
33
+
34
+ /**
35
+ * Add the default set of general-purpose agent tools.
36
+ */
37
+ export async function addDefaultTools(
38
+ agent: AgentEx | Agent,
39
+ timezone: string,
40
+ _platform: IPlatform,
41
+ fileManager: ISessionFileManager
42
+ ): Promise<void> {
43
+ await agent.addAgentToolProvider(datetimeTool(timezone));
44
+ await agent.addAgentToolProvider(calculatorTool);
45
+ await agent.addAgentToolProvider(renderTool(fileManager));
46
+ await agent.addAgentToolProvider(webSearchTool());
47
+ await agent.addAgentToolProvider(openURLTool());
48
+ await agent.addAgentToolProvider(fileManagerTool(fileManager));
49
+ }
@@ -0,0 +1,62 @@
1
+ import { AgentEx, IAgentToolProvider, ToolCallResult } from "../agent";
2
+ import { ToolDescriptor } from "../llm";
3
+ import { OPEN_URL_MAX_LENGTH } from "../toolSettings";
4
+ import { makeParseArgsFn } from "./utils";
5
+ import { htmlToText } from "./contentExtractors/htmlToText";
6
+ import { pdfToText } from "./contentExtractors/pdfToText";
7
+
8
+ const OPEN_URL_DESC: ToolDescriptor = {
9
+ type: "function",
10
+ function: {
11
+ name: "open_url",
12
+ description:
13
+ "Download and extract text content from a URL (supports HTML and PDF)",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ url: {
18
+ type: "string",
19
+ description: "URL to download (HTML pages or PDF files)",
20
+ },
21
+ },
22
+ required: ["url"],
23
+ },
24
+ },
25
+ };
26
+
27
+ export async function openURL(url: string): Promise<string> {
28
+ const response = await fetch(url);
29
+ if (!response.ok) {
30
+ const status = String(response.status);
31
+ const code = response.statusText;
32
+ throw new Error(`Failed to fetch ${url}: ${status} ${code}`);
33
+ }
34
+
35
+ const contentType = response.headers.get("content-type") || "";
36
+ const isPdf =
37
+ contentType.includes("application/pdf") ||
38
+ url.toLowerCase().endsWith(".pdf");
39
+
40
+ if (isPdf) {
41
+ const buffer = await response.arrayBuffer();
42
+ return pdfToText(buffer, OPEN_URL_MAX_LENGTH);
43
+ } else {
44
+ const html = await response.text();
45
+ return htmlToText(html, OPEN_URL_MAX_LENGTH);
46
+ }
47
+ }
48
+
49
+ export function openURLTool(): IAgentToolProvider {
50
+ const getURL = makeParseArgsFn(["url"] as const);
51
+ const toolFn = async (_: AgentEx, args: unknown): Promise<ToolCallResult> => {
52
+ const { url } = getURL(args);
53
+ return { response: await openURL(url) };
54
+ };
55
+
56
+ return {
57
+ // eslint-disable-next-line @typescript-eslint/require-await
58
+ setup: async (agent: AgentEx) => {
59
+ agent.addAgentTool(OPEN_URL_DESC, toolFn);
60
+ },
61
+ };
62
+ }