@finalai/agent-provider 0.1.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.
Potentially problematic release.
This version of @finalai/agent-provider might be problematic. Click here for more details.
- package/README.md +12 -0
- package/dist/modules/channel-context.d.mts +102 -0
- package/dist/modules/channel-context.mjs +52 -0
- package/dist/modules/channel-context.mjs.map +7 -0
- package/dist/modules/channel-socket.d.mts +65 -0
- package/dist/modules/channel-socket.mjs +188 -0
- package/dist/modules/channel-socket.mjs.map +7 -0
- package/dist/modules/channel.d.mts +382 -0
- package/dist/modules/channel.mjs +146 -0
- package/dist/modules/channel.mjs.map +7 -0
- package/dist/modules/conformance/agent.d.mts +20 -0
- package/dist/modules/conformance/agent.mjs +623 -0
- package/dist/modules/conformance/agent.mjs.map +7 -0
- package/dist/modules/conformance/fixtures.d.mts +41 -0
- package/dist/modules/conformance/fixtures.mjs +190 -0
- package/dist/modules/conformance/fixtures.mjs.map +7 -0
- package/dist/modules/conformance/harness-kit.d.mts +82 -0
- package/dist/modules/conformance/harness-kit.mjs +1303 -0
- package/dist/modules/conformance/harness-kit.mjs.map +7 -0
- package/dist/modules/conformance/image.d.mts +2 -0
- package/dist/modules/conformance/image.mjs +79 -0
- package/dist/modules/conformance/image.mjs.map +7 -0
- package/dist/modules/conformance/index.d.mts +8 -0
- package/dist/modules/conformance/index.mjs +12 -0
- package/dist/modules/conformance/index.mjs.map +7 -0
- package/dist/modules/conformance/local-harness.d.mts +25 -0
- package/dist/modules/conformance/local-harness.mjs +108 -0
- package/dist/modules/conformance/local-harness.mjs.map +7 -0
- package/dist/modules/conformance/protocol.d.mts +30 -0
- package/dist/modules/conformance/protocol.mjs +56 -0
- package/dist/modules/conformance/protocol.mjs.map +7 -0
- package/dist/modules/conformance/sandbox-kit.d.mts +19 -0
- package/dist/modules/conformance/sandbox-kit.mjs +320 -0
- package/dist/modules/conformance/sandbox-kit.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/chat-completions.d.mts +30 -0
- package/dist/modules/conformance/scripted-model/chat-completions.mjs +125 -0
- package/dist/modules/conformance/scripted-model/chat-completions.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/directives.d.mts +84 -0
- package/dist/modules/conformance/scripted-model/directives.mjs +304 -0
- package/dist/modules/conformance/scripted-model/directives.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/handler.d.mts +14 -0
- package/dist/modules/conformance/scripted-model/handler.mjs +133 -0
- package/dist/modules/conformance/scripted-model/handler.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/index.d.mts +6 -0
- package/dist/modules/conformance/scripted-model/index.mjs +7 -0
- package/dist/modules/conformance/scripted-model/index.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/messages.d.mts +39 -0
- package/dist/modules/conformance/scripted-model/messages.mjs +139 -0
- package/dist/modules/conformance/scripted-model/messages.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/responses.d.mts +62 -0
- package/dist/modules/conformance/scripted-model/responses.mjs +273 -0
- package/dist/modules/conformance/scripted-model/responses.mjs.map +7 -0
- package/dist/modules/conformance/scripted-model/sse.d.mts +15 -0
- package/dist/modules/conformance/scripted-model/sse.mjs +75 -0
- package/dist/modules/conformance/scripted-model/sse.mjs.map +7 -0
- package/dist/modules/conformance/v1-agent.d.mts +24 -0
- package/dist/modules/conformance/v1-agent.mjs +40 -0
- package/dist/modules/conformance/v1-agent.mjs.map +7 -0
- package/dist/modules/credential-routes.d.mts +24 -0
- package/dist/modules/credential-routes.mjs +25 -0
- package/dist/modules/credential-routes.mjs.map +7 -0
- package/dist/modules/credentials.d.mts +206 -0
- package/dist/modules/credentials.mjs +207 -0
- package/dist/modules/credentials.mjs.map +7 -0
- package/dist/modules/dynamic.d.mts +32 -0
- package/dist/modules/dynamic.mjs +77 -0
- package/dist/modules/dynamic.mjs.map +7 -0
- package/dist/modules/harness.d.mts +397 -0
- package/dist/modules/harness.mjs +213 -0
- package/dist/modules/harness.mjs.map +7 -0
- package/dist/modules/index.d.mts +15 -0
- package/dist/modules/index.mjs +40 -0
- package/dist/modules/index.mjs.map +7 -0
- package/dist/modules/json.d.mts +12 -0
- package/dist/modules/json.mjs +19 -0
- package/dist/modules/json.mjs.map +7 -0
- package/dist/modules/local-channel-socket.d.mts +23 -0
- package/dist/modules/local-channel-socket.mjs +141 -0
- package/dist/modules/local-channel-socket.mjs.map +7 -0
- package/dist/modules/manifest.d.mts +558 -0
- package/dist/modules/manifest.mjs +266 -0
- package/dist/modules/manifest.mjs.map +7 -0
- package/dist/modules/mcp-content.d.mts +21 -0
- package/dist/modules/mcp-content.mjs +86 -0
- package/dist/modules/mcp-content.mjs.map +7 -0
- package/dist/modules/provider-ref.d.mts +29 -0
- package/dist/modules/provider-ref.mjs +55 -0
- package/dist/modules/provider-ref.mjs.map +7 -0
- package/dist/modules/sandbox/bytes.d.mts +9 -0
- package/dist/modules/sandbox/bytes.mjs +154 -0
- package/dist/modules/sandbox/bytes.mjs.map +7 -0
- package/dist/modules/sandbox/client.d.mts +51 -0
- package/dist/modules/sandbox/client.mjs +500 -0
- package/dist/modules/sandbox/client.mjs.map +7 -0
- package/dist/modules/sandbox/confined-files.d.mts +22 -0
- package/dist/modules/sandbox/confined-files.mjs +142 -0
- package/dist/modules/sandbox/confined-files.mjs.map +7 -0
- package/dist/modules/sandbox/convenience.d.mts +10 -0
- package/dist/modules/sandbox/convenience.mjs +116 -0
- package/dist/modules/sandbox/convenience.mjs.map +7 -0
- package/dist/modules/sandbox/editor.d.mts +49 -0
- package/dist/modules/sandbox/editor.mjs +43 -0
- package/dist/modules/sandbox/editor.mjs.map +7 -0
- package/dist/modules/sandbox/frame-channel.d.mts +47 -0
- package/dist/modules/sandbox/frame-channel.mjs +153 -0
- package/dist/modules/sandbox/frame-channel.mjs.map +7 -0
- package/dist/modules/sandbox/index.d.mts +18 -0
- package/dist/modules/sandbox/index.mjs +70 -0
- package/dist/modules/sandbox/index.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-channel.d.mts +9 -0
- package/dist/modules/sandbox/mcp-channel.mjs +77 -0
- package/dist/modules/sandbox/mcp-channel.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-hosted.d.mts +46 -0
- package/dist/modules/sandbox/mcp-hosted.mjs +221 -0
- package/dist/modules/sandbox/mcp-hosted.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-search.d.mts +51 -0
- package/dist/modules/sandbox/mcp-search.mjs +129 -0
- package/dist/modules/sandbox/mcp-search.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-shell.d.mts +21 -0
- package/dist/modules/sandbox/mcp-shell.mjs +205 -0
- package/dist/modules/sandbox/mcp-shell.mjs.map +7 -0
- package/dist/modules/sandbox/mcp-testing.d.mts +11 -0
- package/dist/modules/sandbox/mcp-testing.mjs +33 -0
- package/dist/modules/sandbox/mcp-testing.mjs.map +7 -0
- package/dist/modules/sandbox/mcp.d.mts +83 -0
- package/dist/modules/sandbox/mcp.mjs +971 -0
- package/dist/modules/sandbox/mcp.mjs.map +7 -0
- package/dist/modules/sandbox/memory-sandbox.d.mts +32 -0
- package/dist/modules/sandbox/memory-sandbox.mjs +416 -0
- package/dist/modules/sandbox/memory-sandbox.mjs.map +7 -0
- package/dist/modules/sandbox/memory-shell.d.mts +29 -0
- package/dist/modules/sandbox/memory-shell.mjs +278 -0
- package/dist/modules/sandbox/memory-shell.mjs.map +7 -0
- package/dist/modules/sandbox/open-scope-node.d.mts +4 -0
- package/dist/modules/sandbox/open-scope-node.mjs +19 -0
- package/dist/modules/sandbox/open-scope-node.mjs.map +7 -0
- package/dist/modules/sandbox/open-scope.d.mts +16 -0
- package/dist/modules/sandbox/open-scope.mjs +66 -0
- package/dist/modules/sandbox/open-scope.mjs.map +7 -0
- package/dist/modules/sandbox/paths.d.mts +7 -0
- package/dist/modules/sandbox/paths.mjs +50 -0
- package/dist/modules/sandbox/paths.mjs.map +7 -0
- package/dist/modules/sandbox/rpc-peer.d.mts +35 -0
- package/dist/modules/sandbox/rpc-peer.mjs +224 -0
- package/dist/modules/sandbox/rpc-peer.mjs.map +7 -0
- package/dist/modules/sandbox/seeds.d.mts +26 -0
- package/dist/modules/sandbox/seeds.mjs +228 -0
- package/dist/modules/sandbox/seeds.mjs.map +7 -0
- package/dist/modules/sandbox/server.d.mts +66 -0
- package/dist/modules/sandbox/server.mjs +567 -0
- package/dist/modules/sandbox/server.mjs.map +7 -0
- package/dist/modules/sandbox/snapshots.d.mts +49 -0
- package/dist/modules/sandbox/snapshots.mjs +55 -0
- package/dist/modules/sandbox/snapshots.mjs.map +7 -0
- package/dist/modules/sandbox/stream-mux.d.mts +18 -0
- package/dist/modules/sandbox/stream-mux.mjs +285 -0
- package/dist/modules/sandbox/stream-mux.mjs.map +7 -0
- package/dist/modules/sandbox/testing.d.mts +25 -0
- package/dist/modules/sandbox/testing.mjs +64 -0
- package/dist/modules/sandbox/testing.mjs.map +7 -0
- package/dist/modules/sandbox/types.d.mts +352 -0
- package/dist/modules/sandbox/types.mjs +86 -0
- package/dist/modules/sandbox/types.mjs.map +7 -0
- package/dist/modules/sandbox/wire.d.mts +671 -0
- package/dist/modules/sandbox/wire.mjs +537 -0
- package/dist/modules/sandbox/wire.mjs.map +7 -0
- package/dist/modules/sandbox/workspace-root.d.mts +8 -0
- package/dist/modules/sandbox/workspace-root.mjs +79 -0
- package/dist/modules/sandbox/workspace-root.mjs.map +7 -0
- package/dist/modules/schemas.d.mts +121 -0
- package/dist/modules/schemas.mjs +146 -0
- package/dist/modules/schemas.mjs.map +7 -0
- package/dist/modules/semantic-events.d.mts +31 -0
- package/dist/modules/semantic-events.mjs +23 -0
- package/dist/modules/semantic-events.mjs.map +7 -0
- package/package.json +199 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { bytesToBase64, bytesToStream, encodeSandboxText, readAllBytes, utf8Encode } from "./bytes.mjs";
|
|
2
|
+
import { readTextFileStream, runCollect } from "./convenience.mjs";
|
|
3
|
+
import { resolvePosixPath } from "./paths.mjs";
|
|
4
|
+
import { createRpcPeer } from "./rpc-peer.mjs";
|
|
5
|
+
import {
|
|
6
|
+
SandboxCapability
|
|
7
|
+
} from "./types.mjs";
|
|
8
|
+
import {
|
|
9
|
+
frameChannelFromWebSocket,
|
|
10
|
+
HANDLE_RPC_IDEMPOTENT_METHODS,
|
|
11
|
+
HANDLE_RPC_VERSION,
|
|
12
|
+
handleRpcClientProtocols,
|
|
13
|
+
HandleRpcCloseCode,
|
|
14
|
+
handleRpcError,
|
|
15
|
+
HandleRpcErrorCode,
|
|
16
|
+
HandleRpcMethod,
|
|
17
|
+
handleRpcMethodSchemas,
|
|
18
|
+
isHandleRpcError,
|
|
19
|
+
MAX_INLINE_WRITE_BYTES,
|
|
20
|
+
parseHandleRpcResult
|
|
21
|
+
} from "./wire.mjs";
|
|
22
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
+
const defaultOpenChannel = (WebSocketImpl) => async (target) => {
|
|
24
|
+
const Impl = WebSocketImpl ?? globalThis.WebSocket;
|
|
25
|
+
if (Impl == null)
|
|
26
|
+
throw new Error("no WebSocket implementation available; pass WebSocket or openChannel");
|
|
27
|
+
const { channel, opened } = frameChannelFromWebSocket(
|
|
28
|
+
new Impl(target.url, handleRpcClientProtocols(target.token))
|
|
29
|
+
);
|
|
30
|
+
await opened;
|
|
31
|
+
return channel;
|
|
32
|
+
};
|
|
33
|
+
const isRetryable = (cause) => isHandleRpcError(cause) && cause.retryable;
|
|
34
|
+
const connectSandboxClient = async (args) => {
|
|
35
|
+
const now = args.now ?? Date.now;
|
|
36
|
+
const sleep = args.sleep ?? defaultSleep;
|
|
37
|
+
const randomId = args.randomId ?? (() => crypto.randomUUID());
|
|
38
|
+
const openChannel = args.openChannel ?? defaultOpenChannel(args.WebSocket);
|
|
39
|
+
const attempts = args.reconnect?.attempts ?? 5;
|
|
40
|
+
const baseDelayMs = args.reconnect?.baseDelayMs ?? 100;
|
|
41
|
+
const maxDelayMs = args.reconnect?.maxDelayMs ?? 5e3;
|
|
42
|
+
const refreshMarginMs = args.refreshMarginMs ?? 6e4;
|
|
43
|
+
const binary = args.binary ?? true;
|
|
44
|
+
let origin = {
|
|
45
|
+
url: args.url,
|
|
46
|
+
token: args.token,
|
|
47
|
+
expiresAt: args.expiresAt ?? null
|
|
48
|
+
};
|
|
49
|
+
let current = null;
|
|
50
|
+
let connecting = null;
|
|
51
|
+
let closedByUser = false;
|
|
52
|
+
let refreshTimer = null;
|
|
53
|
+
const live = /* @__PURE__ */ new Set();
|
|
54
|
+
let lastInfo = null;
|
|
55
|
+
const refreshOrigin = async () => {
|
|
56
|
+
if (args.refreshToken == null) return false;
|
|
57
|
+
const fresh = await args.refreshToken();
|
|
58
|
+
origin = {
|
|
59
|
+
url: fresh.url ?? origin.url,
|
|
60
|
+
token: fresh.token,
|
|
61
|
+
expiresAt: fresh.expiresAt
|
|
62
|
+
};
|
|
63
|
+
return true;
|
|
64
|
+
};
|
|
65
|
+
const scheduleRefresh = (connection) => {
|
|
66
|
+
if (refreshTimer != null) clearTimeout(refreshTimer);
|
|
67
|
+
refreshTimer = null;
|
|
68
|
+
const expiresAt = connection.info.expiresAt ?? connection.target.expiresAt;
|
|
69
|
+
if (expiresAt == null || args.refreshToken == null || closedByUser) return;
|
|
70
|
+
const remaining = Date.parse(expiresAt) - now();
|
|
71
|
+
if (Number.isNaN(remaining)) return;
|
|
72
|
+
const delay = Math.max(0, Math.min(remaining - refreshMarginMs, remaining / 2));
|
|
73
|
+
refreshTimer = setTimeout(
|
|
74
|
+
() => {
|
|
75
|
+
void refreshConnection(connection).catch(() => void 0);
|
|
76
|
+
},
|
|
77
|
+
Math.min(2147483647, delay)
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
const refreshConnection = async (connection) => {
|
|
81
|
+
if (closedByUser || connection.peer.isClosed()) return;
|
|
82
|
+
await refreshOrigin();
|
|
83
|
+
if (origin.url === connection.target.url) {
|
|
84
|
+
const result = await requestOn(connection, HandleRpcMethod.AuthRefresh, {
|
|
85
|
+
token: origin.token
|
|
86
|
+
});
|
|
87
|
+
connection.target = {
|
|
88
|
+
...connection.target,
|
|
89
|
+
token: origin.token,
|
|
90
|
+
expiresAt: result.expiresAt
|
|
91
|
+
};
|
|
92
|
+
connection.info = { ...connection.info, expiresAt: result.expiresAt };
|
|
93
|
+
scheduleRefresh(connection);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (current === connection) current = null;
|
|
97
|
+
await getConnection();
|
|
98
|
+
};
|
|
99
|
+
const initialize = async (target) => {
|
|
100
|
+
const channel = await openChannel({ url: target.url, token: target.token });
|
|
101
|
+
let connection = null;
|
|
102
|
+
const peer = createRpcPeer({
|
|
103
|
+
channel,
|
|
104
|
+
binary,
|
|
105
|
+
onClose: () => {
|
|
106
|
+
if (connection == null) return;
|
|
107
|
+
live.delete(connection);
|
|
108
|
+
if (current === connection) current = null;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
try {
|
|
112
|
+
const raw = await peer.request(HandleRpcMethod.Initialize, {
|
|
113
|
+
handleRpcVersion: HANDLE_RPC_VERSION,
|
|
114
|
+
token: target.token,
|
|
115
|
+
client: args.clientInfo
|
|
116
|
+
});
|
|
117
|
+
const info = handleRpcMethodSchemas[HandleRpcMethod.Initialize].result.parse(raw);
|
|
118
|
+
connection = { peer, target, info, nextStreamId: 1 };
|
|
119
|
+
live.add(connection);
|
|
120
|
+
return connection;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
peer.close(HandleRpcCloseCode.Normal, "initialize failed");
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const establish = async () => {
|
|
127
|
+
let failures = 0;
|
|
128
|
+
let refreshed = false;
|
|
129
|
+
for (; ; ) {
|
|
130
|
+
if (closedByUser)
|
|
131
|
+
throw handleRpcError({
|
|
132
|
+
code: HandleRpcErrorCode.ConnectionClosed,
|
|
133
|
+
message: "client closed"
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
const connection = await initialize(origin);
|
|
137
|
+
current = connection;
|
|
138
|
+
lastInfo = connection.info;
|
|
139
|
+
scheduleRefresh(connection);
|
|
140
|
+
return connection;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (closedByUser) throw error;
|
|
143
|
+
const unauthorized = isHandleRpcError(error) && error.code === HandleRpcErrorCode.Unauthorized;
|
|
144
|
+
if (unauthorized && !refreshed && await refreshOrigin().catch(() => false)) {
|
|
145
|
+
refreshed = true;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (unauthorized) throw error;
|
|
149
|
+
failures += 1;
|
|
150
|
+
if (failures >= attempts) {
|
|
151
|
+
throw handleRpcError({
|
|
152
|
+
code: HandleRpcErrorCode.ConnectionClosed,
|
|
153
|
+
message: `could not connect to the sandbox: ${error instanceof Error ? error.message : String(error)}`,
|
|
154
|
+
retryable: true
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
await sleep(Math.min(maxDelayMs, baseDelayMs * 2 ** (failures - 1)));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const getConnection = () => {
|
|
162
|
+
if (current != null && !current.peer.isClosed()) return Promise.resolve(current);
|
|
163
|
+
connecting ??= establish().finally(() => {
|
|
164
|
+
connecting = null;
|
|
165
|
+
});
|
|
166
|
+
return connecting;
|
|
167
|
+
};
|
|
168
|
+
const requestOn = async (connection, method, params, options) => {
|
|
169
|
+
const raw = await connection.peer.request(method, params, { signal: options?.signal });
|
|
170
|
+
return parseHandleRpcResult(method, raw);
|
|
171
|
+
};
|
|
172
|
+
const callOnConnection = async (method, params, options) => {
|
|
173
|
+
const idempotent = HANDLE_RPC_IDEMPOTENT_METHODS.includes(method);
|
|
174
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
175
|
+
const connection = await getConnection();
|
|
176
|
+
try {
|
|
177
|
+
return { connection, result: await requestOn(connection, method, params, options) };
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (attempt === 0 && idempotent && isRetryable(error)) continue;
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const call = async (method, params, options) => (await callOnConnection(method, params, options)).result;
|
|
185
|
+
const allocateStreamId = (connection) => {
|
|
186
|
+
const id = connection.nextStreamId;
|
|
187
|
+
connection.nextStreamId += 1;
|
|
188
|
+
return id;
|
|
189
|
+
};
|
|
190
|
+
const first = await getConnection();
|
|
191
|
+
const describe = () => current?.info ?? lastInfo ?? first.info;
|
|
192
|
+
const readFile = async (options) => {
|
|
193
|
+
const connection = await getConnection();
|
|
194
|
+
const streamId = allocateStreamId(connection);
|
|
195
|
+
const stream = connection.peer.streams.openIncoming(streamId);
|
|
196
|
+
try {
|
|
197
|
+
const result = await requestOn(
|
|
198
|
+
connection,
|
|
199
|
+
HandleRpcMethod.ReadFile,
|
|
200
|
+
{
|
|
201
|
+
path: options.path,
|
|
202
|
+
root: options.root,
|
|
203
|
+
streamId,
|
|
204
|
+
offset: options.offset,
|
|
205
|
+
length: options.length
|
|
206
|
+
},
|
|
207
|
+
{ signal: options.abortSignal }
|
|
208
|
+
);
|
|
209
|
+
if (!result.found) {
|
|
210
|
+
connection.peer.streams.discardIncoming(streamId);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
connection.peer.streams.discardIncoming(streamId);
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
options.abortSignal?.addEventListener(
|
|
218
|
+
"abort",
|
|
219
|
+
() => connection.peer.streams.cancelIncoming(streamId, "aborted"),
|
|
220
|
+
{
|
|
221
|
+
once: true
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
return stream;
|
|
225
|
+
};
|
|
226
|
+
const writeFile = async (options) => {
|
|
227
|
+
const connection = await getConnection();
|
|
228
|
+
const streamId = allocateStreamId(connection);
|
|
229
|
+
const sending = connection.peer.streams.openOutgoing(streamId, options.content);
|
|
230
|
+
sending.catch(() => void 0);
|
|
231
|
+
try {
|
|
232
|
+
await requestOn(
|
|
233
|
+
connection,
|
|
234
|
+
HandleRpcMethod.WriteFile,
|
|
235
|
+
{ path: options.path, streamId },
|
|
236
|
+
{ signal: options.abortSignal }
|
|
237
|
+
);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
connection.peer.streams.abortOutgoing(streamId, "request failed");
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const writeBinaryFile = async (options) => {
|
|
244
|
+
if (options.content.byteLength > MAX_INLINE_WRITE_BYTES) {
|
|
245
|
+
await writeFile({
|
|
246
|
+
path: options.path,
|
|
247
|
+
content: bytesToStream(options.content),
|
|
248
|
+
abortSignal: options.abortSignal
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
await call(
|
|
253
|
+
HandleRpcMethod.WriteFile,
|
|
254
|
+
{ path: options.path, contentBase64: bytesToBase64(options.content) },
|
|
255
|
+
{ signal: options.abortSignal }
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
const spawn = async (options) => {
|
|
259
|
+
const connection = await getConnection();
|
|
260
|
+
const processId = randomId();
|
|
261
|
+
const stdoutStreamId = allocateStreamId(connection);
|
|
262
|
+
const stderrStreamId = allocateStreamId(connection);
|
|
263
|
+
const stdinStreamId = allocateStreamId(connection);
|
|
264
|
+
const stdout = connection.peer.streams.openIncoming(stdoutStreamId);
|
|
265
|
+
const stderr = connection.peer.streams.openIncoming(stderrStreamId);
|
|
266
|
+
let stdinWriter = null;
|
|
267
|
+
let stdinSource;
|
|
268
|
+
if (options.stdin == null) {
|
|
269
|
+
const pipe = new TransformStream();
|
|
270
|
+
stdinWriter = pipe.writable.getWriter();
|
|
271
|
+
stdinSource = pipe.readable;
|
|
272
|
+
} else if (options.stdin instanceof ReadableStream) {
|
|
273
|
+
stdinSource = options.stdin;
|
|
274
|
+
} else {
|
|
275
|
+
stdinSource = bytesToStream(utf8Encode(options.stdin));
|
|
276
|
+
}
|
|
277
|
+
connection.peer.streams.openOutgoing(stdinStreamId, stdinSource).catch(() => void 0);
|
|
278
|
+
let pid;
|
|
279
|
+
try {
|
|
280
|
+
const result = await requestOn(connection, HandleRpcMethod.Spawn, {
|
|
281
|
+
processId,
|
|
282
|
+
command: options.command,
|
|
283
|
+
args: options.args,
|
|
284
|
+
cwd: options.cwd,
|
|
285
|
+
env: options.env,
|
|
286
|
+
shell: options.shell,
|
|
287
|
+
timeoutMs: options.timeoutMs,
|
|
288
|
+
stdinStreamId,
|
|
289
|
+
stdoutStreamId,
|
|
290
|
+
stderrStreamId
|
|
291
|
+
});
|
|
292
|
+
pid = result.pid;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
connection.peer.streams.discardIncoming(stdoutStreamId);
|
|
295
|
+
connection.peer.streams.discardIncoming(stderrStreamId);
|
|
296
|
+
connection.peer.streams.abortOutgoing(stdinStreamId, "spawn failed");
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
let waiting = null;
|
|
300
|
+
const kill = async (signal) => {
|
|
301
|
+
try {
|
|
302
|
+
await requestOn(connection, HandleRpcMethod.ProcessKill, { processId, signal });
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if (isHandleRpcError(error) && (error.code === HandleRpcErrorCode.NotFound || error.retryable))
|
|
305
|
+
return;
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
options.abortSignal?.addEventListener("abort", () => void kill().catch(() => void 0), {
|
|
310
|
+
once: true
|
|
311
|
+
});
|
|
312
|
+
return {
|
|
313
|
+
pid,
|
|
314
|
+
stdout,
|
|
315
|
+
stderr,
|
|
316
|
+
write: async (data, writeOptions) => {
|
|
317
|
+
if (stdinWriter == null) throw new Error("stdin was provided at spawn");
|
|
318
|
+
const bytes = data instanceof Uint8Array ? data : utf8Encode(data);
|
|
319
|
+
if (bytes.byteLength > 0) await stdinWriter.write(bytes);
|
|
320
|
+
if (writeOptions?.end === true) await stdinWriter.close();
|
|
321
|
+
},
|
|
322
|
+
wait: () => {
|
|
323
|
+
waiting ??= requestOn(
|
|
324
|
+
connection,
|
|
325
|
+
HandleRpcMethod.ProcessWait,
|
|
326
|
+
{ processId },
|
|
327
|
+
{ signal: options.abortSignal }
|
|
328
|
+
);
|
|
329
|
+
return waiting;
|
|
330
|
+
},
|
|
331
|
+
kill
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
const client = {
|
|
335
|
+
get id() {
|
|
336
|
+
return describe().id;
|
|
337
|
+
},
|
|
338
|
+
get defaultWorkingDirectory() {
|
|
339
|
+
return describe().defaultWorkingDirectory;
|
|
340
|
+
},
|
|
341
|
+
get ports() {
|
|
342
|
+
return describe().ports;
|
|
343
|
+
},
|
|
344
|
+
get description() {
|
|
345
|
+
return describe().description;
|
|
346
|
+
},
|
|
347
|
+
get capabilities() {
|
|
348
|
+
return describe().capabilities;
|
|
349
|
+
},
|
|
350
|
+
confinedFiles: describe().confinedFiles === true,
|
|
351
|
+
providerHooks: describe().providerHooks === true,
|
|
352
|
+
onRuntimeShutdown: async () => {
|
|
353
|
+
await call(HandleRpcMethod.OnRuntimeShutdown, {});
|
|
354
|
+
},
|
|
355
|
+
onSandboxStop: async () => {
|
|
356
|
+
await call(HandleRpcMethod.OnSandboxStop, {});
|
|
357
|
+
},
|
|
358
|
+
onSandboxDelete: async (options) => {
|
|
359
|
+
await call(HandleRpcMethod.OnSandboxDelete, options ?? {});
|
|
360
|
+
},
|
|
361
|
+
readFile: (options) => readFile(options),
|
|
362
|
+
readFileRange: readFile,
|
|
363
|
+
readBinaryFile: async (options) => {
|
|
364
|
+
const stream = await readFile(options);
|
|
365
|
+
return stream == null ? null : readAllBytes(stream);
|
|
366
|
+
},
|
|
367
|
+
readTextFile: async (options) => {
|
|
368
|
+
const { abortSignal: _abortSignal, ...params } = options;
|
|
369
|
+
const parsed = handleRpcMethodSchemas[HandleRpcMethod.ReadTextFile].params.safeParse(params);
|
|
370
|
+
if (!parsed.success)
|
|
371
|
+
throw handleRpcError({
|
|
372
|
+
code: HandleRpcErrorCode.InvalidParams,
|
|
373
|
+
message: parsed.error.message
|
|
374
|
+
});
|
|
375
|
+
const stream = await readFile({ path: options.path, abortSignal: options.abortSignal });
|
|
376
|
+
return stream == null ? null : readTextFileStream(stream, options);
|
|
377
|
+
},
|
|
378
|
+
writeFile,
|
|
379
|
+
writeBinaryFile,
|
|
380
|
+
writeTextFile: async (options) => writeBinaryFile({
|
|
381
|
+
path: options.path,
|
|
382
|
+
content: encodeSandboxText(options.content, options.encoding),
|
|
383
|
+
abortSignal: options.abortSignal
|
|
384
|
+
}),
|
|
385
|
+
spawn,
|
|
386
|
+
run: (options) => runCollect({ spawn }, options),
|
|
387
|
+
stat: async (options) => (await call(
|
|
388
|
+
HandleRpcMethod.Stat,
|
|
389
|
+
{ path: options.path, root: options.root },
|
|
390
|
+
{ signal: options.abortSignal }
|
|
391
|
+
)).stat,
|
|
392
|
+
readDir: async (options) => (await call(
|
|
393
|
+
HandleRpcMethod.ReadDir,
|
|
394
|
+
{ path: options.path, root: options.root },
|
|
395
|
+
{ signal: options.abortSignal }
|
|
396
|
+
)).entries,
|
|
397
|
+
resolvePath: (path) => resolvePosixPath(describe().defaultWorkingDirectory, path),
|
|
398
|
+
removePath: async (options) => {
|
|
399
|
+
const { abortSignal, ...params } = options;
|
|
400
|
+
await call(HandleRpcMethod.RemovePath, params, { signal: abortSignal });
|
|
401
|
+
},
|
|
402
|
+
getPortEndpoint: (options) => call(HandleRpcMethod.GetPortEndpoint, options),
|
|
403
|
+
getPortUrl: async (options) => (await call(HandleRpcMethod.GetPortEndpoint, options)).url,
|
|
404
|
+
stop: async () => {
|
|
405
|
+
await call(HandleRpcMethod.Stop, {});
|
|
406
|
+
},
|
|
407
|
+
destroy: async () => {
|
|
408
|
+
await call(HandleRpcMethod.Destroy, {});
|
|
409
|
+
},
|
|
410
|
+
close: () => {
|
|
411
|
+
closedByUser = true;
|
|
412
|
+
if (refreshTimer != null) clearTimeout(refreshTimer);
|
|
413
|
+
for (const connection of live)
|
|
414
|
+
connection.peer.close(HandleRpcCloseCode.Normal, "client closed");
|
|
415
|
+
live.clear();
|
|
416
|
+
current = null;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const mcpEndpoint = (connection, endpoint) => {
|
|
420
|
+
if ("url" in endpoint) return endpoint;
|
|
421
|
+
return {
|
|
422
|
+
name: endpoint.name,
|
|
423
|
+
connect: async (options) => {
|
|
424
|
+
const opened = await requestOn(
|
|
425
|
+
connection,
|
|
426
|
+
HandleRpcMethod.McpOpen,
|
|
427
|
+
{ endpointKey: endpoint.endpointKey },
|
|
428
|
+
{ signal: options?.abortSignal }
|
|
429
|
+
);
|
|
430
|
+
let closed = false;
|
|
431
|
+
const requireOpen = () => {
|
|
432
|
+
if (closed)
|
|
433
|
+
throw handleRpcError({
|
|
434
|
+
code: HandleRpcErrorCode.ConnectionClosed,
|
|
435
|
+
message: "MCP channel closed"
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
const channel = {
|
|
439
|
+
send: async (message, sendOptions) => {
|
|
440
|
+
requireOpen();
|
|
441
|
+
await requestOn(
|
|
442
|
+
connection,
|
|
443
|
+
HandleRpcMethod.McpSend,
|
|
444
|
+
{ channelId: opened.channelId, message },
|
|
445
|
+
{ signal: sendOptions?.abortSignal }
|
|
446
|
+
);
|
|
447
|
+
},
|
|
448
|
+
receive: async (receiveOptions) => {
|
|
449
|
+
requireOpen();
|
|
450
|
+
return (await requestOn(
|
|
451
|
+
connection,
|
|
452
|
+
HandleRpcMethod.McpReceive,
|
|
453
|
+
{ channelId: opened.channelId },
|
|
454
|
+
{ signal: receiveOptions?.abortSignal }
|
|
455
|
+
)).messages;
|
|
456
|
+
},
|
|
457
|
+
close: async () => {
|
|
458
|
+
if (closed) return;
|
|
459
|
+
closed = true;
|
|
460
|
+
if (!connection.peer.isClosed())
|
|
461
|
+
await requestOn(connection, HandleRpcMethod.McpClose, {
|
|
462
|
+
channelId: opened.channelId
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
if (options?.abortSignal?.aborted === true) {
|
|
467
|
+
await channel.close().catch(() => void 0);
|
|
468
|
+
options.abortSignal.throwIfAborted();
|
|
469
|
+
}
|
|
470
|
+
return channel;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
const capabilities = first.info.capabilities;
|
|
475
|
+
if (capabilities.includes(SandboxCapability.NetworkPolicy)) {
|
|
476
|
+
client.setNetworkPolicy = async (policy) => {
|
|
477
|
+
await call(HandleRpcMethod.SetNetworkPolicy, policy);
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (capabilities.includes(SandboxCapability.ComputerUse)) {
|
|
481
|
+
client.computerUse = async () => {
|
|
482
|
+
const { connection, result } = await callOnConnection(HandleRpcMethod.ComputerUse, {});
|
|
483
|
+
return mcpEndpoint(connection, result);
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (capabilities.includes(SandboxCapability.Screen)) {
|
|
487
|
+
client.screen = (options) => call(HandleRpcMethod.Screen, options ?? {});
|
|
488
|
+
}
|
|
489
|
+
if (capabilities.includes(SandboxCapability.LocalMcp)) {
|
|
490
|
+
client.localMcp = async () => {
|
|
491
|
+
const { connection, result } = await callOnConnection(HandleRpcMethod.LocalMcp, {});
|
|
492
|
+
return result.endpoints.map((endpoint) => mcpEndpoint(connection, endpoint));
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return client;
|
|
496
|
+
};
|
|
497
|
+
export {
|
|
498
|
+
connectSandboxClient
|
|
499
|
+
};
|
|
500
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/sandbox/client.ts"],
|
|
4
|
+
"sourcesContent": ["import { bytesToBase64, bytesToStream, encodeSandboxText, readAllBytes, utf8Encode } from \"./bytes.mjs\";\nimport { readTextFileStream, runCollect } from \"./convenience.mjs\";\nimport { resolvePosixPath } from \"./paths.mjs\";\nimport { createRpcPeer, type RpcPeerIface } from \"./rpc-peer.mjs\";\nimport {\n SandboxCapability,\n type AbortableIface,\n type DirEntryIface,\n type McpChannelIface,\n type NetworkPolicyIface,\n type NetworkSandboxSessionIface,\n type SandboxMcpEndpoint,\n type SandboxProcessIface,\n type SandboxProcessOptionsIface,\n type ScreenSessionIface,\n type StatIface,\n} from \"./types.mjs\";\nimport {\n frameChannelFromWebSocket,\n HANDLE_RPC_IDEMPOTENT_METHODS,\n HANDLE_RPC_VERSION,\n handleRpcClientProtocols,\n HandleRpcCloseCode,\n handleRpcError,\n HandleRpcErrorCode,\n HandleRpcMethod,\n handleRpcMethodSchemas,\n isHandleRpcError,\n MAX_INLINE_WRITE_BYTES,\n parseHandleRpcResult,\n type FrameChannelIface,\n type HandleRpcParams,\n type HandleRpcResult,\n type InitializeResultIface,\n type WebSocketConstructorIface,\n} from \"./wire.mjs\";\n\n// NetworkSandboxSessionIface implemented purely over the handle RPC: no\n// provider code, only the wire. Works wherever a WebSocket exists (Workers,\n// browsers, Node 22+) or over any frame channel through `openChannel`.\n\nexport interface SandboxClientIface extends NetworkSandboxSessionIface {\n readonly confinedFiles: boolean;\n readonly providerHooks: boolean;\n onSandboxStop(): Promise<void>;\n onSandboxDelete(options?: { reason?: string }): Promise<void>;\n onRuntimeShutdown(): Promise<void>;\n readonly capabilities: readonly SandboxCapability[];\n // A byte range of a file, streamed; null when the file does not exist.\n readFileRange(options: {\n path: string;\n root?: string;\n offset: number;\n length?: number;\n abortSignal?: AbortSignal;\n }): Promise<ReadableStream<Uint8Array> | null>;\n // Closes every connection and fails in-flight calls.\n close(): void;\n}\n\nexport interface SandboxClientCredentialIface {\n token: string;\n expiresAt: string | null;\n // A new stable endpoint URL, when the issuer moved it.\n url?: string;\n}\n\nexport interface ConnectSandboxClientArgsIface {\n // The session service's stable per-binding address; the first call to reach\n // it attaches the sandbox, and every call is served there.\n url: string;\n token: string;\n expiresAt?: string | null;\n // Defaults to the global WebSocket.\n WebSocket?: WebSocketConstructorIface;\n // Replaces WebSocket dialing entirely (relays, tests).\n openChannel?: (target: { url: string; token: string }) => Promise<FrameChannelIface>;\n // Returns a fresh credential for the stable endpoint (`url`), e.g. from the\n // broker's `sandbox` route.\n refreshToken?: () => Promise<SandboxClientCredentialIface>;\n // Send stream chunks as binary frames (default true).\n binary?: boolean;\n reconnect?: { attempts?: number; baseDelayMs?: number; maxDelayMs?: number };\n // How long before expiry to refresh the credential (default 60 s).\n refreshMarginMs?: number;\n clientInfo?: { name: string; version: string };\n now?: () => number;\n sleep?: (ms: number) => Promise<void>;\n randomId?: () => string;\n}\n\ninterface TargetIface {\n url: string;\n token: string;\n expiresAt: string | null;\n}\n\ninterface ConnectionIface {\n peer: RpcPeerIface;\n target: TargetIface;\n info: InitializeResultIface;\n nextStreamId: number;\n}\n\nconst defaultSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nconst defaultOpenChannel =\n (WebSocketImpl: WebSocketConstructorIface | undefined) =>\n async (target: { url: string; token: string }): Promise<FrameChannelIface> => {\n const Impl = WebSocketImpl ?? globalThis.WebSocket;\n if (Impl == null)\n throw new Error('no WebSocket implementation available; pass WebSocket or openChannel');\n const { channel, opened } = frameChannelFromWebSocket(\n new Impl(target.url, handleRpcClientProtocols(target.token)),\n );\n await opened;\n return channel;\n };\n\nconst isRetryable = (cause: unknown): boolean => isHandleRpcError(cause) && cause.retryable;\n\nexport const connectSandboxClient = async (\n args: ConnectSandboxClientArgsIface,\n): Promise<SandboxClientIface> => {\n const now = args.now ?? Date.now;\n const sleep = args.sleep ?? defaultSleep;\n const randomId = args.randomId ?? (() => crypto.randomUUID());\n const openChannel = args.openChannel ?? defaultOpenChannel(args.WebSocket);\n const attempts = args.reconnect?.attempts ?? 5;\n const baseDelayMs = args.reconnect?.baseDelayMs ?? 100;\n const maxDelayMs = args.reconnect?.maxDelayMs ?? 5_000;\n const refreshMarginMs = args.refreshMarginMs ?? 60_000;\n const binary = args.binary ?? true;\n\n let origin: TargetIface = {\n url: args.url,\n token: args.token,\n expiresAt: args.expiresAt ?? null,\n };\n let current: ConnectionIface | null = null;\n let connecting: Promise<ConnectionIface> | null = null;\n let closedByUser = false;\n let refreshTimer: ReturnType<typeof setTimeout> | null = null;\n const live = new Set<ConnectionIface>();\n let lastInfo: InitializeResultIface | null = null;\n\n const refreshOrigin = async (): Promise<boolean> => {\n if (args.refreshToken == null) return false;\n const fresh = await args.refreshToken();\n origin = {\n url: fresh.url ?? origin.url,\n token: fresh.token,\n expiresAt: fresh.expiresAt,\n };\n return true;\n };\n\n const scheduleRefresh = (connection: ConnectionIface) => {\n if (refreshTimer != null) clearTimeout(refreshTimer);\n refreshTimer = null;\n const expiresAt = connection.info.expiresAt ?? connection.target.expiresAt;\n if (expiresAt == null || args.refreshToken == null || closedByUser) return;\n const remaining = Date.parse(expiresAt) - now();\n if (Number.isNaN(remaining)) return;\n const delay = Math.max(0, Math.min(remaining - refreshMarginMs, remaining / 2));\n refreshTimer = setTimeout(\n () => {\n void refreshConnection(connection).catch(() => undefined);\n },\n Math.min(2_147_483_647, delay),\n );\n };\n\n // The connection takes the new token in place; in-flight calls continue.\n // A moved endpoint (a new `url`) is dialed fresh instead, and in-flight\n // calls finish on the old connection.\n const refreshConnection = async (connection: ConnectionIface) => {\n if (closedByUser || connection.peer.isClosed()) return;\n await refreshOrigin();\n if (origin.url === connection.target.url) {\n const result = await requestOn(connection, HandleRpcMethod.AuthRefresh, {\n token: origin.token,\n });\n connection.target = {\n ...connection.target,\n token: origin.token,\n expiresAt: result.expiresAt,\n };\n connection.info = { ...connection.info, expiresAt: result.expiresAt };\n scheduleRefresh(connection);\n return;\n }\n if (current === connection) current = null;\n await getConnection();\n };\n\n const initialize = async (target: TargetIface): Promise<ConnectionIface> => {\n const channel = await openChannel({ url: target.url, token: target.token });\n let connection: ConnectionIface | null = null;\n const peer = createRpcPeer({\n channel,\n binary,\n onClose: () => {\n if (connection == null) return;\n live.delete(connection);\n if (current === connection) current = null;\n },\n });\n try {\n const raw = await peer.request(HandleRpcMethod.Initialize, {\n handleRpcVersion: HANDLE_RPC_VERSION,\n token: target.token,\n client: args.clientInfo,\n });\n const info = handleRpcMethodSchemas[HandleRpcMethod.Initialize].result.parse(raw);\n connection = { peer, target, info, nextStreamId: 1 };\n live.add(connection);\n return connection;\n } catch (error) {\n peer.close(HandleRpcCloseCode.Normal, 'initialize failed');\n throw error;\n }\n };\n\n const establish = async (): Promise<ConnectionIface> => {\n let failures = 0;\n let refreshed = false;\n for (;;) {\n if (closedByUser)\n throw handleRpcError({\n code: HandleRpcErrorCode.ConnectionClosed,\n message: 'client closed',\n });\n try {\n const connection = await initialize(origin);\n current = connection;\n lastInfo = connection.info;\n scheduleRefresh(connection);\n return connection;\n } catch (error) {\n if (closedByUser) throw error;\n const unauthorized =\n isHandleRpcError(error) && error.code === HandleRpcErrorCode.Unauthorized;\n if (unauthorized && !refreshed && (await refreshOrigin().catch(() => false))) {\n refreshed = true;\n continue;\n }\n if (unauthorized) throw error;\n failures += 1;\n if (failures >= attempts) {\n throw handleRpcError({\n code: HandleRpcErrorCode.ConnectionClosed,\n message: `could not connect to the sandbox: ${error instanceof Error ? error.message : String(error)}`,\n retryable: true,\n });\n }\n await sleep(Math.min(maxDelayMs, baseDelayMs * 2 ** (failures - 1)));\n }\n }\n };\n\n const getConnection = (): Promise<ConnectionIface> => {\n if (current != null && !current.peer.isClosed()) return Promise.resolve(current);\n connecting ??= establish().finally(() => {\n connecting = null;\n });\n return connecting;\n };\n\n const requestOn = async <M extends HandleRpcMethod>(\n connection: ConnectionIface,\n method: M,\n params: HandleRpcParams<M>,\n options?: { signal?: AbortSignal },\n ): Promise<HandleRpcResult<M>> => {\n const raw = await connection.peer.request(method, params, { signal: options?.signal });\n return parseHandleRpcResult(method, raw);\n };\n\n const callOnConnection = async <M extends HandleRpcMethod>(\n method: M,\n params: HandleRpcParams<M>,\n options?: { signal?: AbortSignal },\n ): Promise<{ connection: ConnectionIface; result: HandleRpcResult<M> }> => {\n const idempotent = HANDLE_RPC_IDEMPOTENT_METHODS.includes(method);\n for (let attempt = 0; ; attempt += 1) {\n const connection = await getConnection();\n try {\n return { connection, result: await requestOn(connection, method, params, options) };\n } catch (error) {\n if (attempt === 0 && idempotent && isRetryable(error)) continue;\n throw error;\n }\n }\n };\n\n const call = async <M extends HandleRpcMethod>(\n method: M,\n params: HandleRpcParams<M>,\n options?: { signal?: AbortSignal },\n ): Promise<HandleRpcResult<M>> => (await callOnConnection(method, params, options)).result;\n\n const allocateStreamId = (connection: ConnectionIface): number => {\n const id = connection.nextStreamId;\n connection.nextStreamId += 1;\n return id;\n };\n\n const first = await getConnection();\n const describe = (): InitializeResultIface => current?.info ?? lastInfo ?? first.info;\n\n const readFile = async (options: {\n path: string;\n root?: string;\n offset?: number;\n length?: number;\n abortSignal?: AbortSignal;\n }) => {\n const connection = await getConnection();\n const streamId = allocateStreamId(connection);\n const stream = connection.peer.streams.openIncoming(streamId);\n try {\n const result = await requestOn(\n connection,\n HandleRpcMethod.ReadFile,\n {\n path: options.path,\n root: options.root,\n streamId,\n offset: options.offset,\n length: options.length,\n },\n { signal: options.abortSignal },\n );\n if (!result.found) {\n connection.peer.streams.discardIncoming(streamId);\n return null;\n }\n } catch (error) {\n connection.peer.streams.discardIncoming(streamId);\n throw error;\n }\n options.abortSignal?.addEventListener(\n 'abort',\n () => connection.peer.streams.cancelIncoming(streamId, 'aborted'),\n {\n once: true,\n },\n );\n return stream;\n };\n\n const writeFile = async (options: {\n path: string;\n content: ReadableStream<Uint8Array>;\n abortSignal?: AbortSignal;\n }) => {\n const connection = await getConnection();\n const streamId = allocateStreamId(connection);\n const sending = connection.peer.streams.openOutgoing(streamId, options.content);\n sending.catch(() => undefined);\n try {\n await requestOn(\n connection,\n HandleRpcMethod.WriteFile,\n { path: options.path, streamId },\n { signal: options.abortSignal },\n );\n } catch (error) {\n connection.peer.streams.abortOutgoing(streamId, 'request failed');\n throw error;\n }\n };\n\n const writeBinaryFile = async (options: {\n path: string;\n content: Uint8Array;\n abortSignal?: AbortSignal;\n }) => {\n if (options.content.byteLength > MAX_INLINE_WRITE_BYTES) {\n await writeFile({\n path: options.path,\n content: bytesToStream(options.content),\n abortSignal: options.abortSignal,\n });\n return;\n }\n await call(\n HandleRpcMethod.WriteFile,\n { path: options.path, contentBase64: bytesToBase64(options.content) },\n { signal: options.abortSignal },\n );\n };\n\n const spawn = async (options: SandboxProcessOptionsIface): Promise<SandboxProcessIface> => {\n const connection = await getConnection();\n const processId = randomId();\n const stdoutStreamId = allocateStreamId(connection);\n const stderrStreamId = allocateStreamId(connection);\n const stdinStreamId = allocateStreamId(connection);\n const stdout = connection.peer.streams.openIncoming(stdoutStreamId);\n const stderr = connection.peer.streams.openIncoming(stderrStreamId);\n let stdinWriter: WritableStreamDefaultWriter<Uint8Array> | null = null;\n let stdinSource: ReadableStream<Uint8Array>;\n if (options.stdin == null) {\n const pipe = new TransformStream<Uint8Array, Uint8Array>();\n stdinWriter = pipe.writable.getWriter();\n stdinSource = pipe.readable;\n } else if (options.stdin instanceof ReadableStream) {\n stdinSource = options.stdin;\n } else {\n stdinSource = bytesToStream(utf8Encode(options.stdin));\n }\n connection.peer.streams.openOutgoing(stdinStreamId, stdinSource).catch(() => undefined);\n let pid: string | null;\n try {\n const result = await requestOn(connection, HandleRpcMethod.Spawn, {\n processId,\n command: options.command,\n args: options.args,\n cwd: options.cwd,\n env: options.env,\n shell: options.shell,\n timeoutMs: options.timeoutMs,\n stdinStreamId,\n stdoutStreamId,\n stderrStreamId,\n });\n pid = result.pid;\n } catch (error) {\n connection.peer.streams.discardIncoming(stdoutStreamId);\n connection.peer.streams.discardIncoming(stderrStreamId);\n connection.peer.streams.abortOutgoing(stdinStreamId, 'spawn failed');\n throw error;\n }\n let waiting: Promise<{ exitCode: number }> | null = null;\n const kill = async (signal?: string) => {\n try {\n await requestOn(connection, HandleRpcMethod.ProcessKill, { processId, signal });\n } catch (error) {\n // Already exited and reaped, or the connection (and so the process) is gone.\n if (\n isHandleRpcError(error) &&\n (error.code === HandleRpcErrorCode.NotFound || error.retryable)\n )\n return;\n throw error;\n }\n };\n options.abortSignal?.addEventListener('abort', () => void kill().catch(() => undefined), {\n once: true,\n });\n return {\n pid,\n stdout,\n stderr,\n write: async (data, writeOptions) => {\n if (stdinWriter == null) throw new Error('stdin was provided at spawn');\n const bytes = data instanceof Uint8Array ? data : utf8Encode(data);\n if (bytes.byteLength > 0) await stdinWriter.write(bytes);\n if (writeOptions?.end === true) await stdinWriter.close();\n },\n wait: () => {\n waiting ??= requestOn(\n connection,\n HandleRpcMethod.ProcessWait,\n { processId },\n { signal: options.abortSignal },\n );\n return waiting;\n },\n kill,\n };\n };\n\n const client: SandboxClientIface = {\n get id() {\n return describe().id;\n },\n get defaultWorkingDirectory() {\n return describe().defaultWorkingDirectory;\n },\n get ports() {\n return describe().ports;\n },\n get description() {\n return describe().description;\n },\n get capabilities() {\n return describe().capabilities;\n },\n confinedFiles: describe().confinedFiles === true,\n providerHooks: describe().providerHooks === true,\n onRuntimeShutdown: async () => {\n await call(HandleRpcMethod.OnRuntimeShutdown, {});\n },\n onSandboxStop: async () => {\n await call(HandleRpcMethod.OnSandboxStop, {});\n },\n onSandboxDelete: async (options) => {\n await call(HandleRpcMethod.OnSandboxDelete, options ?? {});\n },\n readFile: (options) => readFile(options),\n readFileRange: readFile,\n readBinaryFile: async (options) => {\n const stream = await readFile(options);\n return stream == null ? null : readAllBytes(stream);\n },\n readTextFile: async (options) => {\n const { abortSignal: _abortSignal, ...params } = options;\n const parsed = handleRpcMethodSchemas[HandleRpcMethod.ReadTextFile].params.safeParse(params);\n if (!parsed.success)\n throw handleRpcError({\n code: HandleRpcErrorCode.InvalidParams,\n message: parsed.error.message,\n });\n const stream = await readFile({ path: options.path, abortSignal: options.abortSignal });\n return stream == null ? null : readTextFileStream(stream, options);\n },\n writeFile,\n writeBinaryFile,\n writeTextFile: async (options) =>\n writeBinaryFile({\n path: options.path,\n content: encodeSandboxText(options.content, options.encoding),\n abortSignal: options.abortSignal,\n }),\n spawn,\n run: (options) => runCollect({ spawn }, options),\n stat: async (options): Promise<StatIface | null> =>\n (\n await call(\n HandleRpcMethod.Stat,\n { path: options.path, root: options.root },\n { signal: options.abortSignal },\n )\n ).stat,\n readDir: async (options): Promise<DirEntryIface[]> =>\n (\n await call(\n HandleRpcMethod.ReadDir,\n { path: options.path, root: options.root },\n { signal: options.abortSignal },\n )\n ).entries,\n resolvePath: (path) => resolvePosixPath(describe().defaultWorkingDirectory, path),\n removePath: async (options) => {\n const { abortSignal, ...params } = options;\n await call(HandleRpcMethod.RemovePath, params, { signal: abortSignal });\n },\n getPortEndpoint: (options) => call(HandleRpcMethod.GetPortEndpoint, options),\n getPortUrl: async (options) => (await call(HandleRpcMethod.GetPortEndpoint, options)).url,\n stop: async () => {\n await call(HandleRpcMethod.Stop, {});\n },\n destroy: async () => {\n await call(HandleRpcMethod.Destroy, {});\n },\n close: () => {\n closedByUser = true;\n if (refreshTimer != null) clearTimeout(refreshTimer);\n for (const connection of live)\n connection.peer.close(HandleRpcCloseCode.Normal, 'client closed');\n live.clear();\n current = null;\n },\n };\n\n const mcpEndpoint = (\n connection: ConnectionIface,\n endpoint: HandleRpcResult<HandleRpcMethod.ComputerUse>,\n ): SandboxMcpEndpoint => {\n if ('url' in endpoint) return endpoint;\n return {\n name: endpoint.name,\n connect: async (options?: AbortableIface): Promise<McpChannelIface> => {\n const opened = await requestOn(\n connection,\n HandleRpcMethod.McpOpen,\n { endpointKey: endpoint.endpointKey },\n { signal: options?.abortSignal },\n );\n let closed = false;\n const requireOpen = (): void => {\n if (closed)\n throw handleRpcError({\n code: HandleRpcErrorCode.ConnectionClosed,\n message: 'MCP channel closed',\n });\n };\n const channel: McpChannelIface = {\n send: async (message, sendOptions) => {\n requireOpen();\n await requestOn(\n connection,\n HandleRpcMethod.McpSend,\n { channelId: opened.channelId, message },\n { signal: sendOptions?.abortSignal },\n );\n },\n receive: async (receiveOptions) => {\n requireOpen();\n return (\n await requestOn(\n connection,\n HandleRpcMethod.McpReceive,\n { channelId: opened.channelId },\n { signal: receiveOptions?.abortSignal },\n )\n ).messages;\n },\n close: async () => {\n if (closed) return;\n closed = true;\n if (!connection.peer.isClosed())\n await requestOn(connection, HandleRpcMethod.McpClose, {\n channelId: opened.channelId,\n });\n },\n };\n if (options?.abortSignal?.aborted === true) {\n await channel.close().catch(() => undefined);\n options.abortSignal.throwIfAborted();\n }\n return channel;\n },\n };\n };\n\n const capabilities = first.info.capabilities;\n if (capabilities.includes(SandboxCapability.NetworkPolicy)) {\n client.setNetworkPolicy = async (policy: NetworkPolicyIface) => {\n await call(HandleRpcMethod.SetNetworkPolicy, policy);\n };\n }\n if (capabilities.includes(SandboxCapability.ComputerUse)) {\n client.computerUse = async (): Promise<SandboxMcpEndpoint> => {\n const { connection, result } = await callOnConnection(HandleRpcMethod.ComputerUse, {});\n return mcpEndpoint(connection, result);\n };\n }\n if (capabilities.includes(SandboxCapability.Screen)) {\n client.screen = (options): Promise<ScreenSessionIface> =>\n call(HandleRpcMethod.Screen, options ?? {});\n }\n if (capabilities.includes(SandboxCapability.LocalMcp)) {\n client.localMcp = async (): Promise<SandboxMcpEndpoint[]> => {\n const { connection, result } = await callOnConnection(HandleRpcMethod.LocalMcp, {});\n return result.endpoints.map((endpoint) => mcpEndpoint(connection, endpoint));\n };\n }\n return client;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AbortableIface, ConfinedSandboxFilesIface, SandboxSessionIface } from "./types.mjs";
|
|
2
|
+
export declare const CONFINED_FILES_SCRIPT = "import json, os, stat, sys, datetime\np = json.loads(sys.argv[1])\nroot = os.path.normpath(p['root'])\npath = os.path.normpath(p['path'])\nif not root.startswith('/') or not path.startswith('/') or os.path.commonpath([root, path]) != root:\n raise ValueError('path outside workspace')\nfd = os.open('/', os.O_RDONLY | os.O_DIRECTORY)\ntry:\n parts = [x for x in path.split('/') if x]\n for i, part in enumerate(parts):\n flags = os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK\n if i < len(parts) - 1 or p['operation'] == 'directory':\n flags |= os.O_DIRECTORY\n next_fd = os.open(part, flags, dir_fd=fd)\n os.close(fd)\n fd = next_fd\n info = os.fstat(fd)\n def kind(s):\n return 'directory' if stat.S_ISDIR(s.st_mode) else 'file' if stat.S_ISREG(s.st_mode) else 'symlink' if stat.S_ISLNK(s.st_mode) else 'other'\n if p['operation'] == 'stat':\n print(json.dumps({'path': path, 'type': kind(info), 'size': info.st_size, 'modifiedAt': datetime.datetime.fromtimestamp(info.st_mtime, datetime.timezone.utc).isoformat(), 'mode': stat.S_IMODE(info.st_mode)}))\n elif p['operation'] == 'directory':\n entries = []\n for name in os.listdir(fd):\n try:\n s = os.stat(name, dir_fd=fd, follow_symlinks=False)\n except FileNotFoundError:\n continue\n entries.append({'name': name, 'path': os.path.join(path, name), 'type': kind(s), 'size': s.st_size})\n print(json.dumps(entries))\n else:\n if not stat.S_ISREG(info.st_mode):\n raise ValueError('not a regular file')\n sys.stdout.buffer.write(b'OK\\n')\n sys.stdout.buffer.flush()\n while True:\n chunk = os.read(fd, 65536)\n if not chunk:\n break\n sys.stdout.buffer.write(chunk)\nexcept FileNotFoundError:\n if p['operation'] == 'read':\n sys.stdout.buffer.write(b'MISSING\\n')\n else:\n print('null')\nfinally:\n os.close(fd)\n";
|
|
3
|
+
interface ConfinedPathIface extends AbortableIface {
|
|
4
|
+
path: string;
|
|
5
|
+
root: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const confinedStat: (sandbox: Pick<SandboxSessionIface, "run" | "spawn">, options: ConfinedPathIface) => Promise<{
|
|
8
|
+
path: string;
|
|
9
|
+
type: import("./types.mjs").SandboxEntryType;
|
|
10
|
+
size: number;
|
|
11
|
+
modifiedAt: string | null;
|
|
12
|
+
mode: number | null;
|
|
13
|
+
} | null>;
|
|
14
|
+
export declare const confinedReadDir: (sandbox: Pick<SandboxSessionIface, "run" | "spawn">, options: ConfinedPathIface) => Promise<{
|
|
15
|
+
name: string;
|
|
16
|
+
path: string;
|
|
17
|
+
type: import("./types.mjs").SandboxEntryType;
|
|
18
|
+
size: number | null;
|
|
19
|
+
}[]>;
|
|
20
|
+
export declare const confinedReadFile: (sandbox: Pick<SandboxSessionIface, "run" | "spawn">, options: ConfinedPathIface) => Promise<ReadableStream<Uint8Array<ArrayBufferLike>> | null>;
|
|
21
|
+
export declare const createConfinedFileReader: (sandbox: Pick<SandboxSessionIface, "run" | "spawn">) => ConfinedSandboxFilesIface;
|
|
22
|
+
export {};
|