@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.
- package/package.json +5 -2
- package/.env.development +0 -6
- package/.env.test +0 -7
- package/.prettierrc.json +0 -11
- package/context_system.md +0 -498
- 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 -327
- package/src/agent/agent.ts +0 -699
- package/src/agent/agentUtils.ts +0 -286
- package/src/agent/compressingContextManager.ts +0 -129
- package/src/agent/context.ts +0 -265
- package/src/agent/contextWithWorkspace.ts +0 -162
- package/src/agent/documentSummarizer.ts +0 -157
- package/src/agent/dummyLLM.ts +0 -130
- package/src/agent/iAgentEventHandler.ts +0 -64
- package/src/agent/imageGenLLM.ts +0 -101
- 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 -99
- package/src/agent/openAILLMStreaming.ts +0 -648
- package/src/agent/promptProvider.ts +0 -87
- package/src/agent/repeatLLM.ts +0 -62
- package/src/agent/sudoMcpServerManager.ts +0 -361
- package/src/agent/test_data/harrypotter.txt +0 -6065
- package/src/agent/tokenAuth.ts +0 -50
- package/src/agent/tokenCounter.test.ts +0 -243
- package/src/agent/tokenCounter.ts +0 -483
- package/src/agent/toolSettings.ts +0 -24
- package/src/agent/tools/calculatorTool.ts +0 -50
- package/src/agent/tools/contentExtractors/htmlToText.ts +0 -61
- package/src/agent/tools/contentExtractors/pdfToText.ts +0 -60
- package/src/agent/tools/datetimeTool.ts +0 -41
- package/src/agent/tools/fileManager/fileManagerTool.ts +0 -199
- package/src/agent/tools/fileManager/index.ts +0 -50
- package/src/agent/tools/fileManager/memoryFileManager.ts +0 -120
- package/src/agent/tools/fileManager/mimeTypes.ts +0 -60
- package/src/agent/tools/fileManager/prompt.ts +0 -38
- package/src/agent/tools/fileManager/types.ts +0 -189
- package/src/agent/tools/index.ts +0 -49
- package/src/agent/tools/openUrlTool.ts +0 -62
- package/src/agent/tools/renderTool.ts +0 -92
- package/src/agent/tools/utils.ts +0 -74
- package/src/agent/tools/webSearch.ts +0 -138
- package/src/agent/tools/webSearchTool.ts +0 -44
- package/src/chat/client/chatClient.ts +0 -967
- 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 -21
- package/src/chat/client/interfaces.ts +0 -34
- package/src/chat/client/sessionClient.ts +0 -574
- package/src/chat/client/sessionFiles.ts +0 -142
- package/src/chat/client/teamManager.ts +0 -29
- package/src/chat/constants.ts +0 -6
- package/src/chat/data/apiKeyManager.ts +0 -76
- package/src/chat/data/dataModels.ts +0 -107
- package/src/chat/data/database.ts +0 -997
- package/src/chat/data/dbMcpServerConfigs.ts +0 -59
- package/src/chat/data/dbSessionFiles.ts +0 -107
- package/src/chat/data/dbSessionMessages.ts +0 -102
- 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 -899
- package/src/chat/server/README.md +0 -127
- package/src/chat/server/chatContextManager.ts +0 -660
- package/src/chat/server/connectionManager.test.ts +0 -246
- package/src/chat/server/connectionManager.ts +0 -506
- package/src/chat/server/conversation.ts +0 -319
- package/src/chat/server/errorUtils.ts +0 -28
- package/src/chat/server/imageGeneratorTools.ts +0 -179
- package/src/chat/server/openAIRouterLLM.ts +0 -168
- package/src/chat/server/openSession.ts +0 -1945
- package/src/chat/server/openSessionMessageSender.ts +0 -4
- package/src/chat/server/promptRefiner.ts +0 -106
- package/src/chat/server/server.ts +0 -178
- package/src/chat/server/sessionFileManager.ts +0 -151
- package/src/chat/server/sessionRegistry.test.ts +0 -137
- package/src/chat/server/sessionRegistry.ts +0 -1553
- package/src/chat/server/test-utils/mockFactories.ts +0 -422
- package/src/chat/server/titleGenerator.test.ts +0 -103
- package/src/chat/server/titleGenerator.ts +0 -143
- package/src/chat/server/tools.ts +0 -170
- package/src/chat/utils/agentSessionMap.ts +0 -76
- package/src/chat/utils/approvalManager.ts +0 -189
- package/src/chat/utils/asyncLock.ts +0 -43
- package/src/chat/utils/asyncQueue.ts +0 -62
- package/src/chat/utils/multiAsyncQueue.ts +0 -66
- package/src/chat/utils/responseAwaiter.ts +0 -181
- 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 -584
- package/src/test/approvalManager.test.ts +0 -141
- package/src/test/chatContextManager.test.ts +0 -552
- 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 -176
- package/src/test/tools.test.ts +0 -64
- package/src/tool/agentChat.ts +0 -203
- package/src/tool/agentMain.ts +0 -180
- package/src/tool/chatMain.ts +0 -621
- package/src/tool/commandPrompt.ts +0 -264
- package/src/tool/files.ts +0 -82
- 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/src/agent/agent.ts
DELETED
|
@@ -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
|
-
}
|