@xalia/agent 0.6.10 → 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.
Files changed (161) hide show
  1. package/package.json +5 -2
  2. package/.env.development +0 -6
  3. package/.env.test +0 -7
  4. package/.prettierrc.json +0 -11
  5. package/context_system.md +0 -498
  6. package/eslint.config.mjs +0 -38
  7. package/scripts/chat_server +0 -8
  8. package/scripts/git_message +0 -31
  9. package/scripts/git_wip +0 -21
  10. package/scripts/pr_message +0 -18
  11. package/scripts/pr_review +0 -16
  12. package/scripts/setup_chat +0 -90
  13. package/scripts/shutdown_chat_server +0 -42
  14. package/scripts/start_chat_server +0 -24
  15. package/scripts/sudomcp_import +0 -23
  16. package/scripts/test_chat +0 -327
  17. package/src/agent/agent.ts +0 -699
  18. package/src/agent/agentUtils.ts +0 -286
  19. package/src/agent/compressingContextManager.ts +0 -129
  20. package/src/agent/context.ts +0 -265
  21. package/src/agent/contextWithWorkspace.ts +0 -162
  22. package/src/agent/documentSummarizer.ts +0 -157
  23. package/src/agent/dummyLLM.ts +0 -130
  24. package/src/agent/iAgentEventHandler.ts +0 -64
  25. package/src/agent/imageGenLLM.ts +0 -101
  26. package/src/agent/imageGenerator.ts +0 -45
  27. package/src/agent/iplatform.ts +0 -18
  28. package/src/agent/llm.ts +0 -74
  29. package/src/agent/mcpServerManager.ts +0 -541
  30. package/src/agent/nullAgentEventHandler.ts +0 -26
  31. package/src/agent/nullPlatform.ts +0 -13
  32. package/src/agent/openAI.ts +0 -123
  33. package/src/agent/openAILLM.ts +0 -99
  34. package/src/agent/openAILLMStreaming.ts +0 -648
  35. package/src/agent/promptProvider.ts +0 -87
  36. package/src/agent/repeatLLM.ts +0 -62
  37. package/src/agent/sudoMcpServerManager.ts +0 -361
  38. package/src/agent/test_data/harrypotter.txt +0 -6065
  39. package/src/agent/tokenAuth.ts +0 -50
  40. package/src/agent/tokenCounter.test.ts +0 -243
  41. package/src/agent/tokenCounter.ts +0 -483
  42. package/src/agent/toolSettings.ts +0 -24
  43. package/src/agent/tools/calculatorTool.ts +0 -50
  44. package/src/agent/tools/contentExtractors/htmlToText.ts +0 -61
  45. package/src/agent/tools/contentExtractors/pdfToText.ts +0 -60
  46. package/src/agent/tools/datetimeTool.ts +0 -41
  47. package/src/agent/tools/fileManager/fileManagerTool.ts +0 -199
  48. package/src/agent/tools/fileManager/index.ts +0 -50
  49. package/src/agent/tools/fileManager/memoryFileManager.ts +0 -120
  50. package/src/agent/tools/fileManager/mimeTypes.ts +0 -60
  51. package/src/agent/tools/fileManager/prompt.ts +0 -38
  52. package/src/agent/tools/fileManager/types.ts +0 -189
  53. package/src/agent/tools/index.ts +0 -49
  54. package/src/agent/tools/openUrlTool.ts +0 -62
  55. package/src/agent/tools/renderTool.ts +0 -92
  56. package/src/agent/tools/utils.ts +0 -74
  57. package/src/agent/tools/webSearch.ts +0 -138
  58. package/src/agent/tools/webSearchTool.ts +0 -44
  59. package/src/chat/client/chatClient.ts +0 -967
  60. package/src/chat/client/connection.test.ts +0 -241
  61. package/src/chat/client/connection.ts +0 -286
  62. package/src/chat/client/constants.ts +0 -1
  63. package/src/chat/client/index.ts +0 -21
  64. package/src/chat/client/interfaces.ts +0 -34
  65. package/src/chat/client/sessionClient.ts +0 -574
  66. package/src/chat/client/sessionFiles.ts +0 -142
  67. package/src/chat/client/teamManager.ts +0 -29
  68. package/src/chat/constants.ts +0 -6
  69. package/src/chat/data/apiKeyManager.ts +0 -76
  70. package/src/chat/data/dataModels.ts +0 -107
  71. package/src/chat/data/database.ts +0 -997
  72. package/src/chat/data/dbMcpServerConfigs.ts +0 -59
  73. package/src/chat/data/dbSessionFiles.ts +0 -107
  74. package/src/chat/data/dbSessionMessages.ts +0 -102
  75. package/src/chat/protocol/connectionMessages.ts +0 -49
  76. package/src/chat/protocol/constants.ts +0 -55
  77. package/src/chat/protocol/errors.ts +0 -16
  78. package/src/chat/protocol/messages.ts +0 -899
  79. package/src/chat/server/README.md +0 -127
  80. package/src/chat/server/chatContextManager.ts +0 -660
  81. package/src/chat/server/connectionManager.test.ts +0 -246
  82. package/src/chat/server/connectionManager.ts +0 -506
  83. package/src/chat/server/conversation.ts +0 -319
  84. package/src/chat/server/errorUtils.ts +0 -28
  85. package/src/chat/server/imageGeneratorTools.ts +0 -179
  86. package/src/chat/server/openAIRouterLLM.ts +0 -168
  87. package/src/chat/server/openSession.ts +0 -1945
  88. package/src/chat/server/openSessionMessageSender.ts +0 -4
  89. package/src/chat/server/promptRefiner.ts +0 -106
  90. package/src/chat/server/server.ts +0 -178
  91. package/src/chat/server/sessionFileManager.ts +0 -151
  92. package/src/chat/server/sessionRegistry.test.ts +0 -137
  93. package/src/chat/server/sessionRegistry.ts +0 -1553
  94. package/src/chat/server/test-utils/mockFactories.ts +0 -422
  95. package/src/chat/server/titleGenerator.test.ts +0 -103
  96. package/src/chat/server/titleGenerator.ts +0 -143
  97. package/src/chat/server/tools.ts +0 -170
  98. package/src/chat/utils/agentSessionMap.ts +0 -76
  99. package/src/chat/utils/approvalManager.ts +0 -189
  100. package/src/chat/utils/asyncLock.ts +0 -43
  101. package/src/chat/utils/asyncQueue.ts +0 -62
  102. package/src/chat/utils/multiAsyncQueue.ts +0 -66
  103. package/src/chat/utils/responseAwaiter.ts +0 -181
  104. package/src/chat/utils/userResolver.ts +0 -48
  105. package/src/chat/utils/websocket.ts +0 -16
  106. package/src/index.ts +0 -0
  107. package/src/test/agent.test.ts +0 -584
  108. package/src/test/approvalManager.test.ts +0 -141
  109. package/src/test/chatContextManager.test.ts +0 -552
  110. package/src/test/clientServerConnection.test.ts +0 -205
  111. package/src/test/compressingContextManager.test.ts +0 -77
  112. package/src/test/context.test.ts +0 -150
  113. package/src/test/contextTestTools.ts +0 -95
  114. package/src/test/conversation.test.ts +0 -109
  115. package/src/test/db.test.ts +0 -363
  116. package/src/test/dbMcpServerConfigs.test.ts +0 -112
  117. package/src/test/dbSessionFiles.test.ts +0 -258
  118. package/src/test/dbSessionMessages.test.ts +0 -85
  119. package/src/test/dbTestTools.ts +0 -157
  120. package/src/test/imageLoad.test.ts +0 -15
  121. package/src/test/mcpServerManager.test.ts +0 -114
  122. package/src/test/multiAsyncQueue.test.ts +0 -183
  123. package/src/test/openaiStreaming.test.ts +0 -177
  124. package/src/test/prompt.test.ts +0 -27
  125. package/src/test/promptProvider.test.ts +0 -33
  126. package/src/test/responseAwaiter.test.ts +0 -103
  127. package/src/test/sudoMcpServerManager.test.ts +0 -63
  128. package/src/test/testTools.ts +0 -176
  129. package/src/test/tools.test.ts +0 -64
  130. package/src/tool/agentChat.ts +0 -203
  131. package/src/tool/agentMain.ts +0 -180
  132. package/src/tool/chatMain.ts +0 -621
  133. package/src/tool/commandPrompt.ts +0 -264
  134. package/src/tool/files.ts +0 -82
  135. package/src/tool/main.ts +0 -25
  136. package/src/tool/nodePlatform.ts +0 -73
  137. package/src/tool/options.ts +0 -144
  138. package/src/tool/prompt.ts +0 -101
  139. package/test_data/background_test_profile.json +0 -6
  140. package/test_data/background_test_script.json +0 -11
  141. package/test_data/dummyllm_script_crash.json +0 -32
  142. package/test_data/dummyllm_script_image_gen.json +0 -19
  143. package/test_data/dummyllm_script_image_gen_fe.json +0 -29
  144. package/test_data/dummyllm_script_invoke_image_gen_tool.json +0 -37
  145. package/test_data/dummyllm_script_render_tool.json +0 -29
  146. package/test_data/dummyllm_script_simplecalc.json +0 -28
  147. package/test_data/dummyllm_script_test_auto_approve.json +0 -81
  148. package/test_data/dummyllm_script_test_simplecalc_addition.json +0 -29
  149. package/test_data/frog.png +0 -0
  150. package/test_data/frog.png.b64 +0 -1
  151. package/test_data/git_message_profile.json +0 -4
  152. package/test_data/git_wip_system.txt +0 -5
  153. package/test_data/image_gen_test_profile.json +0 -5
  154. package/test_data/pr_message_profile.json +0 -4
  155. package/test_data/pr_review_profile.json +0 -4
  156. package/test_data/prompt_simplecalc.txt +0 -1
  157. package/test_data/simplecalc_profile.json +0 -4
  158. package/test_data/sudomcp_import_profile.json +0 -4
  159. package/test_data/test_script_profile.json +0 -8
  160. package/tsconfig.json +0 -13
  161. package/vitest.config.ts +0 -39
@@ -1,699 +0,0 @@
1
- import { strict as assert } from "assert";
2
-
3
- export { AgentProfile } from "@xalia/xmcp/sdk";
4
- import { AgentProfile, getLogger } from "@xalia/xmcp/sdk";
5
-
6
- import * as openai from "./openAI";
7
- import { McpServerManager } from "./mcpServerManager";
8
- import {
9
- ILLM,
10
- ToolDescriptor,
11
- MessageParam,
12
- AssistantMessageParam,
13
- UserMessageParam,
14
- MessageToolCall,
15
- Completion,
16
- Message,
17
- ToolMessageParam,
18
- } from "./llm";
19
- import { IAgentEventHandler } from "./iAgentEventHandler";
20
- import { IContextManager, IContextTransaction } from "./context";
21
- import { ChatCompletionContentPartImage } from "openai/resources";
22
-
23
- import { MAX_TOOL_CALL_RESPONSE_LENGTH } from "./toolSettings";
24
-
25
- export const DEFAULT_LLM_URL = "http://localhost:5001/v1";
26
-
27
- /**
28
- * The message to append to the agent output if the agent is interrupted by a
29
- * signal from the user.
30
- */
31
- export const USER_STOP_MESSAGE = " AGENT INTERRUPTED";
32
-
33
- /**
34
- * An agent's response, with optional extra image data.
35
- * `ChatCompletionMessageParam` may one day be updated to allow image data (as
36
- * it does for audio data), but for now image data is not included and so we
37
- * keep it separate.
38
- */
39
- export type AssistantResponse = {
40
- message: MessageParam;
41
- images?: ChatCompletionContentPartImage[];
42
- };
43
-
44
- export interface IAgentToolProvider {
45
- /**
46
- * Any initial setup to be performed by the tool (loading data, etc). This
47
- * function is responsible for registering any tools that this provider
48
- * exposes.
49
- */
50
- setup(agent: AgentEx): Promise<void>;
51
- }
52
-
53
- export type ToolCallResult<
54
- Meta extends Record<string, string> = Record<string, string>,
55
- > = {
56
- /**
57
- * Response to pass to the LLM
58
- */
59
- response: string;
60
-
61
- /**
62
- * An (optional) structured response.
63
- */
64
- structuredContent?: unknown;
65
-
66
- /**
67
- * (Optional) Application-specific meta data about the tool call result.
68
- */
69
- _meta?: Meta;
70
-
71
- /**
72
- * If set, `response` is used in the next round of the Agent loop (if any),
73
- * but `overwriteResponse` is passed to the ContextManager to be stored, and
74
- * to be used for future LLM invocations.
75
- */
76
- overwriteResponse?: string;
77
-
78
- /**
79
- * If set, the arguments to this tool all, as stored in the context and used
80
- * for future LLM invocations, will be overwritten by this value. This is
81
- * intended for the case where the LLM generates a large amount of data to
82
- * be saved which we do not want to appear in the context at every
83
- * iteration.
84
- */
85
- overwriteArgs?: string;
86
- };
87
-
88
- export type ToolHandler = (
89
- agent: AgentEx,
90
- args: unknown
91
- ) => Promise<ToolCallResult>;
92
-
93
- const logger = getLogger();
94
-
95
- export interface IConversation {
96
- userMessage(msg?: string, imageB64?: string): void;
97
- getConversation(): MessageParam[];
98
-
99
- getAgentProfile(): AgentProfile;
100
-
101
- setSystemPrompt(systemPrompt: string): void;
102
- setModel(model: string): void;
103
-
104
- shutdown(): Promise<void>;
105
- }
106
-
107
- type RegisteredTools = {
108
- handler: ToolHandler;
109
- };
110
-
111
- /**
112
- * An agent attached to an ILLM which updates a context via an
113
- * IContextTransaction interface (where IContextTransaction is like a DB tx or
114
- * DB writer, for staging changes and reading back state as-if those changes
115
- * were applied).
116
- */
117
- export class AgentEx {
118
- mcpServerManager: McpServerManager;
119
- llm: ILLM;
120
-
121
- /// Flag to stop the Agent loop.
122
- stopFlag: boolean;
123
- /// Function to stop the LLM (only present while it is active)
124
- stopFn: ((msg: string) => void) | undefined;
125
-
126
- /// The full list of tools, ready to pass to the LLM
127
- private tools: ToolDescriptor[] = [];
128
-
129
- /// Handlers for "agent" (or "built-in") tools. These do not require
130
- /// approval from the user.
131
- private agentTools = new Map<string, RegisteredTools>();
132
-
133
- constructor(mcpServerManager: McpServerManager, llm: ILLM) {
134
- this.mcpServerManager = mcpServerManager;
135
- this.llm = llm;
136
- this.stopFlag = false;
137
- this.stopFn = undefined;
138
- }
139
-
140
- public async shutdown(): Promise<void> {
141
- this.stop("shutting down");
142
- return this.mcpServerManager.shutdown();
143
- }
144
-
145
- public stop(msg?: string) {
146
- this.stopFlag = true;
147
- if (this.stopFn) {
148
- this.stopFn(msg || USER_STOP_MESSAGE);
149
- }
150
- }
151
-
152
- public getMcpServerManager(): McpServerManager {
153
- return this.mcpServerManager;
154
- }
155
-
156
- // TODO: rename
157
- public async userMessagesRaw(
158
- contextTx: IContextTransaction,
159
- eventHandler: IAgentEventHandler
160
- ): Promise<AssistantResponse | undefined> {
161
- this.stopFlag = false;
162
-
163
- // New user messages have already been added to the `contextTx`.
164
-
165
- // Image and audio handling
166
- //
167
- // `ChatCompletions` (responses from the LLM) can contain `audio` and
168
- // `images` tags. However, the `ChatCompletionMessageParam` type does not
169
- // allow for "assistant" messages with images / audio.
170
- //
171
- // As such, our current approach is to extract all assistant-generated
172
- // media and return it separately.
173
-
174
- const images: ChatCompletionContentPartImage[] = [];
175
-
176
- // We convert the `ChatCompletionsMessage` into a
177
- // `ChatCompletionAssistantMessageParam` and extract image data.
178
-
179
- let completion = await this.chatCompletion(
180
- contextTx.getLLMContext(),
181
- eventHandler
182
- );
183
- let message = this.processCompletion(completion, images, eventHandler);
184
- contextTx.addMessage(message);
185
-
186
- // While there are tool calls to make, invoke them and loop
187
-
188
- while (message.tool_calls && message.tool_calls.length > 0) {
189
- // Signal the event handler of the assistant message with tool calls
190
- // BEFORE processing tool results. This ensures the order of messages
191
- // in pendingMessages matches the order in the LLM context:
192
- // [user, assistant(tool_calls), tool_result, assistant(final)]
193
- eventHandler.onCompletion(message);
194
-
195
- // TODO: Execute all tool calls in parallel
196
-
197
- // [indexInContext, ToolCallResult][]
198
- const toolCallResults: [number, ToolCallResult][] = [];
199
- for (const toolCall of message.tool_calls ?? []) {
200
- // Execute the tool call, add the result to the context as an LLM
201
- // mesage, and record the index of the message alongside the result in
202
- // `toolCallResults`.
203
-
204
- const result = await this.doToolCall(toolCall, eventHandler);
205
- const toolResult: ToolMessageParam = {
206
- role: "tool",
207
- tool_call_id: toolCall.id,
208
- content: result.response,
209
- ...(result._meta ? { _meta: result._meta } : {}),
210
- ...(result.structuredContent
211
- ? { structuredContent: result.structuredContent }
212
- : {}),
213
- };
214
-
215
- const toolResultHandle = contextTx.addMessage(toolResult);
216
- toolCallResults.push([toolResultHandle, result]);
217
-
218
- // Immediately broadcast the tool result to the frontend for UI
219
- // feedback. This ensures the frontend knows the tool executed
220
- // successfully without waiting for the next LLM completion to
221
- // finish streaming
222
- eventHandler.onToolCallResult(toolResult);
223
-
224
- // If the tool call requested that its args be redacted, this can be
225
- // done now - before the next LLM invocation.
226
-
227
- if (result.overwriteArgs) {
228
- logger.debug(
229
- `updating args for toolcall ${toolCall.id}: ${result.overwriteArgs}`
230
- );
231
- toolCall.function.arguments = result.overwriteArgs;
232
- logger.debug(`agent message after update ${JSON.stringify(message)}`);
233
- }
234
- }
235
-
236
- // Get a new completion using the untouched tool call results. Note
237
- // that, since we are deferring the `onToolCallResult` calls (so they
238
- // can be redacted), we must take care that the errors in
239
- // `chatCompletion` do not disrupt this, so the caller has a consistent
240
- // view of the conversation state.
241
-
242
- try {
243
- completion = await this.chatCompletion(
244
- contextTx.getLLMContext(),
245
- eventHandler
246
- );
247
- message = this.processCompletion(completion, images, eventHandler);
248
- contextTx.addMessage(message);
249
- } finally {
250
- // Now that the tool call results have been passed to the LLM, perform
251
- // any updates on them if overwriteResponse was requested. If so, send
252
- // the updated tool result to the frontend to replace the original.
253
-
254
- toolCallResults.forEach(([handle, tcr]) => {
255
- if (tcr.overwriteResponse) {
256
- const ctxMsg = contextTx.getMessage(handle);
257
- ctxMsg.content = tcr.overwriteResponse;
258
- assert(ctxMsg.role === "tool");
259
- eventHandler.onToolCallResult(ctxMsg);
260
- }
261
- });
262
-
263
- // Note, if an error DID occur, the ContextManager does not see any of
264
- // the new context.
265
- }
266
- }
267
-
268
- // Signal the event handler of the final completion.
269
- eventHandler.onCompletion(message);
270
-
271
- return { message, images: images.length === 0 ? undefined : images };
272
- }
273
-
274
- async chatCompletion(
275
- context: MessageParam[],
276
- eventHandler: IAgentEventHandler
277
- ): Promise<Completion> {
278
- if (this.stopFlag) {
279
- return {
280
- id: "user_stopped",
281
- choices: [
282
- {
283
- finish_reason: "stop",
284
- index: 0,
285
- message: {
286
- content: USER_STOP_MESSAGE,
287
- role: "assistant",
288
- refusal: null,
289
- },
290
- logprobs: null,
291
- },
292
- ],
293
- created: Date.now(),
294
- model: this.llm.getModel(),
295
- object: "chat.completion",
296
- };
297
- }
298
-
299
- // Compute the full list of available tools
300
-
301
- let tools: ToolDescriptor[] | undefined;
302
- const mcpTools = this.mcpServerManager.getOpenAITools();
303
- logger.debug(`[chatCompletion] mcpTools: ${JSON.stringify(mcpTools)}`);
304
- const enabledTools = this.tools.concat(mcpTools);
305
- if (enabledTools.length > 0) {
306
- tools = enabledTools;
307
- }
308
- logger.debug(`[chatCompletion] tools: ${JSON.stringify(tools)}`);
309
-
310
- // Log system prompt length
311
- if (context.length > 0 && context[0].role === "system") {
312
- const systemPrompt = context[0].content as string;
313
- logger.info(
314
- `[chatCompletion] System prompt length: ${String(systemPrompt.length)}`
315
- );
316
- }
317
-
318
- const { stop, completion: completionP } =
319
- await this.llm.getConversationResponse(
320
- context,
321
- tools,
322
- eventHandler.onAgentMessage.bind(eventHandler),
323
- eventHandler.onReasoning.bind(eventHandler)
324
- );
325
-
326
- this.stopFn = stop;
327
- const completion = await completionP;
328
- this.stopFn = undefined;
329
-
330
- logger.debug(`Received chat completion ${JSON.stringify(completion)}`);
331
- return completion;
332
- }
333
-
334
- public addAgentToolProvider(toolProvider: IAgentToolProvider): Promise<void> {
335
- return toolProvider.setup(this);
336
- }
337
-
338
- public addAgentTool(tool: ToolDescriptor, handler: ToolHandler) {
339
- const name = tool.function.name;
340
- if (this.agentTools.has(name)) {
341
- throw new Error(`tool ${name} already added`);
342
- }
343
-
344
- logger.debug(`Adding tool ${name}`);
345
-
346
- this.tools.push(tool);
347
- this.agentTools.set(name, { handler });
348
- }
349
-
350
- public removeAgentTool(name: string) {
351
- if (!this.agentTools.has(name)) {
352
- logger.warn(`[removeTool] tool ${name} not present`);
353
- }
354
-
355
- // Find idx of the tool in the list
356
- const idx = (() => {
357
- let idx = 0;
358
- while (idx < this.tools.length) {
359
- if (this.tools[idx].function.name === name) {
360
- return idx;
361
- }
362
- idx++;
363
- }
364
- return -1;
365
- })();
366
- assert(idx > -1);
367
-
368
- // Remove entries
369
- this.tools.splice(idx, 1);
370
- this.agentTools.delete(name);
371
- }
372
-
373
- /**
374
- * Handle the details of getting approval (if required), invoking the tool
375
- * handler, informing the IAgentEventHandler of the result, and returns the
376
- * ChatCompletionToolMessageParam to be used in the conversation.
377
- */
378
- private async doToolCall(
379
- toolCall: MessageToolCall,
380
- eventHandler: IAgentEventHandler
381
- ): Promise<ToolCallResult> {
382
- if (this.stopFlag) {
383
- return { response: USER_STOP_MESSAGE };
384
- }
385
-
386
- // If the tool is and "agent" (internal) tool, we can just execute it.
387
- // Otherwise, call the event handler to get permission and invoke the
388
- // external tool handler.
389
-
390
- let result: ToolCallResult;
391
- try {
392
- const toolName = toolCall.function.name;
393
- const agentTool = this.agentTools.get(toolName);
394
- const isAgentTool = !!agentTool;
395
-
396
- if (isAgentTool) {
397
- // Internal (agent) tool
398
- if (!(await eventHandler.onToolCall(toolCall, true))) {
399
- result = { response: "User denied tool request." };
400
- } else {
401
- const args: unknown = JSON.parse(toolCall.function.arguments || "{}");
402
- result = await agentTool.handler(this, args);
403
- }
404
- } else {
405
- // McpServer tool call (agentTool === undefined). Sanity check the
406
- // tool call data, get approval, and then invoke.
407
-
408
- const args: unknown = JSON.parse(toolCall.function.arguments || "{}");
409
- const tc = this.mcpServerManager.verifyToolCall(toolName, args);
410
- if (!(await eventHandler.onToolCall(toolCall, false))) {
411
- result = { response: "User denied tool request." };
412
- } else {
413
- result = await this.mcpServerManager.invoke(tc);
414
- }
415
- logger.debug(`tool call result ${JSON.stringify(result)}`);
416
- }
417
- } catch (e) {
418
- let msg: string;
419
- if (e instanceof Error) {
420
- msg = e.message;
421
- } else if (typeof e === "string") {
422
- msg = e;
423
- } else {
424
- msg = String(e);
425
- }
426
- logger.error(`tool call error: ${msg}`);
427
- result = {
428
- response: `tool call error: ${msg}`,
429
- };
430
- }
431
-
432
- // Final sanity check on the tool call response length.
433
- if (result.response.length > MAX_TOOL_CALL_RESPONSE_LENGTH) {
434
- logger.warn(
435
- "[Agent.doToolCall]: truncating tool call result.response for call:\n" +
436
- JSON.stringify(toolCall)
437
- );
438
- result.response =
439
- result.response.slice(0, MAX_TOOL_CALL_RESPONSE_LENGTH) +
440
- " ..truncated";
441
- }
442
-
443
- return result;
444
- }
445
-
446
- private processCompletion(
447
- completion: Completion,
448
- images: ChatCompletionContentPartImage[],
449
- eventHandler: IAgentEventHandler
450
- ): AssistantMessageParam {
451
- // Add any images into the list, and call the event handler
452
-
453
- const compMessage = completion.choices[0].message;
454
- if (compMessage.images) {
455
- for (const image of compMessage.images) {
456
- eventHandler.onImage(image);
457
- images.push(image);
458
- }
459
- }
460
-
461
- return completionToAssistantMessageParam(compMessage);
462
- }
463
- }
464
-
465
- /**
466
- * Higher-level abstraction over AgentEx, which abstracts out the context
467
- * transactions. A single agent is associated with an IContextManager and
468
- * internally creates and commits transactions during each call to
469
- * `userMessage*`.
470
- */
471
- export class Agent implements IConversation {
472
- private eventHandler: IAgentEventHandler;
473
- private contextManager: IContextManager;
474
- private agentEx: AgentEx;
475
-
476
- private constructor(
477
- eventHandler: IAgentEventHandler,
478
- mcpServerManager: McpServerManager,
479
- llm: ILLM,
480
- contextManager: IContextManager
481
- ) {
482
- this.eventHandler = eventHandler;
483
- this.contextManager = contextManager;
484
- this.agentEx = new AgentEx(mcpServerManager, llm);
485
- }
486
-
487
- public static initializeWithLLM(
488
- eventHandler: IAgentEventHandler,
489
- llm: ILLM,
490
- contextManager: IContextManager,
491
- mcpServerManager?: McpServerManager
492
- ): Agent {
493
- return new Agent(
494
- eventHandler,
495
- mcpServerManager ?? new McpServerManager(),
496
- llm,
497
- contextManager
498
- );
499
- }
500
-
501
- public async shutdown(): Promise<void> {
502
- return this.agentEx.shutdown();
503
- }
504
-
505
- public getAgentProfile(): AgentProfile {
506
- return new AgentProfile(
507
- this.agentEx.llm.getModel(),
508
- this.getSystemPrompt(),
509
- this.agentEx.mcpServerManager.getMcpServerSettings()
510
- );
511
- }
512
-
513
- public getConversation(): MessageParam[] {
514
- const llmMessages = this.contextManager.getLLMContext();
515
- assert(
516
- llmMessages[0].role === "system",
517
- "first message must have system role"
518
- );
519
- return [...llmMessages.slice(1)];
520
- }
521
-
522
- public getMcpServerManager(): McpServerManager {
523
- return this.agentEx.mcpServerManager;
524
- }
525
-
526
- /**
527
- * Like `userMessage`, but can be awaited, and accepts the user name.
528
- */
529
- public async userMessageEx(
530
- msg?: string,
531
- imageB64?: string,
532
- name?: string
533
- ): Promise<AssistantResponse | undefined> {
534
- const userMessage = createUserMessage(msg, imageB64, name);
535
- if (!userMessage) {
536
- return undefined;
537
- }
538
- return this.userMessageRaw(userMessage);
539
- }
540
-
541
- public async userMessageRaw(
542
- userMessage: UserMessageParam
543
- ): Promise<AssistantResponse | undefined> {
544
- return this.userMessagesRaw([userMessage]);
545
- }
546
-
547
- public async userMessagesRaw(
548
- userMessages: UserMessageParam[]
549
- ): Promise<AssistantResponse | undefined> {
550
- const tx = await this.contextManager.startTx(userMessages);
551
- const result = await this.agentEx.userMessagesRaw(tx, this.eventHandler);
552
- await this.contextManager.commit(tx);
553
- return result;
554
- }
555
-
556
- public userMessage(msg?: string, imageB64?: string): void {
557
- void this.userMessageEx(msg, imageB64);
558
- }
559
-
560
- public getModel(): string {
561
- return this.agentEx.llm.getModel();
562
- }
563
-
564
- public setModel(model: string) {
565
- logger.debug(`Set model ${model}`);
566
- this.agentEx.llm.setModel(model);
567
- }
568
-
569
- public getSystemPrompt(): string {
570
- return this.contextManager.getAgentPrompt();
571
- }
572
-
573
- /**
574
- * Set the system prompt
575
- */
576
- public setSystemPrompt(systemMsg: string) {
577
- this.contextManager.setAgentPrompt(systemMsg);
578
- }
579
-
580
- public addAgentToolProvider(toolProvider: IAgentToolProvider): Promise<void> {
581
- return this.agentEx.addAgentToolProvider(toolProvider);
582
- }
583
-
584
- public addAgentTool(tool: ToolDescriptor, handler: ToolHandler) {
585
- this.agentEx.addAgentTool(tool, handler);
586
- }
587
- }
588
-
589
- /**
590
- * Returns the ChatCompletionMessageParam constructed from (optional) text and
591
- * (optional) image. If neither is given (null message), then undefined is
592
- * returned.
593
- **/
594
- export function createUserMessage(
595
- msg?: string,
596
- imageB64?: string,
597
- name?: string
598
- ): UserMessageParam | undefined {
599
- const content = (() => {
600
- if (!imageB64) {
601
- if (!msg) {
602
- return undefined;
603
- }
604
- return msg;
605
- }
606
-
607
- const content: openai.ChatCompletionContentPart[] = [];
608
- if (msg) {
609
- content.push({
610
- type: "text",
611
- text: msg,
612
- });
613
- }
614
- if (imageB64) {
615
- content.push({
616
- type: "image_url",
617
- image_url: {
618
- url: imageB64,
619
- },
620
- });
621
- }
622
- return content;
623
- })();
624
-
625
- if (!content) {
626
- return undefined;
627
- }
628
-
629
- return {
630
- role: "user",
631
- content,
632
- name,
633
- };
634
- }
635
-
636
- export function createUserMessageEnsure(
637
- msg?: string,
638
- imageB64?: string,
639
- name?: string
640
- ): UserMessageParam {
641
- const userMsg = createUserMessage(msg, imageB64, name);
642
- assert(userMsg, "createUserMessageEnsure");
643
- return userMsg;
644
- }
645
-
646
- export function completionToAssistantMessageParam(
647
- compMessage: Message
648
- ): AssistantMessageParam {
649
- // Strip down the `ChatCompletionMessage` to a
650
- // `ChatCompletionAssistantMessageParam`, only including the non-null
651
- // elements. For reference:
652
- //
653
- // Response from the LLM:
654
- //
655
- // export interface ChatCompletionMessage {
656
- // role: 'assistant';
657
- // audio?: ChatCompletionAudio | null;
658
- // content: string | null;
659
- // refusal: string | null;
660
- // tool_calls?: Array<ChatCompletionMessageToolCall>;
661
- //
662
- // annotations?: Array<ChatCompletionMessage.Annotation>;
663
- // // openrouter
664
- // images?: Array<ChatCompletionContentPartImage>
665
- // }
666
- //
667
- // Input to the LLM
668
- //
669
- // export interface ChatCompletionAssistantMessageParam {
670
- // role: "assistant";
671
- // audio?: ChatCompletionAssistantMessageParam.Audio | null;
672
- // content?:
673
- // | string
674
- // | Array<ChatCompletionContentPartText |
675
- // ChatCompletionContentPartRefusal>
676
- // | null;
677
- // refusal?: string | null;
678
- // tool_calls?: Array<ChatCompletionMessageToolCall>;
679
- //
680
- // name?: string;
681
- // }
682
-
683
- const message: AssistantMessageParam = {
684
- role: "assistant",
685
- };
686
- if (compMessage.audio) {
687
- message.audio = compMessage.audio;
688
- }
689
- if (compMessage.content) {
690
- message.content = compMessage.content;
691
- }
692
- if (compMessage.refusal) {
693
- message.refusal = compMessage.refusal;
694
- }
695
- if (compMessage.tool_calls) {
696
- message.tool_calls = compMessage.tool_calls;
697
- }
698
- return message;
699
- }