@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,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,
|
|
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
|
-
|
|
11
|
+
ISessionFileManager,
|
|
12
|
+
ISessionFileManagerEventHandler,
|
|
15
13
|
SessionFileDescriptor,
|
|
16
|
-
|
|
14
|
+
ParsedContent,
|
|
17
15
|
getSessionFileMimeTypeFromDataUrl,
|
|
18
|
-
} from "
|
|
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
|
|
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
|
-
//
|
|
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(
|
|
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(
|
|
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
|
-
}
|