poe-code 8.0.0 → 9.0.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/dist/cli/commands/harness.js +12 -3
- package/dist/cli/commands/harness.js.map +1 -1
- package/dist/index.js +1515 -712
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/package.json +15 -2
- package/packages/safejs/dist/chunks/chunk-4YIDX44G.js +40 -0
- package/packages/safejs/dist/chunks/chunk-4YIDX44G.js.map +7 -0
- package/packages/safejs/dist/chunks/chunk-55ZK3CR7.js +7802 -0
- package/packages/safejs/dist/chunks/chunk-55ZK3CR7.js.map +7 -0
- package/packages/safejs/dist/chunks/chunk-GQBEEFVQ.js +28798 -0
- package/packages/safejs/dist/chunks/chunk-GQBEEFVQ.js.map +7 -0
- package/packages/safejs/dist/chunks/chunk-NI4WFFTL.js +5701 -0
- package/packages/safejs/dist/chunks/chunk-NI4WFFTL.js.map +7 -0
- package/packages/safejs/dist/chunks/inMemory-RLZ4QGDZ.js +48 -0
- package/packages/safejs/dist/chunks/inMemory-RLZ4QGDZ.js.map +7 -0
- package/packages/safejs/dist/chunks/server-3SNT3RE2.js +12518 -0
- package/packages/safejs/dist/chunks/server-3SNT3RE2.js.map +7 -0
- package/packages/safejs/dist/chunks/types-BTF37PZ6.js +348 -0
- package/packages/safejs/dist/chunks/types-BTF37PZ6.js.map +7 -0
- package/packages/safejs/dist/cli.d.ts +2 -0
- package/packages/safejs/dist/cli.js +550 -491
- package/packages/safejs/dist/cli.js.map +7 -0
- package/packages/safejs/dist/core.js +14 -4
- package/packages/safejs/dist/core.js.map +7 -0
- package/packages/safejs/dist/index.d.ts +3 -0
- package/packages/safejs/dist/index.js +791 -30
- package/packages/safejs/dist/index.js.map +7 -0
- package/packages/safejs/dist/interp/host-bridge.d.ts +2 -0
- package/packages/safejs/dist/interp/host-bridge.js +20 -9
- package/packages/safejs/dist/interp/host-call.d.ts +5 -0
- package/packages/safejs/dist/interp/host-call.js +48 -3
- package/packages/safejs/dist/interp/resources.d.ts +7 -0
- package/packages/safejs/dist/interp/resources.js +36 -0
- package/packages/safejs/dist/modules/mcp-transport.d.ts +26 -0
- package/packages/safejs/dist/modules/mcp-transport.js +259 -0
- package/packages/safejs/dist/modules/mcp.d.ts +15 -1
- package/packages/safejs/dist/modules/mcp.js +107 -0
- package/packages/safejs/dist/run.js +6 -3
- package/packages/safejs/dist/snapshot/dump-format.d.ts +1 -1
- package/packages/safejs/dist/snapshot/dump-format.js +1 -1
- package/packages/safejs/dist/snapshot/replay-data.d.ts +1 -0
- package/packages/safejs/dist/snapshot/replay-data.js +3 -1
- package/packages/safejs/dist/snapshot/replay-inputs.d.ts +2 -2
- package/packages/safejs/dist/snapshot/replay-inputs.js +2 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { HttpTransport, McpClient, StdioTransport } from "tiny-mcp-client";
|
|
4
|
+
export function parseMcpConfig(source, directory) {
|
|
5
|
+
const parsed = JSON.parse(source);
|
|
6
|
+
const options = normalizeMcpOptions(parsed, [
|
|
7
|
+
"servers",
|
|
8
|
+
"requestTimeoutMs",
|
|
9
|
+
"closeTimeoutMs",
|
|
10
|
+
"maxToolPages"
|
|
11
|
+
]);
|
|
12
|
+
for (const server of Object.values(options.servers)) {
|
|
13
|
+
if ("command" in server) {
|
|
14
|
+
server.cwd = path.resolve(directory, server.cwd ?? ".");
|
|
15
|
+
if (server.command.includes("/") || server.command.includes("\\")) {
|
|
16
|
+
server.command = path.resolve(directory, server.command);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return options;
|
|
21
|
+
}
|
|
22
|
+
export function normalizeMcpOptions(value, keys = [
|
|
23
|
+
"servers",
|
|
24
|
+
"requestTimeoutMs",
|
|
25
|
+
"closeTimeoutMs",
|
|
26
|
+
"maxToolPages",
|
|
27
|
+
"signal",
|
|
28
|
+
"fetch",
|
|
29
|
+
"spawn"
|
|
30
|
+
]) {
|
|
31
|
+
const options = readRecord(value, "MCP options", keys);
|
|
32
|
+
const servers = readRecord(options.servers, "MCP servers");
|
|
33
|
+
const normalized = Object.create(null);
|
|
34
|
+
for (const [name, input] of Object.entries(servers)) {
|
|
35
|
+
if (name.length === 0 || name.trim() !== name)
|
|
36
|
+
throw new TypeError("MCP server names must be non-empty without surrounding whitespace.");
|
|
37
|
+
const server = readRecord(input, `MCP server ${name}`);
|
|
38
|
+
if (Object.hasOwn(server, "command")) {
|
|
39
|
+
readRecord(server, `MCP server ${name}`, ["command", "args", "cwd", "env"]);
|
|
40
|
+
const command = readText(server.command, "MCP command");
|
|
41
|
+
if (server.args !== undefined &&
|
|
42
|
+
(!Array.isArray(server.args) ||
|
|
43
|
+
server.args.some((argument) => typeof argument !== "string")))
|
|
44
|
+
throw new TypeError("MCP args must be an array of strings.");
|
|
45
|
+
normalized[name] = {
|
|
46
|
+
command,
|
|
47
|
+
...(server.args === undefined ? {} : { args: [...server.args] }),
|
|
48
|
+
...(server.cwd === undefined ? {} : { cwd: readText(server.cwd, "MCP cwd") }),
|
|
49
|
+
env: readStrings(server.env ?? {}, "MCP environment")
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
readRecord(server, `MCP server ${name}`, ["url", "headers"]);
|
|
54
|
+
const url = new URL(readText(server.url, "MCP URL"));
|
|
55
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
56
|
+
throw new TypeError("MCP URL must use HTTP or HTTPS without credentials or a fragment.");
|
|
57
|
+
const headers = readStrings(server.headers ?? {}, "MCP headers");
|
|
58
|
+
new Headers(headers);
|
|
59
|
+
normalized[name] = { url: url.href, headers };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const name of ["fetch", "spawn"])
|
|
63
|
+
if (options[name] !== undefined && typeof options[name] !== "function")
|
|
64
|
+
throw new TypeError(`MCP ${name} must be a function.`);
|
|
65
|
+
if (options.signal !== undefined && !(options.signal instanceof AbortSignal))
|
|
66
|
+
throw new TypeError("MCP signal must be an AbortSignal.");
|
|
67
|
+
return {
|
|
68
|
+
servers: normalized,
|
|
69
|
+
requestTimeoutMs: readTimeout(options.requestTimeoutMs, 30_000),
|
|
70
|
+
closeTimeoutMs: readTimeout(options.closeTimeoutMs, 1_000),
|
|
71
|
+
maxToolPages: readTimeout(options.maxToolPages, 100),
|
|
72
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
73
|
+
...(options.fetch === undefined ? {} : { fetch: options.fetch }),
|
|
74
|
+
...(options.spawn === undefined ? {} : { spawn: options.spawn })
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function readRecord(value, label, keys) {
|
|
78
|
+
if (typeof value !== "object" ||
|
|
79
|
+
value === null ||
|
|
80
|
+
Array.isArray(value) ||
|
|
81
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
82
|
+
throw new TypeError(`${label} must be a plain object.`);
|
|
83
|
+
const result = Object.create(null);
|
|
84
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
|
|
85
|
+
if (!("value" in descriptor) || (keys !== undefined && !keys.includes(key)))
|
|
86
|
+
throw new TypeError(`${label} contains unsupported field ${key}.`);
|
|
87
|
+
result[key] = descriptor.value;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
function readText(value, label) {
|
|
92
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0"))
|
|
93
|
+
throw new TypeError(`${label} must be a non-empty string without NUL characters.`);
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
function readStrings(value, label) {
|
|
97
|
+
const entries = readRecord(value, label);
|
|
98
|
+
for (const [name, entry] of Object.entries(entries))
|
|
99
|
+
if (typeof entry !== "string" || name.includes("\0") || entry.includes("\0"))
|
|
100
|
+
throw new TypeError(`${label} must contain strings without NUL characters.`);
|
|
101
|
+
return entries;
|
|
102
|
+
}
|
|
103
|
+
function readTimeout(value, fallback) {
|
|
104
|
+
if (value === undefined)
|
|
105
|
+
return fallback;
|
|
106
|
+
if (typeof value !== "number" ||
|
|
107
|
+
!Number.isSafeInteger(value) ||
|
|
108
|
+
value <= 0 ||
|
|
109
|
+
value > 2_147_483_647)
|
|
110
|
+
throw new TypeError("MCP timeouts must be positive 32-bit integers.");
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
export function connectMcpTransport(server, options, signal) {
|
|
114
|
+
signal?.throwIfAborted();
|
|
115
|
+
options.signal?.throwIfAborted();
|
|
116
|
+
const client = new McpClient({
|
|
117
|
+
clientInfo: { name: "safejs", version: "1" },
|
|
118
|
+
requestTimeoutMs: options.requestTimeoutMs
|
|
119
|
+
});
|
|
120
|
+
let child;
|
|
121
|
+
const activeRequests = new Set();
|
|
122
|
+
const transport = "command" in server
|
|
123
|
+
? new StdioTransport({
|
|
124
|
+
...server,
|
|
125
|
+
spawn(command, args, spawnOptions) {
|
|
126
|
+
child =
|
|
127
|
+
options.spawn === undefined
|
|
128
|
+
? spawn(command, args, spawnOptions)
|
|
129
|
+
: options.spawn(command, args, spawnOptions);
|
|
130
|
+
return child;
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
: new HttpTransport({
|
|
134
|
+
...server,
|
|
135
|
+
async fetch(input, init) {
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
activeRequests.add(controller);
|
|
138
|
+
const cancel = () => controller.abort(init?.signal?.reason);
|
|
139
|
+
init?.signal?.addEventListener("abort", cancel, { once: true });
|
|
140
|
+
if (init?.signal?.aborted)
|
|
141
|
+
cancel();
|
|
142
|
+
const timeout = setTimeout(() => controller.abort(new Error("MCP HTTP request timed out.")), init?.method === "DELETE" ? options.closeTimeoutMs : options.requestTimeoutMs);
|
|
143
|
+
const finish = () => {
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
activeRequests.delete(controller);
|
|
146
|
+
init?.signal?.removeEventListener("abort", cancel);
|
|
147
|
+
};
|
|
148
|
+
try {
|
|
149
|
+
const response = await (options.fetch ?? fetch)(input, {
|
|
150
|
+
...init,
|
|
151
|
+
signal: controller.signal,
|
|
152
|
+
redirect: "error"
|
|
153
|
+
});
|
|
154
|
+
if (response.body === null) {
|
|
155
|
+
finish();
|
|
156
|
+
return response;
|
|
157
|
+
}
|
|
158
|
+
const reader = response.body.getReader();
|
|
159
|
+
let stopReading;
|
|
160
|
+
const release = () => {
|
|
161
|
+
finish();
|
|
162
|
+
if (stopReading !== undefined)
|
|
163
|
+
controller.signal.removeEventListener("abort", stopReading);
|
|
164
|
+
};
|
|
165
|
+
const body = new ReadableStream({
|
|
166
|
+
start(stream) {
|
|
167
|
+
stopReading = () => {
|
|
168
|
+
void reader.cancel(controller.signal.reason).catch(() => undefined);
|
|
169
|
+
stream.error(controller.signal.reason);
|
|
170
|
+
release();
|
|
171
|
+
};
|
|
172
|
+
controller.signal.addEventListener("abort", stopReading, { once: true });
|
|
173
|
+
if (controller.signal.aborted)
|
|
174
|
+
stopReading();
|
|
175
|
+
},
|
|
176
|
+
async pull(stream) {
|
|
177
|
+
try {
|
|
178
|
+
const result = await reader.read();
|
|
179
|
+
if (controller.signal.aborted)
|
|
180
|
+
return;
|
|
181
|
+
if (result.done) {
|
|
182
|
+
release();
|
|
183
|
+
stream.close();
|
|
184
|
+
}
|
|
185
|
+
else
|
|
186
|
+
stream.enqueue(result.value);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
release();
|
|
190
|
+
stream.error(error);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
async cancel(reason) {
|
|
194
|
+
release();
|
|
195
|
+
await reader.cancel(reason);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return new Response(body, {
|
|
199
|
+
status: response.status,
|
|
200
|
+
statusText: response.statusText,
|
|
201
|
+
headers: response.headers
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
finish();
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
let closing;
|
|
211
|
+
const cancel = () => {
|
|
212
|
+
void close().catch(() => undefined);
|
|
213
|
+
};
|
|
214
|
+
function close() {
|
|
215
|
+
closing ??= (async () => {
|
|
216
|
+
signal?.removeEventListener("abort", cancel);
|
|
217
|
+
options.signal?.removeEventListener("abort", cancel);
|
|
218
|
+
for (const request of activeRequests)
|
|
219
|
+
request.abort(new Error("MCP connection closed."));
|
|
220
|
+
await client.close();
|
|
221
|
+
transport.dispose();
|
|
222
|
+
await new Promise((resolve, reject) => {
|
|
223
|
+
let forced;
|
|
224
|
+
const timeout = setTimeout(() => {
|
|
225
|
+
if (child !== undefined && child.exitCode === null && child.signalCode === null)
|
|
226
|
+
child.kill("SIGKILL");
|
|
227
|
+
forced = setTimeout(() => reject(new Error("MCP transport did not close.")), options.closeTimeoutMs);
|
|
228
|
+
}, options.closeTimeoutMs);
|
|
229
|
+
void transport.closed.then(() => {
|
|
230
|
+
clearTimeout(timeout);
|
|
231
|
+
clearTimeout(forced);
|
|
232
|
+
resolve();
|
|
233
|
+
}, (error) => {
|
|
234
|
+
clearTimeout(timeout);
|
|
235
|
+
clearTimeout(forced);
|
|
236
|
+
reject(error);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
})();
|
|
240
|
+
return closing;
|
|
241
|
+
}
|
|
242
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
243
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
244
|
+
const aborted = signal?.aborted || options.signal?.aborted;
|
|
245
|
+
if (aborted)
|
|
246
|
+
cancel();
|
|
247
|
+
const connecting = aborted
|
|
248
|
+
? Promise.reject(signal?.aborted ? signal.reason : options.signal?.reason)
|
|
249
|
+
: client.connect(transport);
|
|
250
|
+
const ready = connecting.then(() => client, async (error) => {
|
|
251
|
+
await close().catch(() => undefined);
|
|
252
|
+
throw signal?.aborted
|
|
253
|
+
? signal.reason
|
|
254
|
+
: options.signal?.aborted
|
|
255
|
+
? options.signal.reason
|
|
256
|
+
: error;
|
|
257
|
+
});
|
|
258
|
+
return { ready, close };
|
|
259
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type McpModuleOptions } from "./mcp-transport.js";
|
|
1
2
|
export type McpModuleServerHandle = {
|
|
2
3
|
command: string;
|
|
3
4
|
args?: string[];
|
|
@@ -42,8 +43,21 @@ type McpConnection = {
|
|
|
42
43
|
}>): Promise<McpModuleToolBatchResult[]>;
|
|
43
44
|
};
|
|
44
45
|
export type ConnectMcp = (server: McpModuleServerHandle) => Promise<McpConnection> | McpConnection;
|
|
45
|
-
export
|
|
46
|
+
export type McpNamedServerHandle = {
|
|
47
|
+
name: string;
|
|
48
|
+
};
|
|
49
|
+
export type ManagedMcpClient = McpModuleClient & {
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
export type ManagedMcpModule = {
|
|
53
|
+
servers: Record<string, ManagedMcpClient>;
|
|
54
|
+
server(name: string): McpNamedServerHandle;
|
|
55
|
+
client(handle: McpNamedServerHandle | string): Promise<ManagedMcpClient>;
|
|
56
|
+
};
|
|
57
|
+
type CustomMcpModule = {
|
|
46
58
|
server(server: McpModuleServerHandle): McpModuleServerHandle;
|
|
47
59
|
client(handle: McpModuleServerHandle): Promise<McpModuleClient>;
|
|
48
60
|
};
|
|
61
|
+
export declare function makeMcpModule(options: McpModuleOptions): ManagedMcpModule;
|
|
62
|
+
export declare function makeMcpModule(connectMcp: ConnectMcp): CustomMcpModule;
|
|
49
63
|
export {};
|
|
@@ -1,5 +1,112 @@
|
|
|
1
|
+
import { McpError } from "tiny-mcp-client";
|
|
2
|
+
import { hostErrorData } from "../error/shape.js";
|
|
3
|
+
import { declareHostOperation } from "../interp/host-bridge.js";
|
|
4
|
+
import { runResources } from "../interp/resources.js";
|
|
5
|
+
import { connectMcpTransport, normalizeMcpOptions } from "./mcp-transport.js";
|
|
1
6
|
const DEFAULT_TOOL_BATCH_CONCURRENCY = 4;
|
|
2
7
|
export function makeMcpModule(connectMcp) {
|
|
8
|
+
if (typeof connectMcp !== "function") {
|
|
9
|
+
const options = normalizeMcpOptions(connectMcp);
|
|
10
|
+
const servers = Object.create(null);
|
|
11
|
+
for (const [name, config] of Object.entries(options.servers)) {
|
|
12
|
+
const connections = new WeakMap();
|
|
13
|
+
const directScope = {};
|
|
14
|
+
const connection = () => {
|
|
15
|
+
const scope = runResources.getStore();
|
|
16
|
+
scope?.signal.throwIfAborted();
|
|
17
|
+
options.signal?.throwIfAborted();
|
|
18
|
+
const owner = scope ?? directScope;
|
|
19
|
+
let current = connections.get(owner);
|
|
20
|
+
if (current === undefined) {
|
|
21
|
+
current = connectMcpTransport(config, options, scope?.signal);
|
|
22
|
+
connections.set(owner, current);
|
|
23
|
+
scope?.add(current.close);
|
|
24
|
+
}
|
|
25
|
+
return current.ready;
|
|
26
|
+
};
|
|
27
|
+
const invoke = async (operation) => {
|
|
28
|
+
try {
|
|
29
|
+
const result = await operation(await connection());
|
|
30
|
+
runResources.getStore()?.signal.throwIfAborted();
|
|
31
|
+
options.signal?.throwIfAborted();
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
runResources.getStore()?.signal.throwIfAborted();
|
|
36
|
+
options.signal?.throwIfAborted();
|
|
37
|
+
if (error instanceof McpError) {
|
|
38
|
+
hostErrorData.set(error, {
|
|
39
|
+
code: error.code,
|
|
40
|
+
...(error.data === undefined ? {} : { data: error.data })
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
servers[name] = {
|
|
47
|
+
tools: declareHostOperation(async () => invoke(async (client) => {
|
|
48
|
+
const tools = [];
|
|
49
|
+
const cursors = new Set();
|
|
50
|
+
let pages = 0;
|
|
51
|
+
let cursor;
|
|
52
|
+
do {
|
|
53
|
+
const result = await client.listTools(cursor === undefined ? undefined : { cursor });
|
|
54
|
+
pages += 1;
|
|
55
|
+
tools.push(...normalizeToolsResult(result));
|
|
56
|
+
cursor = result.nextCursor;
|
|
57
|
+
if (cursor !== undefined) {
|
|
58
|
+
if (typeof cursor !== "string" || cursors.has(cursor))
|
|
59
|
+
throw new Error("MCP tools pagination did not advance.");
|
|
60
|
+
cursors.add(cursor);
|
|
61
|
+
if (pages >= options.maxToolPages)
|
|
62
|
+
throw new Error("MCP tools page limit exceeded.");
|
|
63
|
+
}
|
|
64
|
+
} while (cursor !== undefined);
|
|
65
|
+
return tools;
|
|
66
|
+
}), "re-issue"),
|
|
67
|
+
tool: declareHostOperation(async (toolName, args) => {
|
|
68
|
+
const request = createNullRecord({
|
|
69
|
+
name: readNonEmptyTrimmedString(toolName, "MCP tool name"),
|
|
70
|
+
...(args === undefined ? {} : { arguments: readToolArguments(args) })
|
|
71
|
+
});
|
|
72
|
+
return invoke((client) => client.callTool(request));
|
|
73
|
+
}, "read-side-effect"),
|
|
74
|
+
toolBatch: declareHostOperation(async (calls) => {
|
|
75
|
+
const normalized = readToolBatchCalls(calls);
|
|
76
|
+
if (normalized.length === 0)
|
|
77
|
+
return [];
|
|
78
|
+
return invoke((client) => executeToolBatch(client, normalized));
|
|
79
|
+
}, "read-side-effect"),
|
|
80
|
+
close: declareHostOperation(async () => {
|
|
81
|
+
const owner = runResources.getStore() ?? directScope;
|
|
82
|
+
const current = connections.get(owner);
|
|
83
|
+
if (current !== undefined) {
|
|
84
|
+
await current.close();
|
|
85
|
+
connections.delete(owner);
|
|
86
|
+
}
|
|
87
|
+
}, "re-issue")
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const readName = (value) => {
|
|
91
|
+
const name = readNonEmptyString(value, "MCP server name");
|
|
92
|
+
if (!Object.hasOwn(servers, name))
|
|
93
|
+
throw new Error(`MCP server '${name}' is not configured.`);
|
|
94
|
+
return name;
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
servers,
|
|
98
|
+
server(name) {
|
|
99
|
+
return createNullRecord({ name: readName(name) });
|
|
100
|
+
},
|
|
101
|
+
async client(handle) {
|
|
102
|
+
if (typeof handle === "string")
|
|
103
|
+
return servers[readName(handle)];
|
|
104
|
+
if (!isRecord(handle) || Object.keys(handle).length !== 1 || !Object.hasOwn(handle, "name"))
|
|
105
|
+
throw new TypeError("MCP client requires a named server handle.");
|
|
106
|
+
return servers[readName(getOwnProperty(handle, "name"))];
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
3
110
|
return {
|
|
4
111
|
server(server) {
|
|
5
112
|
return normalizeServerHandle(server);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hashSource } from "./parse/hash.js";
|
|
2
|
+
import { withRunResources } from "./interp/resources.js";
|
|
2
3
|
import { createReplayableRandom } from "./random.js";
|
|
3
4
|
import { attachErrorSpan, describeThrownValue, materializeWrappedErrorCause, replaceErrorStack } from "./error/shape.js";
|
|
4
5
|
import { parseExecutableModule } from "./parse/parser.js";
|
|
@@ -43,7 +44,7 @@ export function run(source, options = {}) {
|
|
|
43
44
|
const lifecycle = { hostCallbackDepth: 0 };
|
|
44
45
|
const dumpController = createDumpController(lifecycle);
|
|
45
46
|
const promiseTracker = createSandboxPromiseRejectionTracker();
|
|
46
|
-
const
|
|
47
|
+
const execute = async () => {
|
|
47
48
|
const promiseReplay = new PromiseReplay(options.snapshot?.promiseReplay);
|
|
48
49
|
return promiseReplayContext.run(promiseReplay, async () => {
|
|
49
50
|
const deactivateOtelSink = activateOtelSink(options.otelSink);
|
|
@@ -140,7 +141,8 @@ export function run(source, options = {}) {
|
|
|
140
141
|
if (!isSandboxClosure(binding))
|
|
141
142
|
throw new TypeError("Invalid initial promise operation.");
|
|
142
143
|
return binding.call([]);
|
|
143
|
-
});
|
|
144
|
+
}, hostCalls.rebindHostCapability.bind(hostCalls));
|
|
145
|
+
hostCalls.validateHostCapabilities();
|
|
144
146
|
budget.setRetainedDataUsage(initialInputs, measureSandboxData([
|
|
145
147
|
...Object.values(initialInputs.values.bindings),
|
|
146
148
|
...Object.values(initialInputs.values.imports),
|
|
@@ -310,7 +312,8 @@ export function run(source, options = {}) {
|
|
|
310
312
|
deactivateOtelSink();
|
|
311
313
|
}
|
|
312
314
|
});
|
|
313
|
-
}
|
|
315
|
+
};
|
|
316
|
+
const result = withRunResources(options.signal, () => withSandboxPromiseRejectionTracker(promiseTracker, execute));
|
|
314
317
|
return attachDumpController(result, dumpController);
|
|
315
318
|
}
|
|
316
319
|
async function throwIfReturnedPromiseRejected(result) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const DUMP_FORMAT_VERSION = 1;
|
|
2
|
-
export const EXECUTION_SEMANTICS = "jobs-
|
|
2
|
+
export const EXECUTION_SEMANTICS = "jobs-v5";
|
|
3
3
|
import { assertSnapshotGraphDepth } from "../graph-depth.js";
|
|
4
4
|
import { sandboxErrorTypes } from "../error/shape.js";
|
|
5
5
|
import { getSandboxArgumentEntries, isSandboxArguments } from "../interp/arguments.js";
|
|
@@ -61,5 +61,6 @@ export declare function encodeReplayData(value: SandboxValue, options?: {
|
|
|
61
61
|
export declare function decodeReplayData(input: unknown, options?: {
|
|
62
62
|
resolveCapability?: (id: string) => SandboxClosure | undefined;
|
|
63
63
|
resolvePromise?: (id: string) => SandboxPromise | undefined;
|
|
64
|
+
onCapabilityRestored?: (original: SandboxClosure, restored: SandboxClosure) => void;
|
|
64
65
|
}): SandboxValue;
|
|
65
66
|
export {};
|
|
@@ -185,7 +185,7 @@ export function decodeReplayData(input, options = {}) {
|
|
|
185
185
|
restored.set(id, capability);
|
|
186
186
|
return capability;
|
|
187
187
|
}
|
|
188
|
-
|
|
188
|
+
const copy = createSandboxClosure({
|
|
189
189
|
...capability,
|
|
190
190
|
boundTarget: capability.boundTarget,
|
|
191
191
|
cancellationSignal: capability.cancellationSignal,
|
|
@@ -202,6 +202,8 @@ export function decodeReplayData(input, options = {}) {
|
|
|
202
202
|
return properties;
|
|
203
203
|
}
|
|
204
204
|
});
|
|
205
|
+
options.onCapabilityRestored?.(capability, copy);
|
|
206
|
+
return copy;
|
|
205
207
|
}
|
|
206
208
|
if (kind === "map") {
|
|
207
209
|
const result = createSandboxMap();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SandboxPromise, type SandboxValue } from "../interp/values.js";
|
|
1
|
+
import { type SandboxClosure, type SandboxPromise, type SandboxValue } from "../interp/values.js";
|
|
2
2
|
import { type ReplayData } from "./replay-data.js";
|
|
3
3
|
export type ReplayInputs = {
|
|
4
4
|
bindings: Record<string, SandboxValue>;
|
|
@@ -6,7 +6,7 @@ export type ReplayInputs = {
|
|
|
6
6
|
entryPointArgs: SandboxValue[] | undefined;
|
|
7
7
|
importMeta: SandboxValue;
|
|
8
8
|
};
|
|
9
|
-
export declare function prepareReplayInputs(current: ReplayInputs, saved?: unknown, preparePromise?: (promise: SandboxPromise | undefined, id: string) => SandboxPromise): {
|
|
9
|
+
export declare function prepareReplayInputs(current: ReplayInputs, saved?: unknown, preparePromise?: (promise: SandboxPromise | undefined, id: string) => SandboxPromise, onCapabilityRestored?: (original: SandboxClosure, restored: SandboxClosure) => void): {
|
|
10
10
|
values: ReplayInputs;
|
|
11
11
|
snapshot: ReplayData;
|
|
12
12
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createSandboxPromise, isSandboxClosure, isSandboxMap, isSandboxSet, isSandboxPromise } from "../interp/values.js";
|
|
2
2
|
import { decodeReplayData, encodeReplayData } from "./replay-data.js";
|
|
3
3
|
const validationPromise = createSandboxPromise(Promise.resolve(undefined));
|
|
4
|
-
export function prepareReplayInputs(current, saved, preparePromise) {
|
|
4
|
+
export function prepareReplayInputs(current, saved, preparePromise, onCapabilityRestored) {
|
|
5
5
|
const identities = new WeakMap();
|
|
6
6
|
const capabilities = new Map();
|
|
7
7
|
const promises = new Map();
|
|
@@ -81,6 +81,7 @@ export function prepareReplayInputs(current, saved, preparePromise) {
|
|
|
81
81
|
assertReplayInputShape(validated);
|
|
82
82
|
const restored = decodeReplayData(saved, {
|
|
83
83
|
resolveCapability,
|
|
84
|
+
onCapabilityRestored,
|
|
84
85
|
resolvePromise: (id) => {
|
|
85
86
|
const value = readCapability(id);
|
|
86
87
|
if (!promises.has(id) && preparePromise !== undefined)
|