@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
@@ -37,12 +37,15 @@ exports.chatMain = void 0;
|
|
37
37
|
const cmd_ts_1 = require("cmd-ts");
|
38
38
|
const process_1 = require("process");
|
39
39
|
const fs = __importStar(require("fs"));
|
40
|
+
const assert_1 = require("assert");
|
40
41
|
const tool_1 = require("@xalia/xmcp/tool");
|
41
42
|
const sdk_1 = require("@xalia/xmcp/sdk");
|
42
43
|
const client_1 = require("../chat/client");
|
43
44
|
const server_1 = require("../chat/server");
|
44
45
|
const prompt_1 = require("./prompt");
|
45
46
|
const options = __importStar(require("./options"));
|
47
|
+
const commandPrompt_1 = require("./commandPrompt");
|
48
|
+
const nodePlatform_1 = require("./nodePlatform");
|
46
49
|
const logger = (0, sdk_1.getLogger)();
|
47
50
|
const server = (0, cmd_ts_1.command)({
|
48
51
|
name: "server",
|
@@ -117,22 +120,27 @@ const client = (0, cmd_ts_1.command)({
|
|
117
120
|
await runClientTest(host, port, apiKey, session);
|
118
121
|
return;
|
119
122
|
}
|
120
|
-
if (script) {
|
121
|
-
await runScript(host, port, apiKey, session, agentProfile, script);
|
122
|
-
return;
|
123
|
-
}
|
124
123
|
const onMessage = getCLIOnMessage();
|
125
|
-
const repl =
|
126
|
-
|
124
|
+
const repl = (() => {
|
125
|
+
if (script) {
|
126
|
+
const scriptLines = fs.readFileSync(script, "utf8").split("\n");
|
127
|
+
return new prompt_1.ScriptPrompt(scriptLines);
|
128
|
+
}
|
129
|
+
return new prompt_1.Prompt();
|
130
|
+
})();
|
131
|
+
const cmdPrompt = new commandPrompt_1.CommandPrompt(repl);
|
132
|
+
const client = await client_1.ChatClient.init(host, port, apiKey, onMessage, async () => await cmdPrompt.shutdown(), nodePlatform_1.NODE_PLATFORM, session, agentProfile);
|
127
133
|
logger.debug("client created");
|
128
134
|
while (true) {
|
129
|
-
const msgText = await
|
130
|
-
|
135
|
+
const [msgText, imageFile] = await cmdPrompt.getNextPrompt(undefined, // TODO: client
|
136
|
+
client.getSudoMcpServerManager());
|
137
|
+
(0, assert_1.strict)(imageFile === undefined);
|
131
138
|
if (msgText === undefined) {
|
132
139
|
logger.debug("exiting...");
|
133
140
|
client.close();
|
134
141
|
return;
|
135
142
|
}
|
143
|
+
// TODO: other prompts
|
136
144
|
if (msgText.length > 0) {
|
137
145
|
const msg = {
|
138
146
|
type: "msg",
|
@@ -168,26 +176,13 @@ function getCLIOnMessage() {
|
|
168
176
|
}
|
169
177
|
break;
|
170
178
|
default:
|
171
|
-
process_1.stdout.write(`(${JSON.stringify(msg)}
|
179
|
+
process_1.stdout.write(`(unrecognised) ${JSON.stringify(msg)}\n`);
|
172
180
|
break;
|
173
181
|
}
|
174
182
|
};
|
175
183
|
}
|
176
|
-
async function runScript(host, port, apiKey, session, agentProfile, script) {
|
177
|
-
const client = await client_1.ChatClient.init(host, port, apiKey, getCLIOnMessage(), () => { }, session, agentProfile);
|
178
|
-
const scriptData = fs.readFileSync(script, "utf8");
|
179
|
-
const lines = scriptData.split("\n");
|
180
|
-
for (const line of lines) {
|
181
|
-
await new Promise((r) => setTimeout(r, 1000));
|
182
|
-
client.sendMessage({ type: "msg", message: line });
|
183
|
-
}
|
184
|
-
// 2 second wait period
|
185
|
-
await new Promise((r) => setTimeout(r, 2000));
|
186
|
-
client.close();
|
187
|
-
return;
|
188
|
-
}
|
189
184
|
async function runClientTest(host, port, apiKey, session) {
|
190
|
-
const client = await client_1.ChatClient.init(host, port, apiKey, getCLIOnMessage(), () => { }, session);
|
185
|
+
const client = await client_1.ChatClient.init(host, port, apiKey, getCLIOnMessage(), async () => { }, nodePlatform_1.NODE_PLATFORM, session);
|
191
186
|
for (let i = 1; i <= 60; i++) {
|
192
187
|
await new Promise((r) => setTimeout(r, 1000));
|
193
188
|
const message = {
|
@@ -0,0 +1,248 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
19
|
+
var ownKeys = function(o) {
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
21
|
+
var ar = [];
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
23
|
+
return ar;
|
24
|
+
};
|
25
|
+
return ownKeys(o);
|
26
|
+
};
|
27
|
+
return function (mod) {
|
28
|
+
if (mod && mod.__esModule) return mod;
|
29
|
+
var result = {};
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
31
|
+
__setModuleDefault(result, mod);
|
32
|
+
return result;
|
33
|
+
};
|
34
|
+
})();
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
37
|
+
};
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
39
|
+
exports.CommandPrompt = void 0;
|
40
|
+
exports.parsePrompt = parsePrompt;
|
41
|
+
const fs = __importStar(require("fs"));
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
43
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
44
|
+
const tools_1 = require("../agent/tools");
|
45
|
+
/**
|
46
|
+
* A prompt parser which can accept commands or messages from an IPrompt.
|
47
|
+
* Commands are sent to an IMcpServerManager, ISkillManager and IConversation.
|
48
|
+
*/
|
49
|
+
class CommandPrompt {
|
50
|
+
constructor(prompt) {
|
51
|
+
this.prompt = prompt;
|
52
|
+
}
|
53
|
+
shutdown() {
|
54
|
+
return this.prompt.shutdown();
|
55
|
+
}
|
56
|
+
async getNextPrompt(agent, sudoMcpServerManager) {
|
57
|
+
while (true) {
|
58
|
+
// Get a line, detecting the EOF signal.
|
59
|
+
const line = await this.prompt.run();
|
60
|
+
if (typeof line === "undefined") {
|
61
|
+
console.log("closing ...");
|
62
|
+
return [undefined, undefined];
|
63
|
+
}
|
64
|
+
if (line.length === 0) {
|
65
|
+
continue;
|
66
|
+
}
|
67
|
+
// Extract prompt or commands
|
68
|
+
const { msg, cmds } = parsePrompt(line);
|
69
|
+
// If there are no commands, this must be a prompt only
|
70
|
+
if (!cmds) {
|
71
|
+
return [msg, undefined];
|
72
|
+
}
|
73
|
+
// There are commands. If it's image, return [prompt, image]. If it's
|
74
|
+
// quit, return [undefined, undefined], otherwise it must be a command.
|
75
|
+
// Execute it and prompt again.
|
76
|
+
switch (cmds[0]) {
|
77
|
+
case "i": // image
|
78
|
+
return [msg, cmds[1]];
|
79
|
+
case "q":
|
80
|
+
case "quit":
|
81
|
+
case "exit":
|
82
|
+
return [undefined, undefined];
|
83
|
+
default:
|
84
|
+
break;
|
85
|
+
}
|
86
|
+
try {
|
87
|
+
await this.runCommand(agent, sudoMcpServerManager.getMcpServerManager(), sudoMcpServerManager, cmds);
|
88
|
+
}
|
89
|
+
catch (e) {
|
90
|
+
console.log(`ERROR: ${e}`);
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
94
|
+
async runCommand(agent, mcpServerManager, sudoMcpServerManager, cmds) {
|
95
|
+
switch (cmds[0]) {
|
96
|
+
case "lt":
|
97
|
+
this.listTools(mcpServerManager);
|
98
|
+
break;
|
99
|
+
case "ls":
|
100
|
+
this.listServers(sudoMcpServerManager);
|
101
|
+
break;
|
102
|
+
case "as":
|
103
|
+
await this.addServer(sudoMcpServerManager, cmds[1]);
|
104
|
+
break;
|
105
|
+
case "rs":
|
106
|
+
await mcpServerManager.removeMcpServer(cmds[1]);
|
107
|
+
break;
|
108
|
+
case "e":
|
109
|
+
mcpServerManager.enableTool(cmds[1], cmds[2]);
|
110
|
+
console.log(`Enabled tool ${cmds[2]} for server ${cmds[1]}`);
|
111
|
+
break;
|
112
|
+
case "d":
|
113
|
+
mcpServerManager.disableTool(cmds[1], cmds[2]);
|
114
|
+
console.log(`Disabled tool ${cmds[2]} for server ${cmds[1]}`);
|
115
|
+
break;
|
116
|
+
case "ea":
|
117
|
+
mcpServerManager.enableAllTools(cmds[1]);
|
118
|
+
console.log(`Enabled all tools for server ${cmds[1]}`);
|
119
|
+
break;
|
120
|
+
case "da":
|
121
|
+
mcpServerManager.disableAllTools(cmds[1]);
|
122
|
+
console.log(`Disabled all tools for server ${cmds[1]}`);
|
123
|
+
break;
|
124
|
+
case "wc":
|
125
|
+
this.writeConversation(agent, cmds[1]);
|
126
|
+
break;
|
127
|
+
case "wa":
|
128
|
+
this.writeAgentProfile(agent, cmds[1]);
|
129
|
+
break;
|
130
|
+
case "h":
|
131
|
+
case "help":
|
132
|
+
case "?":
|
133
|
+
this.helpMenu();
|
134
|
+
break;
|
135
|
+
default:
|
136
|
+
console.log(`error: Unknown command ${cmds[0]}`);
|
137
|
+
}
|
138
|
+
}
|
139
|
+
helpMenu() {
|
140
|
+
const write = console.log;
|
141
|
+
write(`Tool management commands:`);
|
142
|
+
write(` ${chalk_1.default.yellow("/lt")} List tools: `);
|
143
|
+
write(` ${chalk_1.default.yellow("/ls")} List servers`);
|
144
|
+
write(` ${chalk_1.default.yellow("/as <server>")} Add server`);
|
145
|
+
write(` ${chalk_1.default.yellow("/rs <server>")} Remove server`);
|
146
|
+
write(` ${chalk_1.default.yellow("/e <server> <tool>")} Enable tool`);
|
147
|
+
write(` ${chalk_1.default.yellow("/d <server> <tool>")} Disable tool`);
|
148
|
+
write(` ${chalk_1.default.yellow("/ea <server>")} Enable all tools`);
|
149
|
+
write(` ${chalk_1.default.yellow("/da <server>")} Disable all tools`);
|
150
|
+
write(` ${chalk_1.default.yellow("/wc <file-name>")} Write conversation file`);
|
151
|
+
write(` ${chalk_1.default.yellow("/wa <file-name>")} Write agent profile file`);
|
152
|
+
write(` ${chalk_1.default.yellow("/q")} Quit`);
|
153
|
+
}
|
154
|
+
listTools(mcpServerManager) {
|
155
|
+
console.log("Mcp servers and tools (* - enabled):");
|
156
|
+
const serverNames = mcpServerManager.getMcpServerNames();
|
157
|
+
for (const serverName of serverNames) {
|
158
|
+
const server = mcpServerManager.getMcpServer(serverName);
|
159
|
+
console.log(` ${chalk_1.default.green(serverName)}`);
|
160
|
+
const tools = server.getTools();
|
161
|
+
const enabled = server.getEnabledTools();
|
162
|
+
for (const tool of tools) {
|
163
|
+
const isEnabled = enabled[tool.name] ? "*" : " ";
|
164
|
+
console.log(` [${isEnabled}] ${tool.name}`);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
listServers(sudoMcpServerManager) {
|
169
|
+
console.log(`Available MCP Servers:`);
|
170
|
+
const serverBriefs = sudoMcpServerManager.getServerBriefs();
|
171
|
+
serverBriefs.sort((a, b) => a.name.localeCompare(b.name));
|
172
|
+
for (const brief of serverBriefs) {
|
173
|
+
console.log(`- ${chalk_1.default.green(brief.name)}: ${brief.description}`);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
/**
|
177
|
+
* Adds server and enables all tools.
|
178
|
+
*/
|
179
|
+
async addServer(sudoMcpServerManager, serverName) {
|
180
|
+
try {
|
181
|
+
await sudoMcpServerManager.addMcpServer(serverName, true);
|
182
|
+
}
|
183
|
+
catch (e) {
|
184
|
+
if (e instanceof sdk_1.InvalidConfiguration) {
|
185
|
+
throw `${e}. \n
|
186
|
+
Have you installed server ${serverName} using the SudoMCP CLI tool?`;
|
187
|
+
}
|
188
|
+
else {
|
189
|
+
throw e;
|
190
|
+
}
|
191
|
+
}
|
192
|
+
const msm = sudoMcpServerManager.getMcpServerManager();
|
193
|
+
console.log(`Added server: ${serverName} (enabled all tools).`);
|
194
|
+
console.log(`Current tool list:`);
|
195
|
+
this.listTools(msm);
|
196
|
+
}
|
197
|
+
writeConversation(agent, fileName) {
|
198
|
+
const conversation = agent.getConversation();
|
199
|
+
fs.writeFileSync(fileName, JSON.stringify(conversation), {
|
200
|
+
encoding: "utf8",
|
201
|
+
});
|
202
|
+
console.log(`Conversation written to ${fileName}`);
|
203
|
+
}
|
204
|
+
writeAgentProfile(agent, fileName) {
|
205
|
+
const profile = agent.getAgentProfile();
|
206
|
+
fs.writeFileSync(fileName, JSON.stringify(profile));
|
207
|
+
console.log(`AgentProfile written to ${fileName}`);
|
208
|
+
}
|
209
|
+
async promptToolCall(toolCall) {
|
210
|
+
(0, tools_1.displayToolCall)(toolCall);
|
211
|
+
const response = await this.prompt.run("Approve tool call? (Y/n) ");
|
212
|
+
return response === "y" || response === "yes" || response == "";
|
213
|
+
}
|
214
|
+
}
|
215
|
+
exports.CommandPrompt = CommandPrompt;
|
216
|
+
/**
|
217
|
+
* Support prompts of the form:
|
218
|
+
* - some text (msg: some text, cmds: undefined)
|
219
|
+
* - :i image.png some text (msg: some text, cmds: ["i", "image.png"])
|
220
|
+
* - :i image.png (msg: undefined, cmds: ["i", "image.png"])
|
221
|
+
* - :l (msg: undefined, cmds: ["l"])
|
222
|
+
* - :e toolName .. (msg: undefined, cmds: ["e", "toolName", ...])
|
223
|
+
* - :ea toolName .. (msg: undefined, cmds: ["ea", "toolName", ...])
|
224
|
+
*/
|
225
|
+
function parsePrompt(prompt) {
|
226
|
+
prompt = prompt.trim();
|
227
|
+
let msg = undefined;
|
228
|
+
let cmds = undefined;
|
229
|
+
if (prompt.startsWith(":") || prompt.startsWith("/")) {
|
230
|
+
cmds = prompt.split(" ");
|
231
|
+
cmds[0] = cmds[0].slice(1);
|
232
|
+
if (cmds[0] == "i") {
|
233
|
+
// :i is special as it may have a trailing message
|
234
|
+
const fileDelim = prompt.indexOf(" ", 3);
|
235
|
+
if (fileDelim < 0) {
|
236
|
+
cmds = [cmds[0], prompt.slice(3)];
|
237
|
+
}
|
238
|
+
else {
|
239
|
+
msg = prompt.slice(fileDelim + 1);
|
240
|
+
cmds = [cmds[0], prompt.slice(3, fileDelim)];
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
else {
|
245
|
+
msg = prompt;
|
246
|
+
}
|
247
|
+
return { msg, cmds };
|
248
|
+
}
|
@@ -3,9 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.Prompt = void 0;
|
7
|
-
|
6
|
+
exports.ScriptPrompt = exports.Prompt = void 0;
|
7
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
8
8
|
const readline_1 = __importDefault(require("readline"));
|
9
|
+
const logger = (0, sdk_1.getLogger)();
|
9
10
|
const DEFAULT_PROMPT = "USER: ";
|
10
11
|
class Prompt {
|
11
12
|
constructor() {
|
@@ -37,7 +38,7 @@ class Prompt {
|
|
37
38
|
}
|
38
39
|
});
|
39
40
|
}
|
40
|
-
shutdown() {
|
41
|
+
async shutdown() {
|
41
42
|
this.prompt.close();
|
42
43
|
}
|
43
44
|
resolve() {
|
@@ -49,35 +50,30 @@ class Prompt {
|
|
49
50
|
}
|
50
51
|
exports.Prompt = Prompt;
|
51
52
|
/**
|
52
|
-
*
|
53
|
-
* - some text (msg: some text, cmds: undefined)
|
54
|
-
* - :i image.png some text (msg: some text, cmds: ["i", "image.png"])
|
55
|
-
* - :i image.png (msg: undefined, cmds: ["i", "image.png"])
|
56
|
-
* - :l (msg: undefined, cmds: ["l"])
|
57
|
-
* - :e toolName .. (msg: undefined, cmds: ["e", "toolName", ...])
|
58
|
-
* - :ea toolName .. (msg: undefined, cmds: ["ea", "toolName", ...])
|
53
|
+
* A prompt which just reads from a script
|
59
54
|
*/
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
let cmds = undefined;
|
64
|
-
if (prompt.startsWith(":") || prompt.startsWith("/")) {
|
65
|
-
cmds = prompt.split(" ");
|
66
|
-
cmds[0] = cmds[0].slice(1);
|
67
|
-
if (cmds[0] == "i") {
|
68
|
-
// :i is special as it may have a trailing message
|
69
|
-
const fileDelim = prompt.indexOf(" ", 3);
|
70
|
-
if (fileDelim < 0) {
|
71
|
-
cmds = [cmds[0], prompt.slice(3)];
|
72
|
-
}
|
73
|
-
else {
|
74
|
-
msg = prompt.slice(fileDelim + 1);
|
75
|
-
cmds = [cmds[0], prompt.slice(3, fileDelim)];
|
76
|
-
}
|
77
|
-
}
|
55
|
+
class ScriptPrompt {
|
56
|
+
constructor(lines) {
|
57
|
+
this.lines = lines;
|
78
58
|
}
|
79
|
-
|
80
|
-
|
59
|
+
run(_prompt) {
|
60
|
+
return new Promise((r) => {
|
61
|
+
setTimeout(() => {
|
62
|
+
logger.debug(`[ScriptPrompt.run]: ${this.lines.length} remaining`);
|
63
|
+
if (this.lines.length === 0) {
|
64
|
+
logger.debug("[ScriptPrompt.run]: returning undefined");
|
65
|
+
r(undefined);
|
66
|
+
}
|
67
|
+
else {
|
68
|
+
const line = this.lines.shift();
|
69
|
+
logger.debug(`[ScriptPrompt.run]: returning: ${line}`);
|
70
|
+
r(line);
|
71
|
+
}
|
72
|
+
}, 1000);
|
73
|
+
});
|
74
|
+
}
|
75
|
+
shutdown() {
|
76
|
+
return new Promise((r) => setTimeout(r, 100));
|
81
77
|
}
|
82
|
-
return { msg, cmds };
|
83
78
|
}
|
79
|
+
exports.ScriptPrompt = ScriptPrompt;
|
package/package.json
CHANGED
package/scripts/test_chat
CHANGED
@@ -73,7 +73,7 @@ pushd _test_chat
|
|
73
73
|
|
74
74
|
# User 0 creates an agent profile
|
75
75
|
|
76
|
-
echo '{"system_prompt":"You are a helpful agent
|
76
|
+
echo '{"system_prompt":"You are a helpful agent in a multi-user chat session. If you have nothing to contribute and are not being addressed, just say `No comment`.","model":"repeat","mcp_settings":{"duckduckgo-search":[]}}' > profile0.json
|
77
77
|
${client} config --force --dev --api-key ${apikey0}
|
78
78
|
${client} agent-profile set profile0 --profile profile0.json
|
79
79
|
|
@@ -82,6 +82,22 @@ pushd _test_chat
|
|
82
82
|
echo 'tell me a joke' > script0
|
83
83
|
${agent} chat client --session test_session --agent-profile profile0 --script script0
|
84
84
|
|
85
|
+
# Check that the duckduckgo-search server is available
|
86
|
+
# Have a participant remove it and check that got reflected in the db
|
87
|
+
# Have a participant re-add it and check that got reflected in the db
|
88
|
+
|
89
|
+
${client} agent-profile get profile0 | grep duckduckgo
|
90
|
+
echo ":rs duckduckgo-search" > script_rm_duckduckgo
|
91
|
+
${agent} chat client --session chatuser0/test_session --script script_rm_duckduckgo
|
92
|
+
sleep 1
|
93
|
+
(${client} agent-profile get profile0 | grep duckduckgo) && ( \
|
94
|
+
echo "ERROR: expected duckduckgo to be removed from agent profile" ; \
|
95
|
+
exit 1 \
|
96
|
+
)
|
97
|
+
echo ":as duckduckgo-search" > script_add_duckduckgo
|
98
|
+
${agent} chat client --session chatuser0/test_session --script script_add_duckduckgo
|
99
|
+
${client} agent-profile get profile0 | grep duckduckgo
|
100
|
+
|
85
101
|
# Run both clients with a script that continues the conversation
|
86
102
|
|
87
103
|
echo -e "why is that funny?\ntell me another.\nwhat is my name?\nhow old are you?\nwhere are your parents?" > script1
|
package/src/agent/agent.ts
CHANGED
@@ -29,10 +29,24 @@ export type OnToolCallCB = {
|
|
29
29
|
(msg: ChatCompletionMessageToolCall): Promise<boolean>;
|
30
30
|
};
|
31
31
|
|
32
|
+
export interface IConversation {
|
33
|
+
userMessage(msg?: string, imageB64?: string): void;
|
34
|
+
getConversation(): ChatCompletionMessageParam[];
|
35
|
+
|
36
|
+
// TODO: remove?
|
37
|
+
resetConversation(): void;
|
38
|
+
|
39
|
+
getAgentProfile(): AgentProfile;
|
40
|
+
getSystemPrompt(): string;
|
41
|
+
setSystemPrompt(systemPrompt: string): void;
|
42
|
+
getModel(): string;
|
43
|
+
setModel(model: string): void;
|
44
|
+
}
|
45
|
+
|
32
46
|
dotenv.config();
|
33
47
|
const logger = getLogger();
|
34
48
|
|
35
|
-
export class Agent {
|
49
|
+
export class Agent implements IConversation {
|
36
50
|
private toolHandlers: { [toolName: string]: ToolHandler } = {};
|
37
51
|
|
38
52
|
private constructor(
|
@@ -48,7 +62,8 @@ export class Agent {
|
|
48
62
|
onMessage: OnMessageCB,
|
49
63
|
onToolCall: OnToolCallCB,
|
50
64
|
systemPrompt: string | undefined,
|
51
|
-
llm: ILLM
|
65
|
+
llm: ILLM,
|
66
|
+
mcpServerManager?: McpServerManager
|
52
67
|
): Promise<Agent> {
|
53
68
|
// Initialize messages with system prompt
|
54
69
|
const messages = [
|
@@ -58,14 +73,11 @@ export class Agent {
|
|
58
73
|
} as OpenAI.ChatCompletionMessageParam,
|
59
74
|
];
|
60
75
|
|
61
|
-
// Create the server manager
|
62
|
-
const mcpServerManager = new McpServerManager();
|
63
|
-
|
64
76
|
return new Agent(
|
65
77
|
onMessage,
|
66
78
|
onToolCall,
|
67
79
|
messages,
|
68
|
-
mcpServerManager,
|
80
|
+
mcpServerManager ?? new McpServerManager(),
|
69
81
|
[],
|
70
82
|
llm
|
71
83
|
);
|
@@ -78,7 +90,7 @@ export class Agent {
|
|
78
90
|
public getAgentProfile(): AgentProfile {
|
79
91
|
return new AgentProfile(
|
80
92
|
this.llm.getModel(),
|
81
|
-
this.
|
93
|
+
this.getSystemPrompt(),
|
82
94
|
this.mcpServerManager.getMcpServerSettings()
|
83
95
|
);
|
84
96
|
}
|
@@ -108,7 +120,10 @@ export class Agent {
|
|
108
120
|
return this.mcpServerManager;
|
109
121
|
}
|
110
122
|
|
111
|
-
|
123
|
+
/**
|
124
|
+
* Like `userMessage`, but can be awaited, and accepts the user name.
|
125
|
+
*/
|
126
|
+
public async userMessageEx(
|
112
127
|
msg?: string,
|
113
128
|
imageB64?: string,
|
114
129
|
name?: string
|
@@ -159,7 +174,15 @@ export class Agent {
|
|
159
174
|
return completion.choices[0].message;
|
160
175
|
}
|
161
176
|
|
162
|
-
public
|
177
|
+
public userMessage(msg?: string, imageB64?: string): void {
|
178
|
+
this.userMessageEx(msg, imageB64);
|
179
|
+
}
|
180
|
+
|
181
|
+
public getModel(): string {
|
182
|
+
return this.llm.getModel();
|
183
|
+
}
|
184
|
+
|
185
|
+
public setModel(model: string) {
|
163
186
|
logger.debug(`Set model ${model}`);
|
164
187
|
this.llm.setModel(model);
|
165
188
|
}
|
@@ -173,7 +196,7 @@ export class Agent {
|
|
173
196
|
this.messages.splice(1);
|
174
197
|
}
|
175
198
|
|
176
|
-
public
|
199
|
+
public getSystemPrompt(): string {
|
177
200
|
assert(this.messages[0].role === "system");
|
178
201
|
return this.messages[0].content as string;
|
179
202
|
}
|
@@ -181,7 +204,7 @@ export class Agent {
|
|
181
204
|
/**
|
182
205
|
* Set the system prompt
|
183
206
|
*/
|
184
|
-
public
|
207
|
+
public setSystemPrompt(systemMsg: string) {
|
185
208
|
assert(this.messages[0].role === "system");
|
186
209
|
this.messages[0].content = systemMsg;
|
187
210
|
}
|
package/src/agent/agentUtils.ts
CHANGED
@@ -9,12 +9,19 @@ import { OpenAILLMStreaming } from "./openAILLMStreaming";
|
|
9
9
|
import { DummyLLM } from "./dummyLLM";
|
10
10
|
import { ILLM } from "./llm";
|
11
11
|
import { strict as assert } from "assert";
|
12
|
+
import { RepeatLLM } from "./repeatLLM";
|
13
|
+
import { McpServerManager } from "./mcpServerManager";
|
12
14
|
|
13
15
|
const logger = getLogger();
|
14
16
|
|
15
17
|
export const DEFAULT_LLM_URL = "http://localhost:5001/v1";
|
16
18
|
export const DEFAULT_LLM_MODEL = "gpt-4o";
|
17
19
|
|
20
|
+
export const XALIA_APP_HEADER = {
|
21
|
+
"HTTP-Referer": "xalia.ai",
|
22
|
+
"X-Title": "Xalia",
|
23
|
+
};
|
24
|
+
|
18
25
|
/**
|
19
26
|
* Util function to create an Agent from some config information.
|
20
27
|
*/
|
@@ -26,7 +33,8 @@ async function createAgent(
|
|
26
33
|
onToolCall: OnToolCallCB,
|
27
34
|
platform: IPlatform,
|
28
35
|
openaiApiKey: string | undefined,
|
29
|
-
stream: boolean = false
|
36
|
+
stream: boolean = false,
|
37
|
+
mcpServerManager?: McpServerManager
|
30
38
|
): Promise<Agent> {
|
31
39
|
let llm: ILLM | undefined;
|
32
40
|
|
@@ -41,6 +49,8 @@ async function createAgent(
|
|
41
49
|
const responses: OpenAI.ChatCompletion.Choice[] = JSON.parse(script);
|
42
50
|
logger.debug(`Initializing Dummy Agent: ${llmUrl}`);
|
43
51
|
llm = new DummyLLM(responses);
|
52
|
+
} else if (model === "repeat") {
|
53
|
+
llm = new RepeatLLM();
|
44
54
|
} else {
|
45
55
|
// Regular Agent
|
46
56
|
if (!openaiApiKey) {
|
@@ -56,7 +66,13 @@ async function createAgent(
|
|
56
66
|
}
|
57
67
|
|
58
68
|
assert(llm);
|
59
|
-
return Agent.initializeWithLLM(
|
69
|
+
return Agent.initializeWithLLM(
|
70
|
+
onMessage,
|
71
|
+
onToolCall,
|
72
|
+
systemPrompt,
|
73
|
+
llm,
|
74
|
+
mcpServerManager
|
75
|
+
);
|
60
76
|
}
|
61
77
|
|
62
78
|
/**
|
@@ -109,6 +125,39 @@ export async function createAgentWithSkills(
|
|
109
125
|
return [agent, sudoMcpServerManager];
|
110
126
|
}
|
111
127
|
|
128
|
+
export async function createAgentFromSkillManager(
|
129
|
+
llmUrl: string,
|
130
|
+
agentProfile: AgentProfile,
|
131
|
+
onMessage: OnMessageCB,
|
132
|
+
onToolCall: OnToolCallCB,
|
133
|
+
platform: IPlatform,
|
134
|
+
llmApiKey: string | undefined,
|
135
|
+
skillManager: SkillManager,
|
136
|
+
conversation: OpenAI.ChatCompletionMessageParam[] | undefined,
|
137
|
+
stream: boolean = false
|
138
|
+
): Promise<Agent> {
|
139
|
+
// Create agent
|
140
|
+
logger.debug("[createAgentAndSudoMcpServerManager] creating agent ...");
|
141
|
+
const mcpServerManager = skillManager.getMcpServerManager();
|
142
|
+
const agent = await createAgent(
|
143
|
+
llmUrl,
|
144
|
+
agentProfile.model,
|
145
|
+
agentProfile.system_prompt,
|
146
|
+
onMessage,
|
147
|
+
onToolCall,
|
148
|
+
platform,
|
149
|
+
llmApiKey,
|
150
|
+
stream,
|
151
|
+
mcpServerManager
|
152
|
+
);
|
153
|
+
if (conversation) {
|
154
|
+
agent.setConversation(conversation);
|
155
|
+
}
|
156
|
+
|
157
|
+
logger.debug("[createAgentWithSkills] done");
|
158
|
+
return agent;
|
159
|
+
}
|
160
|
+
|
112
161
|
/**
|
113
162
|
* An "non-interactive" agent is one which is not intended to be used
|
114
163
|
* interactively (settings cannot be dyanmically adjusted, intermediate
|
@@ -182,7 +231,7 @@ export async function runOneShot(
|
|
182
231
|
approveToolsUpTo
|
183
232
|
);
|
184
233
|
|
185
|
-
const response = await agent.
|
234
|
+
const response = await agent.userMessageEx(prompt, image);
|
186
235
|
await agent.shutdown();
|
187
236
|
logger.debug("[runOneShot]: shutdown done");
|
188
237
|
|