@xalia/agent 0.6.8 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.development +6 -0
- package/.env.test +7 -0
- package/README.md +11 -0
- package/context_system.md +498 -0
- package/dist/agent/src/agent/agent.js +169 -87
- package/dist/agent/src/agent/agentUtils.js +24 -18
- package/dist/agent/src/agent/compressingContextManager.js +10 -14
- package/dist/agent/src/agent/context.js +101 -127
- package/dist/agent/src/agent/contextWithWorkspace.js +133 -0
- package/dist/agent/src/agent/documentSummarizer.js +126 -0
- package/dist/agent/src/agent/dummyLLM.js +25 -22
- package/dist/agent/src/agent/imageGenLLM.js +22 -25
- package/dist/agent/src/agent/imageGenerator.js +2 -10
- package/dist/agent/src/agent/llm.js +1 -1
- package/dist/agent/src/agent/openAILLM.js +15 -12
- package/dist/agent/src/agent/openAILLMStreaming.js +73 -39
- package/dist/agent/src/agent/repeatLLM.js +16 -7
- package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
- package/dist/agent/src/agent/tokenCounter.js +390 -0
- package/dist/agent/src/agent/tokenCounter.test.js +206 -0
- package/dist/agent/src/agent/toolSettings.js +17 -0
- package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
- package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
- package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
- package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
- package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
- package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
- package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
- package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
- package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
- package/dist/agent/src/agent/tools/index.js +64 -0
- package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
- package/dist/agent/src/agent/tools/renderTool.js +89 -0
- package/dist/agent/src/agent/tools/utils.js +61 -0
- package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
- package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
- package/dist/agent/src/chat/client/chatClient.js +63 -2
- package/dist/agent/src/chat/client/connection.js +6 -1
- package/dist/agent/src/chat/client/index.js +4 -1
- package/dist/agent/src/chat/client/sessionClient.js +28 -9
- package/dist/agent/src/chat/constants.js +8 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
- package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
- package/dist/agent/src/chat/protocol/messages.js +9 -0
- package/dist/agent/src/chat/server/chatContextManager.js +186 -156
- package/dist/agent/src/chat/server/conversation.js +3 -0
- package/dist/agent/src/chat/server/imageGeneratorTools.js +39 -16
- package/dist/agent/src/chat/server/openAIRouterLLM.js +111 -0
- package/dist/agent/src/chat/server/openSession.js +253 -91
- package/dist/agent/src/chat/server/promptRefiner.js +86 -0
- package/dist/agent/src/chat/server/server.js +10 -2
- package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
- package/dist/agent/src/chat/server/sessionRegistry.js +152 -6
- package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
- package/dist/agent/src/chat/server/titleGenerator.js +112 -0
- package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
- package/dist/agent/src/chat/server/tools.js +64 -253
- package/dist/agent/src/chat/utils/approvalManager.js +6 -3
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
- package/dist/agent/src/test/agent.test.js +16 -17
- package/dist/agent/src/test/chatContextManager.test.js +44 -30
- package/dist/agent/src/test/clientServerConnection.test.js +1 -2
- package/dist/agent/src/test/compressingContextManager.test.js +22 -36
- package/dist/agent/src/test/context.test.js +55 -17
- package/dist/agent/src/test/contextTestTools.js +87 -0
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
- package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
- package/dist/agent/src/test/testTools.js +6 -1
- package/dist/agent/src/test/tools.test.js +27 -9
- package/dist/agent/src/tool/agentChat.js +5 -2
- package/dist/agent/src/tool/chatMain.js +56 -15
- package/dist/agent/src/tool/commandPrompt.js +2 -2
- package/dist/agent/src/tool/files.js +7 -8
- package/package.json +4 -1
- package/scripts/test_chat +195 -173
- package/src/agent/agent.ts +257 -137
- package/src/agent/agentUtils.ts +32 -20
- package/src/agent/compressingContextManager.ts +13 -44
- package/src/agent/context.ts +165 -159
- package/src/agent/contextWithWorkspace.ts +162 -0
- package/src/agent/documentSummarizer.ts +157 -0
- package/src/agent/dummyLLM.ts +27 -23
- package/src/agent/imageGenLLM.ts +28 -32
- package/src/agent/imageGenerator.ts +3 -18
- package/src/agent/llm.ts +2 -2
- package/src/agent/openAILLM.ts +17 -13
- package/src/agent/openAILLMStreaming.ts +99 -43
- package/src/agent/repeatLLM.ts +19 -7
- package/src/agent/sudoMcpServerManager.ts +41 -20
- package/src/agent/test_data/harrypotter.txt +6065 -0
- package/src/agent/tokenCounter.test.ts +243 -0
- package/src/agent/tokenCounter.ts +483 -0
- package/src/agent/toolSettings.ts +24 -0
- package/src/agent/tools/calculatorTool.ts +50 -0
- package/src/agent/tools/contentExtractors/pdfToText.ts +60 -0
- package/src/agent/tools/datetimeTool.ts +41 -0
- package/src/agent/tools/fileManager/fileManagerTool.ts +199 -0
- package/src/agent/tools/fileManager/index.ts +50 -0
- package/src/agent/tools/fileManager/memoryFileManager.ts +120 -0
- package/src/{chat/data → agent/tools/fileManager}/mimeTypes.ts +3 -1
- package/src/agent/tools/fileManager/prompt.ts +38 -0
- package/src/{chat/data/dbSessionFileModels.ts → agent/tools/fileManager/types.ts} +76 -0
- package/src/agent/tools/index.ts +49 -0
- package/src/agent/tools/openUrlTool.ts +62 -0
- package/src/agent/tools/renderTool.ts +92 -0
- package/src/agent/tools/utils.ts +74 -0
- package/src/{chat/utils/search.ts → agent/tools/webSearch.ts} +0 -1
- package/src/agent/tools/webSearchTool.ts +44 -0
- package/src/chat/client/chatClient.ts +92 -3
- package/src/chat/client/connection.ts +11 -1
- package/src/chat/client/index.ts +3 -0
- package/src/chat/client/sessionClient.ts +40 -11
- package/src/chat/client/sessionFiles.ts +1 -1
- package/src/chat/constants.ts +6 -0
- package/src/chat/data/dataModels.ts +12 -0
- package/src/chat/data/dbSessionFiles.ts +12 -4
- package/src/chat/data/dbSessionMessages.ts +34 -0
- package/src/chat/protocol/messages.ts +94 -14
- package/src/chat/server/chatContextManager.ts +255 -221
- package/src/chat/server/connectionManager.ts +1 -1
- package/src/chat/server/conversation.ts +3 -0
- package/src/chat/server/imageGeneratorTools.ts +62 -30
- package/src/chat/server/openAIRouterLLM.ts +168 -0
- package/src/chat/server/openSession.ts +381 -138
- package/src/chat/server/promptRefiner.ts +106 -0
- package/src/chat/server/server.ts +9 -2
- package/src/chat/server/sessionFileManager.ts +35 -306
- package/src/chat/server/sessionRegistry.test.ts +0 -1
- package/src/chat/server/sessionRegistry.ts +228 -4
- package/src/chat/server/titleGenerator.test.ts +103 -0
- package/src/chat/server/titleGenerator.ts +143 -0
- package/src/chat/server/tools.ts +92 -281
- package/src/chat/utils/approvalManager.ts +9 -3
- package/src/chat/utils/multiAsyncQueue.ts +4 -0
- package/src/test/agent.test.ts +25 -30
- package/src/test/chatContextManager.test.ts +68 -38
- package/src/test/clientServerConnection.test.ts +0 -2
- package/src/test/compressingContextManager.test.ts +29 -34
- package/src/test/context.test.ts +59 -15
- package/src/test/contextTestTools.ts +95 -0
- package/src/test/dbMcpServerConfigs.test.ts +4 -4
- package/src/test/dbSessionFiles.test.ts +16 -16
- package/src/test/testTools.ts +8 -3
- package/src/test/tools.test.ts +30 -5
- package/src/tool/agentChat.ts +12 -3
- package/src/tool/chatMain.ts +59 -18
- package/src/tool/commandPrompt.ts +2 -2
- package/src/tool/files.ts +1 -3
- package/dist/agent/src/agent/tools.js +0 -44
- package/src/agent/tools.ts +0 -57
- /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
- /package/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.ts +0 -0
|
@@ -0,0 +1,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
|
-
|
|
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
|
+
}
|