@xalia/agent 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/agent/src/agent/agent.js +103 -54
  2. package/dist/agent/src/agent/agentUtils.js +22 -21
  3. package/dist/agent/src/agent/compressingContextManager.js +3 -2
  4. package/dist/agent/src/agent/dummyLLM.js +1 -3
  5. package/dist/agent/src/agent/imageGenLLM.js +67 -0
  6. package/dist/agent/src/agent/imageGenerator.js +43 -0
  7. package/dist/agent/src/agent/llm.js +27 -0
  8. package/dist/agent/src/agent/mcpServerManager.js +18 -6
  9. package/dist/agent/src/agent/nullAgentEventHandler.js +6 -0
  10. package/dist/agent/src/agent/openAILLM.js +3 -3
  11. package/dist/agent/src/agent/openAILLMStreaming.js +41 -6
  12. package/dist/agent/src/chat/client/chatClient.js +84 -13
  13. package/dist/agent/src/chat/client/sessionClient.js +47 -6
  14. package/dist/agent/src/chat/client/sessionFiles.js +102 -0
  15. package/dist/agent/src/chat/data/apiKeyManager.js +38 -7
  16. package/dist/agent/src/chat/data/database.js +83 -70
  17. package/dist/agent/src/chat/data/dbSessionFileModels.js +49 -0
  18. package/dist/agent/src/chat/data/dbSessionFiles.js +76 -0
  19. package/dist/agent/src/chat/data/dbSessionMessages.js +57 -0
  20. package/dist/agent/src/chat/data/mimeTypes.js +44 -0
  21. package/dist/agent/src/chat/protocol/messages.js +21 -0
  22. package/dist/agent/src/chat/server/chatContextManager.js +14 -7
  23. package/dist/agent/src/chat/server/connectionManager.js +14 -36
  24. package/dist/agent/src/chat/server/connectionManager.test.js +2 -16
  25. package/dist/agent/src/chat/server/conversation.js +69 -45
  26. package/dist/agent/src/chat/server/imageGeneratorTools.js +111 -0
  27. package/dist/agent/src/chat/server/openSession.js +205 -43
  28. package/dist/agent/src/chat/server/server.js +5 -8
  29. package/dist/agent/src/chat/server/sessionFileManager.js +171 -38
  30. package/dist/agent/src/chat/server/sessionRegistry.js +199 -32
  31. package/dist/agent/src/chat/server/test-utils/mockFactories.js +12 -11
  32. package/dist/agent/src/chat/server/tools.js +27 -6
  33. package/dist/agent/src/chat/utils/multiAsyncQueue.js +9 -1
  34. package/dist/agent/src/test/agent.test.js +15 -11
  35. package/dist/agent/src/test/chatContextManager.test.js +4 -0
  36. package/dist/agent/src/test/clientServerConnection.test.js +2 -2
  37. package/dist/agent/src/test/db.test.js +33 -70
  38. package/dist/agent/src/test/dbSessionFiles.test.js +179 -0
  39. package/dist/agent/src/test/dbSessionMessages.test.js +67 -0
  40. package/dist/agent/src/test/dbTestTools.js +6 -5
  41. package/dist/agent/src/test/imageLoad.test.js +1 -1
  42. package/dist/agent/src/test/mcpServerManager.test.js +1 -1
  43. package/dist/agent/src/test/multiAsyncQueue.test.js +50 -0
  44. package/dist/agent/src/test/testTools.js +12 -0
  45. package/dist/agent/src/tool/agentChat.js +25 -6
  46. package/dist/agent/src/tool/agentMain.js +1 -1
  47. package/dist/agent/src/tool/chatMain.js +113 -4
  48. package/dist/agent/src/tool/commandPrompt.js +7 -3
  49. package/dist/agent/src/tool/files.js +23 -15
  50. package/dist/agent/src/tool/options.js +2 -2
  51. package/package.json +1 -1
  52. package/scripts/test_chat +124 -66
  53. package/src/agent/agent.ts +145 -38
  54. package/src/agent/agentUtils.ts +27 -21
  55. package/src/agent/compressingContextManager.ts +5 -4
  56. package/src/agent/context.ts +1 -1
  57. package/src/agent/dummyLLM.ts +1 -3
  58. package/src/agent/iAgentEventHandler.ts +15 -2
  59. package/src/agent/imageGenLLM.ts +99 -0
  60. package/src/agent/imageGenerator.ts +60 -0
  61. package/src/agent/llm.ts +128 -4
  62. package/src/agent/mcpServerManager.ts +26 -7
  63. package/src/agent/nullAgentEventHandler.ts +6 -0
  64. package/src/agent/openAILLM.ts +3 -8
  65. package/src/agent/openAILLMStreaming.ts +60 -14
  66. package/src/chat/client/chatClient.ts +119 -14
  67. package/src/chat/client/sessionClient.ts +75 -9
  68. package/src/chat/client/sessionFiles.ts +145 -0
  69. package/src/chat/data/apiKeyManager.ts +55 -7
  70. package/src/chat/data/dataModels.ts +16 -7
  71. package/src/chat/data/database.ts +107 -92
  72. package/src/chat/data/dbSessionFileModels.ts +91 -0
  73. package/src/chat/data/dbSessionFiles.ts +99 -0
  74. package/src/chat/data/dbSessionMessages.ts +68 -0
  75. package/src/chat/data/mimeTypes.ts +58 -0
  76. package/src/chat/protocol/messages.ts +127 -13
  77. package/src/chat/server/chatContextManager.ts +36 -13
  78. package/src/chat/server/connectionManager.test.ts +1 -22
  79. package/src/chat/server/connectionManager.ts +18 -53
  80. package/src/chat/server/conversation.ts +96 -57
  81. package/src/chat/server/imageGeneratorTools.ts +138 -0
  82. package/src/chat/server/openSession.ts +287 -49
  83. package/src/chat/server/server.ts +5 -11
  84. package/src/chat/server/sessionFileManager.ts +223 -63
  85. package/src/chat/server/sessionRegistry.ts +285 -41
  86. package/src/chat/server/test-utils/mockFactories.ts +13 -13
  87. package/src/chat/server/tools.ts +43 -8
  88. package/src/chat/utils/agentSessionMap.ts +2 -2
  89. package/src/chat/utils/multiAsyncQueue.ts +11 -1
  90. package/src/test/agent.test.ts +23 -14
  91. package/src/test/chatContextManager.test.ts +7 -2
  92. package/src/test/clientServerConnection.test.ts +3 -3
  93. package/src/test/compressingContextManager.test.ts +1 -1
  94. package/src/test/context.test.ts +2 -1
  95. package/src/test/conversation.test.ts +1 -1
  96. package/src/test/db.test.ts +41 -83
  97. package/src/test/dbSessionFiles.test.ts +258 -0
  98. package/src/test/dbSessionMessages.test.ts +85 -0
  99. package/src/test/dbTestTools.ts +9 -5
  100. package/src/test/imageLoad.test.ts +2 -2
  101. package/src/test/mcpServerManager.test.ts +3 -1
  102. package/src/test/multiAsyncQueue.test.ts +58 -0
  103. package/src/test/testTools.ts +15 -1
  104. package/src/tool/agentChat.ts +35 -7
  105. package/src/tool/agentMain.ts +7 -7
  106. package/src/tool/chatMain.ts +126 -5
  107. package/src/tool/commandPrompt.ts +10 -5
  108. package/src/tool/files.ts +30 -13
  109. package/src/tool/options.ts +1 -1
  110. package/test_data/dummyllm_script_image_gen.json +19 -0
  111. package/test_data/dummyllm_script_invoke_image_gen_tool.json +30 -0
  112. package/test_data/image_gen_test_profile.json +5 -0
@@ -10,7 +10,6 @@ import {
10
10
  ServerConnectionMessage,
11
11
  } from "../protocol/connectionMessages";
12
12
  import { ChatErrorMessage, ChatFatalError } from "../protocol/errors";
13
- import { ApiKeyManager } from "../data/apiKeyManager";
14
13
 
15
14
  const logger = getLogger();
16
15
 
@@ -32,18 +31,15 @@ export interface IUserConnectionManager<ServerMsgT> {
32
31
  * Send message to a specific connection.
33
32
  */
34
33
  sendToConnection(connectionId: string, message: ServerMsgT): void;
35
-
36
- /**
37
- * Get cached api key for a user. This key will not be available
38
- * after the user disconnects.
39
- */
40
- getLiveUserApiKey(userId: string): string | undefined;
41
34
  }
42
35
 
43
36
  /**
44
- * Interface for processing messages from the client.
37
+ * Interface for processing messages from the client. Also handles guest
38
+ * admission.
45
39
  */
46
40
  export interface IMessageProcessor<ClientMsgT> {
41
+ authenticate(token: string): Promise<string | undefined>;
42
+
47
43
  processMessage(
48
44
  connectionId: string,
49
45
  userId: string,
@@ -54,25 +50,18 @@ export interface IMessageProcessor<ClientMsgT> {
54
50
  * Handle user disconnect - clean up from all sessions.
55
51
  * Called when a connection is closed to ensure proper cleanup.
56
52
  */
57
- handleUserDisconnect(userId: string): void;
53
+ handleUserDisconnect(userId: string): Promise<void>;
58
54
  }
59
55
 
60
56
  class Connection {
61
57
  public ws: ws.WebSocket;
62
58
  public userId: string;
63
- public apiKey: string;
64
59
  public heartbeat: NodeJS.Timeout;
65
60
  public lastMessageTime: number;
66
61
 
67
- constructor(
68
- ws: ws.WebSocket,
69
- userId: string,
70
- apiKey: string,
71
- heartbeat: NodeJS.Timeout
72
- ) {
62
+ constructor(ws: ws.WebSocket, userId: string, heartbeat: NodeJS.Timeout) {
73
63
  this.ws = ws;
74
64
  this.userId = userId;
75
- this.apiKey = apiKey;
76
65
  this.heartbeat = heartbeat;
77
66
  this.lastMessageTime = Date.now();
78
67
  }
@@ -96,18 +85,15 @@ export class ConnectionManager<ClientMsgT, ServerMsgT>
96
85
 
97
86
  // this is a singleton object and can only be initialized by .init method
98
87
  private constructor(
99
- private apiKeyManager: ApiKeyManager,
100
88
  private sessionMessageProcessor: IMessageProcessor<ClientMsgT>
101
89
  ) {}
102
90
 
103
91
  static init<ClientMsgT, ServerMsgT>(
104
- apiKeyManager: ApiKeyManager,
105
92
  createMsgProcessor: (
106
93
  cm: IUserConnectionManager<ServerMsgT>
107
94
  ) => IMessageProcessor<ClientMsgT>
108
95
  ): ConnectionManager<ClientMsgT, ServerMsgT> {
109
96
  const connManager = new ConnectionManager(
110
- apiKeyManager,
111
97
  {} as IMessageProcessor<ClientMsgT>
112
98
  );
113
99
  connManager.sessionMessageProcessor = createMsgProcessor(connManager);
@@ -120,17 +106,15 @@ export class ConnectionManager<ClientMsgT, ServerMsgT>
120
106
  */
121
107
  async handleConnection(
122
108
  webSocket: ws.WebSocket,
123
- apiKey: string,
109
+ token: string,
124
110
  req?: { headers: { [key: string]: string | string[] | undefined } }
125
111
  ): Promise<void> {
126
112
  const connectionId = uuidv4();
127
113
  logger.info(`[ConnectionManager] New connection: ${connectionId}`);
128
114
 
129
115
  try {
130
- // Verify API key
131
- const userData = await this.apiKeyManager.verifyApiKey(apiKey);
132
- if (!userData) {
133
- logger.error(`[ConnectionManager] Invalid API key: ${apiKey}`);
116
+ const userUUID = await this.sessionMessageProcessor.authenticate(token);
117
+ if (!userUUID) {
134
118
  throw new ChatFatalError("invalid api key");
135
119
  }
136
120
 
@@ -142,14 +126,14 @@ export class ConnectionManager<ClientMsgT, ServerMsgT>
142
126
  // Register connection
143
127
  this.connections.set(
144
128
  connectionId,
145
- new Connection(webSocket, userData.uuid, apiKey, heartbeat)
129
+ new Connection(webSocket, userUUID, heartbeat)
146
130
  );
147
131
 
148
132
  // Add to user connections
149
- if (!this.userToConnections.has(userData.uuid)) {
150
- this.userToConnections.set(userData.uuid, new Set());
133
+ if (!this.userToConnections.has(userUUID)) {
134
+ this.userToConnections.set(userUUID, new Set());
151
135
  }
152
- const userConnections = this.userToConnections.get(userData.uuid);
136
+ const userConnections = this.userToConnections.get(userUUID);
153
137
  if (userConnections) {
154
138
  userConnections.add(connectionId);
155
139
  }
@@ -164,7 +148,7 @@ export class ConnectionManager<ClientMsgT, ServerMsgT>
164
148
  const response: ServerConnectionReady = {
165
149
  t: "ready",
166
150
  c_id: connectionId,
167
- user_uuid: userData.uuid,
151
+ user_uuid: userUUID,
168
152
  };
169
153
 
170
154
  // Check if connection still exists before sending
@@ -179,7 +163,7 @@ export class ConnectionManager<ClientMsgT, ServerMsgT>
179
163
 
180
164
  logger.info(
181
165
  `[ConnectionManager] Connection ${connectionId} registered ` +
182
- `for user ${userData.uuid}, version: ${clientVersion}`
166
+ `for user ${userUUID}, version: ${clientVersion}`
183
167
  );
184
168
  } catch (error) {
185
169
  const errorMessage =
@@ -383,7 +367,9 @@ export class ConnectionManager<ClientMsgT, ServerMsgT>
383
367
  userConnections.delete(connectionId);
384
368
  if (userConnections.size === 0) {
385
369
  this.userToConnections.delete(conn.userId);
386
- this.sessionMessageProcessor.handleUserDisconnect(conn.userId);
370
+ const _ = this.sessionMessageProcessor.handleUserDisconnect(
371
+ conn.userId
372
+ );
387
373
  }
388
374
  }
389
375
 
@@ -517,25 +503,4 @@ export class ConnectionManager<ClientMsgT, ServerMsgT>
517
503
  const connections = this.userToConnections.get(userId);
518
504
  return connections ? Array.from(connections) : [];
519
505
  }
520
-
521
- /**
522
- * Implementation of IUserConnectionManager interface.
523
- * Get cached api key for a user. This key will not be available
524
- * after the user disconnects.
525
- */
526
- public getLiveUserApiKey(userId: string): string | undefined {
527
- const connectionIds = this.getUserConnections(userId);
528
-
529
- // Return API key from the first active connection
530
- // (all connections for a user should have the same API key)
531
- for (const connectionId of connectionIds) {
532
- const conn = this.connections.get(connectionId);
533
- if (conn) {
534
- return conn.apiKey;
535
- }
536
- }
537
-
538
- // No active connections for this user
539
- return undefined;
540
- }
541
506
  }
@@ -3,11 +3,13 @@
3
3
 
4
4
  import { strict as assert } from "assert";
5
5
 
6
+ import { createUserMessage } from "../../agent/agent";
6
7
  import {
8
+ ChatCompletionAssistantMessageParam,
7
9
  ChatCompletionMessageParam,
10
+ ChatCompletionToolMessageParam,
8
11
  ChatCompletionUserMessageParam,
9
- createUserMessage,
10
- } from "../../agent/agent";
12
+ } from "../../agent/llm";
11
13
  import { SessionMessage, UserMessageData } from "../data/dataModels";
12
14
  import {
13
15
  ServerAgentMessage,
@@ -96,45 +98,109 @@ export function sessionMessagesToLLMConversation(
96
98
  return { firstIndex, conversation };
97
99
  }
98
100
 
99
- export function sessionMessagesToChatMessages(
101
+ /**
102
+ * Convert the DB data (SessionMessage) for a ChatCompletionUserMessageParam
103
+ * into a ServerUserMessage message.
104
+ */
105
+ export function userMessageToConversationMessage(
106
+ userMessage: ChatCompletionUserMessageParam,
107
+ message_idx: number,
108
+ defaultUserUuid: string,
109
+ session_id: string
110
+ ): ServerUserMessage {
111
+ // The name on the message should be the uuid
112
+
113
+ const userMsgData = llmUserMessageToUserMessageData(userMessage);
114
+ const user_uuid = userMessage.name || defaultUserUuid;
115
+ const msg: ServerUserMessage = {
116
+ type: "user_msg",
117
+ message: userMsgData.message,
118
+ message_idx,
119
+ user_uuid,
120
+ session_id,
121
+ };
122
+ if (userMsgData.imageB64) {
123
+ msg.imageB64 = userMsgData.imageB64;
124
+ }
125
+ return msg;
126
+ }
127
+
128
+ /**
129
+ * Convert the DB data (SessionMessage) for a
130
+ * ChatCompletionAssistantMessageParam into a ServerAgentMessage message.
131
+ */
132
+ export function assistantMessageToConversationMessage(
133
+ msg: ChatCompletionAssistantMessageParam,
134
+ message_idx: number,
135
+ session_id: string
136
+ ): ServerAgentMessage {
137
+ assert(!msg.audio);
138
+ return {
139
+ type: "agent_msg",
140
+ message: msg,
141
+ message_idx,
142
+ session_id,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Convert the DB data (SessionMessage) for a ChatCompletionToolMessageParam
148
+ * into a ServerToolCallResult message.
149
+ */
150
+ export function toolResultToConversationMessage(
151
+ toolCall: ChatCompletionToolMessageParam,
152
+ message_idx: number,
153
+ session_id: string
154
+ ): ServerToolCallResult {
155
+ return {
156
+ type: "tool_call_result",
157
+ result: toolCall,
158
+ message_idx,
159
+ session_id,
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Convert a set of SessionMessages (stored in the DB) into Conversation
165
+ * messages (protocol which can be sent over the wire).
166
+ */
167
+ export function sessionMessagesToConversationMessages(
100
168
  sessionMessages: SessionMessage[],
101
169
  defaultUserUuid: string,
102
170
  session_id: string
103
- ): (ServerUserMessage | ServerAgentMessage)[] {
104
- const msgs: (ServerUserMessage | ServerAgentMessage)[] = [];
171
+ ): ConversationMessage[] {
172
+ const msgs: ConversationMessage[] = [];
105
173
  for (const sm of sessionMessages) {
106
174
  const ccmp = sm.content;
107
175
  const message_idx = sm.message_idx;
108
- switch (ccmp.role) {
109
- case "developer":
110
- throw new Error("developer messages not handled yet");
176
+ const msgRole = ccmp.role;
177
+ switch (msgRole) {
178
+ case "system":
179
+ throw new Error("cannot convert system to ConversationMessage");
111
180
  case "assistant":
112
- assert(!ccmp.audio);
113
- if (ccmp.content) {
114
- msgs.push({
115
- type: "agent_msg",
116
- message: ccmp,
117
- message_idx,
118
- session_id,
119
- });
120
- }
121
- // TODO: do we want to convert tool calls etc?
181
+ msgs.push(
182
+ assistantMessageToConversationMessage(ccmp, message_idx, session_id)
183
+ );
122
184
  break;
123
185
  case "user":
124
- {
125
- const msg = userMessageToChatMessage(
186
+ msgs.push(
187
+ userMessageToConversationMessage(
126
188
  ccmp,
127
189
  message_idx,
128
190
  defaultUserUuid,
129
191
  session_id
130
- );
131
- if (msg) {
132
- msgs.push(msg);
133
- }
134
- }
192
+ )
193
+ );
135
194
  break;
136
- default:
195
+ case "tool":
196
+ msgs.push(
197
+ toolResultToConversationMessage(ccmp, message_idx, session_id)
198
+ );
137
199
  break;
200
+ default: {
201
+ const _: never = ccmp;
202
+ throw new Error(`unexpected message role: ${msgRole as string}`);
203
+ }
138
204
  }
139
205
  }
140
206
 
@@ -143,7 +209,7 @@ export function sessionMessagesToChatMessages(
143
209
 
144
210
  export function llmUserMessageToUserMessageData(
145
211
  userMessage: ChatCompletionUserMessageParam
146
- ): UserMessageData | undefined {
212
+ ): UserMessageData {
147
213
  if (typeof userMessage.content === "string") {
148
214
  return {
149
215
  message: userMessage.content,
@@ -163,15 +229,15 @@ export function llmUserMessageToUserMessageData(
163
229
  break;
164
230
  case "input_audio":
165
231
  throw new ChatErrorMessage(
166
- "userMessageToChatMessage: audio content not supported"
232
+ "llmUserMessageToUserMessageData: audio content not supported"
167
233
  );
168
234
  case "file":
169
235
  throw new ChatErrorMessage(
170
- "userMessageToChatMessage: file content not supported"
236
+ "llmUserMessageToUserMessageData: file content not supported"
171
237
  );
172
238
  default:
173
239
  throw new ChatErrorMessage(
174
- "userMessageToChatMessage: unexpected content.type"
240
+ "llmUserMessageToUserMessageData: unexpected content.type"
175
241
  );
176
242
  }
177
243
  }
@@ -185,33 +251,6 @@ export function llmUserMessageToUserMessageData(
185
251
  return finalMsg;
186
252
  }
187
253
 
188
- export function userMessageToChatMessage(
189
- userMessage: ChatCompletionUserMessageParam,
190
- message_idx: number,
191
- defaultUserUuid: string,
192
- session_id: string
193
- ): ServerUserMessage | undefined {
194
- // The name on the message should be the uuid
195
-
196
- const userMsgData = llmUserMessageToUserMessageData(userMessage);
197
- if (!userMsgData) {
198
- return undefined;
199
- }
200
-
201
- const user_uuid = userMessage.name || defaultUserUuid;
202
- const msg: ServerUserMessage = {
203
- type: "user_msg",
204
- message: userMsgData.message,
205
- message_idx,
206
- user_uuid,
207
- session_id,
208
- };
209
- if (userMsgData.imageB64) {
210
- msg.imageB64 = userMsgData.imageB64;
211
- }
212
- return msg;
213
- }
214
-
215
254
  export function chatToolResultMessageToSessionMessage(
216
255
  chatMessage: ServerToolCallResult
217
256
  ): SessionMessage {
@@ -0,0 +1,138 @@
1
+ import { getLogger } from "@xalia/xmcp/sdk";
2
+
3
+ import { Agent, IAgentToolProvider, ToolCallResult } from "../../agent/agent";
4
+ import { ChatCompletionTool } from "../../agent/llm";
5
+ import { ImageGenerator } from "../../agent/imageGenerator";
6
+
7
+ import { ChatSessionFileManager } from "./sessionFileManager";
8
+ import { makeParseArgsFn } from "./tools";
9
+ import { getMimeTypeFromDataUrl } from "../data/mimeTypes";
10
+
11
+ const logger = getLogger();
12
+
13
+ function createImageGenerator(
14
+ llmUrl: string,
15
+ llmApiKey: string
16
+ ): Promise<ImageGenerator> {
17
+ const imageGenModel = process.env["GEN_IMAGE_MODEL"];
18
+ logger.debug(`[genImageFileTool] model: ${imageGenModel || "(default)"}`);
19
+ return ImageGenerator.init(llmUrl, llmApiKey, imageGenModel);
20
+ }
21
+
22
+ // gen_image
23
+ //
24
+ // Simple tool to generate image output as tool output. The user can then
25
+ // decide how/whether to use the image.
26
+
27
+ export async function genImageTool(
28
+ llmUrl: string,
29
+ llmApiKey: string
30
+ ): Promise<IAgentToolProvider> {
31
+ const GEN_IMAGE_DESC: ChatCompletionTool = {
32
+ type: "function",
33
+ function: {
34
+ name: "gen_image",
35
+ description: "Generate image",
36
+ parameters: {
37
+ type: "object",
38
+ properties: {
39
+ prompt: {
40
+ type: "string",
41
+ description: "Prompt/instructions for image generator",
42
+ },
43
+ input_img: {
44
+ type: "string",
45
+ description: "(Optional) input image data",
46
+ },
47
+ },
48
+ required: ["prompt"],
49
+ },
50
+ },
51
+ } as const;
52
+ const imageGenerator = await createImageGenerator(llmUrl, llmApiKey);
53
+ const getPromptInputImg = makeParseArgsFn(
54
+ ["prompt"] as const,
55
+ ["input_img"] as const
56
+ );
57
+ const toolFn = async (_: Agent, args: unknown): Promise<ToolCallResult> => {
58
+ const { prompt, input_img } = getPromptInputImg(args);
59
+ const image = await imageGenerator.generate(prompt, input_img);
60
+ return {
61
+ response: image,
62
+ overwriteArgs: JSON.stringify({ prompt }),
63
+ // overwriteResponse: "Image generated",
64
+ // // For now, let the UI see the image.
65
+ // passOriginalResponseToEventHandler: true,
66
+ };
67
+ };
68
+
69
+ return {
70
+ // eslint-disable-next-line @typescript-eslint/require-await
71
+ setup: async (agent: Agent) => {
72
+ agent.addAgentTool(GEN_IMAGE_DESC, toolFn);
73
+ },
74
+ };
75
+ }
76
+
77
+ // gen_image_file
78
+
79
+ export async function genImageFileTool(
80
+ llmUrl: string,
81
+ llmApiKey: string,
82
+ fileManager: ChatSessionFileManager
83
+ ): Promise<IAgentToolProvider> {
84
+ const GEN_IMAGE_FILE_DESC: ChatCompletionTool = {
85
+ type: "function",
86
+ function: {
87
+ name: "gen_image_file",
88
+ description: "Generate image (into session file manager)",
89
+ parameters: {
90
+ type: "object",
91
+ properties: {
92
+ prompt: {
93
+ type: "string",
94
+ description: "Prompt/instructions for image generator",
95
+ },
96
+ name: {
97
+ type: "string",
98
+ description: "Name of file to (over)write",
99
+ },
100
+ summary: {
101
+ type: "string",
102
+ description: "Summary (for file manager)",
103
+ },
104
+ input_name: {
105
+ type: "string",
106
+ description: "(Optional) input image filename",
107
+ },
108
+ },
109
+ required: ["prompt", "name", "summary"],
110
+ },
111
+ },
112
+ } as const;
113
+
114
+ const imageGenerator = await createImageGenerator(llmUrl, llmApiKey);
115
+ const getPromptNameSummary = makeParseArgsFn(
116
+ ["prompt", "name", "summary"] as const,
117
+ ["input_name"] as const
118
+ );
119
+ const toolFn = async (_: Agent, args: unknown): Promise<ToolCallResult> => {
120
+ const { prompt, name, summary, input_name } = getPromptNameSummary(args);
121
+ const input_image = input_name
122
+ ? await fileManager.getFileContent(input_name)
123
+ : undefined;
124
+
125
+ const image = await imageGenerator.generate(prompt, input_image);
126
+ const mimeType = getMimeTypeFromDataUrl(image);
127
+ await fileManager.putFileContent(name, summary, image);
128
+ const url = fileManager.getSessionFileRelativeUrl(name);
129
+ return { response: url, metadata: { type: mimeType } };
130
+ };
131
+
132
+ return {
133
+ // eslint-disable-next-line @typescript-eslint/require-await
134
+ setup: async (agent: Agent) => {
135
+ agent.addAgentTool(GEN_IMAGE_FILE_DESC, toolFn);
136
+ },
137
+ };
138
+ }