@xalia/agent 0.6.1 → 0.6.3
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/dist/agent/src/agent/agent.js +109 -57
- package/dist/agent/src/agent/agentUtils.js +24 -26
- package/dist/agent/src/agent/compressingContextManager.js +3 -2
- package/dist/agent/src/agent/dummyLLM.js +1 -3
- package/dist/agent/src/agent/imageGenLLM.js +67 -0
- package/dist/agent/src/agent/imageGenerator.js +43 -0
- package/dist/agent/src/agent/llm.js +27 -0
- package/dist/agent/src/agent/mcpServerManager.js +18 -6
- package/dist/agent/src/agent/nullAgentEventHandler.js +6 -0
- package/dist/agent/src/agent/openAILLM.js +3 -3
- package/dist/agent/src/agent/openAILLMStreaming.js +41 -6
- package/dist/agent/src/chat/client/chatClient.js +154 -235
- package/dist/agent/src/chat/client/constants.js +1 -2
- package/dist/agent/src/chat/client/sessionClient.js +47 -15
- package/dist/agent/src/chat/client/sessionFiles.js +102 -0
- package/dist/agent/src/chat/data/apiKeyManager.js +38 -7
- package/dist/agent/src/chat/data/database.js +83 -70
- package/dist/agent/src/chat/data/dbSessionFileModels.js +49 -0
- package/dist/agent/src/chat/data/dbSessionFiles.js +76 -0
- package/dist/agent/src/chat/data/dbSessionMessages.js +57 -0
- package/dist/agent/src/chat/data/mimeTypes.js +44 -0
- package/dist/agent/src/chat/protocol/messages.js +21 -1
- package/dist/agent/src/chat/server/chatContextManager.js +19 -16
- package/dist/agent/src/chat/server/connectionManager.js +14 -36
- package/dist/agent/src/chat/server/connectionManager.test.js +3 -16
- package/dist/agent/src/chat/server/conversation.js +73 -44
- package/dist/agent/src/chat/server/imageGeneratorTools.js +111 -0
- package/dist/agent/src/chat/server/openSession.js +398 -233
- package/dist/agent/src/chat/server/openSessionMessageSender.js +2 -0
- package/dist/agent/src/chat/server/server.js +5 -8
- package/dist/agent/src/chat/server/sessionFileManager.js +171 -38
- package/dist/agent/src/chat/server/sessionRegistry.js +214 -42
- package/dist/agent/src/chat/server/test-utils/mockFactories.js +12 -11
- package/dist/agent/src/chat/server/tools.js +27 -6
- package/dist/agent/src/chat/utils/approvalManager.js +82 -64
- package/dist/agent/src/chat/utils/multiAsyncQueue.js +9 -1
- package/dist/agent/src/chat/{client/responseHandler.js → utils/responseAwaiter.js} +41 -18
- package/dist/agent/src/test/agent.test.js +104 -63
- package/dist/agent/src/test/approvalManager.test.js +79 -35
- package/dist/agent/src/test/chatContextManager.test.js +16 -17
- package/dist/agent/src/test/clientServerConnection.test.js +2 -2
- package/dist/agent/src/test/db.test.js +33 -70
- package/dist/agent/src/test/dbSessionFiles.test.js +179 -0
- package/dist/agent/src/test/dbSessionMessages.test.js +67 -0
- package/dist/agent/src/test/dbTestTools.js +6 -5
- package/dist/agent/src/test/imageLoad.test.js +1 -1
- package/dist/agent/src/test/mcpServerManager.test.js +1 -1
- package/dist/agent/src/test/multiAsyncQueue.test.js +50 -0
- package/dist/agent/src/test/responseAwaiter.test.js +74 -0
- package/dist/agent/src/test/testTools.js +12 -0
- package/dist/agent/src/tool/agentChat.js +25 -6
- package/dist/agent/src/tool/agentMain.js +1 -1
- package/dist/agent/src/tool/chatMain.js +115 -6
- package/dist/agent/src/tool/commandPrompt.js +7 -3
- package/dist/agent/src/tool/files.js +23 -15
- package/dist/agent/src/tool/options.js +2 -2
- package/package.json +1 -1
- package/scripts/setup_chat +2 -2
- package/scripts/test_chat +95 -36
- package/src/agent/agent.ts +152 -41
- package/src/agent/agentUtils.ts +34 -41
- package/src/agent/compressingContextManager.ts +5 -4
- package/src/agent/context.ts +1 -1
- package/src/agent/dummyLLM.ts +1 -3
- package/src/agent/iAgentEventHandler.ts +15 -2
- package/src/agent/imageGenLLM.ts +99 -0
- package/src/agent/imageGenerator.ts +60 -0
- package/src/agent/llm.ts +128 -4
- package/src/agent/mcpServerManager.ts +26 -7
- package/src/agent/nullAgentEventHandler.ts +6 -0
- package/src/agent/openAILLM.ts +3 -8
- package/src/agent/openAILLMStreaming.ts +60 -14
- package/src/chat/client/chatClient.ts +262 -286
- package/src/chat/client/constants.ts +0 -2
- package/src/chat/client/sessionClient.ts +82 -20
- package/src/chat/client/sessionFiles.ts +142 -0
- package/src/chat/data/apiKeyManager.ts +55 -7
- package/src/chat/data/dataModels.ts +17 -7
- package/src/chat/data/database.ts +107 -92
- package/src/chat/data/dbSessionFileModels.ts +91 -0
- package/src/chat/data/dbSessionFiles.ts +99 -0
- package/src/chat/data/dbSessionMessages.ts +68 -0
- package/src/chat/data/mimeTypes.ts +58 -0
- package/src/chat/protocol/messages.ts +136 -25
- package/src/chat/server/chatContextManager.ts +42 -24
- package/src/chat/server/connectionManager.test.ts +2 -22
- package/src/chat/server/connectionManager.ts +18 -53
- package/src/chat/server/conversation.ts +106 -59
- package/src/chat/server/imageGeneratorTools.ts +138 -0
- package/src/chat/server/openSession.ts +606 -325
- package/src/chat/server/openSessionMessageSender.ts +4 -0
- package/src/chat/server/server.ts +5 -11
- package/src/chat/server/sessionFileManager.ts +223 -63
- package/src/chat/server/sessionRegistry.ts +317 -52
- package/src/chat/server/test-utils/mockFactories.ts +13 -13
- package/src/chat/server/tools.ts +43 -8
- package/src/chat/utils/agentSessionMap.ts +2 -2
- package/src/chat/utils/approvalManager.ts +153 -81
- package/src/chat/utils/multiAsyncQueue.ts +11 -1
- package/src/chat/{client/responseHandler.ts → utils/responseAwaiter.ts} +73 -23
- package/src/test/agent.test.ts +152 -75
- package/src/test/approvalManager.test.ts +108 -40
- package/src/test/chatContextManager.test.ts +26 -22
- package/src/test/clientServerConnection.test.ts +3 -3
- package/src/test/compressingContextManager.test.ts +1 -1
- package/src/test/context.test.ts +2 -1
- package/src/test/conversation.test.ts +1 -1
- package/src/test/db.test.ts +41 -83
- package/src/test/dbSessionFiles.test.ts +258 -0
- package/src/test/dbSessionMessages.test.ts +85 -0
- package/src/test/dbTestTools.ts +9 -5
- package/src/test/imageLoad.test.ts +2 -2
- package/src/test/mcpServerManager.test.ts +3 -1
- package/src/test/multiAsyncQueue.test.ts +58 -0
- package/src/test/responseAwaiter.test.ts +103 -0
- package/src/test/testTools.ts +15 -1
- package/src/tool/agentChat.ts +36 -8
- package/src/tool/agentMain.ts +7 -7
- package/src/tool/chatMain.ts +128 -7
- package/src/tool/commandPrompt.ts +10 -5
- package/src/tool/files.ts +30 -13
- package/src/tool/options.ts +1 -1
- package/test_data/dummyllm_script_image_gen.json +19 -0
- package/test_data/dummyllm_script_invoke_image_gen_tool.json +30 -0
- package/test_data/image_gen_test_profile.json +5 -0
- package/dist/agent/src/test/responseHandler.test.js +0 -61
- package/src/test/responseHandler.test.ts +0 -78
|
@@ -128,21 +128,18 @@ export function createMockUserConnectionManager(): {
|
|
|
128
128
|
mock: IUserConnectionManager<ServerToClient>;
|
|
129
129
|
spies: {
|
|
130
130
|
sendToUsers: ReturnType<typeof vi.fn>;
|
|
131
|
-
getLiveUserApiKey: ReturnType<typeof vi.fn>;
|
|
132
131
|
};
|
|
133
132
|
} {
|
|
134
133
|
const spies = {
|
|
135
134
|
sendToUsers: vi.fn(),
|
|
136
135
|
sendToConnection: vi.fn(),
|
|
137
136
|
sendServerError: vi.fn(),
|
|
138
|
-
getLiveUserApiKey: vi.fn(),
|
|
139
137
|
};
|
|
140
138
|
|
|
141
139
|
const mock = {
|
|
142
140
|
sendToUsers: spies.sendToUsers,
|
|
143
141
|
sendToConnection: spies.sendToConnection,
|
|
144
142
|
sendServerError: spies.sendServerError,
|
|
145
|
-
getLiveUserApiKey: spies.getLiveUserApiKey,
|
|
146
143
|
} as IUserConnectionManager<ServerToClient>;
|
|
147
144
|
|
|
148
145
|
return { mock, spies };
|
|
@@ -154,16 +151,25 @@ export function createMockUserConnectionManager(): {
|
|
|
154
151
|
export function createMockSessionRegistry(): {
|
|
155
152
|
mock: IMessageProcessor<ClientToServer>;
|
|
156
153
|
spies: {
|
|
154
|
+
authenticate: ReturnType<typeof vi.fn>;
|
|
157
155
|
processMessage: ReturnType<typeof vi.fn>;
|
|
158
156
|
handleUserDisconnect: ReturnType<typeof vi.fn>;
|
|
159
157
|
};
|
|
160
158
|
} {
|
|
161
159
|
const spies = {
|
|
160
|
+
authenticate: vi.fn().mockImplementation((apiKey) => {
|
|
161
|
+
if (apiKey === "valid-api-key")
|
|
162
|
+
return Promise.resolve(MOCK_USERS.owner.uuid);
|
|
163
|
+
if (apiKey === "participant-api-key")
|
|
164
|
+
return Promise.resolve(MOCK_USERS.participant.uuid);
|
|
165
|
+
return Promise.resolve(null);
|
|
166
|
+
}),
|
|
162
167
|
processMessage: vi.fn(),
|
|
163
168
|
handleUserDisconnect: vi.fn(),
|
|
164
169
|
};
|
|
165
170
|
|
|
166
171
|
const mock = {
|
|
172
|
+
authenticate: spies.authenticate,
|
|
167
173
|
processMessage: spies.processMessage,
|
|
168
174
|
handleUserDisconnect: spies.handleUserDisconnect,
|
|
169
175
|
} as IMessageProcessor<ClientToServer>;
|
|
@@ -267,6 +273,7 @@ export function createMockSessionList(): Array<SessionData> {
|
|
|
267
273
|
workspace: undefined,
|
|
268
274
|
updated_at: MOCK_SESSIONS.active.updated_at || "",
|
|
269
275
|
user_uuid: MOCK_SESSIONS.active.user_uuid,
|
|
276
|
+
agent_paused: false,
|
|
270
277
|
},
|
|
271
278
|
{
|
|
272
279
|
session_uuid: MOCK_SESSIONS.secondary.uuid,
|
|
@@ -276,6 +283,7 @@ export function createMockSessionList(): Array<SessionData> {
|
|
|
276
283
|
workspace: undefined,
|
|
277
284
|
updated_at: MOCK_SESSIONS.secondary.updated_at || "",
|
|
278
285
|
user_uuid: MOCK_SESSIONS.secondary.user_uuid,
|
|
286
|
+
agent_paused: false,
|
|
279
287
|
},
|
|
280
288
|
];
|
|
281
289
|
}
|
|
@@ -409,14 +417,6 @@ export function setupStandardMockBehaviors(mocks: {
|
|
|
409
417
|
}
|
|
410
418
|
|
|
411
419
|
// Setup user connection manager mocks
|
|
412
|
-
if (mocks.userConnectionManager) {
|
|
413
|
-
|
|
414
|
-
(userId: string) => {
|
|
415
|
-
if (userId === MOCK_USERS.owner.uuid) return "valid-api-key";
|
|
416
|
-
if (userId === MOCK_USERS.participant.uuid)
|
|
417
|
-
return "participant-api-key";
|
|
418
|
-
return undefined;
|
|
419
|
-
}
|
|
420
|
-
);
|
|
421
|
-
}
|
|
420
|
+
// if (mocks.userConnectionManager) {
|
|
421
|
+
// }
|
|
422
422
|
}
|
package/src/chat/server/tools.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { IPlatform } from "../../agent/iplatform";
|
|
|
10
10
|
import { htmlToText } from "../utils/htmlToText";
|
|
11
11
|
import { getLogger } from "@xalia/xmcp/sdk";
|
|
12
12
|
import { webSearch } from "../utils/search";
|
|
13
|
+
import { ChatSessionFileManager, fileManagerTool } from "./sessionFileManager";
|
|
14
|
+
import { genImageFileTool } from "./imageGeneratorTools";
|
|
13
15
|
|
|
14
16
|
const logger = getLogger();
|
|
15
17
|
|
|
@@ -17,10 +19,19 @@ const logger = getLogger();
|
|
|
17
19
|
* Returns a function which parses an `args` struct and attempts to extract
|
|
18
20
|
* multiple string parameters with the given names. e.g.
|
|
19
21
|
*
|
|
20
|
-
* const parseFn = makeParseArgsFn(
|
|
22
|
+
* const parseFn = makeParseArgsFn(
|
|
23
|
+
* ["arg0", "arg1"] as const,
|
|
24
|
+
* ["opt0"] as const)
|
|
21
25
|
*
|
|
22
|
-
* creates
|
|
23
|
-
*
|
|
26
|
+
* creates
|
|
27
|
+
*
|
|
28
|
+
* parseFn: (args: unknown) => {
|
|
29
|
+
* arg0: string,
|
|
30
|
+
* arg1: string,
|
|
31
|
+
* opt0: string|undefined
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* which can be used to parse tool arguments.
|
|
24
35
|
*
|
|
25
36
|
* NOTE, the complex type parameters ensures that the name list is a
|
|
26
37
|
* compile-time value, which in turn ensures that the return value of this
|
|
@@ -28,19 +39,36 @@ const logger = getLogger();
|
|
|
28
39
|
*/
|
|
29
40
|
export function makeParseArgsFn<
|
|
30
41
|
T extends readonly string[] & (string extends T[number] ? never : unknown),
|
|
31
|
-
|
|
42
|
+
U extends readonly string[] & (string extends U[number] ? never : unknown),
|
|
43
|
+
>(
|
|
44
|
+
names: T,
|
|
45
|
+
optNames?: U
|
|
46
|
+
): (
|
|
47
|
+
args: unknown
|
|
48
|
+
) => { [K in T[number]]: string } & { [K in U[number]]: string | undefined } {
|
|
32
49
|
return (args: unknown) => {
|
|
33
|
-
if (typeof args !== "object") {
|
|
50
|
+
if (!args || typeof args !== "object") {
|
|
34
51
|
throw new Error(`invalid args: ${typeof args}`);
|
|
35
52
|
}
|
|
36
|
-
const argsObj = args as Record<string, string>;
|
|
53
|
+
const argsObj = args as Record<string, string | undefined>;
|
|
37
54
|
for (const name of names) {
|
|
38
55
|
const val = argsObj[name];
|
|
39
56
|
if (typeof val !== "string") {
|
|
40
57
|
throw new Error(`invalid expr args.${name}: ${typeof val}`);
|
|
41
58
|
}
|
|
42
59
|
}
|
|
43
|
-
|
|
60
|
+
if (optNames) {
|
|
61
|
+
for (const name of optNames) {
|
|
62
|
+
const val = argsObj[name];
|
|
63
|
+
if (typeof val !== "undefined" && typeof val !== "string") {
|
|
64
|
+
throw new Error(`invalid expr args.${name}: ${typeof val}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return argsObj as { [K in T[number]]: string } & {
|
|
70
|
+
[K in U[number]]: string | undefined;
|
|
71
|
+
};
|
|
44
72
|
};
|
|
45
73
|
}
|
|
46
74
|
|
|
@@ -255,11 +283,18 @@ export function openURLTool(): IAgentToolProvider {
|
|
|
255
283
|
export async function addDefaultChatTools(
|
|
256
284
|
agent: Agent,
|
|
257
285
|
timezone: string,
|
|
258
|
-
platform: IPlatform
|
|
286
|
+
platform: IPlatform,
|
|
287
|
+
fileManager: ChatSessionFileManager,
|
|
288
|
+
llmUrl: string,
|
|
289
|
+
llmApiKey: string
|
|
259
290
|
): Promise<void> {
|
|
260
291
|
await agent.addAgentToolProvider(datetimeTool(timezone));
|
|
261
292
|
await agent.addAgentToolProvider(calculatorTool);
|
|
262
293
|
await agent.addAgentToolProvider(renderTool(platform));
|
|
263
294
|
await agent.addAgentToolProvider(webSearchTool());
|
|
264
295
|
await agent.addAgentToolProvider(openURLTool());
|
|
296
|
+
await agent.addAgentToolProvider(fileManagerTool(fileManager));
|
|
297
|
+
await agent.addAgentToolProvider(
|
|
298
|
+
await genImageFileTool(llmUrl, llmApiKey, fileManager)
|
|
299
|
+
);
|
|
265
300
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SavedAgentProfile, getLogger } from "@xalia/xmcp/sdk";
|
|
2
|
-
import {
|
|
2
|
+
import { AgentSessionData, SessionDescriptor } from "../data/dataModels";
|
|
3
3
|
|
|
4
4
|
const logger = getLogger();
|
|
5
5
|
|
|
@@ -23,7 +23,7 @@ export function emptyAgentProfile(agentUuid: string): SavedAgentProfile {
|
|
|
23
23
|
|
|
24
24
|
// build agentSessionMap from sessions and agents
|
|
25
25
|
export function buildAgentSessionMap(
|
|
26
|
-
sessions: Map<string,
|
|
26
|
+
sessions: Map<string, SessionDescriptor>,
|
|
27
27
|
agents: Map<string, SavedAgentProfile>
|
|
28
28
|
): Map<string, AgentSessionData> {
|
|
29
29
|
const agentSessionMap: Map<string, AgentSessionData> = new Map();
|
|
@@ -1,108 +1,180 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AgentPreferences,
|
|
3
|
+
getLogger,
|
|
4
|
+
prefsGetAutoApprove,
|
|
5
|
+
prefsSetAutoApprove,
|
|
6
|
+
} from "@xalia/xmcp/sdk";
|
|
7
|
+
import { ResponseAwaiter } from "./responseAwaiter";
|
|
8
|
+
import {
|
|
9
|
+
ClientToolCallApprovalResult,
|
|
10
|
+
ServerToClient,
|
|
11
|
+
ServerToolAutoApprovalSet,
|
|
12
|
+
} from "../protocol/messages";
|
|
13
|
+
import { Database } from "../data/database";
|
|
14
|
+
import { ChatCompletionMessageToolCall } from "../../agent/llm";
|
|
15
|
+
import { ISessionMessageSender } from "../server/openSessionMessageSender";
|
|
2
16
|
|
|
3
17
|
const logger = getLogger();
|
|
4
18
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Thrown in the resultP promise when an approval times out.
|
|
12
|
-
*/
|
|
13
|
-
export class ApprovalTimeout extends Error {
|
|
14
|
-
constructor(message: string) {
|
|
15
|
-
super(message);
|
|
16
|
-
this.name = "ApprovalTimeout";
|
|
17
|
-
}
|
|
19
|
+
export interface IAgentPreferencesWriter {
|
|
20
|
+
updatePreferences(
|
|
21
|
+
agentProfileUUID: string,
|
|
22
|
+
settings: AgentPreferences
|
|
23
|
+
): Promise<void>;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
export class
|
|
21
|
-
constructor(
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
export class DbAgentPreferencesWriter implements IAgentPreferencesWriter {
|
|
27
|
+
constructor(private db: Database) {}
|
|
28
|
+
|
|
29
|
+
updatePreferences(
|
|
30
|
+
agentProfileUUID: string,
|
|
31
|
+
preferences: AgentPreferences
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
return this.db.updateAgentProfilePreferences(agentProfileUUID, preferences);
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
type ApprovalData = {
|
|
28
|
-
resolve: (result: ApprovalResult) => void;
|
|
29
|
-
error: (e: Error) => void;
|
|
30
|
-
timeoutId?: NodeJS.Timeout;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
37
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* When an approval or rejection (or timeout) is received, the promise is
|
|
39
|
-
* resolved.
|
|
38
|
+
* Handles an in-memory caching / updating of the auto-approve settings for
|
|
39
|
+
* tool calls. Also handles querying the client for approval and waiting for
|
|
40
|
+
* responses.
|
|
40
41
|
*/
|
|
41
|
-
export class
|
|
42
|
-
private
|
|
43
|
-
private
|
|
42
|
+
export class ToolApprovalManager {
|
|
43
|
+
private sessionUUID: string;
|
|
44
|
+
private agentProfileUUID: string;
|
|
45
|
+
private agentProfilePreferences: AgentPreferences;
|
|
46
|
+
private sender: ISessionMessageSender<ServerToClient>;
|
|
47
|
+
private writer: IAgentPreferencesWriter;
|
|
48
|
+
private responseAwaiter: ResponseAwaiter<ClientToolCallApprovalResult>;
|
|
44
49
|
|
|
45
|
-
constructor(
|
|
46
|
-
|
|
50
|
+
constructor(
|
|
51
|
+
sessionUUID: string,
|
|
52
|
+
agentProfileUUID: string,
|
|
53
|
+
agentProfilePreferences: AgentPreferences,
|
|
54
|
+
sender: ISessionMessageSender<ServerToClient>,
|
|
55
|
+
writer: IAgentPreferencesWriter,
|
|
56
|
+
timeoutMs?: number
|
|
57
|
+
) {
|
|
58
|
+
this.sessionUUID = sessionUUID;
|
|
59
|
+
this.agentProfileUUID = agentProfileUUID;
|
|
60
|
+
this.agentProfilePreferences = agentProfilePreferences;
|
|
61
|
+
this.sender = sender;
|
|
62
|
+
this.writer = writer;
|
|
63
|
+
this.responseAwaiter = ResponseAwaiter.init(
|
|
64
|
+
undefined,
|
|
65
|
+
(msg) => msg.id,
|
|
66
|
+
timeoutMs
|
|
67
|
+
);
|
|
47
68
|
}
|
|
48
69
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Check for auto-approval, or query the client. Handle approval response
|
|
72
|
+
* (or timeout) and update auto-approval settings.
|
|
73
|
+
*
|
|
74
|
+
* The returned `requested` value indicates whether approval was requested.
|
|
75
|
+
*/
|
|
76
|
+
public async getApproval(
|
|
77
|
+
serverName: string,
|
|
78
|
+
tool: string,
|
|
79
|
+
toolCall: ChatCompletionMessageToolCall
|
|
80
|
+
): Promise<{ approved: boolean; requested: boolean }> {
|
|
81
|
+
const autoApproved = prefsGetAutoApprove(
|
|
82
|
+
this.agentProfilePreferences,
|
|
83
|
+
serverName,
|
|
84
|
+
tool
|
|
85
|
+
);
|
|
86
|
+
if (autoApproved) {
|
|
87
|
+
return { approved: true, requested: false };
|
|
56
88
|
}
|
|
57
|
-
}
|
|
58
89
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
// Query the owner for approval
|
|
91
|
+
|
|
92
|
+
const id = this.generateUniqueId(toolCall.function.name);
|
|
93
|
+
try {
|
|
94
|
+
const approvalP = this.responseAwaiter.waitForResponse(id);
|
|
95
|
+
|
|
96
|
+
this.sender.broadcast({
|
|
97
|
+
type: "approve_tool_call",
|
|
98
|
+
id,
|
|
99
|
+
tool_call: toolCall,
|
|
100
|
+
session_id: this.sessionUUID,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
logger.debug(`[ApprovalManager.getApproval] awaiting approval ${id}`);
|
|
104
|
+
const approval = await approvalP;
|
|
105
|
+
logger.debug(
|
|
106
|
+
`[ApprovalManager.getApproval] approval ${JSON.stringify(approval)}`
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Handle any auto-approve update, informing other clients.
|
|
110
|
+
|
|
111
|
+
if (approval.auto_approve) {
|
|
112
|
+
logger.debug("[ApprovalManager.getApproval] updated preferences");
|
|
113
|
+
const autoApprovalMsg = await this.setAutoApprove(
|
|
114
|
+
serverName,
|
|
115
|
+
tool,
|
|
116
|
+
true
|
|
117
|
+
);
|
|
118
|
+
if (autoApprovalMsg) {
|
|
119
|
+
this.sender.broadcast(autoApprovalMsg);
|
|
120
|
+
}
|
|
74
121
|
}
|
|
75
122
|
|
|
76
|
-
|
|
77
|
-
|
|
123
|
+
// Broadcast the result of the approval
|
|
124
|
+
|
|
125
|
+
this.sender.broadcast({
|
|
126
|
+
type: "tool_call_approval_result",
|
|
127
|
+
id: approval.id,
|
|
128
|
+
result: approval.result,
|
|
129
|
+
session_id: this.sessionUUID,
|
|
130
|
+
});
|
|
78
131
|
|
|
79
|
-
|
|
80
|
-
|
|
132
|
+
return { approved: approval.result, requested: true };
|
|
133
|
+
} catch (e) {
|
|
134
|
+
logger.debug(
|
|
135
|
+
`[OpenSession.onToolCall] error waiting for approval ${id}: ` +
|
|
136
|
+
String(e)
|
|
137
|
+
);
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
81
140
|
}
|
|
82
141
|
|
|
83
142
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
143
|
+
* Handle a request to set auto-approval for a given tool. If there was a
|
|
144
|
+
* change, return the message to be broadcast.
|
|
86
145
|
*/
|
|
87
|
-
public
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
):
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
146
|
+
public async setAutoApprove(
|
|
147
|
+
serverName: string,
|
|
148
|
+
tool: string,
|
|
149
|
+
autoApprove: boolean
|
|
150
|
+
): Promise<ServerToolAutoApprovalSet | undefined> {
|
|
151
|
+
if (
|
|
152
|
+
prefsSetAutoApprove(
|
|
153
|
+
this.agentProfilePreferences,
|
|
154
|
+
serverName,
|
|
155
|
+
tool,
|
|
156
|
+
autoApprove
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
await this.writer.updatePreferences(
|
|
160
|
+
this.agentProfileUUID,
|
|
161
|
+
this.agentProfilePreferences
|
|
162
|
+
);
|
|
163
|
+
return {
|
|
164
|
+
type: "tool_auto_approval_set",
|
|
165
|
+
server_name: serverName,
|
|
166
|
+
tool,
|
|
167
|
+
auto_approve: autoApprove,
|
|
168
|
+
session_id: this.sessionUUID,
|
|
169
|
+
};
|
|
102
170
|
}
|
|
171
|
+
}
|
|
103
172
|
|
|
104
|
-
|
|
105
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Forward all approval result messages here.
|
|
175
|
+
*/
|
|
176
|
+
public onApprovalResult(msg: ClientToolCallApprovalResult): void {
|
|
177
|
+
this.responseAwaiter.onMessage(msg);
|
|
106
178
|
}
|
|
107
179
|
|
|
108
180
|
private generateUniqueId(tag: string): string {
|
|
@@ -3,6 +3,7 @@ export class MultiAsyncQueue<T> {
|
|
|
3
3
|
private process: (queueEntry: T[]) => Promise<void>;
|
|
4
4
|
private maxBacklog: number;
|
|
5
5
|
private running: boolean = false;
|
|
6
|
+
private paused: boolean = false;
|
|
6
7
|
|
|
7
8
|
constructor(
|
|
8
9
|
process: (queueEntry: T[]) => Promise<void>,
|
|
@@ -30,8 +31,17 @@ export class MultiAsyncQueue<T> {
|
|
|
30
31
|
return true;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
public pause() {
|
|
35
|
+
this.paused = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public unpause() {
|
|
39
|
+
this.paused = false;
|
|
40
|
+
setTimeout(() => void this.tryNext(), 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
private async tryNext() {
|
|
34
|
-
if (this.running) {
|
|
44
|
+
if (this.running || this.paused) {
|
|
35
45
|
return;
|
|
36
46
|
}
|
|
37
47
|
|
|
@@ -2,12 +2,13 @@ import { getLogger } from "@xalia/xmcp/sdk";
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_TIMEOUT_MS: number = 10000;
|
|
4
4
|
|
|
5
|
-
type
|
|
6
|
-
type ServerMsg = { type: string; client_message_id?: string };
|
|
5
|
+
type ServerMsg = { type: string };
|
|
7
6
|
// Messages of type S which also have a message field. The message
|
|
8
7
|
// representing an error should be of this type.
|
|
9
8
|
type ServerErr<S> = Extract<S, { message: string }>;
|
|
10
9
|
|
|
10
|
+
type IdExtractor<S extends ServerMsg> = (msg: S) => string | undefined;
|
|
11
|
+
|
|
11
12
|
type WaitingEntry<ServerMessageT> = {
|
|
12
13
|
resolve: (s: ServerMessageT) => void;
|
|
13
14
|
error: (e: Error) => void;
|
|
@@ -16,13 +17,19 @@ type WaitingEntry<ServerMessageT> = {
|
|
|
16
17
|
|
|
17
18
|
const logger = getLogger();
|
|
18
19
|
|
|
20
|
+
function defaultImmediate<T>(x: T): T {
|
|
21
|
+
return x;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
/**
|
|
20
25
|
*
|
|
21
26
|
* Handles response messages and timeouts for client request messages.
|
|
22
27
|
*
|
|
23
|
-
* Create a
|
|
28
|
+
* Create a ResponseAwaiter for a specific class of queries
|
|
24
29
|
*
|
|
25
|
-
* this.responseHandler = new
|
|
30
|
+
* this.responseHandler = new ResponseAwaiter<SomeRequest, SomeResponse>(
|
|
31
|
+
* (msg: SomeResponse) => msg.client_message_id;
|
|
32
|
+
* )
|
|
26
33
|
*
|
|
27
34
|
* Use as follows:
|
|
28
35
|
*
|
|
@@ -36,14 +43,15 @@ const logger = getLogger();
|
|
|
36
43
|
*
|
|
37
44
|
* // Get a Promise representing the response to this message, which
|
|
38
45
|
* // we can await.
|
|
39
|
-
* const response =
|
|
46
|
+
* const response =
|
|
47
|
+
* await this.responseHandler.waitForResponse(client_message_id);
|
|
40
48
|
*
|
|
41
49
|
* // Perform any processing on the response
|
|
42
50
|
* return response.response_data;
|
|
43
51
|
* }
|
|
44
52
|
* ```
|
|
45
53
|
*
|
|
46
|
-
*
|
|
54
|
+
* ResponseAwaiter must be informed of relevant messages in order to resolve
|
|
47
55
|
* responses:
|
|
48
56
|
*
|
|
49
57
|
* ```
|
|
@@ -57,46 +65,82 @@ const logger = getLogger();
|
|
|
57
65
|
* }
|
|
58
66
|
* }
|
|
59
67
|
* ```
|
|
68
|
+
*
|
|
69
|
+
* If some actions need to happen immediately on receipt of the message,
|
|
70
|
+
* instead of when the `Promise.resolve` function is resolved, add an
|
|
71
|
+
* `immediateAction` callback, which can optionally transfrom `ServerMsgT`
|
|
72
|
+
* into `FinalResponseT` to be passed back by `waitForResponse`.
|
|
60
73
|
*/
|
|
61
|
-
export class
|
|
62
|
-
ClientMsgT extends ClientMsg,
|
|
74
|
+
export class ResponseAwaiter<
|
|
63
75
|
ServerMsgT extends ServerMsg,
|
|
76
|
+
FinalResponseT = ServerMsgT,
|
|
64
77
|
> {
|
|
65
|
-
readonly waiting: Map<string, WaitingEntry<
|
|
78
|
+
readonly waiting: Map<string, WaitingEntry<FinalResponseT>>;
|
|
79
|
+
readonly idExtractor: IdExtractor<ServerMsgT>;
|
|
80
|
+
readonly immediateAction: (x: ServerMsgT) => FinalResponseT;
|
|
66
81
|
readonly timeoutMS: number;
|
|
67
82
|
readonly errorType: ServerErr<ServerMsgT>["type"] | undefined;
|
|
68
83
|
|
|
69
84
|
/**
|
|
70
85
|
* errorType: the type field of the message which represents an error.
|
|
71
86
|
*/
|
|
72
|
-
constructor(
|
|
87
|
+
private constructor(
|
|
73
88
|
// value of the `type` field of an error message type,
|
|
74
89
|
// e.g. "session_error". Compiler should ensure that the `ServerMsgT` with
|
|
75
90
|
// this `type` field at least has a `message` field.
|
|
76
91
|
errorType: undefined | ServerErr<ServerMsgT>["type"],
|
|
92
|
+
idExtractor: IdExtractor<ServerMsgT>,
|
|
93
|
+
immediateAction: (x: ServerMsgT) => FinalResponseT,
|
|
77
94
|
timeoutMS: number = DEFAULT_TIMEOUT_MS
|
|
78
95
|
) {
|
|
79
96
|
this.waiting = new Map();
|
|
97
|
+
this.idExtractor = idExtractor;
|
|
98
|
+
this.immediateAction = immediateAction;
|
|
80
99
|
this.timeoutMS = timeoutMS;
|
|
81
100
|
this.errorType = errorType;
|
|
82
101
|
}
|
|
83
102
|
|
|
103
|
+
static init<S extends ServerMsg>(
|
|
104
|
+
errorType: undefined | ServerErr<S>["type"],
|
|
105
|
+
idExtractor: IdExtractor<S>,
|
|
106
|
+
timeoutMS: number = DEFAULT_TIMEOUT_MS
|
|
107
|
+
): ResponseAwaiter<S, S> {
|
|
108
|
+
return new ResponseAwaiter(
|
|
109
|
+
errorType,
|
|
110
|
+
idExtractor,
|
|
111
|
+
defaultImmediate,
|
|
112
|
+
timeoutMS
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static initWithImmediate<S extends ServerMsg, F>(
|
|
117
|
+
errorType: undefined | ServerErr<S>["type"],
|
|
118
|
+
idExtractor: IdExtractor<S>,
|
|
119
|
+
immediateAction: (x: S) => F,
|
|
120
|
+
timeoutMS: number = DEFAULT_TIMEOUT_MS
|
|
121
|
+
): ResponseAwaiter<S, F> {
|
|
122
|
+
return new ResponseAwaiter(
|
|
123
|
+
errorType,
|
|
124
|
+
idExtractor,
|
|
125
|
+
immediateAction,
|
|
126
|
+
timeoutMS
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
84
130
|
/**
|
|
85
131
|
* Given a request message, return a promise representing the corresponding
|
|
86
132
|
* response message from the server.
|
|
87
133
|
*/
|
|
88
|
-
waitForResponse(
|
|
89
|
-
return new Promise<
|
|
90
|
-
const msgId = msg.client_message_id;
|
|
91
|
-
|
|
134
|
+
waitForResponse(msgId: string): Promise<FinalResponseT> {
|
|
135
|
+
return new Promise<FinalResponseT>((resolve, error) => {
|
|
92
136
|
const timeoutId = setTimeout(() => {
|
|
93
137
|
const waiting = this.waiting.get(msgId);
|
|
94
138
|
if (!waiting) {
|
|
95
|
-
logger.warn(`[
|
|
139
|
+
logger.warn(`[ResponseAwaiter] timeout for ${msgId} with no entry`);
|
|
96
140
|
return;
|
|
97
141
|
}
|
|
98
142
|
this.waiting.delete(msgId);
|
|
99
|
-
logger.warn(`[
|
|
143
|
+
logger.warn(`[ResponseAwaiter] timeout for client_msg_id ${msgId}`);
|
|
100
144
|
error(new Error(`timeout for client_msg_id ${msgId}`));
|
|
101
145
|
}, this.timeoutMS);
|
|
102
146
|
|
|
@@ -104,20 +148,25 @@ export class ResponseHandler<
|
|
|
104
148
|
});
|
|
105
149
|
}
|
|
106
150
|
|
|
151
|
+
waitingForId(msgId: string): boolean {
|
|
152
|
+
return this.waiting.has(msgId);
|
|
153
|
+
}
|
|
154
|
+
|
|
107
155
|
/**
|
|
108
|
-
* Pass response (and error) messages in here.
|
|
156
|
+
* Pass response (and error) messages in here. Returns `true` if the
|
|
157
|
+
* message was consumed. Otherwise `false`.
|
|
109
158
|
*/
|
|
110
|
-
onMessage(msg: ServerMsgT):
|
|
111
|
-
const msgId = msg
|
|
159
|
+
onMessage(msg: ServerMsgT): boolean {
|
|
160
|
+
const msgId = this.idExtractor(msg);
|
|
112
161
|
if (!msgId) {
|
|
113
|
-
return;
|
|
162
|
+
return false;
|
|
114
163
|
}
|
|
115
164
|
const waiting = this.waiting.get(msgId);
|
|
116
165
|
if (!waiting) {
|
|
117
166
|
logger.warn(
|
|
118
|
-
`[
|
|
167
|
+
`[ResponseAwaiter] resolve for ${msgId} with no entry (timeout?)`
|
|
119
168
|
);
|
|
120
|
-
return;
|
|
169
|
+
return false;
|
|
121
170
|
}
|
|
122
171
|
|
|
123
172
|
clearTimeout(waiting.timeoutId);
|
|
@@ -125,7 +174,8 @@ export class ResponseHandler<
|
|
|
125
174
|
if (this.errorType && msg.type === this.errorType) {
|
|
126
175
|
waiting.error(new Error((msg as ServerErr<ServerMsgT>).message));
|
|
127
176
|
} else {
|
|
128
|
-
waiting.resolve(msg);
|
|
177
|
+
waiting.resolve(this.immediateAction(msg));
|
|
129
178
|
}
|
|
179
|
+
return true;
|
|
130
180
|
}
|
|
131
181
|
}
|