@xalia/agent 0.5.3 → 0.5.5
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 +16 -9
- package/dist/agent/src/agent/agentUtils.js +24 -4
- package/dist/agent/src/agent/mcpServerManager.js +19 -9
- package/dist/agent/src/agent/openAILLM.js +3 -1
- package/dist/agent/src/agent/openAILLMStreaming.js +24 -25
- package/dist/agent/src/agent/repeatLLM.js +43 -0
- package/dist/agent/src/agent/sudoMcpServerManager.js +12 -6
- package/dist/agent/src/chat/client.js +259 -36
- package/dist/agent/src/chat/conversationManager.js +243 -24
- package/dist/agent/src/chat/db.js +24 -1
- package/dist/agent/src/chat/frontendClient.js +74 -0
- package/dist/agent/src/chat/server.js +3 -3
- package/dist/agent/src/test/db.test.js +25 -2
- package/dist/agent/src/test/openaiStreaming.test.js +133 -0
- package/dist/agent/src/test/prompt.test.js +2 -2
- package/dist/agent/src/test/sudoMcpServerManager.test.js +1 -1
- package/dist/agent/src/tool/agentChat.js +7 -197
- package/dist/agent/src/tool/chatMain.js +18 -23
- package/dist/agent/src/tool/commandPrompt.js +248 -0
- package/dist/agent/src/tool/prompt.js +27 -31
- package/package.json +1 -1
- package/scripts/test_chat +17 -1
- package/src/agent/agent.ts +34 -11
- package/src/agent/agentUtils.ts +52 -3
- package/src/agent/mcpServerManager.ts +43 -13
- package/src/agent/openAILLM.ts +3 -1
- package/src/agent/openAILLMStreaming.ts +28 -27
- package/src/agent/repeatLLM.ts +51 -0
- package/src/agent/sudoMcpServerManager.ts +41 -12
- package/src/chat/client.ts +353 -40
- package/src/chat/conversationManager.ts +345 -33
- package/src/chat/db.ts +28 -2
- package/src/chat/frontendClient.ts +123 -0
- package/src/chat/messages.ts +146 -2
- package/src/chat/server.ts +3 -3
- package/src/test/db.test.ts +35 -2
- package/src/test/openaiStreaming.test.ts +142 -0
- package/src/test/prompt.test.ts +1 -1
- package/src/test/sudoMcpServerManager.test.ts +1 -1
- package/src/tool/agentChat.ts +13 -211
- package/src/tool/chatMain.ts +28 -43
- package/src/tool/commandPrompt.ts +252 -0
- package/src/tool/prompt.ts +33 -32
package/src/tool/chatMain.ts
CHANGED
@@ -9,16 +9,20 @@ import {
|
|
9
9
|
} from "cmd-ts";
|
10
10
|
import { stdout } from "process";
|
11
11
|
import * as fs from "fs";
|
12
|
+
import { strict as assert } from "assert";
|
12
13
|
|
13
14
|
import { configuration } from "@xalia/xmcp/tool";
|
14
15
|
import { getLogger } from "@xalia/xmcp/sdk";
|
15
16
|
|
17
|
+
import { IConversation } from "../agent/agent";
|
16
18
|
import { ChatClient } from "../chat/client";
|
17
19
|
import { runServer } from "../chat/server";
|
18
20
|
import { ClientUserMessage, ServerToClient } from "../chat/messages";
|
19
21
|
|
20
|
-
import { Prompt } from "./prompt";
|
22
|
+
import { IPrompt, Prompt, ScriptPrompt } from "./prompt";
|
21
23
|
import * as options from "./options";
|
24
|
+
import { CommandPrompt } from "./commandPrompt";
|
25
|
+
import { NODE_PLATFORM } from "./nodePlatform";
|
22
26
|
|
23
27
|
const logger = getLogger();
|
24
28
|
|
@@ -114,19 +118,23 @@ const client = command({
|
|
114
118
|
return;
|
115
119
|
}
|
116
120
|
|
117
|
-
if (script) {
|
118
|
-
await runScript(host, port, apiKey, session, agentProfile, script);
|
119
|
-
return;
|
120
|
-
}
|
121
|
-
|
122
121
|
const onMessage = getCLIOnMessage();
|
123
|
-
const repl =
|
122
|
+
const repl: IPrompt = (() => {
|
123
|
+
if (script) {
|
124
|
+
const scriptLines = fs.readFileSync(script, "utf8").split("\n");
|
125
|
+
return new ScriptPrompt(scriptLines);
|
126
|
+
}
|
127
|
+
return new Prompt();
|
128
|
+
})();
|
129
|
+
const cmdPrompt = new CommandPrompt(repl);
|
130
|
+
|
124
131
|
const client = await ChatClient.init(
|
125
132
|
host,
|
126
133
|
port,
|
127
134
|
apiKey,
|
128
135
|
onMessage,
|
129
|
-
() =>
|
136
|
+
async () => await cmdPrompt.shutdown(),
|
137
|
+
NODE_PLATFORM,
|
130
138
|
session,
|
131
139
|
agentProfile
|
132
140
|
);
|
@@ -134,13 +142,20 @@ const client = command({
|
|
134
142
|
logger.debug("client created");
|
135
143
|
|
136
144
|
while (true) {
|
137
|
-
const msgText = await
|
138
|
-
|
145
|
+
const [msgText, imageFile] = await cmdPrompt.getNextPrompt(
|
146
|
+
undefined as unknown as IConversation, // TODO: client
|
147
|
+
client.getSudoMcpServerManager()
|
148
|
+
);
|
149
|
+
assert(imageFile === undefined);
|
150
|
+
|
139
151
|
if (msgText === undefined) {
|
140
152
|
logger.debug("exiting...");
|
141
153
|
client.close();
|
142
154
|
return;
|
143
155
|
}
|
156
|
+
|
157
|
+
// TODO: other prompts
|
158
|
+
|
144
159
|
if (msgText.length > 0) {
|
145
160
|
const msg: ClientUserMessage = {
|
146
161
|
type: "msg",
|
@@ -176,43 +191,12 @@ function getCLIOnMessage(): (msg: ServerToClient) => void {
|
|
176
191
|
}
|
177
192
|
break;
|
178
193
|
default:
|
179
|
-
stdout.write(`(${JSON.stringify(msg)}
|
194
|
+
stdout.write(`(unrecognised) ${JSON.stringify(msg)}\n`);
|
180
195
|
break;
|
181
196
|
}
|
182
197
|
};
|
183
198
|
}
|
184
199
|
|
185
|
-
async function runScript(
|
186
|
-
host: string,
|
187
|
-
port: number,
|
188
|
-
apiKey: string,
|
189
|
-
session: string,
|
190
|
-
agentProfile: string | undefined,
|
191
|
-
script: string
|
192
|
-
): Promise<void> {
|
193
|
-
const client = await ChatClient.init(
|
194
|
-
host,
|
195
|
-
port,
|
196
|
-
apiKey,
|
197
|
-
getCLIOnMessage(),
|
198
|
-
() => {},
|
199
|
-
session,
|
200
|
-
agentProfile
|
201
|
-
);
|
202
|
-
const scriptData = fs.readFileSync(script, "utf8");
|
203
|
-
const lines = scriptData.split("\n");
|
204
|
-
for (const line of lines) {
|
205
|
-
await new Promise((r) => setTimeout(r, 1000));
|
206
|
-
client.sendMessage({ type: "msg", message: line });
|
207
|
-
}
|
208
|
-
|
209
|
-
// 2 second wait period
|
210
|
-
await new Promise((r) => setTimeout(r, 2000));
|
211
|
-
|
212
|
-
client.close();
|
213
|
-
return;
|
214
|
-
}
|
215
|
-
|
216
200
|
async function runClientTest(
|
217
201
|
host: string,
|
218
202
|
port: number,
|
@@ -224,7 +208,8 @@ async function runClientTest(
|
|
224
208
|
port,
|
225
209
|
apiKey,
|
226
210
|
getCLIOnMessage(),
|
227
|
-
() => {},
|
211
|
+
async () => {},
|
212
|
+
NODE_PLATFORM,
|
228
213
|
session
|
229
214
|
);
|
230
215
|
|
@@ -0,0 +1,252 @@
|
|
1
|
+
import * as fs from "fs";
|
2
|
+
import chalk from "chalk";
|
3
|
+
import OpenAI from "openai";
|
4
|
+
|
5
|
+
import { InvalidConfiguration } from "@xalia/xmcp/sdk";
|
6
|
+
|
7
|
+
import { IConversation } from "../agent/agent";
|
8
|
+
import { displayToolCall } from "../agent/tools";
|
9
|
+
import { ISkillManager } from "../agent/sudoMcpServerManager";
|
10
|
+
import { IMcpServerManager } from "../agent/mcpServerManager";
|
11
|
+
|
12
|
+
import { IPrompt } from "./prompt";
|
13
|
+
|
14
|
+
/**
|
15
|
+
* A prompt parser which can accept commands or messages from an IPrompt.
|
16
|
+
* Commands are sent to an IMcpServerManager, ISkillManager and IConversation.
|
17
|
+
*/
|
18
|
+
export class CommandPrompt {
|
19
|
+
prompt: IPrompt;
|
20
|
+
|
21
|
+
constructor(prompt: IPrompt) {
|
22
|
+
this.prompt = prompt;
|
23
|
+
}
|
24
|
+
|
25
|
+
shutdown(): Promise<void> {
|
26
|
+
return this.prompt.shutdown();
|
27
|
+
}
|
28
|
+
|
29
|
+
async getNextPrompt(
|
30
|
+
agent: IConversation,
|
31
|
+
sudoMcpServerManager: ISkillManager
|
32
|
+
): Promise<[string | undefined, string | undefined]> {
|
33
|
+
while (true) {
|
34
|
+
// Get a line, detecting the EOF signal.
|
35
|
+
const line = await this.prompt.run();
|
36
|
+
if (typeof line === "undefined") {
|
37
|
+
console.log("closing ...");
|
38
|
+
return [undefined, undefined];
|
39
|
+
}
|
40
|
+
if (line.length === 0) {
|
41
|
+
continue;
|
42
|
+
}
|
43
|
+
|
44
|
+
// Extract prompt or commands
|
45
|
+
const { msg, cmds } = parsePrompt(line);
|
46
|
+
|
47
|
+
// If there are no commands, this must be a prompt only
|
48
|
+
if (!cmds) {
|
49
|
+
return [msg, undefined];
|
50
|
+
}
|
51
|
+
|
52
|
+
// There are commands. If it's image, return [prompt, image]. If it's
|
53
|
+
// quit, return [undefined, undefined], otherwise it must be a command.
|
54
|
+
// Execute it and prompt again.
|
55
|
+
switch (cmds[0]) {
|
56
|
+
case "i": // image
|
57
|
+
return [msg, cmds[1]];
|
58
|
+
case "q":
|
59
|
+
case "quit":
|
60
|
+
case "exit":
|
61
|
+
return [undefined, undefined];
|
62
|
+
default:
|
63
|
+
break;
|
64
|
+
}
|
65
|
+
|
66
|
+
try {
|
67
|
+
await this.runCommand(
|
68
|
+
agent,
|
69
|
+
sudoMcpServerManager.getMcpServerManager(),
|
70
|
+
sudoMcpServerManager,
|
71
|
+
cmds
|
72
|
+
);
|
73
|
+
} catch (e) {
|
74
|
+
console.log(`ERROR: ${e}`);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
async runCommand(
|
80
|
+
agent: IConversation,
|
81
|
+
mcpServerManager: IMcpServerManager,
|
82
|
+
sudoMcpServerManager: ISkillManager,
|
83
|
+
cmds: string[]
|
84
|
+
) {
|
85
|
+
switch (cmds[0]) {
|
86
|
+
case "lt":
|
87
|
+
this.listTools(mcpServerManager);
|
88
|
+
break;
|
89
|
+
case "ls":
|
90
|
+
this.listServers(sudoMcpServerManager);
|
91
|
+
break;
|
92
|
+
case "as":
|
93
|
+
await this.addServer(sudoMcpServerManager, cmds[1]);
|
94
|
+
break;
|
95
|
+
case "rs":
|
96
|
+
await mcpServerManager.removeMcpServer(cmds[1]);
|
97
|
+
break;
|
98
|
+
case "e":
|
99
|
+
mcpServerManager.enableTool(cmds[1], cmds[2]);
|
100
|
+
console.log(`Enabled tool ${cmds[2]} for server ${cmds[1]}`);
|
101
|
+
break;
|
102
|
+
case "d":
|
103
|
+
mcpServerManager.disableTool(cmds[1], cmds[2]);
|
104
|
+
console.log(`Disabled tool ${cmds[2]} for server ${cmds[1]}`);
|
105
|
+
break;
|
106
|
+
case "ea":
|
107
|
+
mcpServerManager.enableAllTools(cmds[1]);
|
108
|
+
console.log(`Enabled all tools for server ${cmds[1]}`);
|
109
|
+
break;
|
110
|
+
case "da":
|
111
|
+
mcpServerManager.disableAllTools(cmds[1]);
|
112
|
+
console.log(`Disabled all tools for server ${cmds[1]}`);
|
113
|
+
break;
|
114
|
+
case "wc":
|
115
|
+
this.writeConversation(agent, cmds[1]);
|
116
|
+
break;
|
117
|
+
case "wa":
|
118
|
+
this.writeAgentProfile(agent, cmds[1]);
|
119
|
+
break;
|
120
|
+
case "h":
|
121
|
+
case "help":
|
122
|
+
case "?":
|
123
|
+
this.helpMenu();
|
124
|
+
break;
|
125
|
+
default:
|
126
|
+
console.log(`error: Unknown command ${cmds[0]}`);
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
helpMenu() {
|
131
|
+
const write = console.log;
|
132
|
+
write(`Tool management commands:`);
|
133
|
+
write(` ${chalk.yellow("/lt")} List tools: `);
|
134
|
+
write(` ${chalk.yellow("/ls")} List servers`);
|
135
|
+
write(` ${chalk.yellow("/as <server>")} Add server`);
|
136
|
+
write(` ${chalk.yellow("/rs <server>")} Remove server`);
|
137
|
+
write(` ${chalk.yellow("/e <server> <tool>")} Enable tool`);
|
138
|
+
write(` ${chalk.yellow("/d <server> <tool>")} Disable tool`);
|
139
|
+
write(` ${chalk.yellow("/ea <server>")} Enable all tools`);
|
140
|
+
write(` ${chalk.yellow("/da <server>")} Disable all tools`);
|
141
|
+
write(` ${chalk.yellow("/wc <file-name>")} Write conversation file`);
|
142
|
+
write(` ${chalk.yellow("/wa <file-name>")} Write agent profile file`);
|
143
|
+
write(` ${chalk.yellow("/q")} Quit`);
|
144
|
+
}
|
145
|
+
|
146
|
+
listTools(mcpServerManager: IMcpServerManager) {
|
147
|
+
console.log("Mcp servers and tools (* - enabled):");
|
148
|
+
|
149
|
+
const serverNames = mcpServerManager.getMcpServerNames();
|
150
|
+
for (const serverName of serverNames) {
|
151
|
+
const server = mcpServerManager.getMcpServer(serverName);
|
152
|
+
console.log(` ${chalk.green(serverName)}`);
|
153
|
+
const tools = server.getTools();
|
154
|
+
const enabled = server.getEnabledTools();
|
155
|
+
for (const tool of tools) {
|
156
|
+
const isEnabled = enabled[tool.name] ? "*" : " ";
|
157
|
+
console.log(` [${isEnabled}] ${tool.name}`);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
listServers(sudoMcpServerManager: ISkillManager) {
|
163
|
+
console.log(`Available MCP Servers:`);
|
164
|
+
|
165
|
+
const serverBriefs = sudoMcpServerManager.getServerBriefs();
|
166
|
+
serverBriefs.sort((a, b) => a.name.localeCompare(b.name));
|
167
|
+
for (const brief of serverBriefs) {
|
168
|
+
console.log(`- ${chalk.green(brief.name)}: ${brief.description}`);
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
/**
|
173
|
+
* Adds server and enables all tools.
|
174
|
+
*/
|
175
|
+
async addServer(sudoMcpServerManager: ISkillManager, serverName: string) {
|
176
|
+
try {
|
177
|
+
await sudoMcpServerManager.addMcpServer(serverName, true);
|
178
|
+
} catch (e) {
|
179
|
+
if (e instanceof InvalidConfiguration) {
|
180
|
+
throw `${e}. \n
|
181
|
+
Have you installed server ${serverName} using the SudoMCP CLI tool?`;
|
182
|
+
} else {
|
183
|
+
throw e;
|
184
|
+
}
|
185
|
+
}
|
186
|
+
const msm = sudoMcpServerManager.getMcpServerManager();
|
187
|
+
console.log(`Added server: ${serverName} (enabled all tools).`);
|
188
|
+
console.log(`Current tool list:`);
|
189
|
+
this.listTools(msm);
|
190
|
+
}
|
191
|
+
|
192
|
+
writeConversation(agent: IConversation, fileName: string) {
|
193
|
+
const conversation = agent.getConversation();
|
194
|
+
fs.writeFileSync(fileName, JSON.stringify(conversation), {
|
195
|
+
encoding: "utf8",
|
196
|
+
});
|
197
|
+
console.log(`Conversation written to ${fileName}`);
|
198
|
+
}
|
199
|
+
|
200
|
+
writeAgentProfile(agent: IConversation, fileName: string) {
|
201
|
+
const profile = agent.getAgentProfile();
|
202
|
+
fs.writeFileSync(fileName, JSON.stringify(profile));
|
203
|
+
console.log(`AgentProfile written to ${fileName}`);
|
204
|
+
}
|
205
|
+
|
206
|
+
async promptToolCall(
|
207
|
+
toolCall: OpenAI.ChatCompletionMessageToolCall
|
208
|
+
): Promise<boolean> {
|
209
|
+
displayToolCall(toolCall);
|
210
|
+
const response = await this.prompt.run("Approve tool call? (Y/n) ");
|
211
|
+
return response === "y" || response === "yes" || response == "";
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
/**
|
216
|
+
* Support prompts of the form:
|
217
|
+
* - some text (msg: some text, cmds: undefined)
|
218
|
+
* - :i image.png some text (msg: some text, cmds: ["i", "image.png"])
|
219
|
+
* - :i image.png (msg: undefined, cmds: ["i", "image.png"])
|
220
|
+
* - :l (msg: undefined, cmds: ["l"])
|
221
|
+
* - :e toolName .. (msg: undefined, cmds: ["e", "toolName", ...])
|
222
|
+
* - :ea toolName .. (msg: undefined, cmds: ["ea", "toolName", ...])
|
223
|
+
*/
|
224
|
+
export function parsePrompt(prompt: string): {
|
225
|
+
msg?: string;
|
226
|
+
cmds?: string[];
|
227
|
+
} {
|
228
|
+
prompt = prompt.trim();
|
229
|
+
|
230
|
+
let msg: string | undefined = undefined;
|
231
|
+
let cmds: string[] | undefined = undefined;
|
232
|
+
|
233
|
+
if (prompt.startsWith(":") || prompt.startsWith("/")) {
|
234
|
+
cmds = prompt.split(" ");
|
235
|
+
cmds[0] = cmds[0].slice(1);
|
236
|
+
|
237
|
+
if (cmds[0] == "i") {
|
238
|
+
// :i is special as it may have a trailing message
|
239
|
+
const fileDelim = prompt.indexOf(" ", 3);
|
240
|
+
if (fileDelim < 0) {
|
241
|
+
cmds = [cmds[0], prompt.slice(3)];
|
242
|
+
} else {
|
243
|
+
msg = prompt.slice(fileDelim + 1);
|
244
|
+
cmds = [cmds[0], prompt.slice(3, fileDelim)];
|
245
|
+
}
|
246
|
+
}
|
247
|
+
} else {
|
248
|
+
msg = prompt;
|
249
|
+
}
|
250
|
+
|
251
|
+
return { msg, cmds };
|
252
|
+
}
|
package/src/tool/prompt.ts
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
+
import { getLogger } from "@xalia/xmcp/sdk";
|
1
2
|
import readline from "readline";
|
2
3
|
|
4
|
+
const logger = getLogger();
|
5
|
+
|
3
6
|
const DEFAULT_PROMPT: string = "USER: ";
|
4
7
|
|
5
|
-
export
|
8
|
+
export interface IPrompt {
|
9
|
+
run(prompt?: string): Promise<string | undefined>;
|
10
|
+
shutdown(): Promise<void>;
|
11
|
+
}
|
12
|
+
|
13
|
+
export class Prompt implements IPrompt {
|
6
14
|
private line: string | undefined;
|
7
15
|
private online: { (line: string | undefined): void } | undefined;
|
8
16
|
// private onerror: { (reason?: any): void } | undefined;
|
@@ -41,7 +49,7 @@ export class Prompt {
|
|
41
49
|
});
|
42
50
|
}
|
43
51
|
|
44
|
-
shutdown(): void {
|
52
|
+
async shutdown(): Promise<void> {
|
45
53
|
this.prompt.close();
|
46
54
|
}
|
47
55
|
|
@@ -54,40 +62,33 @@ export class Prompt {
|
|
54
62
|
}
|
55
63
|
|
56
64
|
/**
|
57
|
-
*
|
58
|
-
* - some text (msg: some text, cmds: undefined)
|
59
|
-
* - :i image.png some text (msg: some text, cmds: ["i", "image.png"])
|
60
|
-
* - :i image.png (msg: undefined, cmds: ["i", "image.png"])
|
61
|
-
* - :l (msg: undefined, cmds: ["l"])
|
62
|
-
* - :e toolName .. (msg: undefined, cmds: ["e", "toolName", ...])
|
63
|
-
* - :ea toolName .. (msg: undefined, cmds: ["ea", "toolName", ...])
|
65
|
+
* A prompt which just reads from a script
|
64
66
|
*/
|
65
|
-
export
|
66
|
-
|
67
|
-
cmds?: string[];
|
68
|
-
} {
|
69
|
-
prompt = prompt.trim();
|
67
|
+
export class ScriptPrompt implements IPrompt {
|
68
|
+
lines: string[];
|
70
69
|
|
71
|
-
|
72
|
-
|
70
|
+
constructor(lines: string[]) {
|
71
|
+
this.lines = lines;
|
72
|
+
}
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
run(_prompt?: string): Promise<string | undefined> {
|
75
|
+
return new Promise((r) => {
|
76
|
+
setTimeout(() => {
|
77
|
+
logger.debug(`[ScriptPrompt.run]: ${this.lines.length} remaining`);
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
}
|
87
|
-
}
|
88
|
-
} else {
|
89
|
-
msg = prompt;
|
79
|
+
if (this.lines.length === 0) {
|
80
|
+
logger.debug("[ScriptPrompt.run]: returning undefined");
|
81
|
+
r(undefined);
|
82
|
+
} else {
|
83
|
+
const line = this.lines.shift();
|
84
|
+
logger.debug(`[ScriptPrompt.run]: returning: ${line}`);
|
85
|
+
r(line);
|
86
|
+
}
|
87
|
+
}, 1000);
|
88
|
+
});
|
90
89
|
}
|
91
90
|
|
92
|
-
|
91
|
+
shutdown(): Promise<void> {
|
92
|
+
return new Promise((r) => setTimeout(r, 100));
|
93
|
+
}
|
93
94
|
}
|