@xalia/agent 0.6.9 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/agent/src/agent/agent.js +77 -18
- package/dist/agent/src/agent/agentUtils.js +3 -2
- 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 -19
- 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 +68 -37
- package/dist/agent/src/agent/repeatLLM.js +16 -7
- 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 +28 -0
- package/dist/agent/src/chat/client/index.js +4 -1
- package/dist/agent/src/chat/client/sessionClient.js +28 -2
- package/dist/agent/src/chat/constants.js +8 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
- package/dist/agent/src/chat/protocol/messages.js +5 -0
- package/dist/agent/src/chat/server/chatContextManager.js +45 -25
- package/dist/agent/src/chat/server/conversation.js +3 -0
- package/dist/agent/src/chat/server/imageGeneratorTools.js +20 -8
- package/dist/agent/src/chat/server/openAIRouterLLM.js +0 -3
- package/dist/agent/src/chat/server/openSession.js +218 -55
- package/dist/agent/src/chat/server/promptRefiner.js +86 -0
- package/dist/agent/src/chat/server/server.js +5 -1
- package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
- package/dist/agent/src/chat/server/sessionRegistry.js +87 -0
- 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 +63 -287
- 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 +15 -3
- 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 +34 -7
- package/dist/agent/src/tool/commandPrompt.js +2 -2
- package/dist/agent/src/tool/files.js +7 -8
- package/package.json +8 -2
- package/.env.development +0 -1
- package/.prettierrc.json +0 -11
- package/dist/agent/src/agent/tools.js +0 -44
- package/eslint.config.mjs +0 -38
- package/scripts/chat_server +0 -8
- package/scripts/git_message +0 -31
- package/scripts/git_wip +0 -21
- package/scripts/pr_message +0 -18
- package/scripts/pr_review +0 -16
- package/scripts/setup_chat +0 -90
- package/scripts/shutdown_chat_server +0 -42
- package/scripts/start_chat_server +0 -24
- package/scripts/sudomcp_import +0 -23
- package/scripts/test_chat +0 -308
- package/src/agent/agent.ts +0 -624
- package/src/agent/agentUtils.ts +0 -285
- package/src/agent/compressingContextManager.ts +0 -129
- package/src/agent/context.ts +0 -265
- package/src/agent/contextWithWorkspace.ts +0 -162
- package/src/agent/dummyLLM.ts +0 -126
- package/src/agent/iAgentEventHandler.ts +0 -64
- package/src/agent/imageGenLLM.ts +0 -97
- package/src/agent/imageGenerator.ts +0 -45
- package/src/agent/iplatform.ts +0 -18
- package/src/agent/llm.ts +0 -74
- package/src/agent/mcpServerManager.ts +0 -541
- package/src/agent/nullAgentEventHandler.ts +0 -26
- package/src/agent/nullPlatform.ts +0 -13
- package/src/agent/openAI.ts +0 -123
- package/src/agent/openAILLM.ts +0 -95
- package/src/agent/openAILLMStreaming.ts +0 -609
- package/src/agent/promptProvider.ts +0 -87
- package/src/agent/repeatLLM.ts +0 -50
- package/src/agent/sudoMcpServerManager.ts +0 -361
- package/src/agent/tokenAuth.ts +0 -50
- package/src/agent/tools.ts +0 -57
- package/src/chat/client/chatClient.ts +0 -922
- package/src/chat/client/connection.test.ts +0 -241
- package/src/chat/client/connection.ts +0 -286
- package/src/chat/client/constants.ts +0 -1
- package/src/chat/client/index.ts +0 -18
- package/src/chat/client/interfaces.ts +0 -34
- package/src/chat/client/sessionClient.ts +0 -537
- package/src/chat/client/sessionFiles.ts +0 -142
- package/src/chat/client/teamManager.ts +0 -29
- package/src/chat/data/apiKeyManager.ts +0 -76
- package/src/chat/data/dataModels.ts +0 -101
- package/src/chat/data/database.ts +0 -997
- package/src/chat/data/dbMcpServerConfigs.ts +0 -59
- package/src/chat/data/dbSessionFileModels.ts +0 -113
- package/src/chat/data/dbSessionFiles.ts +0 -99
- package/src/chat/data/dbSessionMessages.ts +0 -102
- package/src/chat/data/mimeTypes.ts +0 -58
- package/src/chat/protocol/connectionMessages.ts +0 -49
- package/src/chat/protocol/constants.ts +0 -55
- package/src/chat/protocol/errors.ts +0 -16
- package/src/chat/protocol/messages.ts +0 -846
- package/src/chat/server/README.md +0 -127
- package/src/chat/server/chatContextManager.ts +0 -639
- package/src/chat/server/connectionManager.test.ts +0 -246
- package/src/chat/server/connectionManager.ts +0 -506
- package/src/chat/server/conversation.ts +0 -316
- package/src/chat/server/errorUtils.ts +0 -28
- package/src/chat/server/imageGeneratorTools.ts +0 -160
- package/src/chat/server/openAIRouterLLM.ts +0 -171
- package/src/chat/server/openSession.ts +0 -1689
- package/src/chat/server/openSessionMessageSender.ts +0 -4
- package/src/chat/server/server.ts +0 -175
- package/src/chat/server/sessionFileManager.ts +0 -422
- package/src/chat/server/sessionRegistry.test.ts +0 -137
- package/src/chat/server/sessionRegistry.ts +0 -1425
- package/src/chat/server/test-utils/mockFactories.ts +0 -422
- package/src/chat/server/tools.ts +0 -397
- package/src/chat/utils/agentSessionMap.ts +0 -76
- package/src/chat/utils/approvalManager.ts +0 -183
- package/src/chat/utils/asyncLock.ts +0 -43
- package/src/chat/utils/asyncQueue.ts +0 -62
- package/src/chat/utils/htmlToText.ts +0 -61
- package/src/chat/utils/multiAsyncQueue.ts +0 -62
- package/src/chat/utils/responseAwaiter.ts +0 -181
- package/src/chat/utils/search.ts +0 -139
- package/src/chat/utils/userResolver.ts +0 -48
- package/src/chat/utils/websocket.ts +0 -16
- package/src/index.ts +0 -0
- package/src/test/agent.test.ts +0 -590
- package/src/test/approvalManager.test.ts +0 -141
- package/src/test/chatContextManager.test.ts +0 -527
- package/src/test/clientServerConnection.test.ts +0 -205
- package/src/test/compressingContextManager.test.ts +0 -77
- package/src/test/context.test.ts +0 -150
- package/src/test/contextTestTools.ts +0 -95
- package/src/test/conversation.test.ts +0 -109
- package/src/test/db.test.ts +0 -363
- package/src/test/dbMcpServerConfigs.test.ts +0 -112
- package/src/test/dbSessionFiles.test.ts +0 -258
- package/src/test/dbSessionMessages.test.ts +0 -85
- package/src/test/dbTestTools.ts +0 -157
- package/src/test/imageLoad.test.ts +0 -15
- package/src/test/mcpServerManager.test.ts +0 -114
- package/src/test/multiAsyncQueue.test.ts +0 -183
- package/src/test/openaiStreaming.test.ts +0 -177
- package/src/test/prompt.test.ts +0 -27
- package/src/test/promptProvider.test.ts +0 -33
- package/src/test/responseAwaiter.test.ts +0 -103
- package/src/test/sudoMcpServerManager.test.ts +0 -63
- package/src/test/testTools.ts +0 -171
- package/src/test/tools.test.ts +0 -39
- package/src/tool/agentChat.ts +0 -194
- package/src/tool/agentMain.ts +0 -180
- package/src/tool/chatMain.ts +0 -594
- package/src/tool/commandPrompt.ts +0 -264
- package/src/tool/files.ts +0 -84
- package/src/tool/main.ts +0 -25
- package/src/tool/nodePlatform.ts +0 -73
- package/src/tool/options.ts +0 -144
- package/src/tool/prompt.ts +0 -101
- package/test_data/background_test_profile.json +0 -6
- package/test_data/background_test_script.json +0 -11
- package/test_data/dummyllm_script_crash.json +0 -32
- package/test_data/dummyllm_script_image_gen.json +0 -19
- package/test_data/dummyllm_script_image_gen_fe.json +0 -29
- package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
- package/test_data/dummyllm_script_render_tool.json +0 -29
- package/test_data/dummyllm_script_simplecalc.json +0 -28
- package/test_data/dummyllm_script_test_auto_approve.json +0 -81
- package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
- package/test_data/frog.png +0 -0
- package/test_data/frog.png.b64 +0 -1
- package/test_data/git_message_profile.json +0 -4
- package/test_data/git_wip_system.txt +0 -5
- package/test_data/image_gen_test_profile.json +0 -5
- package/test_data/pr_message_profile.json +0 -4
- package/test_data/pr_review_profile.json +0 -4
- package/test_data/prompt_simplecalc.txt +0 -1
- package/test_data/simplecalc_profile.json +0 -4
- package/test_data/sudomcp_import_profile.json +0 -4
- package/test_data/test_script_profile.json +0 -8
- package/tsconfig.json +0 -13
- package/vitest.config.ts +0 -39
- /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// -*- typescript -*-
|
|
3
|
-
|
|
4
|
-
import * as dotenv from "dotenv";
|
|
5
|
-
import { getLogger } from "@xalia/xmcp/sdk";
|
|
6
|
-
import { WebSocketServer } from "ws";
|
|
7
|
-
import * as ws from "ws";
|
|
8
|
-
import { IncomingMessage } from "http";
|
|
9
|
-
import { ConnectionManager, IUserConnectionManager } from "./connectionManager";
|
|
10
|
-
import { Database, resolveCompoundName, UserData } from "../data/database";
|
|
11
|
-
import { SessionData } from "../data/dataModels";
|
|
12
|
-
import { ChatFatalError } from "../protocol/errors";
|
|
13
|
-
import { ServerToClient } from "../protocol/messages";
|
|
14
|
-
import { SessionRegistry } from "./sessionRegistry";
|
|
15
|
-
|
|
16
|
-
const DEVELOPMENT: boolean = process.env.DEVELOPMENT === "1";
|
|
17
|
-
|
|
18
|
-
dotenv.config();
|
|
19
|
-
if (DEVELOPMENT) {
|
|
20
|
-
dotenv.config({ path: ".env.development" });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const logger = getLogger();
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Extract error message from unknown error type
|
|
27
|
-
*/
|
|
28
|
-
function extractErrorMessage(e: unknown): string {
|
|
29
|
-
if (typeof e === "string") return e;
|
|
30
|
-
if (e instanceof Error) return e.message;
|
|
31
|
-
return "Unknown connection error";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Send error response and close WebSocket connection
|
|
36
|
-
*/
|
|
37
|
-
function sendErrorAndClose(ws: ws.WebSocket, message: string): void {
|
|
38
|
-
try {
|
|
39
|
-
sendErrorResponse(ws, message);
|
|
40
|
-
} catch (closeError) {
|
|
41
|
-
logger.error(`[server] Error closing connection:`, closeError);
|
|
42
|
-
} finally {
|
|
43
|
-
ws.close(4000, message);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Send error response but keep connection open
|
|
49
|
-
*/
|
|
50
|
-
function sendErrorResponse(ws: ws.WebSocket, message: string): void {
|
|
51
|
-
try {
|
|
52
|
-
ws.send(
|
|
53
|
-
JSON.stringify({
|
|
54
|
-
t: "error",
|
|
55
|
-
e: message,
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
|
-
} catch (sendError) {
|
|
59
|
-
logger.error(`[server] Error sending error response:`, sendError);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function runServer(
|
|
64
|
-
port: number,
|
|
65
|
-
supabaseUrl: string,
|
|
66
|
-
supabaseKey: string,
|
|
67
|
-
xmcpUrl: string
|
|
68
|
-
): Promise<ws.Server> {
|
|
69
|
-
return new Promise((r, _e) => {
|
|
70
|
-
const wss = new WebSocketServer({ port });
|
|
71
|
-
const db = new Database(supabaseUrl, supabaseKey);
|
|
72
|
-
const createSessionRegistry = (
|
|
73
|
-
connManager: IUserConnectionManager<ServerToClient>
|
|
74
|
-
) => {
|
|
75
|
-
return new SessionRegistry(db, connManager, xmcpUrl);
|
|
76
|
-
};
|
|
77
|
-
const connectionManager = ConnectionManager.init(createSessionRegistry);
|
|
78
|
-
|
|
79
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
80
|
-
wss.on("connection", async (ws: ws.WebSocket, req: IncomingMessage) => {
|
|
81
|
-
try {
|
|
82
|
-
logger.info(`[server] connection: ${req.url || "unknown"}`);
|
|
83
|
-
logger.info(`[server] headers: ${JSON.stringify(req.headers)}`);
|
|
84
|
-
|
|
85
|
-
// Extract API key from WebSocket subprotocol header (formatted as
|
|
86
|
-
// comma-separated strings)
|
|
87
|
-
const subprotocols = req.headers["sec-websocket-protocol"];
|
|
88
|
-
if (!subprotocols) {
|
|
89
|
-
throw new ChatFatalError("empty api key");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (typeof subprotocols !== "string") {
|
|
93
|
-
throw new ChatFatalError("subprotocols was not a string");
|
|
94
|
-
}
|
|
95
|
-
const protocols = subprotocols.split(",").map((p) => p.trim());
|
|
96
|
-
const apiKey = protocols[0];
|
|
97
|
-
if (!apiKey) {
|
|
98
|
-
throw new ChatFatalError("empty api key");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Handle via ConnectionManager (multi-session protocol only)
|
|
102
|
-
await connectionManager.handleConnection(ws, apiKey, req);
|
|
103
|
-
} catch (e) {
|
|
104
|
-
logger.error(`[server] Connection error:`, e);
|
|
105
|
-
|
|
106
|
-
// Extract error message consistently
|
|
107
|
-
const errorMessage = extractErrorMessage(e);
|
|
108
|
-
|
|
109
|
-
if (e instanceof ChatFatalError) {
|
|
110
|
-
// Fatal errors: send error response and close connection
|
|
111
|
-
sendErrorAndClose(ws, errorMessage);
|
|
112
|
-
} else {
|
|
113
|
-
// Non-fatal errors: send error response but keep connection open
|
|
114
|
-
sendErrorResponse(ws, errorMessage);
|
|
115
|
-
logger.error(`[server] Client error:`, errorMessage);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
logger.info(`[server] started: ws://localhost:${String(port)}/`);
|
|
121
|
-
|
|
122
|
-
r(wss);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export async function resolveSessionIdFromIdentifier(
|
|
127
|
-
db: Database,
|
|
128
|
-
sessionIdentifier: string
|
|
129
|
-
): Promise<string | undefined> {
|
|
130
|
-
logger.info(`[resolveSessionIdFromIdentifier] ${sessionIdentifier}`);
|
|
131
|
-
let session: SessionData | undefined = undefined;
|
|
132
|
-
const compound = resolveCompoundName(sessionIdentifier);
|
|
133
|
-
logger.info(
|
|
134
|
-
`[resolveSessionIdFromIdentifier] compound: ${JSON.stringify(compound)}`
|
|
135
|
-
);
|
|
136
|
-
if (typeof compound === "string") {
|
|
137
|
-
// Interpret as an id
|
|
138
|
-
session = await db.sessionGetDescriptorById(compound);
|
|
139
|
-
} else {
|
|
140
|
-
session = await db.sessionGetDescriptorByName(compound[0], compound[1]);
|
|
141
|
-
}
|
|
142
|
-
logger.info(`[resolveSessionIdFromIdentifier] ${JSON.stringify(session)}`);
|
|
143
|
-
return session?.session_uuid;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Try as id, then by user/name and return the AgentProfile uuid.
|
|
148
|
-
*/
|
|
149
|
-
export async function resolveAgentProfileId(
|
|
150
|
-
db: Database,
|
|
151
|
-
userData: UserData,
|
|
152
|
-
agentProfileIdentifier: string
|
|
153
|
-
): Promise<string | undefined> {
|
|
154
|
-
let ap = await db.getSavedAgentProfileById(agentProfileIdentifier);
|
|
155
|
-
logger.debug(`[resolveAgentProfileId]: by id: {JSON.stringify(ap)}`);
|
|
156
|
-
if (ap) {
|
|
157
|
-
return agentProfileIdentifier;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
ap = await db.getSavedAgentProfileByName(
|
|
161
|
-
userData.uuid,
|
|
162
|
-
agentProfileIdentifier
|
|
163
|
-
);
|
|
164
|
-
logger.debug(`[resolveAgentProfileId]: by name: {JSON.stringify(ap)}`);
|
|
165
|
-
if (ap) {
|
|
166
|
-
return ap.uuid;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
logger.debug("[resolveAgentProfileId]: agent profile not found");
|
|
170
|
-
return undefined;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Export main classes for external use
|
|
174
|
-
export { ConnectionManager } from "./connectionManager";
|
|
175
|
-
export { OpenSession } from "./openSession";
|
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from "uuid";
|
|
2
|
-
import { strict as assert } from "assert";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
AgentEx,
|
|
6
|
-
IAgentToolProvider,
|
|
7
|
-
ToolCallResult,
|
|
8
|
-
ToolHandler,
|
|
9
|
-
} from "../../agent/agent";
|
|
10
|
-
import { makeParseArgsFn } from "./tools";
|
|
11
|
-
import { Database } from "../data/database";
|
|
12
|
-
import { DbSessionFiles } from "../data/dbSessionFiles";
|
|
13
|
-
import {
|
|
14
|
-
FileMetaData,
|
|
15
|
-
SessionFileDescriptor,
|
|
16
|
-
SessionFileEntry,
|
|
17
|
-
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
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Implementation of ISessionFileManager which can store files in the DB.
|
|
146
|
-
*/
|
|
147
|
-
export class ChatSessionFileManager implements ISessionFileManager {
|
|
148
|
-
private readonly sessionUUID: string;
|
|
149
|
-
private readonly sfc: DbSessionFiles;
|
|
150
|
-
private readonly fileMap: Map<string, SessionFileDescriptor>;
|
|
151
|
-
private readonly fileDataCache: Map<string, string>;
|
|
152
|
-
private eventHandlers: ISessionFileManagerEventHandler[];
|
|
153
|
-
|
|
154
|
-
private constructor(
|
|
155
|
-
sfc: DbSessionFiles,
|
|
156
|
-
sessionUUID: string,
|
|
157
|
-
fileMap: Map<string, SessionFileDescriptor>
|
|
158
|
-
) {
|
|
159
|
-
this.sessionUUID = sessionUUID;
|
|
160
|
-
this.sfc = sfc;
|
|
161
|
-
this.eventHandlers = [];
|
|
162
|
-
this.fileMap = fileMap;
|
|
163
|
-
this.fileDataCache = new Map();
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
static async init(
|
|
167
|
-
db: Database,
|
|
168
|
-
sessionUUID: string
|
|
169
|
-
): Promise<ChatSessionFileManager> {
|
|
170
|
-
const sfc = db.createTypedClient(DbSessionFiles);
|
|
171
|
-
const fileList = await sfc.getFilesForSession(sessionUUID);
|
|
172
|
-
const fileMap = new Map(fileList.map((f) => [f.name, f]));
|
|
173
|
-
return new ChatSessionFileManager(sfc, sessionUUID, fileMap);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
listFiles(): SessionFileDescriptor[] {
|
|
177
|
-
return Array.from(this.fileMap.values());
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async getFileContent(name: string): Promise<string> {
|
|
181
|
-
const cached = this.fileDataCache.get(name);
|
|
182
|
-
if (cached) {
|
|
183
|
-
return cached;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (!this.fileMap.has(name)) {
|
|
187
|
-
throw new Error(`no file '${name}' in session '${this.sessionUUID}'`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const fileData = await this.sfc.getFileContent(this.sessionUUID, name);
|
|
191
|
-
if (!fileData) {
|
|
192
|
-
// logically should not happen
|
|
193
|
-
throw new Error(`empty file '${name}' in session '${this.sessionUUID}'`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
this.fileDataCache.set(name, fileData);
|
|
197
|
-
return fileData;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ISessionFileManager.deleteFile
|
|
201
|
-
async deleteFile(name: string): Promise<void> {
|
|
202
|
-
if (this.fileMap.has(name)) {
|
|
203
|
-
this.fileMap.delete(name);
|
|
204
|
-
this.fileDataCache.delete(name);
|
|
205
|
-
await this.sfc.deleteFile(this.sessionUUID, name);
|
|
206
|
-
this.eventHandlers.forEach((eh) => {
|
|
207
|
-
eh.onFileDeleted(name);
|
|
208
|
-
});
|
|
209
|
-
} else {
|
|
210
|
-
throw new Error(`no file '${name}' in session '${this.sessionUUID}'`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ISessionFileManager.addFile
|
|
215
|
-
async putFileContent(
|
|
216
|
-
name: string | undefined,
|
|
217
|
-
summary: string | undefined,
|
|
218
|
-
data_url: string
|
|
219
|
-
): Promise<SessionFileDescriptor> {
|
|
220
|
-
if (!name) {
|
|
221
|
-
// TODO: have the LLM fill in the name?
|
|
222
|
-
name = uuidv4();
|
|
223
|
-
}
|
|
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
|
-
|
|
229
|
-
const mime_type = getSessionFileMimeTypeFromDataUrl(data_url);
|
|
230
|
-
const existingDesc = this.fileMap.get(name);
|
|
231
|
-
if (existingDesc) {
|
|
232
|
-
existingDesc.summary = summary;
|
|
233
|
-
existingDesc.mime_type = mime_type;
|
|
234
|
-
} else {
|
|
235
|
-
this.fileMap.set(name, { name, summary, mime_type });
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const is_new = !this.fileMap.has(name);
|
|
239
|
-
this.fileDataCache.set(name, data_url);
|
|
240
|
-
await this.sfc.setFileContent(this.sessionUUID, name, summary, data_url);
|
|
241
|
-
this.eventHandlers.forEach((eh) => {
|
|
242
|
-
assert(typeof name === "string");
|
|
243
|
-
eh.onFileChanged({ name, summary, mime_type, data_url }, is_new);
|
|
244
|
-
});
|
|
245
|
-
return { name, mime_type, summary };
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ISessionFileManager.setEventHandler
|
|
249
|
-
addEventHandler(eventHandler: ISessionFileManagerEventHandler) {
|
|
250
|
-
this.eventHandlers.push(eventHandler);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
clearAllFiles(): Promise<void> {
|
|
254
|
-
this.fileMap.clear();
|
|
255
|
-
this.fileDataCache.clear();
|
|
256
|
-
return this.sfc.clearFiles(this.sessionUUID);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
getSessionFileRelativeUrl(name: string): string {
|
|
260
|
-
return `file+session:/./${name}`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
getSessionFileAbsoluteUrl(name: string): string {
|
|
264
|
-
return `file+session://${this.sessionUUID}/${name}`;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
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: AgentEx,
|
|
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
|
-
_: AgentEx,
|
|
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 (_: AgentEx, args: unknown) => {
|
|
403
|
-
const { name } = parseName(args);
|
|
404
|
-
await fileManager.deleteFile(name);
|
|
405
|
-
return { response: "" };
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
setup: (agent: AgentEx) => {
|
|
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
|
-
}
|