@ruifung/codemode-bridge 1.0.3-1
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/LICENSE +202 -0
- package/README.md +378 -0
- package/dist/cli/commands.d.ts +70 -0
- package/dist/cli/commands.js +436 -0
- package/dist/cli/config-manager.d.ts +53 -0
- package/dist/cli/config-manager.js +142 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.js +165 -0
- package/dist/executor/container-executor.d.ts +81 -0
- package/dist/executor/container-executor.js +351 -0
- package/dist/executor/executor-test-suite.d.ts +22 -0
- package/dist/executor/executor-test-suite.js +395 -0
- package/dist/executor/isolated-vm-executor.d.ts +78 -0
- package/dist/executor/isolated-vm-executor.js +368 -0
- package/dist/executor/vm2-executor.d.ts +21 -0
- package/dist/executor/vm2-executor.js +109 -0
- package/dist/executor/wrap-code.d.ts +52 -0
- package/dist/executor/wrap-code.js +80 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/mcp/config.d.ts +44 -0
- package/dist/mcp/config.js +102 -0
- package/dist/mcp/e2e-bridge-test-suite.d.ts +28 -0
- package/dist/mcp/e2e-bridge-test-suite.js +429 -0
- package/dist/mcp/executor.d.ts +31 -0
- package/dist/mcp/executor.js +121 -0
- package/dist/mcp/mcp-adapter.d.ts +12 -0
- package/dist/mcp/mcp-adapter.js +49 -0
- package/dist/mcp/mcp-client.d.ts +85 -0
- package/dist/mcp/mcp-client.js +441 -0
- package/dist/mcp/oauth-handler.d.ts +33 -0
- package/dist/mcp/oauth-handler.js +138 -0
- package/dist/mcp/server.d.ts +25 -0
- package/dist/mcp/server.js +322 -0
- package/dist/mcp/token-persistence.d.ts +57 -0
- package/dist/mcp/token-persistence.js +131 -0
- package/dist/utils/logger.d.ts +44 -0
- package/dist/utils/logger.js +123 -0
- package/package.json +56 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// @ts-ignore - isolated-vm is an optional dependency; this file is only loaded via dynamic import() when available
|
|
2
|
+
import ivm from 'isolated-vm';
|
|
3
|
+
import { wrapCode } from './wrap-code.js';
|
|
4
|
+
const { Isolate, Context } = ivm;
|
|
5
|
+
/**
|
|
6
|
+
* Helper: Convert any value to a string representation for logging
|
|
7
|
+
*/
|
|
8
|
+
function stringify(value) {
|
|
9
|
+
try {
|
|
10
|
+
if (typeof value === 'string')
|
|
11
|
+
return value;
|
|
12
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
13
|
+
return String(value);
|
|
14
|
+
if (value === null)
|
|
15
|
+
return 'null';
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
return 'undefined';
|
|
18
|
+
if (typeof value === 'object') {
|
|
19
|
+
if (value instanceof Error) {
|
|
20
|
+
return `Error: ${value.message}`;
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return `[ ${value.map((v) => stringify(v)).join(', ')} ]`;
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify(value, (key, val) => {
|
|
26
|
+
if (typeof val === 'function')
|
|
27
|
+
return '[Function]';
|
|
28
|
+
return val;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return String(value);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return '[Object]';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Executor implementation using isolated-vm
|
|
39
|
+
*
|
|
40
|
+
* Architecture: Uses Promise chain pattern
|
|
41
|
+
* - Async IIFE is executed in the isolate
|
|
42
|
+
* - Its returned Promise is chained with .then() inside the isolate
|
|
43
|
+
* - The .then handlers call sync Callbacks on the host side
|
|
44
|
+
* - Host Promise resolves when the callback is invoked
|
|
45
|
+
*
|
|
46
|
+
* Features:
|
|
47
|
+
* - True memory isolation (V8 Isolate level)
|
|
48
|
+
* - Hard memory limit enforcement
|
|
49
|
+
* - Accurate timeout handling
|
|
50
|
+
* - Promise-based result capturing
|
|
51
|
+
*
|
|
52
|
+
* Security:
|
|
53
|
+
* - Separate V8 memory heap
|
|
54
|
+
* - No shared memory access
|
|
55
|
+
* - Explicit serialization boundaries
|
|
56
|
+
*/
|
|
57
|
+
export class IsolatedVmExecutor {
|
|
58
|
+
constructor(options = {}) {
|
|
59
|
+
this.context = null;
|
|
60
|
+
this.metrics = null;
|
|
61
|
+
this.options = {
|
|
62
|
+
memoryLimit: options.memoryLimit ?? 128,
|
|
63
|
+
inspector: options.inspector ?? false,
|
|
64
|
+
timeout: options.timeout ?? 30000,
|
|
65
|
+
};
|
|
66
|
+
this.isolate = new Isolate({
|
|
67
|
+
memoryLimit: this.options.memoryLimit,
|
|
68
|
+
inspector: this.options.inspector,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Execute code within a sandboxed isolate using Promise chain pattern
|
|
73
|
+
*
|
|
74
|
+
* The function `fns` are made available as `codemode.*` within the sandbox.
|
|
75
|
+
* Uses Promise chain: async IIFE → .then() in isolate → sync callback → host Promise
|
|
76
|
+
*
|
|
77
|
+
* Returns ExecuteResult with:
|
|
78
|
+
* - result: The return value of the code
|
|
79
|
+
* - error: Error message if execution failed
|
|
80
|
+
* - logs: Array of console.log outputs
|
|
81
|
+
*/
|
|
82
|
+
async execute(code, fns) {
|
|
83
|
+
// Create new context for this execution (fresh globals)
|
|
84
|
+
const context = await this.createContext();
|
|
85
|
+
const logs = [];
|
|
86
|
+
// Tool invocation: Track call IDs for notification pattern
|
|
87
|
+
let toolCallId = 0;
|
|
88
|
+
try {
|
|
89
|
+
// Set up console logging
|
|
90
|
+
try {
|
|
91
|
+
await context.eval('this.console = {}');
|
|
92
|
+
const consoleRef = await context.global.get('console');
|
|
93
|
+
const logCallback = new ivm.Callback((...args) => {
|
|
94
|
+
logs.push(args.map(stringify).join(' '));
|
|
95
|
+
});
|
|
96
|
+
await consoleRef.set('log', logCallback);
|
|
97
|
+
const warnCallback = new ivm.Callback((...args) => {
|
|
98
|
+
logs.push('[WARN] ' + args.map(stringify).join(' '));
|
|
99
|
+
});
|
|
100
|
+
await consoleRef.set('warn', warnCallback);
|
|
101
|
+
const errorCallback = new ivm.Callback((...args) => {
|
|
102
|
+
logs.push('[ERROR] ' + args.map(stringify).join(' '));
|
|
103
|
+
});
|
|
104
|
+
await consoleRef.set('error', errorCallback);
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
// Continue if console setup fails
|
|
108
|
+
}
|
|
109
|
+
// Block dangerous functions
|
|
110
|
+
try {
|
|
111
|
+
await context.eval(`
|
|
112
|
+
this.eval = function() {
|
|
113
|
+
throw new Error("eval is not allowed");
|
|
114
|
+
};
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
// Continue even if this fails
|
|
119
|
+
}
|
|
120
|
+
// Provide setTimeout for async code support (like vm2 does)
|
|
121
|
+
try {
|
|
122
|
+
await context.eval(`
|
|
123
|
+
globalThis.setTimeout = function(fn, delay) {
|
|
124
|
+
Promise.resolve().then(fn);
|
|
125
|
+
};
|
|
126
|
+
`);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
// Continue even if this fails
|
|
130
|
+
}
|
|
131
|
+
// Provide global alias for Node.js compatibility
|
|
132
|
+
try {
|
|
133
|
+
await context.eval(`
|
|
134
|
+
globalThis.global = globalThis;
|
|
135
|
+
`);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
// Continue even if this fails
|
|
139
|
+
}
|
|
140
|
+
// Precompile resolver script once — resolves all pending promises that
|
|
141
|
+
// have results/errors waiting in _toolResults/_toolErrors. The host copies
|
|
142
|
+
// data into those objects via ExternalCopy, then runs this script.
|
|
143
|
+
const resolverScript = await this.isolate.compileScript(`
|
|
144
|
+
(function() {
|
|
145
|
+
for (const id in globalThis._toolResults) {
|
|
146
|
+
if (globalThis._pendingResolvers[id]) {
|
|
147
|
+
globalThis._pendingResolvers[id].resolve(globalThis._toolResults[id]);
|
|
148
|
+
delete globalThis._pendingResolvers[id];
|
|
149
|
+
}
|
|
150
|
+
delete globalThis._toolResults[id];
|
|
151
|
+
}
|
|
152
|
+
for (const id in globalThis._toolErrors) {
|
|
153
|
+
if (globalThis._pendingResolvers[id]) {
|
|
154
|
+
globalThis._pendingResolvers[id].reject(new Error(globalThis._toolErrors[id]));
|
|
155
|
+
delete globalThis._pendingResolvers[id];
|
|
156
|
+
}
|
|
157
|
+
delete globalThis._toolErrors[id];
|
|
158
|
+
}
|
|
159
|
+
})();
|
|
160
|
+
`);
|
|
161
|
+
// Set up tool invocation: precompiled notification pattern
|
|
162
|
+
// 1. hostExecuteTool starts async execution and returns a call ID
|
|
163
|
+
// 2. When done, host copies result into _toolResults[callId] via ExternalCopy
|
|
164
|
+
// 3. Host runs the precompiled resolver script to resolve the Promise
|
|
165
|
+
const hostExecuteTool = new ivm.Callback((toolName, toolArgs) => {
|
|
166
|
+
const callId = toolCallId++;
|
|
167
|
+
const fn = fns[toolName];
|
|
168
|
+
const notifySuccess = async (value) => {
|
|
169
|
+
try {
|
|
170
|
+
const ref = await context.global.get('_toolResults');
|
|
171
|
+
await ref.set(String(callId), new ivm.ExternalCopy(value).copyInto({ transferIn: true }));
|
|
172
|
+
await resolverScript.run(context);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Ignore errors during cleanup
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const notifyError = async (error) => {
|
|
179
|
+
try {
|
|
180
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
181
|
+
const ref = await context.global.get('_toolErrors');
|
|
182
|
+
await ref.set(String(callId), new ivm.ExternalCopy(errorMsg).copyInto({ transferIn: true }));
|
|
183
|
+
await resolverScript.run(context);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Ignore errors during cleanup
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
if (!fn) {
|
|
190
|
+
notifyError(new Error(`Tool '${toolName}' not found`));
|
|
191
|
+
return callId;
|
|
192
|
+
}
|
|
193
|
+
// Execute the tool async, spread array arguments
|
|
194
|
+
fn(...(Array.isArray(toolArgs) ? toolArgs : [toolArgs]))
|
|
195
|
+
.then(notifySuccess)
|
|
196
|
+
.catch(notifyError);
|
|
197
|
+
return callId;
|
|
198
|
+
});
|
|
199
|
+
await context.global.set('_hostExecuteTool', hostExecuteTool);
|
|
200
|
+
// Set up codemode object with tool wrappers
|
|
201
|
+
// Each tool: creates a promise, starts execution, host notifies when done
|
|
202
|
+
const codemodeSandbox = {};
|
|
203
|
+
for (const toolName of Object.keys(fns)) {
|
|
204
|
+
codemodeSandbox[toolName] = toolName; // Just pass the name as string
|
|
205
|
+
}
|
|
206
|
+
await context.global.set('codemode', new ivm.ExternalCopy(codemodeSandbox).copyInto({ transferIn: true }));
|
|
207
|
+
// Set up the tool wrapper in isolate - uses notification pattern
|
|
208
|
+
await context.eval(`
|
|
209
|
+
globalThis._pendingResolvers = {};
|
|
210
|
+
globalThis._toolResults = {};
|
|
211
|
+
globalThis._toolErrors = {};
|
|
212
|
+
|
|
213
|
+
const _codemodeMethods = {};
|
|
214
|
+
for (const toolName of Object.keys(codemode)) {
|
|
215
|
+
_codemodeMethods[toolName] = (...args) => {
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
const callId = _hostExecuteTool(toolName, args);
|
|
218
|
+
globalThis._pendingResolvers[callId] = { resolve, reject };
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
codemode = _codemodeMethods;
|
|
223
|
+
`);
|
|
224
|
+
// Use Promise-based execution with async function
|
|
225
|
+
const resultPromise = new Promise(async (resolve) => {
|
|
226
|
+
// Create resolve/reject callbacks for the isolate
|
|
227
|
+
const resolveCallback = new ivm.Callback((result) => {
|
|
228
|
+
resolve({
|
|
229
|
+
result,
|
|
230
|
+
logs: logs.length > 0 ? logs : undefined,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
const rejectCallback = new ivm.Callback((error) => {
|
|
234
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
235
|
+
resolve({
|
|
236
|
+
result: undefined,
|
|
237
|
+
error: errorMsg,
|
|
238
|
+
logs: logs.length > 0 ? logs : undefined,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
try {
|
|
242
|
+
// Set protocol callbacks in isolate
|
|
243
|
+
await context.global.set('protocol', new ivm.ExternalCopy({
|
|
244
|
+
resolve: resolveCallback,
|
|
245
|
+
reject: rejectCallback,
|
|
246
|
+
}).copyInto({ transferIn: true }));
|
|
247
|
+
// Wrap code so it returns a Promise we can .then()
|
|
248
|
+
const wrappedCode = wrapCode(code, { alwaysAsync: true });
|
|
249
|
+
// Execute the async function and chain its Promise with .then()
|
|
250
|
+
await context.eval(`(${wrappedCode}).then(protocol.resolve, protocol.reject);`);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
254
|
+
resolve({
|
|
255
|
+
result: undefined,
|
|
256
|
+
error: errorMsg,
|
|
257
|
+
logs: logs.length > 0 ? logs : undefined,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
// Wait for the Promise-based execution to complete
|
|
262
|
+
const result = await resultPromise;
|
|
263
|
+
// Capture metrics before cleanup
|
|
264
|
+
await this.captureMetrics();
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
await this.captureMetrics();
|
|
269
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
270
|
+
// Distinguish timeout errors
|
|
271
|
+
if (errorMessage.includes('timeout') ||
|
|
272
|
+
errorMessage.includes('CPU time limit')) {
|
|
273
|
+
return {
|
|
274
|
+
result: undefined,
|
|
275
|
+
error: `Execution timeout (${this.options.timeout}ms exceeded)`,
|
|
276
|
+
logs: logs.length > 0 ? logs : undefined,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// Distinguish memory limit errors
|
|
280
|
+
if (errorMessage.includes('memory') || errorMessage.includes('heap')) {
|
|
281
|
+
return {
|
|
282
|
+
result: undefined,
|
|
283
|
+
error: `Memory limit exceeded (${this.options.memoryLimit}MB)`,
|
|
284
|
+
logs: logs.length > 0 ? logs : undefined,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
result: undefined,
|
|
289
|
+
error: errorMessage,
|
|
290
|
+
logs: logs.length > 0 ? logs : undefined,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Cleanup resources
|
|
296
|
+
*/
|
|
297
|
+
dispose() {
|
|
298
|
+
if (this.context) {
|
|
299
|
+
try {
|
|
300
|
+
this.context.release();
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
// Ignore
|
|
304
|
+
}
|
|
305
|
+
this.context = null;
|
|
306
|
+
}
|
|
307
|
+
if (this.isolate) {
|
|
308
|
+
try {
|
|
309
|
+
this.isolate.dispose();
|
|
310
|
+
}
|
|
311
|
+
catch (e) {
|
|
312
|
+
// Ignore errors during cleanup
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get heap statistics from the isolate
|
|
318
|
+
*/
|
|
319
|
+
async getHeapStatistics() {
|
|
320
|
+
const stats = await this.isolate.getHeapStatistics();
|
|
321
|
+
return {
|
|
322
|
+
total: stats.total_heap_size,
|
|
323
|
+
used: stats.used_heap_size,
|
|
324
|
+
limit: this.options.memoryLimit * 1024 * 1024,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Create or recreate context (resets globals for isolation)
|
|
329
|
+
*/
|
|
330
|
+
async createContext() {
|
|
331
|
+
// Release old context if it exists
|
|
332
|
+
if (this.context) {
|
|
333
|
+
try {
|
|
334
|
+
this.context.release();
|
|
335
|
+
}
|
|
336
|
+
catch (e) {
|
|
337
|
+
// Ignore
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
this.context = await this.isolate.createContext({
|
|
341
|
+
inspector: this.options.inspector,
|
|
342
|
+
});
|
|
343
|
+
return this.context;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Capture execution metrics
|
|
347
|
+
*/
|
|
348
|
+
async captureMetrics() {
|
|
349
|
+
try {
|
|
350
|
+
const stats = await this.isolate.getHeapStatistics();
|
|
351
|
+
this.metrics = {
|
|
352
|
+
cpuTime: BigInt(0),
|
|
353
|
+
wallTime: BigInt(0),
|
|
354
|
+
heapUsed: stats.used_heap_size,
|
|
355
|
+
heapLimit: stats.heap_size_limit,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
// Ignore metrics capture errors
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Factory function to create an isolated-vm executor instance
|
|
365
|
+
*/
|
|
366
|
+
export function createIsolatedVmExecutor(options) {
|
|
367
|
+
return new IsolatedVmExecutor(options);
|
|
368
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executor Implementation for Codemode SDK
|
|
3
|
+
* Implements the @cloudflare/codemode Executor interface using vm2 sandbox
|
|
4
|
+
*/
|
|
5
|
+
import type { Executor, ExecuteResult } from "@cloudflare/codemode";
|
|
6
|
+
/**
|
|
7
|
+
* VM2-based Executor implementation
|
|
8
|
+
* Runs LLM-generated code in an isolated sandbox with access to tools via codemode.* namespace
|
|
9
|
+
*/
|
|
10
|
+
export declare class VM2Executor implements Executor {
|
|
11
|
+
private timeout;
|
|
12
|
+
constructor(timeout?: number);
|
|
13
|
+
execute(code: string, fns: Record<string, (...args: unknown[]) => Promise<unknown>>): Promise<ExecuteResult>;
|
|
14
|
+
private stringify;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Factory function to create a VM2 executor instance
|
|
18
|
+
*/
|
|
19
|
+
export declare function createVM2Executor(options?: {
|
|
20
|
+
timeout?: number;
|
|
21
|
+
}): Executor;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executor Implementation for Codemode SDK
|
|
3
|
+
* Implements the @cloudflare/codemode Executor interface using vm2 sandbox
|
|
4
|
+
*/
|
|
5
|
+
import { VM } from "vm2";
|
|
6
|
+
import { wrapCode } from "./wrap-code.js";
|
|
7
|
+
/**
|
|
8
|
+
* VM2-based Executor implementation
|
|
9
|
+
* Runs LLM-generated code in an isolated sandbox with access to tools via codemode.* namespace
|
|
10
|
+
*/
|
|
11
|
+
export class VM2Executor {
|
|
12
|
+
constructor(timeout = 30000) {
|
|
13
|
+
this.timeout = timeout;
|
|
14
|
+
}
|
|
15
|
+
async execute(code, fns) {
|
|
16
|
+
const logs = [];
|
|
17
|
+
const capturedConsole = {
|
|
18
|
+
log: (...args) => {
|
|
19
|
+
logs.push(args.map((arg) => this.stringify(arg)).join(" "));
|
|
20
|
+
},
|
|
21
|
+
warn: (...args) => {
|
|
22
|
+
logs.push("[WARN] " + args.map((arg) => this.stringify(arg)).join(" "));
|
|
23
|
+
},
|
|
24
|
+
error: (...args) => {
|
|
25
|
+
logs.push("[ERROR] " + args.map((arg) => this.stringify(arg)).join(" "));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
// Wrap code using shared wrapCode utility (handles arrow functions, statements, etc.)
|
|
30
|
+
const wrappedCode = wrapCode(code);
|
|
31
|
+
const vm = new VM({
|
|
32
|
+
timeout: this.timeout,
|
|
33
|
+
eval: false,
|
|
34
|
+
sandbox: {
|
|
35
|
+
console: capturedConsole,
|
|
36
|
+
codemode: fns,
|
|
37
|
+
// Block eval for security
|
|
38
|
+
eval: () => { throw new Error('eval is not allowed'); },
|
|
39
|
+
setTimeout: (fn, delay) => {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
setTimeout(() => { fn(); resolve(); }, delay ?? 0);
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
setInterval: (fn, delay) => {
|
|
45
|
+
return setInterval(fn, delay ?? 0);
|
|
46
|
+
},
|
|
47
|
+
clearInterval: (id) => {
|
|
48
|
+
clearInterval(id);
|
|
49
|
+
},
|
|
50
|
+
clearTimeout: (id) => {
|
|
51
|
+
clearTimeout(id);
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
// vm.run() returns a Promise if the code returns a Promise
|
|
56
|
+
const rawResult = vm.run(wrappedCode, "codemode-execution.js");
|
|
57
|
+
// Await the result if it's a promise
|
|
58
|
+
const result = rawResult instanceof Promise ? await rawResult : rawResult;
|
|
59
|
+
return {
|
|
60
|
+
result,
|
|
61
|
+
...(logs.length > 0 && { logs }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
66
|
+
return {
|
|
67
|
+
result: undefined,
|
|
68
|
+
error: errorMessage,
|
|
69
|
+
...(logs.length > 0 && { logs }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
stringify(value) {
|
|
74
|
+
try {
|
|
75
|
+
if (typeof value === "string")
|
|
76
|
+
return value;
|
|
77
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
78
|
+
return String(value);
|
|
79
|
+
if (value === null)
|
|
80
|
+
return "null";
|
|
81
|
+
if (value === undefined)
|
|
82
|
+
return "undefined";
|
|
83
|
+
if (typeof value === "object") {
|
|
84
|
+
if (value instanceof Error) {
|
|
85
|
+
return `Error: ${value.message}`;
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
return `[ ${value.map((v) => this.stringify(v)).join(", ")} ]`;
|
|
89
|
+
}
|
|
90
|
+
// For objects, use JSON.stringify with a replacer to handle circular refs
|
|
91
|
+
return JSON.stringify(value, (key, val) => {
|
|
92
|
+
if (typeof val === "function")
|
|
93
|
+
return "[Function]";
|
|
94
|
+
return val;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return String(value);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return "[Object]";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Factory function to create a VM2 executor instance
|
|
106
|
+
*/
|
|
107
|
+
export function createVM2Executor(options) {
|
|
108
|
+
return new VM2Executor(options?.timeout);
|
|
109
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared code-wrapping utility for executor implementations.
|
|
3
|
+
*
|
|
4
|
+
* The SDK's normalizeCode() shapes LLM-generated code into a callable
|
|
5
|
+
* function expression (arrow function). The executor's job is to invoke
|
|
6
|
+
* it — `(code)()`. When the executor is called directly (without
|
|
7
|
+
* normalizeCode()), it must also handle raw statements.
|
|
8
|
+
*
|
|
9
|
+
* This module uses acorn AST parsing to reliably classify code shape,
|
|
10
|
+
* matching how the SDK's normalizeCode() detects arrow functions.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The shape of the code as determined by AST parsing.
|
|
14
|
+
*
|
|
15
|
+
* - `async-function` — `async function ...` or `async () => ...`
|
|
16
|
+
* - `sync-arrow` — `(params) => ...` or `param => ...` (non-async)
|
|
17
|
+
* - `sync-function` — `function ...` (non-async named/anonymous function)
|
|
18
|
+
* - `raw-statements` — anything else (variable declarations, calls, etc.)
|
|
19
|
+
*/
|
|
20
|
+
export type CodeShape = 'async-function' | 'sync-arrow' | 'sync-function' | 'raw-statements';
|
|
21
|
+
/**
|
|
22
|
+
* Classify the shape of a code string using acorn AST parsing.
|
|
23
|
+
*
|
|
24
|
+
* Falls back to `'raw-statements'` if the code cannot be parsed
|
|
25
|
+
* (which will cause it to be wrapped in an async IIFE).
|
|
26
|
+
*/
|
|
27
|
+
export declare function classifyCode(code: string): CodeShape;
|
|
28
|
+
export interface WrapCodeOptions {
|
|
29
|
+
/**
|
|
30
|
+
* When true, sync arrow functions and sync function expressions are
|
|
31
|
+
* additionally wrapped to ensure the result is always a Promise.
|
|
32
|
+
*
|
|
33
|
+
* Use this when the executor chains the result with `.then()` and
|
|
34
|
+
* cannot handle non-Promise return values (e.g. isolated-vm).
|
|
35
|
+
*
|
|
36
|
+
* When false, sync functions are simply invoked as `(code)()`, and
|
|
37
|
+
* the caller is responsible for handling non-Promise returns
|
|
38
|
+
* (e.g. VM2 checks `'then' in result`).
|
|
39
|
+
*
|
|
40
|
+
* @default false
|
|
41
|
+
*/
|
|
42
|
+
alwaysAsync?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Wrap code so it can be executed by an executor.
|
|
46
|
+
*
|
|
47
|
+
* - Function expressions / arrow functions → invoked with `(code)()`
|
|
48
|
+
* - Raw statements → wrapped in `(async () => { code })()`
|
|
49
|
+
* - When `alwaysAsync` is true, sync functions are additionally wrapped
|
|
50
|
+
* to ensure the result is a Promise.
|
|
51
|
+
*/
|
|
52
|
+
export declare function wrapCode(code: string, options?: WrapCodeOptions): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared code-wrapping utility for executor implementations.
|
|
3
|
+
*
|
|
4
|
+
* The SDK's normalizeCode() shapes LLM-generated code into a callable
|
|
5
|
+
* function expression (arrow function). The executor's job is to invoke
|
|
6
|
+
* it — `(code)()`. When the executor is called directly (without
|
|
7
|
+
* normalizeCode()), it must also handle raw statements.
|
|
8
|
+
*
|
|
9
|
+
* This module uses acorn AST parsing to reliably classify code shape,
|
|
10
|
+
* matching how the SDK's normalizeCode() detects arrow functions.
|
|
11
|
+
*/
|
|
12
|
+
import * as acorn from 'acorn';
|
|
13
|
+
/**
|
|
14
|
+
* Classify the shape of a code string using acorn AST parsing.
|
|
15
|
+
*
|
|
16
|
+
* Falls back to `'raw-statements'` if the code cannot be parsed
|
|
17
|
+
* (which will cause it to be wrapped in an async IIFE).
|
|
18
|
+
*/
|
|
19
|
+
export function classifyCode(code) {
|
|
20
|
+
const trimmed = code.trim();
|
|
21
|
+
if (!trimmed)
|
|
22
|
+
return 'raw-statements';
|
|
23
|
+
try {
|
|
24
|
+
const ast = acorn.parse(trimmed, {
|
|
25
|
+
ecmaVersion: 'latest',
|
|
26
|
+
sourceType: 'module',
|
|
27
|
+
});
|
|
28
|
+
// Single expression statement — check what kind of function it is
|
|
29
|
+
if (ast.body.length === 1 && ast.body[0].type === 'ExpressionStatement') {
|
|
30
|
+
const expr = ast.body[0].expression;
|
|
31
|
+
if (expr.type === 'ArrowFunctionExpression') {
|
|
32
|
+
return expr.async ? 'async-function' : 'sync-arrow';
|
|
33
|
+
}
|
|
34
|
+
if (expr.type === 'FunctionExpression') {
|
|
35
|
+
return expr.async ? 'async-function' : 'sync-function';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Top-level function declaration (not an expression)
|
|
39
|
+
if (ast.body.length === 1 && ast.body[0].type === 'FunctionDeclaration') {
|
|
40
|
+
const decl = ast.body[0];
|
|
41
|
+
return decl.async ? 'async-function' : 'sync-function';
|
|
42
|
+
}
|
|
43
|
+
return 'raw-statements';
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Parse failed — treat as raw statements
|
|
47
|
+
return 'raw-statements';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Wrap code so it can be executed by an executor.
|
|
52
|
+
*
|
|
53
|
+
* - Function expressions / arrow functions → invoked with `(code)()`
|
|
54
|
+
* - Raw statements → wrapped in `(async () => { code })()`
|
|
55
|
+
* - When `alwaysAsync` is true, sync functions are additionally wrapped
|
|
56
|
+
* to ensure the result is a Promise.
|
|
57
|
+
*/
|
|
58
|
+
export function wrapCode(code, options) {
|
|
59
|
+
const trimmed = code.trim();
|
|
60
|
+
if (!trimmed)
|
|
61
|
+
return '(async () => {})()';
|
|
62
|
+
const shape = classifyCode(trimmed);
|
|
63
|
+
const alwaysAsync = options?.alwaysAsync ?? false;
|
|
64
|
+
switch (shape) {
|
|
65
|
+
case 'async-function':
|
|
66
|
+
// Already async — invoke it, result is a Promise
|
|
67
|
+
return `(${trimmed})()`;
|
|
68
|
+
case 'sync-arrow':
|
|
69
|
+
case 'sync-function':
|
|
70
|
+
if (alwaysAsync) {
|
|
71
|
+
// Wrap in async IIFE to ensure result is a Promise
|
|
72
|
+
return `(async () => (${trimmed})())()`;
|
|
73
|
+
}
|
|
74
|
+
// Just invoke — caller handles sync return
|
|
75
|
+
return `(${trimmed})()`;
|
|
76
|
+
case 'raw-statements':
|
|
77
|
+
// Wrap in async IIFE
|
|
78
|
+
return `(async () => { ${trimmed} })()`;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Loader - Load MCP server configurations from files
|
|
3
|
+
* Supports VS Code's mcp.json format and other config files
|
|
4
|
+
*/
|
|
5
|
+
import type { MCPServerConfig } from "./server.js";
|
|
6
|
+
export interface MCPJsonConfig {
|
|
7
|
+
servers: Record<string, MCPServerConfigEntry>;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface MCPServerConfigEntry {
|
|
11
|
+
type: "stdio" | "http";
|
|
12
|
+
command?: string;
|
|
13
|
+
args?: string[];
|
|
14
|
+
url?: string;
|
|
15
|
+
env?: Record<string, string>;
|
|
16
|
+
oauth?: Record<string, unknown>;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load MCP server configurations from VS Code's mcp.json file
|
|
21
|
+
* Default location: ~/.config/Code/User/mcp.json (Linux/Mac) or
|
|
22
|
+
* %APPDATA%\Code\User\mcp.json (Windows)
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadMCPConfigFile(configPath?: string): MCPJsonConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Get a list of all configured server names from the config file
|
|
27
|
+
*/
|
|
28
|
+
export declare function getServerNames(config: MCPJsonConfig): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Get a server configuration by name
|
|
31
|
+
*/
|
|
32
|
+
export declare function getServerConfig(config: MCPJsonConfig, serverName: string): MCPServerConfig;
|
|
33
|
+
/**
|
|
34
|
+
* Get multiple server configs by name from the config file
|
|
35
|
+
*/
|
|
36
|
+
export declare function getServerConfigs(config: MCPJsonConfig, serverNames: string[]): MCPServerConfig[];
|
|
37
|
+
/**
|
|
38
|
+
* Load and return all healthy servers from the config
|
|
39
|
+
* (filters out offline servers based on availability checks)
|
|
40
|
+
*/
|
|
41
|
+
export declare function loadAvailableServers(config: MCPJsonConfig, serverNames?: string[]): Promise<{
|
|
42
|
+
available: MCPServerConfig[];
|
|
43
|
+
unavailable: string[];
|
|
44
|
+
}>;
|