@xalia/agent 0.5.8 → 0.6.1
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 +23 -8
- package/dist/agent/src/agent/agent.js +173 -96
- package/dist/agent/src/agent/agentUtils.js +82 -53
- package/dist/agent/src/agent/compressingContextManager.js +102 -0
- package/dist/agent/src/agent/context.js +189 -0
- package/dist/agent/src/agent/dummyLLM.js +46 -5
- package/dist/agent/src/agent/iAgentEventHandler.js +2 -0
- package/dist/agent/src/agent/mcpServerManager.js +22 -23
- package/dist/agent/src/agent/nullAgentEventHandler.js +21 -0
- package/dist/agent/src/agent/nullPlatform.js +14 -0
- package/dist/agent/src/agent/openAILLMStreaming.js +12 -7
- package/dist/agent/src/agent/promptProvider.js +63 -0
- package/dist/agent/src/agent/repeatLLM.js +5 -5
- package/dist/agent/src/agent/sudoMcpServerManager.js +11 -9
- package/dist/agent/src/agent/tokenAuth.js +7 -7
- package/dist/agent/src/agent/tools.js +1 -1
- package/dist/agent/src/chat/client/chatClient.js +733 -0
- package/dist/agent/src/chat/client/connection.js +209 -0
- package/dist/agent/src/chat/client/connection.test.js +188 -0
- package/dist/agent/src/chat/client/constants.js +5 -0
- package/dist/agent/src/chat/client/index.js +15 -0
- package/dist/agent/src/chat/client/interfaces.js +2 -0
- package/dist/agent/src/chat/client/responseHandler.js +105 -0
- package/dist/agent/src/chat/client/sessionClient.js +331 -0
- package/dist/agent/src/chat/client/teamManager.js +2 -0
- package/dist/agent/src/chat/{apiKeyManager.js → data/apiKeyManager.js} +4 -0
- package/dist/agent/src/chat/data/dataModels.js +2 -0
- package/dist/agent/src/chat/data/database.js +749 -0
- package/dist/agent/src/chat/data/dbMcpServerConfigs.js +47 -0
- package/dist/agent/src/chat/protocol/connectionMessages.js +5 -0
- package/dist/agent/src/chat/protocol/constants.js +50 -0
- package/dist/agent/src/chat/protocol/errors.js +22 -0
- package/dist/agent/src/chat/protocol/messages.js +110 -0
- package/dist/agent/src/chat/server/chatContextManager.js +405 -0
- package/dist/agent/src/chat/server/connectionManager.js +352 -0
- package/dist/agent/src/chat/server/connectionManager.test.js +159 -0
- package/dist/agent/src/chat/server/conversation.js +198 -0
- package/dist/agent/src/chat/server/errorUtils.js +23 -0
- package/dist/agent/src/chat/server/openSession.js +869 -0
- package/dist/agent/src/chat/server/server.js +177 -0
- package/dist/agent/src/chat/server/sessionFileManager.js +161 -0
- package/dist/agent/src/chat/server/sessionRegistry.js +700 -0
- package/dist/agent/src/chat/server/sessionRegistry.test.js +97 -0
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +307 -0
- package/dist/agent/src/chat/server/tools.js +243 -0
- package/dist/agent/src/chat/utils/agentSessionMap.js +66 -0
- package/dist/agent/src/chat/utils/approvalManager.js +85 -0
- package/dist/agent/src/{utils → chat/utils}/asyncLock.js +3 -3
- package/dist/agent/src/chat/{asyncQueue.js → utils/asyncQueue.js} +12 -2
- package/dist/agent/src/chat/utils/htmlToText.js +84 -0
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +42 -0
- package/dist/agent/src/chat/utils/search.js +145 -0
- package/dist/agent/src/chat/utils/userResolver.js +46 -0
- package/dist/agent/src/chat/{websocket.js → utils/websocket.js} +2 -0
- package/dist/agent/src/test/agent.test.js +332 -0
- package/dist/agent/src/test/approvalManager.test.js +58 -0
- package/dist/agent/src/test/chatContextManager.test.js +392 -0
- package/dist/agent/src/test/clientServerConnection.test.js +158 -0
- package/dist/agent/src/test/compressingContextManager.test.js +65 -0
- package/dist/agent/src/test/context.test.js +83 -0
- package/dist/agent/src/test/conversation.test.js +89 -0
- package/dist/agent/src/test/db.test.js +262 -90
- package/dist/agent/src/test/dbMcpServerConfigs.test.js +72 -0
- package/dist/agent/src/test/dbTestTools.js +99 -0
- package/dist/agent/src/test/imageLoad.test.js +8 -7
- package/dist/agent/src/test/mcpServerManager.test.js +21 -18
- package/dist/agent/src/test/multiAsyncQueue.test.js +101 -0
- package/dist/agent/src/test/openaiStreaming.test.js +12 -11
- package/dist/agent/src/test/prompt.test.js +5 -4
- package/dist/agent/src/test/promptProvider.test.js +28 -0
- package/dist/agent/src/test/responseHandler.test.js +61 -0
- package/dist/agent/src/test/sudoMcpServerManager.test.js +14 -12
- package/dist/agent/src/test/testTools.js +109 -0
- package/dist/agent/src/test/tools.test.js +31 -0
- package/dist/agent/src/tool/agentChat.js +21 -10
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +235 -58
- package/dist/agent/src/tool/commandPrompt.js +15 -9
- package/dist/agent/src/tool/files.js +20 -16
- package/dist/agent/src/tool/nodePlatform.js +47 -3
- package/dist/agent/src/tool/options.js +4 -4
- package/dist/agent/src/tool/prompt.js +19 -13
- package/eslint.config.mjs +14 -1
- package/package.json +14 -6
- package/scripts/chat_server +8 -0
- package/scripts/setup_chat +7 -2
- package/scripts/shutdown_chat_server +3 -0
- package/scripts/test_chat +135 -17
- package/src/agent/agent.ts +270 -135
- package/src/agent/agentUtils.ts +136 -95
- package/src/agent/compressingContextManager.ts +164 -0
- package/src/agent/context.ts +268 -0
- package/src/agent/dummyLLM.ts +76 -8
- package/src/agent/iAgentEventHandler.ts +54 -0
- package/src/agent/iplatform.ts +1 -0
- package/src/agent/mcpServerManager.ts +32 -30
- package/src/agent/nullAgentEventHandler.ts +20 -0
- package/src/agent/nullPlatform.ts +13 -0
- package/src/agent/openAILLMStreaming.ts +12 -6
- package/src/agent/promptProvider.ts +87 -0
- package/src/agent/repeatLLM.ts +5 -5
- package/src/agent/sudoMcpServerManager.ts +13 -11
- package/src/agent/tokenAuth.ts +7 -7
- package/src/agent/tools.ts +3 -1
- package/src/chat/client/chatClient.ts +900 -0
- package/src/chat/client/connection.test.ts +241 -0
- package/src/chat/client/connection.ts +276 -0
- package/src/chat/client/constants.ts +3 -0
- package/src/chat/client/index.ts +18 -0
- package/src/chat/client/interfaces.ts +34 -0
- package/src/chat/client/responseHandler.ts +131 -0
- package/src/chat/client/sessionClient.ts +443 -0
- package/src/chat/client/teamManager.ts +29 -0
- package/src/chat/{apiKeyManager.ts → data/apiKeyManager.ts} +6 -2
- package/src/chat/data/dataModels.ts +85 -0
- package/src/chat/data/database.ts +982 -0
- package/src/chat/data/dbMcpServerConfigs.ts +59 -0
- package/src/chat/protocol/connectionMessages.ts +49 -0
- package/src/chat/protocol/constants.ts +55 -0
- package/src/chat/protocol/errors.ts +16 -0
- package/src/chat/protocol/messages.ts +682 -0
- package/src/chat/server/README.md +127 -0
- package/src/chat/server/chatContextManager.ts +612 -0
- package/src/chat/server/connectionManager.test.ts +266 -0
- package/src/chat/server/connectionManager.ts +541 -0
- package/src/chat/server/conversation.ts +269 -0
- package/src/chat/server/errorUtils.ts +28 -0
- package/src/chat/server/openSession.ts +1332 -0
- package/src/chat/server/server.ts +177 -0
- package/src/chat/server/sessionFileManager.ts +239 -0
- package/src/chat/server/sessionRegistry.test.ts +138 -0
- package/src/chat/server/sessionRegistry.ts +1064 -0
- package/src/chat/server/test-utils/mockFactories.ts +422 -0
- package/src/chat/server/tools.ts +265 -0
- package/src/chat/utils/agentSessionMap.ts +76 -0
- package/src/chat/utils/approvalManager.ts +111 -0
- package/src/{utils → chat/utils}/asyncLock.ts +3 -3
- package/src/chat/{asyncQueue.ts → utils/asyncQueue.ts} +14 -3
- package/src/chat/utils/htmlToText.ts +61 -0
- package/src/chat/utils/multiAsyncQueue.ts +52 -0
- package/src/chat/utils/search.ts +139 -0
- package/src/chat/utils/userResolver.ts +48 -0
- package/src/chat/{websocket.ts → utils/websocket.ts} +2 -0
- package/src/test/agent.test.ts +487 -0
- package/src/test/approvalManager.test.ts +73 -0
- package/src/test/chatContextManager.test.ts +521 -0
- package/src/test/clientServerConnection.test.ts +207 -0
- package/src/test/compressingContextManager.test.ts +82 -0
- package/src/test/context.test.ts +105 -0
- package/src/test/conversation.test.ts +109 -0
- package/src/test/db.test.ts +351 -103
- package/src/test/dbMcpServerConfigs.test.ts +112 -0
- package/src/test/dbTestTools.ts +153 -0
- package/src/test/imageLoad.test.ts +7 -6
- package/src/test/mcpServerManager.test.ts +19 -14
- package/src/test/multiAsyncQueue.test.ts +125 -0
- package/src/test/openaiStreaming.test.ts +11 -10
- package/src/test/prompt.test.ts +4 -3
- package/src/test/promptProvider.test.ts +33 -0
- package/src/test/responseHandler.test.ts +78 -0
- package/src/test/sudoMcpServerManager.test.ts +22 -15
- package/src/test/testTools.ts +146 -0
- package/src/test/tools.test.ts +39 -0
- package/src/tool/agentChat.ts +26 -12
- package/src/tool/agentMain.ts +1 -1
- package/src/tool/chatMain.ts +283 -100
- package/src/tool/commandPrompt.ts +25 -9
- package/src/tool/files.ts +25 -19
- package/src/tool/nodePlatform.ts +52 -3
- package/src/tool/options.ts +4 -2
- package/src/tool/prompt.ts +22 -15
- package/test_data/dummyllm_script_crash.json +32 -0
- package/test_data/frog.png.b64 +1 -0
- package/vitest.config.ts +39 -0
- package/dist/agent/src/chat/client.js +0 -310
- package/dist/agent/src/chat/conversationManager.js +0 -502
- package/dist/agent/src/chat/db.js +0 -218
- package/dist/agent/src/chat/messages.js +0 -29
- package/dist/agent/src/chat/server.js +0 -158
- package/src/chat/client.ts +0 -445
- package/src/chat/conversationManager.ts +0 -730
- package/src/chat/db.ts +0 -304
- package/src/chat/messages.ts +0 -266
- package/src/chat/server.ts +0 -177
- /package/{frog.png → test_data/frog.png} +0 -0
package/src/agent/agent.ts
CHANGED
|
@@ -1,13 +1,51 @@
|
|
|
1
1
|
import * as dotenv from "dotenv";
|
|
2
2
|
import { OpenAI } from "openai";
|
|
3
3
|
import { McpServerManager } from "./mcpServerManager";
|
|
4
|
-
import { ChatCompletionContentPart } from "openai/resources.mjs";
|
|
5
4
|
import { strict as assert } from "assert";
|
|
6
5
|
import { ILLM } from "./llm";
|
|
7
6
|
import { AgentProfile, getLogger } from "@xalia/xmcp/sdk";
|
|
7
|
+
import { IAgentEventHandler } from "./iAgentEventHandler";
|
|
8
|
+
import { IContextManager } from "./context";
|
|
8
9
|
export { AgentProfile } from "@xalia/xmcp/sdk";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
const MAX_TOOL_CALL_RESPONSE_LENGTH = 4000;
|
|
12
|
+
|
|
13
|
+
export interface IAgentToolProvider {
|
|
14
|
+
/**
|
|
15
|
+
* Any initial setup to be performed by the tool (loading data, etc). This
|
|
16
|
+
* function is responsible for registering any tools that this provider
|
|
17
|
+
* exposes.
|
|
18
|
+
*/
|
|
19
|
+
setup(agent: Agent): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ToolCallResult = {
|
|
23
|
+
/**
|
|
24
|
+
* Response to pass to the LLM
|
|
25
|
+
*/
|
|
26
|
+
response: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* If set, `response` is used in the next round of the Agent loop (if any),
|
|
30
|
+
* but `overwriteResponse` is passed to the ContextManager to be stored, and
|
|
31
|
+
* to be used for future LLM invocations.
|
|
32
|
+
*/
|
|
33
|
+
overwriteResponse?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* If set, the arguments to this tool all, as stored in the context and used
|
|
37
|
+
* for future LLM invocations, will be overwritten by this value. This is
|
|
38
|
+
* intended for the case where the LLM generates a large amount of data to
|
|
39
|
+
* be saved which we do not want to appear in the context at every
|
|
40
|
+
* iteration.
|
|
41
|
+
*/
|
|
42
|
+
overwriteArgs?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type ToolHandler = (
|
|
46
|
+
agent: Agent,
|
|
47
|
+
args: unknown
|
|
48
|
+
) => Promise<ToolCallResult>;
|
|
11
49
|
|
|
12
50
|
export type McpServerUrls = (name: string) => string;
|
|
13
51
|
|
|
@@ -22,69 +60,64 @@ export type ChatCompletionAssistantMessageParam =
|
|
|
22
60
|
export type ChatCompletionUserMessageParam =
|
|
23
61
|
OpenAI.ChatCompletionUserMessageParam;
|
|
24
62
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
(msg: string, msgEnd: boolean): Promise<void>;
|
|
28
|
-
};
|
|
63
|
+
export type ChatCompletionToolMessageParam =
|
|
64
|
+
OpenAI.ChatCompletionToolMessageParam;
|
|
29
65
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
(msg: ChatCompletionMessageToolCall): Promise<boolean>;
|
|
33
|
-
};
|
|
66
|
+
dotenv.config();
|
|
67
|
+
const logger = getLogger();
|
|
34
68
|
|
|
35
69
|
export interface IConversation {
|
|
36
70
|
userMessage(msg?: string, imageB64?: string): void;
|
|
37
71
|
getConversation(): ChatCompletionMessageParam[];
|
|
38
72
|
|
|
39
|
-
// TODO: remove?
|
|
40
|
-
resetConversation(): void;
|
|
41
|
-
|
|
42
73
|
getAgentProfile(): AgentProfile;
|
|
43
|
-
|
|
74
|
+
|
|
44
75
|
setSystemPrompt(systemPrompt: string): void;
|
|
45
|
-
getModel(): string;
|
|
46
76
|
setModel(model: string): void;
|
|
47
77
|
|
|
48
78
|
shutdown(): Promise<void>;
|
|
49
79
|
}
|
|
50
80
|
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
type RegisteredTools = {
|
|
82
|
+
handler: ToolHandler;
|
|
83
|
+
};
|
|
53
84
|
|
|
54
85
|
export class Agent implements IConversation {
|
|
55
|
-
private
|
|
86
|
+
private eventHandler: IAgentEventHandler;
|
|
87
|
+
private mcpServerManager: McpServerManager;
|
|
88
|
+
private llm: ILLM;
|
|
89
|
+
private contextManager: IContextManager;
|
|
90
|
+
|
|
91
|
+
/// The full list of tools, ready to pass to the LLM
|
|
92
|
+
private tools: OpenAI.ChatCompletionTool[] = [];
|
|
93
|
+
|
|
94
|
+
/// Handlers for "agent" (or "built-in") tools. These do not require
|
|
95
|
+
/// approval from the user.
|
|
96
|
+
private agentTools = new Map<string, RegisteredTools>();
|
|
56
97
|
|
|
57
98
|
private constructor(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
private messages: ChatCompletionMessageParam[],
|
|
61
|
-
private mcpServerManager: McpServerManager,
|
|
62
|
-
private tools: OpenAI.ChatCompletionTool[],
|
|
63
|
-
private llm: ILLM
|
|
64
|
-
) {}
|
|
65
|
-
|
|
66
|
-
public static async initializeWithLLM(
|
|
67
|
-
onMessage: OnMessageCB,
|
|
68
|
-
onToolCall: OnToolCallCB,
|
|
69
|
-
systemPrompt: string | undefined,
|
|
99
|
+
eventHandler: IAgentEventHandler,
|
|
100
|
+
mcpServerManager: McpServerManager,
|
|
70
101
|
llm: ILLM,
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} as OpenAI.ChatCompletionMessageParam,
|
|
79
|
-
];
|
|
102
|
+
contextManager: IContextManager
|
|
103
|
+
) {
|
|
104
|
+
this.eventHandler = eventHandler;
|
|
105
|
+
this.mcpServerManager = mcpServerManager;
|
|
106
|
+
this.llm = llm;
|
|
107
|
+
this.contextManager = contextManager;
|
|
108
|
+
}
|
|
80
109
|
|
|
110
|
+
public static initializeWithLLM(
|
|
111
|
+
eventHandler: IAgentEventHandler,
|
|
112
|
+
llm: ILLM,
|
|
113
|
+
contextManager: IContextManager,
|
|
114
|
+
mcpServerManager?: McpServerManager
|
|
115
|
+
): Agent {
|
|
81
116
|
return new Agent(
|
|
82
|
-
|
|
83
|
-
onToolCall,
|
|
84
|
-
messages,
|
|
117
|
+
eventHandler,
|
|
85
118
|
mcpServerManager ?? new McpServerManager(),
|
|
86
|
-
|
|
87
|
-
|
|
119
|
+
llm,
|
|
120
|
+
contextManager
|
|
88
121
|
);
|
|
89
122
|
}
|
|
90
123
|
|
|
@@ -101,24 +134,12 @@ export class Agent implements IConversation {
|
|
|
101
134
|
}
|
|
102
135
|
|
|
103
136
|
public getConversation(): ChatCompletionMessageParam[] {
|
|
137
|
+
const llmMessages = this.contextManager.getLLMContext();
|
|
104
138
|
assert(
|
|
105
|
-
|
|
139
|
+
llmMessages[0].role === "system",
|
|
106
140
|
"first message must have system role"
|
|
107
141
|
);
|
|
108
|
-
|
|
109
|
-
// the callers copy.
|
|
110
|
-
return structuredClone(this.messages.slice(1));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
public setConversation(messages: ChatCompletionMessageParam[]) {
|
|
114
|
-
assert(this.messages[0].role == "system");
|
|
115
|
-
assert(
|
|
116
|
-
messages.length === 0 || messages[0].role != "system",
|
|
117
|
-
"conversation contains system msg"
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const newMessages: ChatCompletionMessageParam[] = [this.messages[0]];
|
|
121
|
-
this.messages = newMessages.concat(structuredClone(messages));
|
|
142
|
+
return [...llmMessages.slice(1)];
|
|
122
143
|
}
|
|
123
144
|
|
|
124
145
|
public getMcpServerManager(): McpServerManager {
|
|
@@ -143,49 +164,103 @@ export class Agent implements IConversation {
|
|
|
143
164
|
public async userMessageRaw(
|
|
144
165
|
userMessage: ChatCompletionUserMessageParam
|
|
145
166
|
): Promise<ChatCompletionMessageParam | undefined> {
|
|
146
|
-
this.
|
|
147
|
-
|
|
167
|
+
return this.userMessagesRaw([userMessage]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public async userMessagesRaw(
|
|
171
|
+
userMessages: ChatCompletionUserMessageParam[]
|
|
172
|
+
): Promise<ChatCompletionMessageParam | undefined> {
|
|
173
|
+
// Note: `getLLMContext` returns a copy to we can mutate this array
|
|
174
|
+
const context = this.contextManager.getLLMContext();
|
|
175
|
+
const newMessagesIdx = context.length;
|
|
176
|
+
|
|
177
|
+
// Add the new user messages
|
|
178
|
+
context.push(...userMessages);
|
|
148
179
|
|
|
180
|
+
let completion = await this.chatCompletion(context);
|
|
149
181
|
let message = completion.choices[0].message;
|
|
150
|
-
|
|
182
|
+
context.push(message);
|
|
151
183
|
|
|
152
|
-
// While there are tool calls to make,
|
|
184
|
+
// While there are tool calls to make, invoke them and loop
|
|
153
185
|
|
|
154
186
|
while (message.tool_calls && message.tool_calls.length > 0) {
|
|
187
|
+
// TODO: Execute all tool calls in parallel
|
|
188
|
+
|
|
189
|
+
// [indexInContext, ToolCallResult][]
|
|
190
|
+
const toolCallResults: [number, ToolCallResult][] = [];
|
|
155
191
|
for (const toolCall of message.tool_calls ?? []) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
192
|
+
// Execute the tool call, add the result to the context as an LLM
|
|
193
|
+
// mesage, and record the index of the message alongside the result in
|
|
194
|
+
// `toolCallResults`.
|
|
195
|
+
|
|
196
|
+
const result = await this.doToolCall(toolCall);
|
|
197
|
+
toolCallResults.push([context.length, result]);
|
|
198
|
+
context.push({
|
|
199
|
+
role: "tool",
|
|
200
|
+
tool_call_id: toolCall.id,
|
|
201
|
+
content: result.response,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// If the tool call requested that its args be redacted, this can be
|
|
205
|
+
// done now - before the next LLM invocation.
|
|
206
|
+
|
|
207
|
+
if (result.overwriteArgs) {
|
|
208
|
+
logger.debug(
|
|
209
|
+
`updating args for toolcall ${toolCall.id}: ${result.overwriteArgs}`
|
|
210
|
+
);
|
|
211
|
+
toolCall.function.arguments = result.overwriteArgs;
|
|
212
|
+
logger.debug(`agent message after update ${JSON.stringify(message)}`);
|
|
176
213
|
}
|
|
177
214
|
}
|
|
178
215
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
216
|
+
// Now that any args have been overwritten, signal the event handler of
|
|
217
|
+
// the prevoius completion.
|
|
218
|
+
|
|
219
|
+
this.eventHandler.onCompletion(message);
|
|
220
|
+
|
|
221
|
+
// Get a new completion using the untouched tool call results. Note
|
|
222
|
+
// that, since we are deferring the `onToolCallResult` calls (so they
|
|
223
|
+
// can be redacted), we must take care that the errors in
|
|
224
|
+
// `chatCompletion` do not disrupt this, so the caller has a consistent
|
|
225
|
+
// view of the conversation state.
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
completion = await this.chatCompletion(context); // CAN THROW
|
|
229
|
+
message = completion.choices[0].message;
|
|
230
|
+
context.push(message);
|
|
231
|
+
} finally {
|
|
232
|
+
// Now that the tool call results have been passed to the LLM, perform
|
|
233
|
+
// any updates on them. Pass the (updated) tool-call-result LLM
|
|
234
|
+
// messages to the event handler - note, we want to do this even if
|
|
235
|
+
// the an error occured, so that the caller has an up-to-date picture
|
|
236
|
+
// of the context state when the error occured.
|
|
237
|
+
|
|
238
|
+
toolCallResults.forEach(([indexInContext, tcr]) => {
|
|
239
|
+
const ctxMsg = context[indexInContext];
|
|
240
|
+
if (tcr.overwriteResponse) {
|
|
241
|
+
ctxMsg.content = tcr.overwriteResponse;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
assert(ctxMsg.role === "tool");
|
|
245
|
+
this.eventHandler.onToolCallResult(ctxMsg);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Note, if an error DID occur, the ContextManager does not see any of
|
|
249
|
+
// the new context.
|
|
250
|
+
}
|
|
182
251
|
}
|
|
183
252
|
|
|
253
|
+
// Signal the event handler of the final completion.
|
|
254
|
+
this.eventHandler.onCompletion(message);
|
|
255
|
+
|
|
256
|
+
// Add all new new messages to the context
|
|
257
|
+
this.contextManager.addMessages(context.slice(newMessagesIdx));
|
|
258
|
+
|
|
184
259
|
return completion.choices[0].message;
|
|
185
260
|
}
|
|
186
261
|
|
|
187
262
|
public userMessage(msg?: string, imageB64?: string): void {
|
|
188
|
-
this.userMessageEx(msg, imageB64);
|
|
263
|
+
void this.userMessageEx(msg, imageB64);
|
|
189
264
|
}
|
|
190
265
|
|
|
191
266
|
public getModel(): string {
|
|
@@ -197,85 +272,135 @@ export class Agent implements IConversation {
|
|
|
197
272
|
this.llm.setModel(model);
|
|
198
273
|
}
|
|
199
274
|
|
|
200
|
-
/**
|
|
201
|
-
* Clear the conversation.
|
|
202
|
-
*/
|
|
203
|
-
public resetConversation() {
|
|
204
|
-
assert(this.messages.length > 0);
|
|
205
|
-
// Keep only the system message
|
|
206
|
-
this.messages.splice(1);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
275
|
public getSystemPrompt(): string {
|
|
210
|
-
|
|
211
|
-
return this.messages[0].content as string;
|
|
276
|
+
return this.contextManager.getAgentPrompt();
|
|
212
277
|
}
|
|
213
278
|
|
|
214
279
|
/**
|
|
215
280
|
* Set the system prompt
|
|
216
281
|
*/
|
|
217
282
|
public setSystemPrompt(systemMsg: string) {
|
|
218
|
-
|
|
219
|
-
this.messages[0].content = systemMsg;
|
|
283
|
+
this.contextManager.setAgentPrompt(systemMsg);
|
|
220
284
|
}
|
|
221
285
|
|
|
222
|
-
async chatCompletion(
|
|
286
|
+
async chatCompletion(
|
|
287
|
+
context: ChatCompletionMessageParam[]
|
|
288
|
+
): Promise<OpenAI.Chat.Completions.ChatCompletion> {
|
|
289
|
+
// Compute the full list of available tools
|
|
290
|
+
|
|
223
291
|
let tools: OpenAI.ChatCompletionTool[] | undefined;
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
);
|
|
292
|
+
const mcpTools = this.mcpServerManager.getOpenAITools();
|
|
293
|
+
const enabledTools = this.tools.concat(mcpTools);
|
|
227
294
|
if (enabledTools.length > 0) {
|
|
228
295
|
tools = enabledTools;
|
|
229
296
|
}
|
|
230
|
-
// logger.debug(
|
|
231
|
-
// `chatCompletion: tools: ${JSON.stringify(tools, undefined, 2)}`
|
|
232
|
-
// );
|
|
233
297
|
const completion = await this.llm.getConversationResponse(
|
|
234
|
-
|
|
298
|
+
context,
|
|
235
299
|
tools,
|
|
236
|
-
this.
|
|
300
|
+
this.eventHandler.onAgentMessage.bind(this.eventHandler)
|
|
237
301
|
);
|
|
238
302
|
logger.debug(`Received chat completion ${JSON.stringify(completion)}`);
|
|
239
303
|
return completion;
|
|
240
304
|
}
|
|
241
305
|
|
|
242
|
-
public
|
|
243
|
-
return this
|
|
244
|
-
.getOpenAITools()
|
|
245
|
-
.map((tool) => tool.function.name);
|
|
306
|
+
public addAgentToolProvider(toolProvider: IAgentToolProvider): Promise<void> {
|
|
307
|
+
return toolProvider.setup(this);
|
|
246
308
|
}
|
|
247
309
|
|
|
248
|
-
public
|
|
310
|
+
public addAgentTool(tool: OpenAI.ChatCompletionTool, handler: ToolHandler) {
|
|
249
311
|
const name = tool.function.name;
|
|
250
|
-
if (this.
|
|
251
|
-
throw `tool ${name} already added
|
|
312
|
+
if (this.agentTools.has(name)) {
|
|
313
|
+
throw new Error(`tool ${name} already added`);
|
|
252
314
|
}
|
|
253
315
|
|
|
254
316
|
logger.debug(`Adding tool ${name}`);
|
|
255
317
|
|
|
256
318
|
this.tools.push(tool);
|
|
257
|
-
this.
|
|
319
|
+
this.agentTools.set(name, { handler });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
public removeAgentTool(name: string) {
|
|
323
|
+
if (!this.agentTools.has(name)) {
|
|
324
|
+
logger.warn(`[removeTool] tool ${name} not present`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Find idx of the tool in the list
|
|
328
|
+
const idx = (() => {
|
|
329
|
+
let idx = 0;
|
|
330
|
+
while (idx < this.tools.length) {
|
|
331
|
+
if (this.tools[idx].function.name === name) {
|
|
332
|
+
return idx;
|
|
333
|
+
}
|
|
334
|
+
idx++;
|
|
335
|
+
}
|
|
336
|
+
return -1;
|
|
337
|
+
})();
|
|
338
|
+
assert(idx > -1);
|
|
339
|
+
|
|
340
|
+
// Remove entries
|
|
341
|
+
this.tools.splice(idx, 1);
|
|
342
|
+
this.agentTools.delete(name);
|
|
258
343
|
}
|
|
259
344
|
|
|
260
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Handle the details of getting approval (if required), invoking the tool
|
|
347
|
+
* handler, informing the IAgentEventHandler of the result, and returns the
|
|
348
|
+
* OpenAI.ChatCompletionToolMessageParam to be used in the conversation.
|
|
349
|
+
*/
|
|
350
|
+
private async doToolCall(
|
|
261
351
|
toolCall: ChatCompletionMessageToolCall
|
|
262
|
-
): Promise<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
352
|
+
): Promise<ToolCallResult> {
|
|
353
|
+
// If the tool is and "agent" (internal) tool, we can just execute it.
|
|
354
|
+
// Otherwise, call the event handler to get permission and invoke the
|
|
355
|
+
// external tool handler.
|
|
356
|
+
|
|
357
|
+
let result: ToolCallResult;
|
|
358
|
+
try {
|
|
359
|
+
const toolName = toolCall.function.name;
|
|
360
|
+
const agentTool = this.agentTools.get(toolName);
|
|
361
|
+
const isAgentTool = !!agentTool;
|
|
362
|
+
const approve = await this.eventHandler.onToolCall(toolCall, isAgentTool);
|
|
363
|
+
if (!approve) {
|
|
364
|
+
result = { response: "User denied tool request." };
|
|
365
|
+
} else if (isAgentTool) {
|
|
366
|
+
// Internal (agent) tool
|
|
367
|
+
const args: unknown = JSON.parse(toolCall.function.arguments);
|
|
368
|
+
result = await agentTool.handler(this, args);
|
|
369
|
+
} else {
|
|
370
|
+
// McpServer tool call (agentTool === undefined)
|
|
371
|
+
const args: unknown = JSON.parse(toolCall.function.arguments);
|
|
372
|
+
result = {
|
|
373
|
+
response: await this.mcpServerManager.invoke(toolName, args),
|
|
374
|
+
};
|
|
375
|
+
logger.debug(`tool call result ${JSON.stringify(result)}`);
|
|
376
|
+
}
|
|
377
|
+
} catch (e) {
|
|
378
|
+
let msg: string;
|
|
379
|
+
if (e instanceof Error) {
|
|
380
|
+
msg = e.message;
|
|
381
|
+
} else if (typeof e === "string") {
|
|
382
|
+
msg = e;
|
|
383
|
+
} else {
|
|
384
|
+
msg = String(e);
|
|
385
|
+
}
|
|
386
|
+
logger.error(`tool call error: ${msg}`);
|
|
387
|
+
result = {
|
|
388
|
+
response: `tool call error: ${msg}`,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Final sanity check on the tool call response length.
|
|
393
|
+
if (result.response.length > MAX_TOOL_CALL_RESPONSE_LENGTH) {
|
|
394
|
+
logger.warn(
|
|
395
|
+
"[Agent.doToolCall]: truncating tool call result.response for call:\n" +
|
|
396
|
+
JSON.stringify(toolCall)
|
|
397
|
+
);
|
|
398
|
+
result.response =
|
|
399
|
+
result.response.slice(0, MAX_TOOL_CALL_RESPONSE_LENGTH) +
|
|
400
|
+
" ..truncated";
|
|
273
401
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
tool_call_id: toolCall.id,
|
|
277
|
-
content: result.toString(),
|
|
278
|
-
};
|
|
402
|
+
|
|
403
|
+
return result;
|
|
279
404
|
}
|
|
280
405
|
}
|
|
281
406
|
|
|
@@ -297,7 +422,7 @@ export function createUserMessage(
|
|
|
297
422
|
return msg;
|
|
298
423
|
}
|
|
299
424
|
|
|
300
|
-
const content: ChatCompletionContentPart[] = [];
|
|
425
|
+
const content: OpenAI.ChatCompletionContentPart[] = [];
|
|
301
426
|
if (msg) {
|
|
302
427
|
content.push({
|
|
303
428
|
type: "text",
|
|
@@ -325,3 +450,13 @@ export function createUserMessage(
|
|
|
325
450
|
name,
|
|
326
451
|
};
|
|
327
452
|
}
|
|
453
|
+
|
|
454
|
+
export function createUserMessageEnsure(
|
|
455
|
+
msg?: string,
|
|
456
|
+
imageB64?: string,
|
|
457
|
+
name?: string
|
|
458
|
+
): ChatCompletionUserMessageParam {
|
|
459
|
+
const userMsg = createUserMessage(msg, imageB64, name);
|
|
460
|
+
assert(userMsg);
|
|
461
|
+
return userMsg;
|
|
462
|
+
}
|