@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.
Files changed (66) hide show
  1. package/.prettierrc.json +11 -0
  2. package/README.md +56 -0
  3. package/dist/agent.js +238 -0
  4. package/dist/agentUtils.js +106 -0
  5. package/dist/chat.js +296 -0
  6. package/dist/dummyLLM.js +38 -0
  7. package/dist/files.js +115 -0
  8. package/dist/iplatform.js +2 -0
  9. package/dist/llm.js +2 -0
  10. package/dist/main.js +147 -0
  11. package/dist/mcpServerManager.js +278 -0
  12. package/dist/nodePlatform.js +61 -0
  13. package/dist/openAILLM.js +38 -0
  14. package/dist/openAILLMStreaming.js +431 -0
  15. package/dist/options.js +79 -0
  16. package/dist/prompt.js +83 -0
  17. package/dist/sudoMcpServerManager.js +183 -0
  18. package/dist/test/imageLoad.test.js +14 -0
  19. package/dist/test/mcpServerManager.test.js +71 -0
  20. package/dist/test/prompt.test.js +26 -0
  21. package/dist/test/sudoMcpServerManager.test.js +49 -0
  22. package/dist/tokenAuth.js +39 -0
  23. package/dist/tools.js +44 -0
  24. package/eslint.config.mjs +25 -0
  25. package/frog.png +0 -0
  26. package/package.json +42 -0
  27. package/scripts/git_message +31 -0
  28. package/scripts/git_wip +21 -0
  29. package/scripts/pr_message +18 -0
  30. package/scripts/pr_review +16 -0
  31. package/scripts/sudomcp_import +23 -0
  32. package/scripts/test_script +60 -0
  33. package/src/agent.ts +283 -0
  34. package/src/agentUtils.ts +198 -0
  35. package/src/chat.ts +346 -0
  36. package/src/dummyLLM.ts +50 -0
  37. package/src/files.ts +95 -0
  38. package/src/iplatform.ts +17 -0
  39. package/src/llm.ts +15 -0
  40. package/src/main.ts +187 -0
  41. package/src/mcpServerManager.ts +371 -0
  42. package/src/nodePlatform.ts +24 -0
  43. package/src/openAILLM.ts +51 -0
  44. package/src/openAILLMStreaming.ts +528 -0
  45. package/src/options.ts +103 -0
  46. package/src/prompt.ts +93 -0
  47. package/src/sudoMcpServerManager.ts +278 -0
  48. package/src/test/imageLoad.test.ts +14 -0
  49. package/src/test/mcpServerManager.test.ts +98 -0
  50. package/src/test/prompt.test.src +0 -0
  51. package/src/test/prompt.test.ts +26 -0
  52. package/src/test/sudoMcpServerManager.test.ts +65 -0
  53. package/src/tokenAuth.ts +50 -0
  54. package/src/tools.ts +57 -0
  55. package/test_data/background_test_profile.json +6 -0
  56. package/test_data/background_test_script.json +11 -0
  57. package/test_data/dummyllm_script_simplecalc.json +28 -0
  58. package/test_data/git_message_profile.json +4 -0
  59. package/test_data/git_wip_system.txt +5 -0
  60. package/test_data/pr_message_profile.json +4 -0
  61. package/test_data/pr_review_profile.json +4 -0
  62. package/test_data/prompt_simplecalc.txt +1 -0
  63. package/test_data/simplecalc_profile.json +4 -0
  64. package/test_data/sudomcp_import_profile.json +4 -0
  65. package/test_data/test_script_profile.json +8 -0
  66. package/tsconfig.json +13 -0
@@ -0,0 +1,11 @@
1
+ {
2
+ "overrides": [
3
+ {
4
+ "files": "*.ts",
5
+ "options": {
6
+ "printWidth": 80,
7
+ "trailingComma": "es5"
8
+ }
9
+ }
10
+ ]
11
+ }
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Sudobase Agent
2
+
3
+ ## Setup
4
+
5
+ ```sh
6
+ # In the root folder
7
+ yarn
8
+ yarn workspaces run build
9
+ ```
10
+
11
+ Run a local backend server (follow instructions in `mcppro`) because authentication against deployed backend is WIP.
12
+
13
+ ## Usage:
14
+ To enter a chat with no initial prompt and default system prompt:
15
+ ```sh
16
+ node dist/main.js
17
+ ```
18
+
19
+ Optional arguments are `prompt` (first User message) and `systemprompt`
20
+ ```sh
21
+ node dist/main.js --prompt 'Who is the new pope?' --sysprompt 'You are extremely polite.'
22
+ ```
23
+
24
+ ## Features:
25
+ ### Conversation:
26
+ CLI-mode is a conversation between user and LLM.
27
+
28
+ ### Tool selection:
29
+ We now support MCP tool calls. Currently servers are enabled by editing the `mcpServerUrls.json` file, but this will be improved soon.
30
+
31
+ ### Model selection:
32
+ The CLI uses the default model (`gpt-4o-mini`) but uncomment the `agent.chooseModel` line to switch to `gpt-4.1-2025-04-14`. Right now we can use any OpenAI model that supports tool calling.
33
+
34
+ Supporting inference providers like Together.ai is TODO.
35
+
36
+ ### Callbacks
37
+ The CLI uses an `onMessage` callback to display the Agent's messages and an `onToolCall` callback to request authorization for tool calls.
38
+
39
+ ## Development Notes
40
+
41
+ ### Architecture
42
+ Frontend talks to
43
+ - Agent (for conversation, ChatCompletion)
44
+ - McpServerManager (to enable, disable tools that have been added)
45
+ - SudoMcpServerManager (to access catalog of SudoMCP servers, add to McpServerManager)
46
+
47
+ SudoMcpServerManager:
48
+ - track list of available mcp servers (via sdk/ApiClient)
49
+ - get the list of tools as required by UI (via sdk/ApiClient)
50
+ - add tools to McpServerManager
51
+
52
+ McpServerManager:
53
+ - manager (mcpServer, tool)
54
+ - enabling / disabling
55
+ - list of enabled / available tools per mcp server
56
+ - exposes tools to Agent
package/dist/agent.js ADDED
@@ -0,0 +1,238 @@
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.Agent = exports.AgentProfile = void 0;
37
+ exports.createUserMessage = createUserMessage;
38
+ const dotenv = __importStar(require("dotenv"));
39
+ const mcpServerManager_1 = require("./mcpServerManager");
40
+ const assert_1 = require("assert");
41
+ const sdk_1 = require("@xalia/xmcp/sdk");
42
+ var sdk_2 = require("@xalia/xmcp/sdk");
43
+ Object.defineProperty(exports, "AgentProfile", { enumerable: true, get: function () { return sdk_2.AgentProfile; } });
44
+ dotenv.config();
45
+ const logger = (0, sdk_1.getLogger)();
46
+ class Agent {
47
+ constructor(onMessage, onToolCall, messages, mcpServerManager, tools, llm) {
48
+ this.onMessage = onMessage;
49
+ this.onToolCall = onToolCall;
50
+ this.messages = messages;
51
+ this.mcpServerManager = mcpServerManager;
52
+ this.tools = tools;
53
+ this.llm = llm;
54
+ this.toolHandlers = {};
55
+ }
56
+ static async initializeWithLLM(onMessage, onToolCall, systemPrompt, llm) {
57
+ // Initialize messages with system prompt
58
+ const messages = [
59
+ {
60
+ role: "system",
61
+ content: systemPrompt ?? "You are a helpful assistant",
62
+ },
63
+ ];
64
+ // Create the server manager
65
+ const mcpServerManager = new mcpServerManager_1.McpServerManager();
66
+ return new Agent(onMessage, onToolCall, messages, mcpServerManager, [], llm);
67
+ }
68
+ async shutdown() {
69
+ return this.mcpServerManager.shutdown();
70
+ }
71
+ getAgentProfile() {
72
+ return new sdk_1.AgentProfile(this.llm.getModel(), this.getSystemMessage(), this.mcpServerManager.getMcpServerSettings());
73
+ }
74
+ getConversation() {
75
+ (0, assert_1.strict)(this.messages[0].role == "system", "first message must have system role");
76
+ // Return a copy so future modifications to `this.messages` don't impact
77
+ // the callers copy.
78
+ return structuredClone(this.messages.slice(1));
79
+ }
80
+ setConversation(messages) {
81
+ (0, assert_1.strict)(this.messages[0].role == "system");
82
+ (0, assert_1.strict)(messages[0].role != "system", "conversation contains system msg");
83
+ const newMessages = [this.messages[0]];
84
+ this.messages = newMessages.concat(structuredClone(messages));
85
+ }
86
+ getMcpServerManager() {
87
+ return this.mcpServerManager;
88
+ }
89
+ async userMessage(msg, imageB64) {
90
+ const userMessage = createUserMessage(msg, imageB64);
91
+ if (!userMessage) {
92
+ return undefined;
93
+ }
94
+ this.messages.push(userMessage);
95
+ let completion = await this.chatCompletion();
96
+ let message = completion.choices[0].message;
97
+ this.messages.push(message);
98
+ // While there are tool calls to make, make them and loop
99
+ while (message.tool_calls && message.tool_calls.length > 0) {
100
+ for (const toolCall of message.tool_calls ?? []) {
101
+ const approval = await this.onToolCall(toolCall);
102
+ if (approval) {
103
+ try {
104
+ const result = await this.doToolCall(toolCall);
105
+ logger.debug(`tool call result ${JSON.stringify(result)}`);
106
+ this.messages.push(result);
107
+ }
108
+ catch (e) {
109
+ logger.error(`tool call error: ${e}`);
110
+ this.messages.push({
111
+ role: "tool",
112
+ tool_call_id: toolCall.id,
113
+ content: "Tool call failed.",
114
+ });
115
+ }
116
+ }
117
+ else {
118
+ this.messages.push({
119
+ role: "tool",
120
+ tool_call_id: toolCall.id,
121
+ content: "User denied tool use request.",
122
+ });
123
+ }
124
+ }
125
+ completion = await this.chatCompletion();
126
+ message = completion.choices[0].message;
127
+ this.messages.push(message);
128
+ }
129
+ return completion.choices[0].message;
130
+ }
131
+ chooseModel(model) {
132
+ logger.debug(`Set model ${model}`);
133
+ this.llm.setModel(model);
134
+ }
135
+ /**
136
+ * Clear the conversation.
137
+ */
138
+ resetConversation() {
139
+ (0, assert_1.strict)(this.messages.length > 0);
140
+ // Keep only the system message
141
+ this.messages.splice(1);
142
+ }
143
+ getSystemMessage() {
144
+ (0, assert_1.strict)(this.messages[0].role === "system");
145
+ return this.messages[0].content;
146
+ }
147
+ /**
148
+ * Set the system prompt
149
+ */
150
+ setSystemMessage(systemMsg) {
151
+ (0, assert_1.strict)(this.messages[0].role === "system");
152
+ this.messages[0].content = systemMsg;
153
+ }
154
+ async chatCompletion() {
155
+ let tools;
156
+ const enabledTools = this.tools.concat(this.mcpServerManager.getOpenAITools());
157
+ if (enabledTools.length > 0) {
158
+ tools = enabledTools;
159
+ }
160
+ // logger.debug(
161
+ // `chatCompletion: tools: ${JSON.stringify(tools, undefined, 2)}`
162
+ // );
163
+ const completion = await this.llm.getConversationResponse(this.messages, tools, this.onMessage);
164
+ logger.debug(`Received chat completion ${JSON.stringify(completion)}`);
165
+ return completion;
166
+ }
167
+ toolNames() {
168
+ return this.mcpServerManager
169
+ .getOpenAITools()
170
+ .map((tool) => tool.function.name);
171
+ }
172
+ addTool(tool, handler) {
173
+ const name = tool.function.name;
174
+ if (this.toolHandlers[name]) {
175
+ throw `tool ${name} already added`;
176
+ }
177
+ logger.debug(`Adding tool ${name}`);
178
+ this.tools.push(tool);
179
+ this.toolHandlers[name] = handler;
180
+ }
181
+ async doToolCall(toolCall) {
182
+ const name = toolCall.function.name;
183
+ const args = JSON.parse(toolCall.function.arguments);
184
+ let result = undefined;
185
+ const handler = this.toolHandlers[name];
186
+ if (handler) {
187
+ logger.debug(` found agent tool ${name} ...`);
188
+ result = handler(args);
189
+ }
190
+ else {
191
+ result = await this.mcpServerManager.invoke(name, args);
192
+ }
193
+ return {
194
+ role: "tool",
195
+ tool_call_id: toolCall.id,
196
+ content: result.toString(),
197
+ };
198
+ }
199
+ }
200
+ exports.Agent = Agent;
201
+ /**
202
+ * Returns the ChatCompletionMessageParam constructed from (optional) text and
203
+ * (optional) image. If neither is given (null message), then undefined is
204
+ * returned.
205
+ **/
206
+ function createUserMessage(msg, imageB64) {
207
+ const content = (() => {
208
+ if (!imageB64) {
209
+ if (!msg) {
210
+ return undefined;
211
+ }
212
+ return msg;
213
+ }
214
+ const content = [];
215
+ if (msg) {
216
+ content.push({
217
+ type: "text",
218
+ text: msg,
219
+ });
220
+ }
221
+ if (imageB64) {
222
+ content.push({
223
+ type: "image_url",
224
+ image_url: {
225
+ url: imageB64,
226
+ },
227
+ });
228
+ }
229
+ return content;
230
+ })();
231
+ if (!content) {
232
+ return undefined;
233
+ }
234
+ return {
235
+ role: "user",
236
+ content,
237
+ };
238
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_LLM_URL = void 0;
4
+ exports.createAgentAndSudoMcpServerManager = createAgentAndSudoMcpServerManager;
5
+ exports.createNonInteractiveAgent = createNonInteractiveAgent;
6
+ exports.runOneShot = runOneShot;
7
+ const sdk_1 = require("@xalia/xmcp/sdk");
8
+ const agent_1 = require("./agent");
9
+ const sudoMcpServerManager_1 = require("./sudoMcpServerManager");
10
+ const openAILLM_1 = require("./openAILLM");
11
+ const openAILLMStreaming_1 = require("./openAILLMStreaming");
12
+ const dummyLLM_1 = require("./dummyLLM");
13
+ const assert_1 = require("assert");
14
+ const logger = (0, sdk_1.getLogger)();
15
+ exports.DEFAULT_LLM_URL = "http://localhost:5001/v1";
16
+ /**
17
+ * Util function to create an Agent from some config information.
18
+ */
19
+ async function createAgent(llmUrl, model, systemPrompt, onMessage, onToolCall, platform, openaiApiKey, stream = false) {
20
+ let llm;
21
+ if (model && model.startsWith("dummy:")) {
22
+ // Dummy Agent
23
+ const llmUrl = model.slice(6);
24
+ if (llmUrl.length === 0) {
25
+ throw "malformed dummy:<script>";
26
+ }
27
+ const script = await platform.load(llmUrl);
28
+ logger.debug(` script: ${script}`);
29
+ const responses = JSON.parse(script);
30
+ logger.debug(`Initializing Dummy Agent: ${llmUrl}`);
31
+ llm = new dummyLLM_1.DummyLLM(responses);
32
+ }
33
+ else {
34
+ // Regular Agent
35
+ if (!openaiApiKey) {
36
+ throw "Missing OpenAI API Key";
37
+ }
38
+ logger.debug(`Initializing Agent: ${llmUrl} - ${model}`);
39
+ if (stream) {
40
+ llm = new openAILLMStreaming_1.OpenAILLMStreaming(openaiApiKey, llmUrl, model);
41
+ }
42
+ else {
43
+ llm = new openAILLM_1.OpenAILLM(openaiApiKey, llmUrl, model);
44
+ }
45
+ }
46
+ (0, assert_1.strict)(llm);
47
+ return agent_1.Agent.initializeWithLLM(onMessage, onToolCall, systemPrompt, llm);
48
+ }
49
+ /**
50
+ * Util function to create and initialize an Agent given an AgentProfile.
51
+ */
52
+ async function createAgentAndSudoMcpServerManager(url, agentProfile, onMessage, onToolCall, platform, openaiApiKey, sudomcpConfig, authorizedUrl, conversation, stream = false) {
53
+ // Create agent
54
+ logger.debug("[createAgentAndSudoMcpServerManager] creating agent ...");
55
+ const agent = await createAgent(url, agentProfile.model, agentProfile.system_prompt, onMessage, onToolCall, platform, openaiApiKey, stream);
56
+ if (conversation) {
57
+ agent.setConversation(conversation);
58
+ }
59
+ // Init SudoMcpServerManager
60
+ logger.debug("[createAgentAndSudoMcpServerManager] creating SudoMcpServerManager.");
61
+ const sudoMcpServerManager = await sudoMcpServerManager_1.SudoMcpServerManager.initialize(agent.getMcpServerManager(), platform.openUrl, sudomcpConfig.backend_url, sudomcpConfig.api_key, authorizedUrl);
62
+ logger.debug("[createAgentAndSudoMcpServerManager] restore mcp settings:" +
63
+ JSON.stringify(agentProfile.mcp_settings));
64
+ await sudoMcpServerManager.restoreMcpSettings(agentProfile.mcp_settings);
65
+ logger.debug("[createAgentAndSudoMcpServerManager] done");
66
+ return [agent, sudoMcpServerManager];
67
+ }
68
+ /**
69
+ * An "non-interactive" agent is one which is not intended to be used
70
+ * interactively (settings cannot be dyanmically adjusted, intermediate
71
+ * messages are not used by the caller, the user does not need to approve tool
72
+ * calls, etc).
73
+ */
74
+ async function createNonInteractiveAgent(url, agentProfile, conversation, platform, openaiApiKey, sudomcpConfig, approveToolsUpTo) {
75
+ let remainingToolCalls = approveToolsUpTo;
76
+ const onMessage = async () => { };
77
+ const onToolCall = async () => {
78
+ if (remainingToolCalls !== 0) {
79
+ --remainingToolCalls;
80
+ return true;
81
+ }
82
+ return false;
83
+ };
84
+ const [agent, _] = await createAgentAndSudoMcpServerManager(url, agentProfile, onMessage, onToolCall, platform, openaiApiKey, sudomcpConfig, undefined, conversation);
85
+ return agent;
86
+ }
87
+ /**
88
+ * Create an Agent (from the AgentProfile), pass it a single prompt and output
89
+ * the response.
90
+ */
91
+ async function runOneShot(url, agentProfile, conversation, platform, prompt, image, llmApiKey, sudomcpConfig, approveToolsUpTo) {
92
+ logger.debug("[runOneShot]: start");
93
+ // Create a non-interactive agent and pass any prompt/ image to it. Return
94
+ // the first answer.
95
+ const agent = await createNonInteractiveAgent(url, agentProfile, conversation, platform, llmApiKey, sudomcpConfig, approveToolsUpTo);
96
+ const response = await agent.userMessage(prompt, image);
97
+ await agent.shutdown();
98
+ logger.debug("[runOneShot]: shutdown done");
99
+ if (!response) {
100
+ throw "No message returned from agent";
101
+ }
102
+ return {
103
+ response: "" + response.content,
104
+ conversation: agent.getConversation(),
105
+ };
106
+ }