@sammorrowdrums/mcpi-ext 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +1 -1
- package/README.md +109 -139
- package/docs/code-mode.md +38 -0
- package/docs/skills.md +57 -0
- package/docs/tool-cli.md +54 -0
- package/images/code-mode-histogram.png +0 -0
- package/images/skills-enabling-mcp-tools.png +0 -0
- package/images/tool-cli-grep.png +0 -0
- package/mise.toml +1 -13
- package/package.json +5 -7
- package/scripts/test-tool-cli-smoke.mjs +40 -9
- package/src/index.ts +24 -3
- package/src/tool-cli/index.ts +2 -2
- package/dist/code-mode/code-mode-integration.test.d.ts +0 -2
- package/dist/code-mode/code-mode-integration.test.d.ts.map +0 -1
- package/dist/code-mode/code-mode-integration.test.js +0 -116
- package/dist/code-mode/code-mode-integration.test.js.map +0 -1
- package/dist/code-mode/eligibility.d.ts +0 -10
- package/dist/code-mode/eligibility.d.ts.map +0 -1
- package/dist/code-mode/eligibility.js +0 -14
- package/dist/code-mode/eligibility.js.map +0 -1
- package/dist/code-mode/eligibility.test.d.ts +0 -2
- package/dist/code-mode/eligibility.test.d.ts.map +0 -1
- package/dist/code-mode/eligibility.test.js +0 -53
- package/dist/code-mode/eligibility.test.js.map +0 -1
- package/dist/code-mode/executor.d.ts +0 -35
- package/dist/code-mode/executor.d.ts.map +0 -1
- package/dist/code-mode/executor.js +0 -120
- package/dist/code-mode/executor.js.map +0 -1
- package/dist/code-mode/executor.test.d.ts +0 -2
- package/dist/code-mode/executor.test.d.ts.map +0 -1
- package/dist/code-mode/executor.test.js +0 -130
- package/dist/code-mode/executor.test.js.map +0 -1
- package/dist/code-mode/index.d.ts +0 -65
- package/dist/code-mode/index.d.ts.map +0 -1
- package/dist/code-mode/index.js +0 -175
- package/dist/code-mode/index.js.map +0 -1
- package/dist/code-mode/tools.d.ts +0 -45
- package/dist/code-mode/tools.d.ts.map +0 -1
- package/dist/code-mode/tools.js +0 -70
- package/dist/code-mode/tools.js.map +0 -1
- package/dist/code-mode/type-hints.d.ts +0 -20
- package/dist/code-mode/type-hints.d.ts.map +0 -1
- package/dist/code-mode/type-hints.js +0 -189
- package/dist/code-mode/type-hints.js.map +0 -1
- package/dist/code-mode/type-hints.test.d.ts +0 -2
- package/dist/code-mode/type-hints.test.d.ts.map +0 -1
- package/dist/code-mode/type-hints.test.js +0 -157
- package/dist/code-mode/type-hints.test.js.map +0 -1
- package/dist/docker-e2e.d.ts +0 -55
- package/dist/docker-e2e.d.ts.map +0 -1
- package/dist/docker-e2e.js +0 -235
- package/dist/docker-e2e.js.map +0 -1
- package/dist/docker-e2e.test.d.ts +0 -2
- package/dist/docker-e2e.test.d.ts.map +0 -1
- package/dist/docker-e2e.test.js +0 -176
- package/dist/docker-e2e.test.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -128
- package/dist/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -8
- package/dist/index.test.js.map +0 -1
- package/dist/mcp/client-manager.d.ts +0 -46
- package/dist/mcp/client-manager.d.ts.map +0 -1
- package/dist/mcp/client-manager.js +0 -148
- package/dist/mcp/client-manager.js.map +0 -1
- package/dist/mcp/client-manager.test.d.ts +0 -2
- package/dist/mcp/client-manager.test.d.ts.map +0 -1
- package/dist/mcp/client-manager.test.js +0 -204
- package/dist/mcp/client-manager.test.js.map +0 -1
- package/dist/mcp/config-loader.d.ts +0 -8
- package/dist/mcp/config-loader.d.ts.map +0 -1
- package/dist/mcp/config-loader.js +0 -43
- package/dist/mcp/config-loader.js.map +0 -1
- package/dist/mcp/config-loader.test.d.ts +0 -2
- package/dist/mcp/config-loader.test.d.ts.map +0 -1
- package/dist/mcp/config-loader.test.js +0 -93
- package/dist/mcp/config-loader.test.js.map +0 -1
- package/dist/mcp/config.d.ts +0 -46
- package/dist/mcp/config.d.ts.map +0 -1
- package/dist/mcp/config.js +0 -28
- package/dist/mcp/config.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -5
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -4
- package/dist/mcp/index.js.map +0 -1
- package/dist/skills/discover.d.ts +0 -10
- package/dist/skills/discover.d.ts.map +0 -1
- package/dist/skills/discover.js +0 -60
- package/dist/skills/discover.js.map +0 -1
- package/dist/skills/format.d.ts +0 -10
- package/dist/skills/format.d.ts.map +0 -1
- package/dist/skills/format.js +0 -38
- package/dist/skills/format.js.map +0 -1
- package/dist/skills/format.test.d.ts +0 -2
- package/dist/skills/format.test.d.ts.map +0 -1
- package/dist/skills/format.test.js +0 -63
- package/dist/skills/format.test.js.map +0 -1
- package/dist/skills/index.d.ts +0 -6
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -6
- package/dist/skills/index.js.map +0 -1
- package/dist/skills/load-skill-tool.d.ts +0 -40
- package/dist/skills/load-skill-tool.d.ts.map +0 -1
- package/dist/skills/load-skill-tool.js +0 -104
- package/dist/skills/load-skill-tool.js.map +0 -1
- package/dist/skills/mcp-tool-proxy.d.ts +0 -11
- package/dist/skills/mcp-tool-proxy.d.ts.map +0 -1
- package/dist/skills/mcp-tool-proxy.js +0 -116
- package/dist/skills/mcp-tool-proxy.js.map +0 -1
- package/dist/skills/skill-integration.test.d.ts +0 -2
- package/dist/skills/skill-integration.test.d.ts.map +0 -1
- package/dist/skills/skill-integration.test.js +0 -119
- package/dist/skills/skill-integration.test.js.map +0 -1
- package/dist/skills/skill-registry.d.ts +0 -39
- package/dist/skills/skill-registry.d.ts.map +0 -1
- package/dist/skills/skill-registry.js +0 -48
- package/dist/skills/skill-registry.js.map +0 -1
- package/dist/skills/skill-registry.test.d.ts +0 -2
- package/dist/skills/skill-registry.test.d.ts.map +0 -1
- package/dist/skills/skill-registry.test.js +0 -67
- package/dist/skills/skill-registry.test.js.map +0 -1
- package/dist/test-servers/weather-server.d.ts +0 -12
- package/dist/test-servers/weather-server.d.ts.map +0 -1
- package/dist/test-servers/weather-server.js +0 -139
- package/dist/test-servers/weather-server.js.map +0 -1
- package/dist/test-servers/weather-stdio.d.ts +0 -2
- package/dist/test-servers/weather-stdio.d.ts.map +0 -1
- package/dist/test-servers/weather-stdio.js +0 -11
- package/dist/test-servers/weather-stdio.js.map +0 -1
- package/dist/tool-cli/cli.d.ts +0 -3
- package/dist/tool-cli/cli.d.ts.map +0 -1
- package/dist/tool-cli/cli.js +0 -179
- package/dist/tool-cli/cli.js.map +0 -1
- package/dist/tool-cli/constants.d.ts +0 -7
- package/dist/tool-cli/constants.d.ts.map +0 -1
- package/dist/tool-cli/constants.js +0 -15
- package/dist/tool-cli/constants.js.map +0 -1
- package/dist/tool-cli/format.d.ts +0 -8
- package/dist/tool-cli/format.d.ts.map +0 -1
- package/dist/tool-cli/format.js +0 -57
- package/dist/tool-cli/format.js.map +0 -1
- package/dist/tool-cli/format.test.d.ts +0 -2
- package/dist/tool-cli/format.test.d.ts.map +0 -1
- package/dist/tool-cli/format.test.js +0 -30
- package/dist/tool-cli/format.test.js.map +0 -1
- package/dist/tool-cli/index.d.ts +0 -4
- package/dist/tool-cli/index.d.ts.map +0 -1
- package/dist/tool-cli/index.js +0 -4
- package/dist/tool-cli/index.js.map +0 -1
- package/dist/tool-cli/rpc-client.d.ts +0 -6
- package/dist/tool-cli/rpc-client.d.ts.map +0 -1
- package/dist/tool-cli/rpc-client.js +0 -25
- package/dist/tool-cli/rpc-client.js.map +0 -1
- package/dist/tool-cli/rpc-server.d.ts +0 -48
- package/dist/tool-cli/rpc-server.d.ts.map +0 -1
- package/dist/tool-cli/rpc-server.js +0 -220
- package/dist/tool-cli/rpc-server.js.map +0 -1
- package/dist/tool-cli/rpc-server.test.d.ts +0 -2
- package/dist/tool-cli/rpc-server.test.d.ts.map +0 -1
- package/dist/tool-cli/rpc-server.test.js +0 -154
- package/dist/tool-cli/rpc-server.test.js.map +0 -1
- package/src/tool-cli/cli.ts +0 -232
- package/src/tool-cli/constants.ts +0 -15
- package/src/tool-cli/rpc-client.ts +0 -39
- package/src/tool-cli/rpc-server.test.ts +0 -193
- package/src/tool-cli/rpc-server.ts +0 -287
|
@@ -1,28 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Smoke test: start RPC server with the weather test server,
|
|
4
|
-
* exercise every tool-cli command, then shut down.
|
|
4
|
+
* exercise every tool-cli command via the CLI binary, then shut down.
|
|
5
|
+
*
|
|
6
|
+
* Tests the full wiring: ToolProvider → ToolCliServer → tool-cli CLI.
|
|
5
7
|
*/
|
|
6
8
|
import { McpClientManager } from "../dist/mcp/client-manager.js";
|
|
7
|
-
import {
|
|
9
|
+
import { ToolCliServer } from "@sammorrowdrums/tool-cli/server";
|
|
8
10
|
import { execFile } from "node:child_process";
|
|
9
11
|
import { promisify } from "node:util";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
10
14
|
|
|
11
15
|
const execFileAsync = promisify(execFile);
|
|
12
|
-
|
|
16
|
+
|
|
17
|
+
// Resolve the tool-cli binary from node_modules/.bin
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const CLI = join(__dirname, "..", "node_modules", ".bin", "tool-cli");
|
|
20
|
+
|
|
13
21
|
const manager = new McpClientManager();
|
|
14
|
-
let
|
|
22
|
+
let server;
|
|
15
23
|
|
|
16
24
|
async function run(...args) {
|
|
17
25
|
const { stdout } = await execFileAsync("node", [CLI, ...args], {
|
|
18
26
|
encoding: "utf-8",
|
|
19
|
-
env: {
|
|
27
|
+
env: {
|
|
28
|
+
...process.env,
|
|
29
|
+
TOOL_CLI_PORT: String(server.getPort()),
|
|
30
|
+
TOOL_CLI_TOKEN: token,
|
|
31
|
+
},
|
|
20
32
|
});
|
|
21
33
|
console.log(`$ tool-cli ${args.join(" ")}`);
|
|
22
34
|
console.log(stdout);
|
|
23
35
|
return stdout;
|
|
24
36
|
}
|
|
25
37
|
|
|
38
|
+
let token;
|
|
39
|
+
|
|
26
40
|
try {
|
|
27
41
|
await manager.connectOne("weather", {
|
|
28
42
|
type: "stdio",
|
|
@@ -30,8 +44,25 @@ try {
|
|
|
30
44
|
args: ["dist/test-servers/weather-stdio.js"],
|
|
31
45
|
});
|
|
32
46
|
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
// Bridge McpClientManager to ToolProvider
|
|
48
|
+
const provider = {
|
|
49
|
+
getServerNames: () => manager.getConnectedServers(),
|
|
50
|
+
getTools: (s) => manager.getToolsForServer(s),
|
|
51
|
+
async callTool(s, tool, args) {
|
|
52
|
+
const client = manager.getClient(s);
|
|
53
|
+
if (!client) throw new Error(`No client for "${s}"`);
|
|
54
|
+
const result = await client.callTool({ name: tool, arguments: args });
|
|
55
|
+
return {
|
|
56
|
+
content: result.content,
|
|
57
|
+
isError: result.isError === true ? true : undefined,
|
|
58
|
+
structuredContent: result.structuredContent,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
server = new ToolCliServer(provider);
|
|
64
|
+
const startResult = await server.start(console.log);
|
|
65
|
+
token = startResult.token;
|
|
35
66
|
|
|
36
67
|
// --help: list servers
|
|
37
68
|
const help = await run("--help");
|
|
@@ -50,13 +81,13 @@ try {
|
|
|
50
81
|
const result = await run("weather", "check_weather_for_city", '{"city":"London"}');
|
|
51
82
|
if (!result.includes("London")) throw new Error("call result missing London");
|
|
52
83
|
|
|
53
|
-
await
|
|
84
|
+
await server.stop();
|
|
54
85
|
await manager.disconnectAll();
|
|
55
86
|
console.log("Smoke test passed ✓");
|
|
56
87
|
process.exit(0);
|
|
57
88
|
} catch (err) {
|
|
58
89
|
console.error("Smoke test failed:", err);
|
|
59
|
-
await
|
|
90
|
+
await server?.stop().catch(() => {});
|
|
60
91
|
await manager.disconnectAll().catch(() => {});
|
|
61
92
|
process.exit(1);
|
|
62
93
|
}
|
package/src/index.ts
CHANGED
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
formatMcpSkillsForPrompt,
|
|
16
16
|
registerMcpToolProxies,
|
|
17
17
|
} from "./skills/index.js";
|
|
18
|
-
import {
|
|
18
|
+
import { ToolCliServer, formatToolCliForPrompt } from "./tool-cli/index.js";
|
|
19
|
+
import type { ToolProvider } from "./tool-cli/index.js";
|
|
19
20
|
|
|
20
21
|
export default function (pi: ExtensionAPI) {
|
|
21
22
|
pi.registerTool(dockerE2ETool);
|
|
@@ -27,11 +28,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
27
28
|
|
|
28
29
|
const mcpManager = new McpClientManager();
|
|
29
30
|
const skillRegistry = new SkillRegistry();
|
|
30
|
-
const rpcServer = new ToolCliRpcServer(mcpManager);
|
|
31
31
|
const codeModeManager = new CodeModeManager();
|
|
32
32
|
const enabledTools = new Set<string>();
|
|
33
33
|
const gatedToolNames = new Set<string>();
|
|
34
34
|
|
|
35
|
+
// Bridge McpClientManager to the ToolProvider interface
|
|
36
|
+
const toolProvider: ToolProvider = {
|
|
37
|
+
getServerNames: () => mcpManager.getConnectedServers(),
|
|
38
|
+
getTools: (server) => mcpManager.getToolsForServer(server),
|
|
39
|
+
async callTool(server, tool, args) {
|
|
40
|
+
const client = mcpManager.getClient(server);
|
|
41
|
+
if (!client) throw new Error(`No client for server "${server}"`);
|
|
42
|
+
const result = await client.callTool({ name: tool, arguments: args });
|
|
43
|
+
return {
|
|
44
|
+
content: result.content as unknown[],
|
|
45
|
+
isError: result.isError === true ? true : undefined,
|
|
46
|
+
structuredContent: result.structuredContent as Record<string, unknown> | undefined,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const rpcServer = new ToolCliServer(toolProvider);
|
|
51
|
+
|
|
35
52
|
// Register the load_skill tool so the model can activate MCP skills
|
|
36
53
|
pi.registerTool(createLoadSkillTool({ registry: skillRegistry, mcpManager, enabledTools }));
|
|
37
54
|
|
|
@@ -90,7 +107,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
90
107
|
|
|
91
108
|
// Start the tool-cli RPC server for progressive tool discovery
|
|
92
109
|
try {
|
|
93
|
-
await rpcServer.start(log);
|
|
110
|
+
const { port, token } = await rpcServer.start(log);
|
|
111
|
+
pi.setEnv("TOOL_CLI_PORT", String(port));
|
|
112
|
+
pi.setEnv("TOOL_CLI_TOKEN", token);
|
|
94
113
|
} catch (err) {
|
|
95
114
|
log(`[tool-cli] Failed to start RPC server: ${(err as Error).message}`);
|
|
96
115
|
}
|
|
@@ -151,6 +170,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
151
170
|
});
|
|
152
171
|
|
|
153
172
|
pi.on("session_shutdown", async () => {
|
|
173
|
+
pi.unsetEnv("TOOL_CLI_PORT");
|
|
174
|
+
pi.unsetEnv("TOOL_CLI_TOKEN");
|
|
154
175
|
await rpcServer.stop();
|
|
155
176
|
await mcpManager.disconnectAll();
|
|
156
177
|
skillRegistry.clear();
|
package/src/tool-cli/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ToolCliServer } from "@sammorrowdrums/tool-cli/server";
|
|
2
|
+
export type { ToolProvider } from "@sammorrowdrums/tool-cli/server";
|
|
2
3
|
export { formatToolCliForPrompt } from "./format.js";
|
|
3
|
-
export { DEFAULT_PORT, PORT_ENV_VAR, resolvePort } from "./constants.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"code-mode-integration.test.d.ts","sourceRoot":"","sources":["../../src/code-mode/code-mode-integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
-
import { McpClientManager } from "../mcp/client-manager.js";
|
|
3
|
-
import { CodeModeManager } from "./index.js";
|
|
4
|
-
/**
|
|
5
|
-
* Integration test: connect to the test weather server, verify code mode
|
|
6
|
-
* eligibility, type hint generation, and sandboxed execution with real
|
|
7
|
-
* MCP tool calls returning structuredContent.
|
|
8
|
-
*/
|
|
9
|
-
describe("code mode integration (weather server)", () => {
|
|
10
|
-
const manager = new McpClientManager();
|
|
11
|
-
const codeMode = new CodeModeManager();
|
|
12
|
-
beforeAll(async () => {
|
|
13
|
-
await manager.connectAll({
|
|
14
|
-
mcpServers: {
|
|
15
|
-
"test-weather": {
|
|
16
|
-
type: "stdio",
|
|
17
|
-
command: "node",
|
|
18
|
-
args: ["dist/test-servers/weather-stdio.js"],
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
codeMode.initialize(manager);
|
|
23
|
-
});
|
|
24
|
-
afterAll(async () => {
|
|
25
|
-
await manager.disconnectAll();
|
|
26
|
-
});
|
|
27
|
-
it("discovers eligible tools with readOnlyHint + outputSchema", () => {
|
|
28
|
-
const eligible = codeMode.getEligibleTools();
|
|
29
|
-
expect(eligible.length).toBeGreaterThanOrEqual(2);
|
|
30
|
-
const names = eligible.map((t) => t.name).sort();
|
|
31
|
-
expect(names).toContain("check_weather_for_city");
|
|
32
|
-
expect(names).toContain("check_weekly_forecast_for_city");
|
|
33
|
-
expect(names).toContain("echo");
|
|
34
|
-
});
|
|
35
|
-
it("generates type hints for eligible tools", () => {
|
|
36
|
-
const hints = codeMode.getTypeHints();
|
|
37
|
-
expect(hints).toContain("declare const codemode");
|
|
38
|
-
expect(hints).toContain("check_weather_for_city");
|
|
39
|
-
expect(hints).toContain("check_weekly_forecast_for_city");
|
|
40
|
-
expect(hints).toContain("echo");
|
|
41
|
-
expect(hints).toContain("listTools");
|
|
42
|
-
});
|
|
43
|
-
it("reports as active", () => {
|
|
44
|
-
expect(codeMode.isActive).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
it("generates system prompt section", () => {
|
|
47
|
-
const section = codeMode.formatSystemPromptSection();
|
|
48
|
-
expect(section).toContain("<code_mode>");
|
|
49
|
-
expect(section).toContain("code_search");
|
|
50
|
-
expect(section).toContain("code_execute");
|
|
51
|
-
expect(section).toContain("declare const codemode");
|
|
52
|
-
});
|
|
53
|
-
it("executes code that calls a single tool", async () => {
|
|
54
|
-
const result = await codeMode.executeCode(`
|
|
55
|
-
const weather = await codemode.check_weather_for_city({ city: "Tokyo" });
|
|
56
|
-
return weather;
|
|
57
|
-
`);
|
|
58
|
-
expect(result.error).toBeUndefined();
|
|
59
|
-
expect(result.result).toHaveProperty("temperature", 26);
|
|
60
|
-
expect(result.result).toHaveProperty("conditions", "Sunny");
|
|
61
|
-
expect(result.result).toHaveProperty("humidity", 55);
|
|
62
|
-
});
|
|
63
|
-
it("executes code that chains multiple tool calls", async () => {
|
|
64
|
-
const result = await codeMode.executeCode(`
|
|
65
|
-
const cities = ["London", "Tokyo"];
|
|
66
|
-
const results = [];
|
|
67
|
-
for (const city of cities) {
|
|
68
|
-
const w = await codemode.check_weather_for_city({ city });
|
|
69
|
-
results.push({ city, temp: w.temperature });
|
|
70
|
-
}
|
|
71
|
-
return results;
|
|
72
|
-
`);
|
|
73
|
-
expect(result.error).toBeUndefined();
|
|
74
|
-
expect(result.result).toEqual([
|
|
75
|
-
{ city: "London", temp: 14 },
|
|
76
|
-
{ city: "Tokyo", temp: 26 },
|
|
77
|
-
]);
|
|
78
|
-
});
|
|
79
|
-
it("executes code that uses listTools", async () => {
|
|
80
|
-
const result = await codeMode.executeCode(`
|
|
81
|
-
const tools = await codemode.listTools();
|
|
82
|
-
return tools.sort();
|
|
83
|
-
`);
|
|
84
|
-
expect(result.error).toBeUndefined();
|
|
85
|
-
const names = result.result;
|
|
86
|
-
expect(names).toContain("check_weather_for_city");
|
|
87
|
-
expect(names).toContain("echo");
|
|
88
|
-
});
|
|
89
|
-
it("executes code that calls echo tool", async () => {
|
|
90
|
-
const result = await codeMode.executeCode(`
|
|
91
|
-
const r = await codemode.echo({ message: "hello code mode" });
|
|
92
|
-
return r;
|
|
93
|
-
`);
|
|
94
|
-
expect(result.error).toBeUndefined();
|
|
95
|
-
expect(result.result).toEqual({ echo: "hello code mode" });
|
|
96
|
-
});
|
|
97
|
-
it("handles tool errors gracefully", async () => {
|
|
98
|
-
const result = await codeMode.executeCode(`
|
|
99
|
-
const w = await codemode.check_weather_for_city({ city: "Atlantis" });
|
|
100
|
-
return w;
|
|
101
|
-
`);
|
|
102
|
-
// Atlantis isn't in the data — server returns text-only content (no structuredContent)
|
|
103
|
-
// The executor should still return something (parsed text or raw string)
|
|
104
|
-
expect(result.error).toBeUndefined();
|
|
105
|
-
expect(result.result).toBeDefined();
|
|
106
|
-
});
|
|
107
|
-
it("search mode works for tool discovery", async () => {
|
|
108
|
-
const result = await codeMode.searchTools(`
|
|
109
|
-
const tools = await codemode.listTools();
|
|
110
|
-
return tools;
|
|
111
|
-
`);
|
|
112
|
-
expect(result.error).toBeUndefined();
|
|
113
|
-
expect(Array.isArray(result.result)).toBe(true);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
//# sourceMappingURL=code-mode-integration.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"code-mode-integration.test.js","sourceRoot":"","sources":["../../src/code-mode/code-mode-integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { McpClientManager, McpTool } from "../mcp/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* A tool is eligible for code mode when:
|
|
4
|
-
* 1. annotations.readOnlyHint === true — does not modify its environment
|
|
5
|
-
* 2. outputSchema is defined — results are typed and parseable
|
|
6
|
-
*/
|
|
7
|
-
export declare function isEligibleForCodeMode(tool: McpTool): boolean;
|
|
8
|
-
/** Get all tools eligible for code mode across all connected MCP servers. */
|
|
9
|
-
export declare function getEligibleTools(mcpManager: McpClientManager): McpTool[];
|
|
10
|
-
//# sourceMappingURL=eligibility.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"eligibility.d.ts","sourceRoot":"","sources":["../../src/code-mode/eligibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAG5D;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,EAAE,CAExE"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A tool is eligible for code mode when:
|
|
3
|
-
* 1. annotations.readOnlyHint === true — does not modify its environment
|
|
4
|
-
* 2. outputSchema is defined — results are typed and parseable
|
|
5
|
-
*/
|
|
6
|
-
export function isEligibleForCodeMode(tool) {
|
|
7
|
-
const annotations = tool.annotations;
|
|
8
|
-
return annotations?.readOnlyHint === true && tool.outputSchema != null;
|
|
9
|
-
}
|
|
10
|
-
/** Get all tools eligible for code mode across all connected MCP servers. */
|
|
11
|
-
export function getEligibleTools(mcpManager) {
|
|
12
|
-
return mcpManager.getTools().filter(isEligibleForCodeMode);
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=eligibility.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"eligibility.js","sourceRoot":"","sources":["../../src/code-mode/eligibility.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAa;IACjD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAqD,CAAC;IAC/E,OAAO,WAAW,EAAE,YAAY,KAAK,IAAI,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC;AACzE,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,UAA4B;IAC3D,OAAO,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"eligibility.test.d.ts","sourceRoot":"","sources":["../../src/code-mode/eligibility.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { isEligibleForCodeMode } from "./eligibility.js";
|
|
3
|
-
function makeTool(overrides = {}) {
|
|
4
|
-
return {
|
|
5
|
-
name: "test_tool",
|
|
6
|
-
description: "A test tool",
|
|
7
|
-
inputSchema: { type: "object", properties: {} },
|
|
8
|
-
serverName: "test-server",
|
|
9
|
-
...overrides,
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
describe("isEligibleForCodeMode", () => {
|
|
13
|
-
it("returns true when readOnlyHint is true and outputSchema is defined", () => {
|
|
14
|
-
const tool = makeTool({
|
|
15
|
-
annotations: { readOnlyHint: true },
|
|
16
|
-
outputSchema: { type: "object", properties: { result: { type: "string" } } },
|
|
17
|
-
});
|
|
18
|
-
expect(isEligibleForCodeMode(tool)).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
it("returns false when readOnlyHint is false", () => {
|
|
21
|
-
const tool = makeTool({
|
|
22
|
-
annotations: { readOnlyHint: false },
|
|
23
|
-
outputSchema: { type: "object", properties: {} },
|
|
24
|
-
});
|
|
25
|
-
expect(isEligibleForCodeMode(tool)).toBe(false);
|
|
26
|
-
});
|
|
27
|
-
it("returns false when readOnlyHint is missing", () => {
|
|
28
|
-
const tool = makeTool({
|
|
29
|
-
annotations: {},
|
|
30
|
-
outputSchema: { type: "object", properties: {} },
|
|
31
|
-
});
|
|
32
|
-
expect(isEligibleForCodeMode(tool)).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
it("returns false when annotations is undefined", () => {
|
|
35
|
-
const tool = makeTool({
|
|
36
|
-
annotations: undefined,
|
|
37
|
-
outputSchema: { type: "object", properties: {} },
|
|
38
|
-
});
|
|
39
|
-
expect(isEligibleForCodeMode(tool)).toBe(false);
|
|
40
|
-
});
|
|
41
|
-
it("returns false when outputSchema is missing", () => {
|
|
42
|
-
const tool = makeTool({
|
|
43
|
-
annotations: { readOnlyHint: true },
|
|
44
|
-
outputSchema: undefined,
|
|
45
|
-
});
|
|
46
|
-
expect(isEligibleForCodeMode(tool)).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
it("returns false when both readOnlyHint and outputSchema are missing", () => {
|
|
49
|
-
const tool = makeTool();
|
|
50
|
-
expect(isEligibleForCodeMode(tool)).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
//# sourceMappingURL=eligibility.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"eligibility.test.js","sourceRoot":"","sources":["../../src/code-mode/eligibility.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,SAAS,QAAQ,CAAC,YAA8B,EAAE;IAChD,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,aAAa;QAC1B,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;QAC/C,UAAU,EAAE,aAAa;QACzB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,IAAI,GAAG,QAAQ,CAAC;YACpB,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;YACnC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;SAC7E,CAAC,CAAC;QACH,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,IAAI,GAAG,QAAQ,CAAC;YACpB,WAAW,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE;YACpC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;SACjD,CAAC,CAAC;QACH,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,IAAI,GAAG,QAAQ,CAAC;YACpB,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;SACjD,CAAC,CAAC;QACH,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,IAAI,GAAG,QAAQ,CAAC;YACpB,WAAW,EAAE,SAAS;YACtB,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;SACjD,CAAC,CAAC;QACH,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,IAAI,GAAG,QAAQ,CAAC;YACpB,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;YACnC,YAAY,EAAE,SAAS;SACxB,CAAC,CAAC;QACH,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;QACxB,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/** Result of code execution. */
|
|
2
|
-
export interface ExecuteResult {
|
|
3
|
-
result: unknown;
|
|
4
|
-
error?: string;
|
|
5
|
-
logs: string[];
|
|
6
|
-
}
|
|
7
|
-
/** A function the sandbox can call to invoke an MCP tool. */
|
|
8
|
-
export type ToolDispatchFn = (toolName: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
9
|
-
export interface ExecutorOptions {
|
|
10
|
-
/** Memory limit in MB for the V8 isolate. Default: 128. */
|
|
11
|
-
memoryLimit?: number;
|
|
12
|
-
/** Execution timeout in milliseconds. Default: 30000. */
|
|
13
|
-
timeoutMs?: number;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Execute model-generated JavaScript code in an isolated V8 sandbox.
|
|
17
|
-
*
|
|
18
|
-
* The code runs in a fresh `isolated-vm` isolate with:
|
|
19
|
-
* - Enforced memory limit (default 128MB)
|
|
20
|
-
* - Execution timeout (default 30s)
|
|
21
|
-
* - No access to Node.js APIs, filesystem, or network
|
|
22
|
-
* - Only access to provided tool dispatch functions via `codemode.*` proxy
|
|
23
|
-
*
|
|
24
|
-
* Tool calls are dispatched to the host via `Reference` callbacks —
|
|
25
|
-
* actual MCP tool execution happens outside the sandbox.
|
|
26
|
-
*/
|
|
27
|
-
export declare function executeInSandbox(code: string, toolNames: string[], dispatch: ToolDispatchFn, options?: ExecutorOptions): Promise<ExecuteResult>;
|
|
28
|
-
/**
|
|
29
|
-
* Normalize model-generated code:
|
|
30
|
-
* - Strip markdown code fences
|
|
31
|
-
* - Handle arrow functions, function declarations, export default
|
|
32
|
-
* - Otherwise leave code as a statement block; callers must explicitly `return` a value
|
|
33
|
-
*/
|
|
34
|
-
export declare function normalizeCode(code: string): string;
|
|
35
|
-
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/code-mode/executor.ts"],"names":[],"mappings":"AAGA,gCAAgC;AAChC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,6DAA6D;AAC7D,MAAM,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEnG,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAUxB;AA6ED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA6BlD"}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import ivm from "isolated-vm";
|
|
2
|
-
import { sanitizeToolName } from "./type-hints.js";
|
|
3
|
-
const DEFAULT_MEMORY_LIMIT = 128;
|
|
4
|
-
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
|
-
/**
|
|
6
|
-
* Execute model-generated JavaScript code in an isolated V8 sandbox.
|
|
7
|
-
*
|
|
8
|
-
* The code runs in a fresh `isolated-vm` isolate with:
|
|
9
|
-
* - Enforced memory limit (default 128MB)
|
|
10
|
-
* - Execution timeout (default 30s)
|
|
11
|
-
* - No access to Node.js APIs, filesystem, or network
|
|
12
|
-
* - Only access to provided tool dispatch functions via `codemode.*` proxy
|
|
13
|
-
*
|
|
14
|
-
* Tool calls are dispatched to the host via `Reference` callbacks —
|
|
15
|
-
* actual MCP tool execution happens outside the sandbox.
|
|
16
|
-
*/
|
|
17
|
-
export async function executeInSandbox(code, toolNames, dispatch, options = {}) {
|
|
18
|
-
const memoryLimit = options.memoryLimit ?? DEFAULT_MEMORY_LIMIT;
|
|
19
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
20
|
-
const isolate = new ivm.Isolate({ memoryLimit });
|
|
21
|
-
try {
|
|
22
|
-
return await runInIsolate(isolate, code, toolNames, dispatch, timeoutMs);
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
isolate.dispose();
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
async function runInIsolate(isolate, code, toolNames, dispatch, timeoutMs) {
|
|
29
|
-
const ctx = await isolate.createContext();
|
|
30
|
-
const jail = ctx.global;
|
|
31
|
-
const logs = [];
|
|
32
|
-
// Inject console.log that captures to logs array
|
|
33
|
-
const logCallback = new ivm.Callback((...args) => {
|
|
34
|
-
logs.push(args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" "));
|
|
35
|
-
});
|
|
36
|
-
await jail.set("__log", logCallback);
|
|
37
|
-
// Inject tool dispatcher Reference (async callback)
|
|
38
|
-
const dispatchRef = new ivm.Reference(async (toolName, argsJson) => {
|
|
39
|
-
const args = JSON.parse(argsJson);
|
|
40
|
-
const result = await dispatch(toolName, args);
|
|
41
|
-
return JSON.stringify(result === undefined ? null : result);
|
|
42
|
-
});
|
|
43
|
-
await jail.set("__dispatch", dispatchRef);
|
|
44
|
-
// Build the tool call helper and codemode proxy as setup code
|
|
45
|
-
const toolProxyEntries = toolNames
|
|
46
|
-
.map((name) => {
|
|
47
|
-
const safe = sanitizeToolName(name);
|
|
48
|
-
return ` ${safe}: async (args) => __callTool(${JSON.stringify(name)}, args)`;
|
|
49
|
-
})
|
|
50
|
-
.join(",\n");
|
|
51
|
-
const normalized = normalizeCode(code);
|
|
52
|
-
// Use ctx.eval with { promise: true } instead of compileModule,
|
|
53
|
-
// because module.evaluate() can resolve prematurely with multiple
|
|
54
|
-
// sequential async Reference.apply calls.
|
|
55
|
-
const wrappedCode = `
|
|
56
|
-
(async () => {
|
|
57
|
-
const console = { log: (...args) => __log(args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ')) };
|
|
58
|
-
const __callTool = async (name, args) => {
|
|
59
|
-
const r = await __dispatch.apply(undefined, [name, JSON.stringify(args ?? {})], { arguments: { copy: true }, result: { promise: true, copy: true } });
|
|
60
|
-
return JSON.parse(r);
|
|
61
|
-
};
|
|
62
|
-
const codemode = {
|
|
63
|
-
listTools: async () => ${JSON.stringify(toolNames)},
|
|
64
|
-
describeTools: async (names) => "Use the typed codemode.toolName(args) methods instead.",
|
|
65
|
-
${toolProxyEntries}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const __userFn = async () => {
|
|
69
|
-
${normalized}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
return JSON.stringify({ value: await __userFn() });
|
|
73
|
-
})()
|
|
74
|
-
`;
|
|
75
|
-
try {
|
|
76
|
-
const rawResult = (await ctx.eval(wrappedCode, {
|
|
77
|
-
promise: true,
|
|
78
|
-
copy: true,
|
|
79
|
-
timeout: timeoutMs,
|
|
80
|
-
}));
|
|
81
|
-
const parsed = JSON.parse(rawResult);
|
|
82
|
-
return { result: parsed.value, logs };
|
|
83
|
-
}
|
|
84
|
-
catch (err) {
|
|
85
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
86
|
-
return { result: undefined, error: message, logs };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Normalize model-generated code:
|
|
91
|
-
* - Strip markdown code fences
|
|
92
|
-
* - Handle arrow functions, function declarations, export default
|
|
93
|
-
* - Otherwise leave code as a statement block; callers must explicitly `return` a value
|
|
94
|
-
*/
|
|
95
|
-
export function normalizeCode(code) {
|
|
96
|
-
let normalized = code.trim();
|
|
97
|
-
// Strip markdown code fences
|
|
98
|
-
normalized = normalized.replace(/^```(?:js|javascript|typescript|ts)?\s*\n?/i, "");
|
|
99
|
-
normalized = normalized.replace(/\n?```\s*$/i, "");
|
|
100
|
-
normalized = normalized.trim();
|
|
101
|
-
// If it's an arrow function or function expression, invoke it
|
|
102
|
-
if (/^(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/.test(normalized)) {
|
|
103
|
-
return `return (${normalized})();`;
|
|
104
|
-
}
|
|
105
|
-
// If it's a function declaration, invoke it
|
|
106
|
-
if (/^(?:async\s+)?function\s+/.test(normalized)) {
|
|
107
|
-
const match = normalized.match(/^(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/);
|
|
108
|
-
if (match) {
|
|
109
|
-
return `${normalized}\nreturn ${match[1]}();`;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// If it starts with export default, strip the export default
|
|
113
|
-
if (/^export\s+default\s+/.test(normalized)) {
|
|
114
|
-
normalized = normalized.replace(/^export\s+default\s+/, "");
|
|
115
|
-
return `return (${normalized})();`;
|
|
116
|
-
}
|
|
117
|
-
// Otherwise, treat as a code block — return the last expression
|
|
118
|
-
return normalized;
|
|
119
|
-
}
|
|
120
|
-
//# sourceMappingURL=executor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/code-mode/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"executor.test.d.ts","sourceRoot":"","sources":["../../src/code-mode/executor.test.ts"],"names":[],"mappings":""}
|