@mhingston5/conduit 1.1.4 → 1.1.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/README.md +13 -2
- package/dist/index.js +241 -42
- package/dist/index.js.map +1 -1
- package/dist/pyodide.worker.js.map +1 -0
- package/package.json +1 -1
- package/src/auth.cmd.ts +161 -14
- package/src/core/request.controller.ts +51 -3
- package/src/executors/pyodide.executor.ts +9 -4
- package/src/gateway/auth.service.ts +17 -7
- package/src/gateway/gateway.service.ts +10 -10
- package/src/gateway/upstream.client.ts +11 -3
- package/src/index.ts +20 -3
- package/src/sdk/sdk-generator.ts +26 -2
- package/tests/__snapshots__/assets.test.ts.snap +27 -3
- package/tests/dynamic.tool.test.ts +3 -3
- package/tests/gateway.manifest.test.ts +1 -1
- package/tests/gateway.service.test.ts +1 -1
- package/tests/reference_mcp.ts +5 -3
- package/tests/routing.test.ts +1 -1
- package/tests/sdk/sdk-generator.test.ts +3 -2
- package/tsup.config.ts +1 -1
- package/dist/executors/pyodide.worker.js.map +0 -1
- /package/dist/{executors/pyodide.worker.d.ts → pyodide.worker.d.ts} +0 -0
- /package/dist/{executors/pyodide.worker.js → pyodide.worker.js} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
exports[`Asset Integrity (Golden Tests) > should match Isolate SDK snapshot 1`] = `
|
|
4
4
|
"// Generated SDK for isolated-vm
|
|
5
5
|
const __allowedTools = ["test__*","github__*"];
|
|
6
|
-
const
|
|
6
|
+
const _tools = {
|
|
7
7
|
test: {
|
|
8
8
|
async hello(args) {
|
|
9
9
|
const resStr = await __callTool("test__hello", JSON.stringify(args || {}));
|
|
@@ -28,7 +28,19 @@ const tools = {
|
|
|
28
28
|
const resStr = await __callTool(normalized, JSON.stringify(args || {}));
|
|
29
29
|
return JSON.parse(resStr);
|
|
30
30
|
},
|
|
31
|
-
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const tools = new Proxy(_tools, {
|
|
34
|
+
get: (target, prop) => {
|
|
35
|
+
if (prop in target) return target[prop];
|
|
36
|
+
if (prop === 'then') return undefined;
|
|
37
|
+
if (typeof prop === 'string') {
|
|
38
|
+
throw new Error(\`Namespace '\${prop}' not found. It might be invalid, or all tools in it were disallowed.\`);
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
"
|
|
32
44
|
`;
|
|
33
45
|
|
|
34
46
|
exports[`Asset Integrity (Golden Tests) > should match Python SDK snapshot 1`] = `
|
|
@@ -67,7 +79,7 @@ tools = _Tools()"
|
|
|
67
79
|
exports[`Asset Integrity (Golden Tests) > should match TypeScript SDK snapshot 1`] = `
|
|
68
80
|
"// Generated SDK - Do not edit
|
|
69
81
|
const __allowedTools = ["test__*","github__*"];
|
|
70
|
-
const
|
|
82
|
+
const _tools = {
|
|
71
83
|
test: {
|
|
72
84
|
/** Returns a greeting */
|
|
73
85
|
async hello(args) {
|
|
@@ -93,5 +105,17 @@ const tools = {
|
|
|
93
105
|
return await __internalCallTool(normalized, args);
|
|
94
106
|
},
|
|
95
107
|
};
|
|
108
|
+
|
|
109
|
+
const tools = new Proxy(_tools, {
|
|
110
|
+
get: (target, prop) => {
|
|
111
|
+
if (prop in target) return target[prop];
|
|
112
|
+
if (prop === 'then') return undefined;
|
|
113
|
+
if (typeof prop === 'string') {
|
|
114
|
+
throw new Error(\`Namespace '\${prop}' not found. It might be invalid, or all tools in it were disallowed.\`);
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
96
120
|
(globalThis as any).tools = tools;"
|
|
97
121
|
`;
|
|
@@ -44,10 +44,10 @@ describe('Dynamic Tool Calling (E2E)', () => {
|
|
|
44
44
|
// Register a mock upstream tool
|
|
45
45
|
(gatewayService as any).clients.set('mock', {
|
|
46
46
|
call: vi.fn().mockImplementation((req) => {
|
|
47
|
-
if (req.method === '
|
|
47
|
+
if (req.method === 'tools/call' && req.params.name === 'hello') {
|
|
48
48
|
return { jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text: `Hello ${req.params.arguments.name}` }] } };
|
|
49
49
|
}
|
|
50
|
-
if (req.method === '
|
|
50
|
+
if (req.method === 'tools/list') {
|
|
51
51
|
return { jsonrpc: '2.0', id: req.id, result: { tools: [{ name: 'hello', inputSchema: {} }] } };
|
|
52
52
|
}
|
|
53
53
|
return { jsonrpc: '2.0', id: req.id, result: {} };
|
|
@@ -227,7 +227,7 @@ print(f"RESULT:{result}")
|
|
|
227
227
|
const request = callArgs[0];
|
|
228
228
|
|
|
229
229
|
expect(request).toMatchObject({
|
|
230
|
-
method: '
|
|
230
|
+
method: 'tools/call',
|
|
231
231
|
params: {
|
|
232
232
|
name: 'hello',
|
|
233
233
|
arguments: { name: 'Isolate' }
|
|
@@ -56,7 +56,7 @@ describe('GatewayService (Manifests)', () => {
|
|
|
56
56
|
const stubs = await gateway.listToolStubs('test-upstream', context);
|
|
57
57
|
|
|
58
58
|
expect(mockClient.getManifest).toHaveBeenCalled();
|
|
59
|
-
expect(mockClient.call).toHaveBeenCalledWith(expect.objectContaining({ method: '
|
|
59
|
+
expect(mockClient.call).toHaveBeenCalledWith(expect.objectContaining({ method: 'tools/list' }), context);
|
|
60
60
|
expect(stubs).toHaveLength(1);
|
|
61
61
|
expect(stubs[0].id).toBe('test-upstream__tool_rpc');
|
|
62
62
|
});
|
package/tests/reference_mcp.ts
CHANGED
|
@@ -5,7 +5,7 @@ const server = Fastify();
|
|
|
5
5
|
server.post('/', async (request, reply) => {
|
|
6
6
|
const { method, params, id } = request.body as any;
|
|
7
7
|
|
|
8
|
-
if (method === 'list_tools') {
|
|
8
|
+
if (method === 'list_tools' || method === 'tools/list') {
|
|
9
9
|
return {
|
|
10
10
|
jsonrpc: '2.0',
|
|
11
11
|
id,
|
|
@@ -21,12 +21,14 @@ server.post('/', async (request, reply) => {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (method === 'call_tool') {
|
|
24
|
+
if (method === 'call_tool' || method === 'tools/call') {
|
|
25
|
+
const toolName = params.name || '';
|
|
26
|
+
const args = params.arguments || {};
|
|
25
27
|
return {
|
|
26
28
|
jsonrpc: '2.0',
|
|
27
29
|
id,
|
|
28
30
|
result: {
|
|
29
|
-
content: [{ type: 'text', text: `Echo: ${JSON.stringify(
|
|
31
|
+
content: [{ type: 'text', text: `Echo: ${JSON.stringify(args)}` }]
|
|
30
32
|
}
|
|
31
33
|
};
|
|
32
34
|
}
|
package/tests/routing.test.ts
CHANGED
|
@@ -162,7 +162,7 @@ describe('RequestController Routing', () => {
|
|
|
162
162
|
}, mockContext);
|
|
163
163
|
|
|
164
164
|
expect(mockIsolateExecutor.execute).toHaveBeenCalled();
|
|
165
|
-
expect(result!.result.stdout).toBe('isolate');
|
|
165
|
+
expect(result!.result.structuredContent.stdout).toBe('isolate');
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
});
|
|
@@ -15,12 +15,13 @@ describe('SDKGenerator', () => {
|
|
|
15
15
|
|
|
16
16
|
const code = generator.generateTypeScript(bindings);
|
|
17
17
|
|
|
18
|
-
expect(code).toContain('const
|
|
18
|
+
expect(code).toContain('const _tools = {');
|
|
19
19
|
expect(code).toContain('github: {');
|
|
20
20
|
expect(code).toContain('async createIssue(args)');
|
|
21
21
|
expect(code).toContain('await __internalCallTool("github__createIssue", args)');
|
|
22
22
|
expect(code).toContain('slack: {');
|
|
23
23
|
expect(code).toContain('async sendMessage(args)');
|
|
24
|
+
expect(code).toContain('const tools = new Proxy(_tools');
|
|
24
25
|
expect(code).toContain('(globalThis as any).tools = tools');
|
|
25
26
|
});
|
|
26
27
|
|
|
@@ -58,7 +59,7 @@ describe('SDKGenerator', () => {
|
|
|
58
59
|
it('should handle empty bindings', () => {
|
|
59
60
|
const code = generator.generateTypeScript([]);
|
|
60
61
|
|
|
61
|
-
expect(code).toContain('const
|
|
62
|
+
expect(code).toContain('const _tools = {');
|
|
62
63
|
expect(code).toContain('async $raw(name, args)');
|
|
63
64
|
});
|
|
64
65
|
});
|
package/tsup.config.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/executors/pyodide.worker.ts"],"sourcesContent":["import { parentPort, workerData } from 'node:worker_threads';\nimport { loadPyodide, type PyodideInterface } from 'pyodide';\nimport net from 'node:net';\n\nlet pyodide: PyodideInterface | null = null;\nlet currentStdout = '';\nlet currentStderr = '';\nlet totalOutputBytes = 0;\nlet totalLogEntries = 0;\nlet currentLimits: any = null;\n\nasync function init() {\n if (pyodide) return pyodide;\n\n pyodide = await loadPyodide({\n stdout: (text) => {\n if (currentLimits && (totalOutputBytes > (currentLimits.maxOutputBytes || 1024 * 1024) || totalLogEntries > (currentLimits.maxLogEntries || 10000))) {\n return; // Stop processing logs once limit breached\n }\n currentStdout += text + '\\n';\n totalOutputBytes += text.length + 1;\n totalLogEntries++;\n },\n stderr: (text) => {\n if (currentLimits && (totalOutputBytes > (currentLimits.maxOutputBytes || 1024 * 1024) || totalLogEntries > (currentLimits.maxLogEntries || 10000))) {\n return; // Stop processing logs once limit breached\n }\n currentStderr += text + '\\n';\n totalOutputBytes += text.length + 1;\n totalLogEntries++;\n },\n });\n\n return pyodide;\n}\n\nasync function handleTask(data: any) {\n const { code, limits, ipcInfo, shim } = data;\n currentStdout = '';\n currentStderr = '';\n totalOutputBytes = 0;\n totalLogEntries = 0;\n currentLimits = limits;\n\n try {\n const p = await init();\n\n const sendIPCRequest = async (method: string, params: any) => {\n if (!ipcInfo?.ipcAddress) throw new Error('Conduit IPC address not configured');\n\n return new Promise((resolve, reject) => {\n let client: net.Socket;\n\n if (ipcInfo.ipcAddress.includes(':')) {\n const lastColon = ipcInfo.ipcAddress.lastIndexOf(':');\n const host = ipcInfo.ipcAddress.substring(0, lastColon);\n const port = ipcInfo.ipcAddress.substring(lastColon + 1);\n\n let targetHost = host.replace(/[\\[\\]]/g, '');\n if (targetHost === '0.0.0.0' || targetHost === '::' || targetHost === '::1' || targetHost === '') {\n targetHost = '127.0.0.1';\n }\n\n client = net.createConnection({\n host: targetHost,\n port: parseInt(port)\n });\n } else {\n client = net.createConnection({ path: ipcInfo.ipcAddress });\n }\n\n const id = Math.random().toString(36).substring(7);\n const request = {\n jsonrpc: '2.0',\n id,\n method,\n params: params || {},\n auth: { bearerToken: ipcInfo.ipcToken }\n };\n\n client.on('error', (err) => {\n reject(err);\n client.destroy();\n });\n\n client.write(JSON.stringify(request) + '\\n');\n\n let buffer = '';\n client.on('data', (data) => {\n buffer += data.toString();\n // Robust framing: read until we find a complete JSON object on a line\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // Keep the last partial line\n\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const response = JSON.parse(line);\n if (response.id === id) {\n if (response.error) {\n reject(new Error(response.error.message));\n } else {\n resolve(response.result);\n }\n client.end();\n return;\n }\n } catch (e) {\n // If parse fails, it might be a partial line that we haven't seen the end of yet\n // but since we split by \\n, this shouldn't happen unless the \\n was inside the JSON.\n // However, Conduit ensures JSON-RPC is one line.\n }\n }\n });\n\n client.on('end', () => {\n if (buffer.trim()) {\n try {\n const response = JSON.parse(buffer);\n if (response.id === id) {\n if (response.error) {\n reject(new Error(response.error.message));\n } else {\n resolve(response.result);\n }\n }\n } catch (e) { }\n }\n });\n });\n };\n\n (p as any).globals.set('discover_mcp_tools_js', (options: any) => {\n return sendIPCRequest('mcp_discover_tools', options);\n });\n\n (p as any).globals.set('call_mcp_tool_js', (name: string, args: any) => {\n return sendIPCRequest('mcp_call_tool', { name, arguments: args });\n });\n\n if (shim) {\n await p.runPythonAsync(shim);\n }\n\n const result = await p.runPythonAsync(code);\n\n if (totalOutputBytes > (limits.maxOutputBytes || 1024 * 1024)) {\n throw new Error('[LIMIT_OUTPUT]');\n }\n if (totalLogEntries > (limits.maxLogEntries || 10000)) {\n throw new Error('[LIMIT_LOG]');\n }\n\n parentPort?.postMessage({\n stdout: currentStdout,\n stderr: currentStderr,\n result: String(result),\n success: true,\n });\n } catch (err: any) {\n let isOutput = err.message.includes('[LIMIT_OUTPUT]');\n let isLog = err.message.includes('[LIMIT_LOG]');\n\n // Fallback: check counters if message doesn't match (e.g. wrapped in OSError)\n if (!isOutput && !isLog && currentLimits) {\n if (totalOutputBytes > (currentLimits.maxOutputBytes || 1024 * 1024)) {\n isOutput = true;\n }\n // Check specific log limit breach\n if (totalLogEntries > (currentLimits.maxLogEntries || 10000)) {\n isLog = true;\n }\n }\n\n parentPort?.postMessage({\n stdout: currentStdout,\n stderr: currentStderr,\n error: err.message,\n limitBreached: isOutput ? 'output' : (isLog ? 'log' : undefined),\n success: false,\n });\n }\n}\n\nparentPort?.on('message', async (msg) => {\n if (msg.type === 'execute') {\n await handleTask(msg.data);\n } else if (msg.type === 'ping') {\n parentPort?.postMessage({ type: 'pong' });\n }\n});\n\n// Signal ready\nparentPort?.postMessage({ type: 'ready' });\n\n"],"mappings":"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","names":["data"]}
|
|
File without changes
|
|
File without changes
|