@xalia/agent 0.5.0 → 0.5.2

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 (76) hide show
  1. package/README.md +46 -7
  2. package/dist/{agent.js → agent/src/agent/agent.js} +5 -4
  3. package/dist/{agentUtils.js → agent/src/agent/agentUtils.js} +10 -9
  4. package/dist/{mcpServerManager.js → agent/src/agent/mcpServerManager.js} +2 -1
  5. package/dist/{sudoMcpServerManager.js → agent/src/agent/sudoMcpServerManager.js} +4 -4
  6. package/dist/agent/src/chat/apiKeyManager.js +23 -0
  7. package/dist/agent/src/chat/asyncQueue.js +41 -0
  8. package/dist/agent/src/chat/client.js +126 -0
  9. package/dist/agent/src/chat/conversationManager.js +173 -0
  10. package/dist/agent/src/chat/db.js +186 -0
  11. package/dist/agent/src/chat/messages.js +2 -0
  12. package/dist/agent/src/chat/server.js +158 -0
  13. package/dist/agent/src/index.js +2 -0
  14. package/dist/agent/src/test/db.test.js +73 -0
  15. package/dist/{test → agent/src/test}/imageLoad.test.js +1 -1
  16. package/dist/{test → agent/src/test}/mcpServerManager.test.js +1 -1
  17. package/dist/{test → agent/src/test}/prompt.test.js +1 -1
  18. package/dist/{test → agent/src/test}/sudoMcpServerManager.test.js +3 -3
  19. package/dist/{chat.js → agent/src/tool/agentChat.js} +5 -5
  20. package/dist/{main.js → agent/src/tool/agentMain.js} +9 -15
  21. package/dist/agent/src/tool/chatMain.js +207 -0
  22. package/dist/agent/src/tool/main.js +54 -0
  23. package/dist/{options.js → agent/src/tool/options.js} +36 -2
  24. package/dist/agent/src/utils/asyncLock.js +45 -0
  25. package/dist/supabase/database.types.js +8 -0
  26. package/eslint.config.mjs +14 -14
  27. package/package.json +9 -15
  28. package/scripts/test_chat +84 -0
  29. package/src/{agent.ts → agent/agent.ts} +22 -11
  30. package/src/{agentUtils.ts → agent/agentUtils.ts} +13 -14
  31. package/src/{mcpServerManager.ts → agent/mcpServerManager.ts} +2 -1
  32. package/src/{sudoMcpServerManager.ts → agent/sudoMcpServerManager.ts} +3 -3
  33. package/src/chat/apiKeyManager.ts +24 -0
  34. package/src/chat/asyncQueue.ts +51 -0
  35. package/src/chat/client.ts +142 -0
  36. package/src/chat/conversationManager.ts +283 -0
  37. package/src/chat/db.ts +264 -0
  38. package/src/chat/messages.ts +91 -0
  39. package/src/chat/server.ts +177 -0
  40. package/src/test/db.test.ts +103 -0
  41. package/src/test/imageLoad.test.ts +1 -1
  42. package/src/test/mcpServerManager.test.ts +1 -1
  43. package/src/test/prompt.test.ts +1 -1
  44. package/src/test/sudoMcpServerManager.test.ts +6 -10
  45. package/src/{chat.ts → tool/agentChat.ts} +26 -24
  46. package/src/{main.ts → tool/agentMain.ts} +12 -19
  47. package/src/tool/chatMain.ts +250 -0
  48. package/src/{files.ts → tool/files.ts} +1 -1
  49. package/src/tool/main.ts +25 -0
  50. package/src/{nodePlatform.ts → tool/nodePlatform.ts} +1 -1
  51. package/src/{options.ts → tool/options.ts} +40 -1
  52. package/src/utils/asyncLock.ts +43 -0
  53. package/test_data/simplecalc_profile.json +1 -1
  54. package/test_data/sudomcp_import_profile.json +1 -1
  55. package/test_data/test_script_profile.json +1 -1
  56. package/tsconfig.json +1 -1
  57. package/scripts/test_script +0 -60
  58. /package/dist/{dummyLLM.js → agent/src/agent/dummyLLM.js} +0 -0
  59. /package/dist/{iplatform.js → agent/src/agent/iplatform.js} +0 -0
  60. /package/dist/{llm.js → agent/src/agent/llm.js} +0 -0
  61. /package/dist/{openAILLM.js → agent/src/agent/openAILLM.js} +0 -0
  62. /package/dist/{openAILLMStreaming.js → agent/src/agent/openAILLMStreaming.js} +0 -0
  63. /package/dist/{tokenAuth.js → agent/src/agent/tokenAuth.js} +0 -0
  64. /package/dist/{tools.js → agent/src/agent/tools.js} +0 -0
  65. /package/dist/{files.js → agent/src/tool/files.js} +0 -0
  66. /package/dist/{nodePlatform.js → agent/src/tool/nodePlatform.js} +0 -0
  67. /package/dist/{prompt.js → agent/src/tool/prompt.js} +0 -0
  68. /package/src/{dummyLLM.ts → agent/dummyLLM.ts} +0 -0
  69. /package/src/{iplatform.ts → agent/iplatform.ts} +0 -0
  70. /package/src/{llm.ts → agent/llm.ts} +0 -0
  71. /package/src/{openAILLM.ts → agent/openAILLM.ts} +0 -0
  72. /package/src/{openAILLMStreaming.ts → agent/openAILLMStreaming.ts} +0 -0
  73. /package/src/{tokenAuth.ts → agent/tokenAuth.ts} +0 -0
  74. /package/src/{tools.ts → agent/tools.ts} +0 -0
  75. /package/src/{test/prompt.test.src → index.ts} +0 -0
  76. /package/src/{prompt.ts → tool/prompt.ts} +0 -0
@@ -0,0 +1,207 @@
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.chatMain = void 0;
37
+ const cmd_ts_1 = require("cmd-ts");
38
+ const process_1 = require("process");
39
+ const fs = __importStar(require("fs"));
40
+ const tool_1 = require("@xalia/xmcp/tool");
41
+ const sdk_1 = require("@xalia/xmcp/sdk");
42
+ const client_1 = require("../chat/client");
43
+ const server_1 = require("../chat/server");
44
+ const prompt_1 = require("./prompt");
45
+ const options = __importStar(require("./options"));
46
+ const logger = (0, sdk_1.getLogger)();
47
+ const server = (0, cmd_ts_1.command)({
48
+ name: "server",
49
+ args: {
50
+ port: (0, cmd_ts_1.option)({
51
+ type: cmd_ts_1.number,
52
+ long: "port",
53
+ short: "p",
54
+ env: "CHAT_SERVER_PORT",
55
+ defaultValue: () => 5003,
56
+ }),
57
+ supabaseUrl: options.supabaseUrl,
58
+ supabaseKey: options.supabaseKey,
59
+ llmUrl: options.llmUrl,
60
+ xmcpUrl: options.xmcpUrl,
61
+ pidFile: options.pidFile,
62
+ },
63
+ handler: async ({ port, supabaseUrl, supabaseKey, llmUrl, xmcpUrl, pidFile, }) => {
64
+ if (pidFile) {
65
+ fs.writeFileSync(pidFile, process.pid.toString());
66
+ }
67
+ (0, server_1.runServer)(port, supabaseUrl, supabaseKey, llmUrl, xmcpUrl);
68
+ },
69
+ });
70
+ const client = (0, cmd_ts_1.command)({
71
+ name: "client",
72
+ args: {
73
+ host: (0, cmd_ts_1.option)({
74
+ type: cmd_ts_1.string,
75
+ long: "host",
76
+ short: "h",
77
+ env: "CHAT_SERVER_HOST",
78
+ defaultValue: () => "localhost",
79
+ }),
80
+ port: (0, cmd_ts_1.option)({
81
+ type: cmd_ts_1.number,
82
+ long: "port",
83
+ short: "p",
84
+ env: "CHAT_SERVER_PORT",
85
+ defaultValue: () => 5003,
86
+ }),
87
+ apiKey: options.apiKey,
88
+ session: (0, cmd_ts_1.option)({
89
+ type: cmd_ts_1.string,
90
+ long: "session",
91
+ description: "Session identifier (id, name, user/name)",
92
+ env: "SESSION",
93
+ }),
94
+ agentProfile: (0, cmd_ts_1.option)({
95
+ type: (0, cmd_ts_1.optional)(cmd_ts_1.string),
96
+ long: "agent-profile",
97
+ description: "Create new session using agent profile (id, name)",
98
+ env: "AGENT_PROFILE",
99
+ }),
100
+ script: (0, cmd_ts_1.option)({
101
+ type: (0, cmd_ts_1.optional)(cmd_ts_1.string),
102
+ long: "script",
103
+ description: "Script (file) to execute and then exit.",
104
+ }),
105
+ test: (0, cmd_ts_1.flag)({
106
+ long: "test",
107
+ description: "Run a test client and disconnect",
108
+ }),
109
+ },
110
+ handler: async ({ host, port, apiKey, session, agentProfile, script, test, }) => {
111
+ if (!apiKey) {
112
+ // TODO: configFIle param list in ../main.ts
113
+ const sudomcpConfig = tool_1.configuration.loadEnsureConfig();
114
+ apiKey = sudomcpConfig.api_key;
115
+ }
116
+ if (test) {
117
+ await runClientTest(host, port, apiKey, session);
118
+ return;
119
+ }
120
+ if (script) {
121
+ await runScript(host, port, apiKey, session, agentProfile, script);
122
+ return;
123
+ }
124
+ const onMessage = getCLIOnMessage();
125
+ const repl = new prompt_1.Prompt();
126
+ const client = await client_1.ChatClient.init(host, port, apiKey, onMessage, () => repl.shutdown(), session, agentProfile);
127
+ logger.debug("client created");
128
+ while (true) {
129
+ const msgText = await repl.run("ME: ");
130
+ logger.debug(`prompt got '${msgText}'`);
131
+ if (msgText === undefined) {
132
+ logger.debug("exiting...");
133
+ client.close();
134
+ return;
135
+ }
136
+ if (msgText.length > 0) {
137
+ const msg = {
138
+ type: "msg",
139
+ message: msgText,
140
+ };
141
+ client.sendMessage(msg);
142
+ }
143
+ else {
144
+ logger.debug("(ignoring empty message)");
145
+ }
146
+ }
147
+ },
148
+ });
149
+ function getCLIOnMessage() {
150
+ let startMsg = true;
151
+ return (msg) => {
152
+ switch (msg.type) {
153
+ case "user_msg":
154
+ process_1.stdout.write(`[${msg.from}]: ${msg.message}\n`);
155
+ break;
156
+ case "agent_msg":
157
+ process_1.stdout.write(`[AGENT]: ${msg.message}\n`);
158
+ break;
159
+ case "agent_msg_chunk":
160
+ if (startMsg) {
161
+ process_1.stdout.write("[AGENT]: ");
162
+ startMsg = false;
163
+ }
164
+ process_1.stdout.write(msg.message);
165
+ if (msg.end) {
166
+ process_1.stdout.write("\n");
167
+ startMsg = true;
168
+ }
169
+ break;
170
+ default:
171
+ process_1.stdout.write(`(${JSON.stringify(msg)})\n`);
172
+ break;
173
+ }
174
+ };
175
+ }
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
+ async function runClientTest(host, port, apiKey, session) {
190
+ const client = await client_1.ChatClient.init(host, port, apiKey, getCLIOnMessage(), () => { }, session);
191
+ for (let i = 1; i <= 60; i++) {
192
+ await new Promise((r) => setTimeout(r, 1000));
193
+ const message = {
194
+ type: "msg",
195
+ message: `add ${i} and ${i + 1}`,
196
+ };
197
+ client.sendMessage(message);
198
+ }
199
+ client.close();
200
+ }
201
+ exports.chatMain = (0, cmd_ts_1.subcommands)({
202
+ name: "chat",
203
+ cmds: {
204
+ server,
205
+ client,
206
+ },
207
+ });
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // -*- typescript -*-
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const dotenv = __importStar(require("dotenv"));
39
+ const cmd_ts_1 = require("cmd-ts");
40
+ const chatMain_1 = require("./chatMain");
41
+ const agentMain_1 = require("./agentMain");
42
+ dotenv.config();
43
+ const main = (0, cmd_ts_1.subcommands)({
44
+ name: "chat",
45
+ cmds: {
46
+ agent: agentMain_1.agentMain,
47
+ chat: chatMain_1.chatMain,
48
+ },
49
+ });
50
+ function handleError(msg) {
51
+ console.error(msg);
52
+ process.exit(1);
53
+ }
54
+ (0, cmd_ts_1.run)(main, process.argv.slice(2)).catch(handleError);
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.llmUrl = exports.llmApiKey = exports.approveToolsUpTo = exports.approveTools = exports.oneShot = exports.llmModel = exports.systemPromptFile = exports.imageFile = exports.promptFile = void 0;
3
+ exports.pidFile = exports.supabaseKey = exports.supabaseUrl = exports.apiKey = exports.xmcpUrl = exports.llmUrl = exports.llmApiKey = exports.approveToolsUpTo = exports.approveTools = exports.oneShot = exports.llmModel = exports.systemPromptFile = exports.imageFile = exports.promptFile = void 0;
4
4
  exports.secretOption = secretOption;
5
5
  const cmd_ts_1 = require("cmd-ts");
6
- const agentUtils_1 = require("./agentUtils");
6
+ const agentUtils_1 = require("../agent/agentUtils");
7
+ const db_1 = require("../chat/db");
7
8
  /// Prevents env content from being displayed in the help text.
8
9
  function secretOption({ long, short, env, description, }) {
9
10
  if (env) {
@@ -77,3 +78,36 @@ exports.llmUrl = (0, cmd_ts_1.option)({
77
78
  env: "LLM_URL",
78
79
  defaultValue: () => agentUtils_1.DEFAULT_LLM_URL,
79
80
  });
81
+ exports.xmcpUrl = (0, cmd_ts_1.option)({
82
+ type: cmd_ts_1.string,
83
+ long: "xmcp-url",
84
+ description: "XMCP URL (OpenAI compatible)",
85
+ env: "XMCP_URL",
86
+ defaultValue: () => "http://localhost:5001/",
87
+ });
88
+ exports.apiKey = (0, cmd_ts_1.option)({
89
+ type: (0, cmd_ts_1.optional)(cmd_ts_1.string),
90
+ long: "api-key",
91
+ short: "k",
92
+ description: "API key",
93
+ env: "API_KEY",
94
+ });
95
+ exports.supabaseUrl = (0, cmd_ts_1.option)({
96
+ type: cmd_ts_1.string,
97
+ long: "supabase-url",
98
+ description: "Supabase URL",
99
+ env: "SUPABASE_URL",
100
+ defaultValue: () => db_1.SUPABASE_LOCAL_URL,
101
+ });
102
+ exports.supabaseKey = (0, cmd_ts_1.option)({
103
+ type: cmd_ts_1.string,
104
+ long: "supabase-key",
105
+ description: "Supabase Key",
106
+ env: "SUPABASE_KEY",
107
+ defaultValue: () => db_1.SUPABASE_LOCAL_KEY,
108
+ });
109
+ exports.pidFile = (0, cmd_ts_1.option)({
110
+ type: (0, cmd_ts_1.optional)(cmd_ts_1.string),
111
+ long: "pid-file",
112
+ description: "write pid file",
113
+ });
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AsyncLock = void 0;
4
+ const assert_1 = require("assert");
5
+ /**
6
+ * Support awaiting exclusive access to a resource.
7
+ */
8
+ class AsyncLock {
9
+ constructor() {
10
+ this.running = false;
11
+ this.callbacks = [];
12
+ }
13
+ lockAndProcess(cb) {
14
+ return new Promise((r, e) => {
15
+ this.callbacks.push(async () => {
16
+ try {
17
+ const result = await cb();
18
+ r(result);
19
+ }
20
+ catch (err) {
21
+ e(err);
22
+ }
23
+ });
24
+ if (!this.running) {
25
+ this.run();
26
+ }
27
+ });
28
+ }
29
+ async run() {
30
+ (0, assert_1.strict)(!this.running);
31
+ this.running = true;
32
+ while (this.callbacks.length > 0) {
33
+ const cb = this.callbacks.shift();
34
+ (0, assert_1.strict)(cb);
35
+ try {
36
+ await cb();
37
+ }
38
+ catch (e) {
39
+ (0, assert_1.strict)(false, `errors should not propagate: ${e}`);
40
+ }
41
+ }
42
+ this.running = false;
43
+ }
44
+ }
45
+ exports.AsyncLock = AsyncLock;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Constants = void 0;
4
+ exports.Constants = {
5
+ public: {
6
+ Enums: {},
7
+ },
8
+ };
package/eslint.config.mjs CHANGED
@@ -1,25 +1,25 @@
1
- import eslint from '@eslint/js';
2
- import tseslint from 'typescript-eslint';
1
+ import eslint from "@eslint/js";
2
+ import tseslint from "typescript-eslint";
3
3
 
4
4
  export default tseslint.config(
5
5
  eslint.configs.recommended,
6
6
  tseslint.configs.recommended,
7
7
  {
8
- "rules": {
8
+ rules: {
9
9
  "@typescript-eslint/no-unused-vars": [
10
10
  "error",
11
11
  {
12
- "args": "all",
13
- "argsIgnorePattern": "^_",
14
- "caughtErrors": "all",
15
- "caughtErrorsIgnorePattern": "^_",
16
- "destructuredArrayIgnorePattern": "^_",
17
- "varsIgnorePattern": "^_",
18
- "ignoreRestSiblings": true
19
- }
12
+ args: "all",
13
+ argsIgnorePattern: "^_",
14
+ caughtErrors: "all",
15
+ caughtErrorsIgnorePattern: "^_",
16
+ destructuredArrayIgnorePattern: "^_",
17
+ varsIgnorePattern: "^_",
18
+ ignoreRestSiblings: true,
19
+ },
20
20
  ],
21
21
  "max-len": ["error", { code: 80 }],
22
- "linebreak-style": ["error", "unix"]
23
- }
24
- }
22
+ "linebreak-style": ["error", "unix"],
23
+ },
24
+ },
25
25
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xalia/agent",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "keywords": [],
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -9,34 +9,28 @@
9
9
  "node": ">=20.0.0"
10
10
  },
11
11
  "bin": {
12
- "agent": "dist/main.js"
12
+ "agent": "dist/agent/src/tool/main.js"
13
13
  },
14
14
  "scripts": {
15
- "build": "tsc && node -e \"require('fs').chmodSync('dist/main.js', '755')\"",
15
+ "build": "tsc && node -e \"require('fs').chmodSync('dist/agent/src/tool/main.js', '755')\"",
16
16
  "lint": "eslint src && yarn prettier --check src/**/*.ts",
17
17
  "format": "yarn prettier --write src/**/*.ts",
18
- "test": "yarn --emoji false build && mocha dist/test/**.js --reporter-option maxDiffSize=0"
18
+ "test": "yarn --emoji false build && mocha dist/agent/src/test/**.js --reporter-option maxDiffSize=0"
19
19
  },
20
20
  "dependencies": {
21
21
  "@modelcontextprotocol/sdk": "=1.11.1",
22
+ "@supabase/supabase-js": "2.49.7",
22
23
  "@types/readline-sync": "^1.4.8",
23
24
  "chalk": "^4.1.2",
24
25
  "cmd-ts": "^0.13.0",
25
26
  "dotenv": "^16.5.0",
26
27
  "openai": "=4.98.0",
27
28
  "readline-sync": "^1.4.10",
28
- "yocto-spinner": "^0.2.2"
29
+ "uuid": "^11.1.0",
30
+ "yocto-spinner": "^0.2.2",
31
+ "ws": "8.18.2"
29
32
  },
30
33
  "devDependencies": {
31
- "@eslint/js": "^9.24.0",
32
- "@types/chai": "^4.0.0",
33
- "@types/mocha": "^10.0.10",
34
- "@types/node": "^22.13.9",
35
- "chai": "^4.0.0",
36
- "eslint": "^9.24.0",
37
- "mocha": "^11.1.0",
38
- "prettier": "3.5.3",
39
- "typescript": "^5.8.3",
40
- "typescript-eslint": "^8.29.1"
34
+ "@types/ws": "^8.18.0"
41
35
  }
42
36
  }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Assumes backend and DB are already running
4
+
5
+ . ../mcppro/env/bin/activate
6
+
7
+ set -x
8
+ set -e
9
+
10
+
11
+ yarn
12
+ yarn build
13
+ agent=`realpath dist/agent/src/tool/main.js`
14
+ client=`realpath ../client/dist/src/tool/main.js`
15
+
16
+ function stop_server() {
17
+ if [ -e chat.pid ] ; then
18
+ kill `cat chat.pid` || echo -n
19
+ fi
20
+ }
21
+
22
+ stop_server
23
+
24
+ LOG_LEVEL=debug ${agent} chat server --pid-file chat.pid > chat_server.log 2>&1 &
25
+ sleep 1
26
+
27
+ mkdir -p _test_chat
28
+ pushd _test_chat
29
+
30
+ echo "XMCP_ADMIN_SECRET=admin_secret" > .env
31
+
32
+ # Create 2 users and give them api keys
33
+
34
+ ${client} user get chatuser0 || \
35
+ mcppro dev add-user chatuser0 chatuser0@users.com "Europe/London"
36
+ ${client} user get chatuser1 || \
37
+ mcppro dev add-user chatuser1 chatuser1@users.com "UTC"
38
+
39
+ apikey0=`${client} admin get-api-keys chatuser0 | jq -r .default.api_key`
40
+ if [ "null" == "${apikey0}" ] ; then
41
+ apikey0=`${client} admin create-api-key chatuser0 default [] | jq -r .api_key`
42
+ fi
43
+ apikey1=`${client} admin get-api-keys chatuser1 | jq -r .default.api_key`
44
+ if [ "null" == "${apikey1}" ] ; then
45
+ apikey1=`${client} admin create-api-key chatuser1 default [] | jq -r .api_key`
46
+ fi
47
+
48
+ echo "${apikey0}" > chatuser0.apikey
49
+ echo "${apikey1}" > chatuser1.apikey
50
+
51
+ # User 0 creates an agent profile
52
+
53
+ echo '{"system_prompt":"You are a helpful agent talking to multiple users. Users put their name at the beginning of their messages, e.g. `chatuser1: <message>`.","mcp_settings":{"duckduckgo-search":[]}}' > profile0.json
54
+ ${client} config --force --dev --api-key ${apikey0}
55
+ ${client} agent-profile set profile0 --profile profile0.json
56
+
57
+ # User 0 creates a session and asks for a joke
58
+
59
+ echo 'tell me a joke' > script0
60
+ ${agent} chat client --session test_session --agent-profile profile0 --script script0
61
+
62
+ # Run both clients with a script that continues the conversation
63
+
64
+ echo -e "why is that funny?\ntell me another.\nwhat is my name?\nhow old are you?\nwhere are your parents?" > script1
65
+ ${agent} chat client --session chatuser0/test_session --api-key ${apikey1} --script script1 >chatuser1.output &
66
+ ${agent} chat client --session chatuser0/test_session --script script1
67
+
68
+ # The name chatuser0 should appear in chatuser1's session output
69
+
70
+ if ! (grep chatuser0 chatuser1.output) ; then
71
+ echo "error: expected chatuser0 in chatuser1's output"
72
+ exit 1
73
+ fi
74
+
75
+ popd
76
+
77
+ stop_server
78
+
79
+ set +e
80
+ set +x
81
+
82
+ echo "============================"
83
+ echo "== CHAT TEST PASSED =="
84
+ echo "============================"
@@ -14,6 +14,11 @@ export type ToolHandler = (args: unknown) => string;
14
14
 
15
15
  export type McpServerUrls = (name: string) => string;
16
16
 
17
+ export type ChatCompletionMessageParam = OpenAI.ChatCompletionMessageParam;
18
+
19
+ export type ChatCompletionMessageToolCall =
20
+ OpenAI.ChatCompletionMessageToolCall;
21
+
17
22
  // Role: If content, give it to UI
18
23
  export type OnMessageCB = {
19
24
  (msg: string, msgEnd: boolean): Promise<void>;
@@ -21,7 +26,7 @@ export type OnMessageCB = {
21
26
 
22
27
  // Role: If tool calls, prompt for permission to handle them
23
28
  export type OnToolCallCB = {
24
- (msg: OpenAI.ChatCompletionMessageToolCall): Promise<boolean>;
29
+ (msg: ChatCompletionMessageToolCall): Promise<boolean>;
25
30
  };
26
31
 
27
32
  dotenv.config();
@@ -33,7 +38,7 @@ export class Agent {
33
38
  private constructor(
34
39
  public onMessage: OnMessageCB,
35
40
  public onToolCall: OnToolCallCB,
36
- private messages: OpenAI.ChatCompletionMessageParam[],
41
+ private messages: ChatCompletionMessageParam[],
37
42
  private mcpServerManager: McpServerManager,
38
43
  private tools: OpenAI.ChatCompletionTool[],
39
44
  private llm: ILLM
@@ -78,7 +83,7 @@ export class Agent {
78
83
  );
79
84
  }
80
85
 
81
- public getConversation(): OpenAI.ChatCompletionMessageParam[] {
86
+ public getConversation(): ChatCompletionMessageParam[] {
82
87
  assert(
83
88
  this.messages[0].role == "system",
84
89
  "first message must have system role"
@@ -88,11 +93,14 @@ export class Agent {
88
93
  return structuredClone(this.messages.slice(1));
89
94
  }
90
95
 
91
- public setConversation(messages: OpenAI.ChatCompletionMessageParam[]) {
96
+ public setConversation(messages: ChatCompletionMessageParam[]) {
92
97
  assert(this.messages[0].role == "system");
93
- assert(messages[0].role != "system", "conversation contains system msg");
98
+ assert(
99
+ messages.length === 0 || messages[0].role != "system",
100
+ "conversation contains system msg"
101
+ );
94
102
 
95
- const newMessages: OpenAI.ChatCompletionMessageParam[] = [this.messages[0]];
103
+ const newMessages: ChatCompletionMessageParam[] = [this.messages[0]];
96
104
  this.messages = newMessages.concat(structuredClone(messages));
97
105
  }
98
106
 
@@ -102,9 +110,10 @@ export class Agent {
102
110
 
103
111
  public async userMessage(
104
112
  msg?: string,
105
- imageB64?: string
106
- ): Promise<OpenAI.ChatCompletionMessageParam | undefined> {
107
- const userMessage = createUserMessage(msg, imageB64);
113
+ imageB64?: string,
114
+ name?: string
115
+ ): Promise<ChatCompletionMessageParam | undefined> {
116
+ const userMessage = createUserMessage(msg, imageB64, name);
108
117
  if (!userMessage) {
109
118
  return undefined;
110
119
  }
@@ -216,7 +225,7 @@ export class Agent {
216
225
  }
217
226
 
218
227
  async doToolCall(
219
- toolCall: OpenAI.ChatCompletionMessageToolCall
228
+ toolCall: ChatCompletionMessageToolCall
220
229
  ): Promise<OpenAI.ChatCompletionToolMessageParam> {
221
230
  const name = toolCall.function.name;
222
231
  const args = JSON.parse(toolCall.function.arguments);
@@ -244,7 +253,8 @@ export class Agent {
244
253
  **/
245
254
  export function createUserMessage(
246
255
  msg?: string,
247
- imageB64?: string
256
+ imageB64?: string,
257
+ name?: string
248
258
  ): ChatCompletionUserMessageParam | undefined {
249
259
  const content = (() => {
250
260
  if (!imageB64) {
@@ -279,5 +289,6 @@ export function createUserMessage(
279
289
  return {
280
290
  role: "user",
281
291
  content,
292
+ name,
282
293
  };
283
294
  }