@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.
Files changed (39) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +378 -0
  3. package/dist/cli/commands.d.ts +70 -0
  4. package/dist/cli/commands.js +436 -0
  5. package/dist/cli/config-manager.d.ts +53 -0
  6. package/dist/cli/config-manager.js +142 -0
  7. package/dist/cli/index.d.ts +19 -0
  8. package/dist/cli/index.js +165 -0
  9. package/dist/executor/container-executor.d.ts +81 -0
  10. package/dist/executor/container-executor.js +351 -0
  11. package/dist/executor/executor-test-suite.d.ts +22 -0
  12. package/dist/executor/executor-test-suite.js +395 -0
  13. package/dist/executor/isolated-vm-executor.d.ts +78 -0
  14. package/dist/executor/isolated-vm-executor.js +368 -0
  15. package/dist/executor/vm2-executor.d.ts +21 -0
  16. package/dist/executor/vm2-executor.js +109 -0
  17. package/dist/executor/wrap-code.d.ts +52 -0
  18. package/dist/executor/wrap-code.js +80 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.js +6 -0
  21. package/dist/mcp/config.d.ts +44 -0
  22. package/dist/mcp/config.js +102 -0
  23. package/dist/mcp/e2e-bridge-test-suite.d.ts +28 -0
  24. package/dist/mcp/e2e-bridge-test-suite.js +429 -0
  25. package/dist/mcp/executor.d.ts +31 -0
  26. package/dist/mcp/executor.js +121 -0
  27. package/dist/mcp/mcp-adapter.d.ts +12 -0
  28. package/dist/mcp/mcp-adapter.js +49 -0
  29. package/dist/mcp/mcp-client.d.ts +85 -0
  30. package/dist/mcp/mcp-client.js +441 -0
  31. package/dist/mcp/oauth-handler.d.ts +33 -0
  32. package/dist/mcp/oauth-handler.js +138 -0
  33. package/dist/mcp/server.d.ts +25 -0
  34. package/dist/mcp/server.js +322 -0
  35. package/dist/mcp/token-persistence.d.ts +57 -0
  36. package/dist/mcp/token-persistence.js +131 -0
  37. package/dist/utils/logger.d.ts +44 -0
  38. package/dist/utils/logger.js +123 -0
  39. 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
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Main entry point for the Code Mode bridge
3
+ */
4
+ export * from "./mcp/config.js";
5
+ export * from "./mcp/server.js";
6
+ export * from "./mcp/executor.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Main entry point for the Code Mode bridge
3
+ */
4
+ export * from "./mcp/config.js";
5
+ export * from "./mcp/server.js";
6
+ export * from "./mcp/executor.js";
@@ -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
+ }>;