@xalia/agent 0.5.0
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/.prettierrc.json +11 -0
- package/README.md +56 -0
- package/dist/agent.js +238 -0
- package/dist/agentUtils.js +106 -0
- package/dist/chat.js +296 -0
- package/dist/dummyLLM.js +38 -0
- package/dist/files.js +115 -0
- package/dist/iplatform.js +2 -0
- package/dist/llm.js +2 -0
- package/dist/main.js +147 -0
- package/dist/mcpServerManager.js +278 -0
- package/dist/nodePlatform.js +61 -0
- package/dist/openAILLM.js +38 -0
- package/dist/openAILLMStreaming.js +431 -0
- package/dist/options.js +79 -0
- package/dist/prompt.js +83 -0
- package/dist/sudoMcpServerManager.js +183 -0
- package/dist/test/imageLoad.test.js +14 -0
- package/dist/test/mcpServerManager.test.js +71 -0
- package/dist/test/prompt.test.js +26 -0
- package/dist/test/sudoMcpServerManager.test.js +49 -0
- package/dist/tokenAuth.js +39 -0
- package/dist/tools.js +44 -0
- package/eslint.config.mjs +25 -0
- package/frog.png +0 -0
- package/package.json +42 -0
- package/scripts/git_message +31 -0
- package/scripts/git_wip +21 -0
- package/scripts/pr_message +18 -0
- package/scripts/pr_review +16 -0
- package/scripts/sudomcp_import +23 -0
- package/scripts/test_script +60 -0
- package/src/agent.ts +283 -0
- package/src/agentUtils.ts +198 -0
- package/src/chat.ts +346 -0
- package/src/dummyLLM.ts +50 -0
- package/src/files.ts +95 -0
- package/src/iplatform.ts +17 -0
- package/src/llm.ts +15 -0
- package/src/main.ts +187 -0
- package/src/mcpServerManager.ts +371 -0
- package/src/nodePlatform.ts +24 -0
- package/src/openAILLM.ts +51 -0
- package/src/openAILLMStreaming.ts +528 -0
- package/src/options.ts +103 -0
- package/src/prompt.ts +93 -0
- package/src/sudoMcpServerManager.ts +278 -0
- package/src/test/imageLoad.test.ts +14 -0
- package/src/test/mcpServerManager.test.ts +98 -0
- package/src/test/prompt.test.src +0 -0
- package/src/test/prompt.test.ts +26 -0
- package/src/test/sudoMcpServerManager.test.ts +65 -0
- package/src/tokenAuth.ts +50 -0
- package/src/tools.ts +57 -0
- package/test_data/background_test_profile.json +6 -0
- package/test_data/background_test_script.json +11 -0
- package/test_data/dummyllm_script_simplecalc.json +28 -0
- package/test_data/git_message_profile.json +4 -0
- package/test_data/git_wip_system.txt +5 -0
- package/test_data/pr_message_profile.json +4 -0
- package/test_data/pr_review_profile.json +4 -0
- package/test_data/prompt_simplecalc.txt +1 -0
- package/test_data/simplecalc_profile.json +4 -0
- package/test_data/sudomcp_import_profile.json +4 -0
- package/test_data/test_script_profile.json +8 -0
- package/tsconfig.json +13 -0
@@ -0,0 +1,278 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.McpServerManager = exports.McpServerInfo = void 0;
|
4
|
+
exports.computeQualifiedName = computeQualifiedName;
|
5
|
+
exports.splitQualifiedName = splitQualifiedName;
|
6
|
+
exports.computeOpenAIToolList = computeOpenAIToolList;
|
7
|
+
exports.mcpToolToOpenAITool = mcpToolToOpenAITool;
|
8
|
+
const sse_js_1 = require("@modelcontextprotocol/sdk/client/sse.js");
|
9
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
10
|
+
const tokenAuth_1 = require("./tokenAuth");
|
11
|
+
const assert_1 = require("assert");
|
12
|
+
const sdk_1 = require("@xalia/xmcp/sdk");
|
13
|
+
const logger = (0, sdk_1.getLogger)();
|
14
|
+
/**
|
15
|
+
* The (read-only) McpServerInfo to expose to external classes. Callers
|
16
|
+
* should not modify this data directly. Only through the McpServerManager
|
17
|
+
* class.
|
18
|
+
*/
|
19
|
+
class McpServerInfo {
|
20
|
+
constructor(tools) {
|
21
|
+
const toolsMap = {};
|
22
|
+
for (const mcpTool of tools) {
|
23
|
+
const toolName = mcpTool.name;
|
24
|
+
toolsMap[toolName] = mcpTool;
|
25
|
+
}
|
26
|
+
this.tools = tools;
|
27
|
+
this.toolsMap = toolsMap;
|
28
|
+
this.enabledToolsMap = {};
|
29
|
+
}
|
30
|
+
getEnabledTools() {
|
31
|
+
return this.enabledToolsMap;
|
32
|
+
}
|
33
|
+
getTools() {
|
34
|
+
return this.tools;
|
35
|
+
}
|
36
|
+
getTool(toolName) {
|
37
|
+
return this.toolsMap[toolName];
|
38
|
+
}
|
39
|
+
}
|
40
|
+
exports.McpServerInfo = McpServerInfo;
|
41
|
+
/**
|
42
|
+
* The internal class holds server info and allows it to be updated. Managed
|
43
|
+
* by McpServerManager. Do not access these methods except via the
|
44
|
+
* McpServerManager.
|
45
|
+
*/
|
46
|
+
class McpServerInfoInternal extends McpServerInfo {
|
47
|
+
constructor(client, tools) {
|
48
|
+
super(tools);
|
49
|
+
const callbacks = {};
|
50
|
+
for (const mcpTool of tools) {
|
51
|
+
const toolName = mcpTool.name;
|
52
|
+
// Create callback
|
53
|
+
const callback = async (argStr) => {
|
54
|
+
logger.debug(`cb for ${toolName} invoked with args (${typeof argStr}): ` +
|
55
|
+
`${JSON.stringify(argStr)}`);
|
56
|
+
const argsObj = JSON.parse(argStr);
|
57
|
+
const toolResult = await client.callTool({
|
58
|
+
name: toolName,
|
59
|
+
arguments: argsObj,
|
60
|
+
});
|
61
|
+
logger.debug(`cb for ${toolName} returned: ${JSON.stringify(toolResult)}`);
|
62
|
+
(0, assert_1.strict)(typeof toolResult === "object");
|
63
|
+
const content = toolResult.content;
|
64
|
+
(0, assert_1.strict)(typeof content === "object");
|
65
|
+
(0, assert_1.strict)(content);
|
66
|
+
const content0 = content[0];
|
67
|
+
(0, assert_1.strict)(typeof content0 === "object");
|
68
|
+
const content0Text = content0.text;
|
69
|
+
(0, assert_1.strict)(typeof content0Text === "string");
|
70
|
+
return content0Text;
|
71
|
+
};
|
72
|
+
callbacks[toolName] = callback;
|
73
|
+
}
|
74
|
+
this.client = client;
|
75
|
+
this.callbacks = callbacks;
|
76
|
+
}
|
77
|
+
async shutdown() {
|
78
|
+
await this.client.close();
|
79
|
+
}
|
80
|
+
enableTool(toolName) {
|
81
|
+
this.enabledToolsMap[toolName] = true;
|
82
|
+
}
|
83
|
+
disableTool(toolName) {
|
84
|
+
delete this.enabledToolsMap[toolName];
|
85
|
+
}
|
86
|
+
getCallback(toolName) {
|
87
|
+
return this.callbacks[toolName];
|
88
|
+
}
|
89
|
+
}
|
90
|
+
/**
|
91
|
+
* Manage a set of MCP servers, where the tools for each server have an
|
92
|
+
* 'enabled' flag. Tools are disabled by default. The set of enabled tools
|
93
|
+
* over all servers is exposed as a single list of OpenAI functions.
|
94
|
+
*/
|
95
|
+
class McpServerManager {
|
96
|
+
constructor() {
|
97
|
+
this.mcpServers = {};
|
98
|
+
this.enabledToolsDirty = true;
|
99
|
+
this.enabledOpenAITools = [];
|
100
|
+
}
|
101
|
+
async shutdown() {
|
102
|
+
await Promise.all(Object.keys(this.mcpServers).map((name) => {
|
103
|
+
logger.debug(`shutting down: ${name}...`);
|
104
|
+
this.mcpServers[name].shutdown();
|
105
|
+
}));
|
106
|
+
this.mcpServers = {};
|
107
|
+
}
|
108
|
+
getMcpServerNames() {
|
109
|
+
return Object.keys(this.mcpServers);
|
110
|
+
}
|
111
|
+
getMcpServer(mcpServerName) {
|
112
|
+
return this.getMcpServerInternal(mcpServerName);
|
113
|
+
}
|
114
|
+
async addMcpServer(mcpServerName, url, apiKey, tools) {
|
115
|
+
logger.debug(`Adding mcp server ${mcpServerName}: ${url}`);
|
116
|
+
const sseTransportOptions = {};
|
117
|
+
if (apiKey) {
|
118
|
+
sseTransportOptions.authProvider = new tokenAuth_1.TokenAuth(apiKey);
|
119
|
+
}
|
120
|
+
const urlO = new URL(url);
|
121
|
+
const transport = new sse_js_1.SSEClientTransport(urlO, sseTransportOptions);
|
122
|
+
const client = new index_js_1.Client({
|
123
|
+
name: "@xalia/agent",
|
124
|
+
version: "1.0.0",
|
125
|
+
});
|
126
|
+
try {
|
127
|
+
await client.connect(transport);
|
128
|
+
}
|
129
|
+
catch (e) {
|
130
|
+
// TODO: is this catch necessary?
|
131
|
+
await client.close();
|
132
|
+
throw e;
|
133
|
+
}
|
134
|
+
await this.addMcpServerWithClient(client, mcpServerName, tools);
|
135
|
+
}
|
136
|
+
/**
|
137
|
+
* Add MCP server from an already connected McpClient.
|
138
|
+
*/
|
139
|
+
async addMcpServerWithClient(client, mcpServerName, tools) {
|
140
|
+
try {
|
141
|
+
// TODO; require the tools to be passed in.
|
142
|
+
if (!tools) {
|
143
|
+
const mcpTools = await client.listTools();
|
144
|
+
tools = mcpTools.tools;
|
145
|
+
}
|
146
|
+
this.mcpServers[mcpServerName] = new McpServerInfoInternal(client, tools);
|
147
|
+
}
|
148
|
+
catch (e) {
|
149
|
+
await client.close();
|
150
|
+
throw e;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
async removeMcpServer(mcpServerName) {
|
154
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
155
|
+
delete this.mcpServers[mcpServerName];
|
156
|
+
await server.shutdown();
|
157
|
+
this.enabledToolsDirty = true;
|
158
|
+
}
|
159
|
+
enableAllTools(mcpServerName) {
|
160
|
+
logger.debug(`enableAllTools: ${mcpServerName}`);
|
161
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
162
|
+
for (const tool of server.getTools()) {
|
163
|
+
logger.debug(`enable: ${tool.name}`);
|
164
|
+
server.enableTool(tool.name);
|
165
|
+
}
|
166
|
+
this.enabledToolsDirty = true;
|
167
|
+
}
|
168
|
+
disableAllTools(mcpServerName) {
|
169
|
+
logger.debug(`disableAllTools: ${mcpServerName}`);
|
170
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
171
|
+
for (const tool of server.getTools()) {
|
172
|
+
logger.debug(`disable: ${tool.name}`);
|
173
|
+
server.disableTool(tool.name);
|
174
|
+
}
|
175
|
+
this.enabledToolsDirty = true;
|
176
|
+
}
|
177
|
+
enableTool(mcpServerName, toolName) {
|
178
|
+
logger.debug(`enableTool: ${mcpServerName} ${toolName}`);
|
179
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
180
|
+
server.enableTool(toolName);
|
181
|
+
this.enabledToolsDirty = true;
|
182
|
+
}
|
183
|
+
disableTool(mcpServerName, toolName) {
|
184
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
185
|
+
server.disableTool(toolName);
|
186
|
+
this.enabledToolsDirty = true;
|
187
|
+
}
|
188
|
+
getOpenAITools() {
|
189
|
+
if (this.enabledToolsDirty) {
|
190
|
+
this.enabledOpenAITools = computeOpenAIToolList(this.mcpServers);
|
191
|
+
this.enabledToolsDirty = false;
|
192
|
+
}
|
193
|
+
return this.enabledOpenAITools;
|
194
|
+
}
|
195
|
+
/**
|
196
|
+
* Note the `qualifiedToolName` is the full `{mcpServerName}/{toolName}` as
|
197
|
+
* in the openai spec.
|
198
|
+
*/
|
199
|
+
async invoke(qualifiedToolName, args) {
|
200
|
+
const [mcpServerName, toolName] = splitQualifiedName(qualifiedToolName);
|
201
|
+
logger.debug(`invoke: qualified: ${qualifiedToolName}`);
|
202
|
+
logger.debug(`invoke: mcpServerName: ${mcpServerName}, toolName: ${toolName}`);
|
203
|
+
logger.debug(`invoke: args: ${JSON.stringify(args)}`);
|
204
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
205
|
+
const cb = server.getCallback(toolName);
|
206
|
+
if (!cb) {
|
207
|
+
throw `Unknown tool ${qualifiedToolName}`;
|
208
|
+
}
|
209
|
+
return cb(JSON.stringify(args));
|
210
|
+
}
|
211
|
+
/**
|
212
|
+
* "Settings" refers to the set of added servers and enabled tools.
|
213
|
+
*/
|
214
|
+
getMcpServerSettings() {
|
215
|
+
const config = {};
|
216
|
+
// NOTE: on load, entires of the form:
|
217
|
+
//
|
218
|
+
// <server>: []
|
219
|
+
//
|
220
|
+
// may be interpreted as "all tools for <server>". If the client has left
|
221
|
+
// a server with no tools enabled, we mark it as disabled.
|
222
|
+
for (const [serverName, server] of Object.entries(this.mcpServers)) {
|
223
|
+
const tools = Object.keys(server.getEnabledTools());
|
224
|
+
if (tools.length > 0) {
|
225
|
+
config[serverName] = tools;
|
226
|
+
}
|
227
|
+
}
|
228
|
+
return config;
|
229
|
+
}
|
230
|
+
getMcpServerInternal(mcpServerName) {
|
231
|
+
const server = this.mcpServers[mcpServerName];
|
232
|
+
if (server) {
|
233
|
+
return server;
|
234
|
+
}
|
235
|
+
throw Error(`unknown server ${mcpServerName}`);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
exports.McpServerManager = McpServerManager;
|
239
|
+
function computeQualifiedName(mcpServerName, toolName) {
|
240
|
+
return `${mcpServerName}__${toolName}`;
|
241
|
+
}
|
242
|
+
function splitQualifiedName(qualifiedToolName) {
|
243
|
+
const delimIdx = qualifiedToolName.indexOf("__");
|
244
|
+
if (delimIdx < 0) {
|
245
|
+
throw Error(`invalid qualified name: ${qualifiedToolName}`);
|
246
|
+
}
|
247
|
+
return [
|
248
|
+
qualifiedToolName.slice(0, delimIdx),
|
249
|
+
qualifiedToolName.slice(delimIdx + 2),
|
250
|
+
];
|
251
|
+
}
|
252
|
+
function computeOpenAIToolList(mcpServers) {
|
253
|
+
const openaiTools = [];
|
254
|
+
for (const mcpServerName in mcpServers) {
|
255
|
+
const mcpServer = mcpServers[mcpServerName];
|
256
|
+
const tools = mcpServer.getTools();
|
257
|
+
const enabled = mcpServer.getEnabledTools();
|
258
|
+
for (const mcpTool of tools) {
|
259
|
+
const toolName = mcpTool.name;
|
260
|
+
if (enabled[toolName]) {
|
261
|
+
const qualifiedName = computeQualifiedName(mcpServerName, toolName);
|
262
|
+
const openaiTool = mcpToolToOpenAITool(mcpTool, qualifiedName);
|
263
|
+
openaiTools.push(openaiTool);
|
264
|
+
}
|
265
|
+
}
|
266
|
+
}
|
267
|
+
return openaiTools;
|
268
|
+
}
|
269
|
+
function mcpToolToOpenAITool(tool, qualifiedName) {
|
270
|
+
return {
|
271
|
+
type: "function",
|
272
|
+
function: {
|
273
|
+
name: qualifiedName || tool.name,
|
274
|
+
description: tool.description,
|
275
|
+
parameters: tool.inputSchema,
|
276
|
+
},
|
277
|
+
};
|
278
|
+
}
|
@@ -0,0 +1,61 @@
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
36
|
+
exports.NODE_PLATFORM = void 0;
|
37
|
+
const fs = __importStar(require("fs"));
|
38
|
+
const child_process_1 = require("child_process");
|
39
|
+
/**
|
40
|
+
* Implementation of the IPlatform interface for node.js
|
41
|
+
*/
|
42
|
+
exports.NODE_PLATFORM = {
|
43
|
+
openUrl: (url) => {
|
44
|
+
const platform = process.platform;
|
45
|
+
if (platform === "darwin") {
|
46
|
+
(0, child_process_1.execSync)(`open '${url}'`);
|
47
|
+
}
|
48
|
+
else if (platform === "linux") {
|
49
|
+
(0, child_process_1.execSync)(`xdg-open '${url}'`);
|
50
|
+
}
|
51
|
+
else if (platform === "win32") {
|
52
|
+
(0, child_process_1.execSync)(`start '${url}'`);
|
53
|
+
}
|
54
|
+
else {
|
55
|
+
throw `Unknown platform ${platform}`;
|
56
|
+
}
|
57
|
+
},
|
58
|
+
load: async (filename) => {
|
59
|
+
return fs.readFileSync(filename, { encoding: "utf8" });
|
60
|
+
},
|
61
|
+
};
|
@@ -0,0 +1,38 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.OpenAILLM = void 0;
|
4
|
+
const openai_1 = require("openai");
|
5
|
+
class OpenAILLM {
|
6
|
+
constructor(apiKey, apiUrl, model) {
|
7
|
+
this.openai = new openai_1.OpenAI({
|
8
|
+
apiKey,
|
9
|
+
baseURL: apiUrl,
|
10
|
+
dangerouslyAllowBrowser: true,
|
11
|
+
});
|
12
|
+
this.model = model || "gpt-4o-mini";
|
13
|
+
}
|
14
|
+
setModel(model) {
|
15
|
+
this.model = model;
|
16
|
+
}
|
17
|
+
getModel() {
|
18
|
+
return this.model;
|
19
|
+
}
|
20
|
+
getUrl() {
|
21
|
+
return this.openai.baseURL;
|
22
|
+
}
|
23
|
+
async getConversationResponse(messages, tools, onMessage) {
|
24
|
+
const completion = await this.openai.chat.completions.create({
|
25
|
+
model: this.model,
|
26
|
+
messages,
|
27
|
+
tools,
|
28
|
+
});
|
29
|
+
if (onMessage) {
|
30
|
+
const message = completion.choices[0].message;
|
31
|
+
if (message.content) {
|
32
|
+
await onMessage(message.content, true);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
return completion;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
exports.OpenAILLM = OpenAILLM;
|