@ai-sdk/sandbox-vercel 1.0.133 → 1.0.134
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/CHANGELOG.md +9 -0
- package/dist/index.d.ts +42 -42
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +823 -1112
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,1187 +1,898 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
var VERCEL_PROVIDER_ID = "vercel-sandbox";
|
|
1
|
+
import { Sandbox } from "@vercel/sandbox";
|
|
2
|
+
import { HarnessCapabilityUnsupportedError, HarnessSandboxAuthenticationError } from "@ai-sdk/harness";
|
|
3
|
+
import { posix } from "node:path";
|
|
4
|
+
import { extractLines } from "@ai-sdk/provider-utils";
|
|
5
|
+
//#region src/vercel-network-policy-manager.ts
|
|
6
|
+
const VERCEL_PROVIDER_ID$2 = "vercel-sandbox";
|
|
7
|
+
/**
|
|
8
|
+
* Owns the complete Vercel Sandbox network-policy value for one live session.
|
|
9
|
+
* Vercel combines network access, request transformations, and forwarding in a
|
|
10
|
+
* single structure, so every mutation must be composed here before replacing
|
|
11
|
+
* that structure through `sandbox.update()`.
|
|
12
|
+
*/
|
|
14
13
|
var VercelNetworkPolicyManager = class {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
async #applyState({
|
|
103
|
-
accessPolicy,
|
|
104
|
-
requestTransformations,
|
|
105
|
-
forwardRules,
|
|
106
|
-
currentPolicy
|
|
107
|
-
}) {
|
|
108
|
-
const effectivePolicy = composeNetworkPolicy({
|
|
109
|
-
accessPolicy,
|
|
110
|
-
requestTransformations,
|
|
111
|
-
forwardRules
|
|
112
|
-
});
|
|
113
|
-
if (!areNetworkPoliciesEqual({
|
|
114
|
-
first: currentPolicy,
|
|
115
|
-
second: effectivePolicy
|
|
116
|
-
})) {
|
|
117
|
-
await this.#sandbox.update({ networkPolicy: effectivePolicy });
|
|
118
|
-
}
|
|
119
|
-
this.#state = {
|
|
120
|
-
accessPolicy: cloneNetworkAccessPolicy(accessPolicy),
|
|
121
|
-
requestTransformations: requestTransformations.map(
|
|
122
|
-
cloneRequestTransformation
|
|
123
|
-
),
|
|
124
|
-
forwardRules: forwardRules.map(cloneForwardRule),
|
|
125
|
-
effectivePolicy
|
|
126
|
-
};
|
|
127
|
-
}
|
|
14
|
+
#sandbox;
|
|
15
|
+
#state;
|
|
16
|
+
#mutationQueue = Promise.resolve();
|
|
17
|
+
constructor({ sandbox }) {
|
|
18
|
+
this.#sandbox = sandbox;
|
|
19
|
+
}
|
|
20
|
+
setNetworkPolicy(policy) {
|
|
21
|
+
return this.#enqueueMutation(async () => {
|
|
22
|
+
const inspection = this.#inspectPolicy();
|
|
23
|
+
if (this.#state == null && inspection.requestTransformationHosts.length > 0) throw createPolicyConflictError("Cannot set the network policy because the current Vercel Sandbox policy contains request transformations whose redacted values cannot be preserved safely. Replace them explicitly with setRequestTransformations() or rehydrate them with addRequestTransformations() first.");
|
|
24
|
+
await this.#applyState({
|
|
25
|
+
accessPolicy: toNetworkAccessPolicy(policy),
|
|
26
|
+
requestTransformations: this.#state?.requestTransformations.map(cloneRequestTransformation) ?? [],
|
|
27
|
+
forwardRules: inspection.forwardRules.map(cloneForwardRule),
|
|
28
|
+
currentPolicy: inspection.currentPolicy
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
setRequestTransformations(transformations) {
|
|
33
|
+
return this.#enqueueMutation(async () => {
|
|
34
|
+
const inspection = this.#inspectPolicy();
|
|
35
|
+
await this.#applyState({
|
|
36
|
+
accessPolicy: inspection.accessPolicy,
|
|
37
|
+
requestTransformations: transformations.map(cloneRequestTransformation),
|
|
38
|
+
forwardRules: inspection.forwardRules.map(cloneForwardRule),
|
|
39
|
+
currentPolicy: inspection.currentPolicy
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
addRequestTransformations(transformations) {
|
|
44
|
+
return this.#enqueueMutation(async () => {
|
|
45
|
+
const inspection = this.#inspectPolicy();
|
|
46
|
+
const incomingTransformations = transformations.map(cloneRequestTransformation);
|
|
47
|
+
if (this.#state == null) {
|
|
48
|
+
const incomingHosts = getRequestTransformationHosts(composeNetworkPolicy({
|
|
49
|
+
accessPolicy: inspection.accessPolicy,
|
|
50
|
+
requestTransformations: incomingTransformations,
|
|
51
|
+
forwardRules: inspection.forwardRules
|
|
52
|
+
}));
|
|
53
|
+
if (!isHostSetSubset({
|
|
54
|
+
subset: inspection.requestTransformationHosts,
|
|
55
|
+
superset: incomingHosts
|
|
56
|
+
})) throw createPolicyConflictError("Cannot add request transformations because the current Vercel Sandbox policy contains request transformations that cannot be attributed to this call. Their header values are redacted, so preserving them safely is not possible.");
|
|
57
|
+
}
|
|
58
|
+
await this.#applyState({
|
|
59
|
+
accessPolicy: inspection.accessPolicy,
|
|
60
|
+
requestTransformations: this.#state == null ? incomingTransformations : mergeRequestTransformations({
|
|
61
|
+
existing: this.#state.requestTransformations,
|
|
62
|
+
incoming: incomingTransformations
|
|
63
|
+
}),
|
|
64
|
+
forwardRules: inspection.forwardRules.map(cloneForwardRule),
|
|
65
|
+
currentPolicy: inspection.currentPolicy
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
#enqueueMutation(operation) {
|
|
70
|
+
const result = this.#mutationQueue.then(operation);
|
|
71
|
+
this.#mutationQueue = result.then(() => void 0, () => void 0);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
#inspectPolicy() {
|
|
75
|
+
if (this.#state != null) return {
|
|
76
|
+
accessPolicy: this.#state.accessPolicy,
|
|
77
|
+
forwardRules: this.#state.forwardRules,
|
|
78
|
+
requestTransformationHosts: [],
|
|
79
|
+
currentPolicy: this.#state.effectivePolicy
|
|
80
|
+
};
|
|
81
|
+
return inspectCurrentPolicy(this.#sandbox.currentSession().networkPolicy ?? "allow-all");
|
|
82
|
+
}
|
|
83
|
+
async #applyState({ accessPolicy, requestTransformations, forwardRules, currentPolicy }) {
|
|
84
|
+
const effectivePolicy = composeNetworkPolicy({
|
|
85
|
+
accessPolicy,
|
|
86
|
+
requestTransformations,
|
|
87
|
+
forwardRules
|
|
88
|
+
});
|
|
89
|
+
if (!areNetworkPoliciesEqual({
|
|
90
|
+
first: currentPolicy,
|
|
91
|
+
second: effectivePolicy
|
|
92
|
+
})) await this.#sandbox.update({ networkPolicy: effectivePolicy });
|
|
93
|
+
this.#state = {
|
|
94
|
+
accessPolicy: cloneNetworkAccessPolicy(accessPolicy),
|
|
95
|
+
requestTransformations: requestTransformations.map(cloneRequestTransformation),
|
|
96
|
+
forwardRules: forwardRules.map(cloneForwardRule),
|
|
97
|
+
effectivePolicy
|
|
98
|
+
};
|
|
99
|
+
}
|
|
128
100
|
};
|
|
129
101
|
function inspectCurrentPolicy(policy) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
accessPolicy: {
|
|
159
|
-
mode: "custom",
|
|
160
|
-
allowedHosts: minimizeHostPatterns(allowedHosts),
|
|
161
|
-
allowedCIDRs: [...policy.subnets?.allow ?? []],
|
|
162
|
-
deniedCIDRs: [...policy.subnets?.deny ?? []]
|
|
163
|
-
},
|
|
164
|
-
forwardRules,
|
|
165
|
-
requestTransformationHosts: [...requestTransformationHosts],
|
|
166
|
-
currentPolicy: cloneNetworkPolicy(policy)
|
|
167
|
-
};
|
|
102
|
+
if (policy === "allow-all" || policy === "deny-all") return {
|
|
103
|
+
accessPolicy: { mode: policy },
|
|
104
|
+
forwardRules: [],
|
|
105
|
+
requestTransformationHosts: [],
|
|
106
|
+
currentPolicy: policy
|
|
107
|
+
};
|
|
108
|
+
const allowedHosts = Array.isArray(policy.allow) ? [...policy.allow] : Object.keys(policy.allow ?? {});
|
|
109
|
+
const forwardRules = [];
|
|
110
|
+
const requestTransformationHosts = /* @__PURE__ */ new Set();
|
|
111
|
+
if (policy.allow != null && !Array.isArray(policy.allow)) for (const [host, rules] of Object.entries(policy.allow)) for (const rule of rules) {
|
|
112
|
+
if (hasRequestTransformation(rule)) requestTransformationHosts.add(host.toLowerCase());
|
|
113
|
+
if (rule.forwardURL != null) forwardRules.push({
|
|
114
|
+
host,
|
|
115
|
+
...rule.match == null ? {} : { match: cloneMatch(rule.match) },
|
|
116
|
+
forwardURL: rule.forwardURL
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
accessPolicy: {
|
|
121
|
+
mode: "custom",
|
|
122
|
+
allowedHosts: minimizeHostPatterns(allowedHosts),
|
|
123
|
+
allowedCIDRs: [...policy.subnets?.allow ?? []],
|
|
124
|
+
deniedCIDRs: [...policy.subnets?.deny ?? []]
|
|
125
|
+
},
|
|
126
|
+
forwardRules,
|
|
127
|
+
requestTransformationHosts: [...requestTransformationHosts],
|
|
128
|
+
currentPolicy: cloneNetworkPolicy(policy)
|
|
129
|
+
};
|
|
168
130
|
}
|
|
169
131
|
function toNetworkAccessPolicy(policy) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
for (const forwardRule of forwardRules) {
|
|
219
|
-
for (const host of getActiveHostPatterns({
|
|
220
|
-
allowedHosts,
|
|
221
|
-
ruleHost: forwardRule.host
|
|
222
|
-
})) {
|
|
223
|
-
appendRule({
|
|
224
|
-
rulesByHost,
|
|
225
|
-
host,
|
|
226
|
-
rule: {
|
|
227
|
-
...forwardRule.match == null ? {} : { match: cloneMatch(forwardRule.match) },
|
|
228
|
-
forwardURL: forwardRule.forwardURL
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const hasActiveRules = [...rulesByHost.values()].some(
|
|
234
|
-
(rules) => rules.length > 0
|
|
235
|
-
);
|
|
236
|
-
if (!hasActiveRules) {
|
|
237
|
-
return toVercelAccessPolicy(accessPolicy);
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
allow: Object.fromEntries(rulesByHost),
|
|
241
|
-
...accessPolicy.mode !== "custom" ? {} : toVercelSubnets(accessPolicy)
|
|
242
|
-
};
|
|
132
|
+
switch (policy.mode) {
|
|
133
|
+
case "allow-all": return { mode: "allow-all" };
|
|
134
|
+
case "deny-all": return { mode: "deny-all" };
|
|
135
|
+
case "custom": {
|
|
136
|
+
const allowedHosts = [...policy.allowedHosts ?? []];
|
|
137
|
+
const allowedCIDRs = [...policy.allowedCIDRs ?? []];
|
|
138
|
+
const deniedCIDRs = [...policy.deniedCIDRs ?? []];
|
|
139
|
+
if (allowedHosts.length === 0 && allowedCIDRs.length === 0 && deniedCIDRs.length === 0) throw createPolicyConflictError("Custom network policy requires at least one of allowedHosts, allowedCIDRs, or deniedCIDRs to be non-empty.");
|
|
140
|
+
return {
|
|
141
|
+
mode: "custom",
|
|
142
|
+
allowedHosts,
|
|
143
|
+
allowedCIDRs,
|
|
144
|
+
deniedCIDRs
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function composeNetworkPolicy({ accessPolicy, requestTransformations, forwardRules }) {
|
|
150
|
+
if (accessPolicy.mode === "deny-all") return "deny-all";
|
|
151
|
+
const rulesByHost = /* @__PURE__ */ new Map();
|
|
152
|
+
const allowedHosts = accessPolicy.mode === "allow-all" ? ["*"] : accessPolicy.allowedHosts;
|
|
153
|
+
for (const host of allowedHosts) rulesByHost.set(host, []);
|
|
154
|
+
for (const transformation of requestTransformations) for (const host of getActiveHostPatterns({
|
|
155
|
+
allowedHosts,
|
|
156
|
+
ruleHost: transformation.match.host
|
|
157
|
+
})) appendRule({
|
|
158
|
+
rulesByHost,
|
|
159
|
+
host,
|
|
160
|
+
rule: toVercelRequestTransformationRule(transformation)
|
|
161
|
+
});
|
|
162
|
+
for (const forwardRule of forwardRules) for (const host of getActiveHostPatterns({
|
|
163
|
+
allowedHosts,
|
|
164
|
+
ruleHost: forwardRule.host
|
|
165
|
+
})) appendRule({
|
|
166
|
+
rulesByHost,
|
|
167
|
+
host,
|
|
168
|
+
rule: {
|
|
169
|
+
...forwardRule.match == null ? {} : { match: cloneMatch(forwardRule.match) },
|
|
170
|
+
forwardURL: forwardRule.forwardURL
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
if (![...rulesByHost.values()].some((rules) => rules.length > 0)) return toVercelAccessPolicy(accessPolicy);
|
|
174
|
+
return {
|
|
175
|
+
allow: Object.fromEntries(rulesByHost),
|
|
176
|
+
...accessPolicy.mode !== "custom" ? {} : toVercelSubnets(accessPolicy)
|
|
177
|
+
};
|
|
243
178
|
}
|
|
244
179
|
function toVercelAccessPolicy(accessPolicy) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
...toVercelSubnets(accessPolicy)
|
|
251
|
-
};
|
|
180
|
+
if (accessPolicy.mode === "allow-all" || accessPolicy.mode === "deny-all") return accessPolicy.mode;
|
|
181
|
+
return {
|
|
182
|
+
...accessPolicy.allowedHosts.length === 0 ? {} : { allow: [...accessPolicy.allowedHosts] },
|
|
183
|
+
...toVercelSubnets(accessPolicy)
|
|
184
|
+
};
|
|
252
185
|
}
|
|
253
186
|
function toVercelSubnets(accessPolicy) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return second;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
function isHostPatternSubset({
|
|
290
|
-
candidate,
|
|
291
|
-
container
|
|
292
|
-
}) {
|
|
293
|
-
const normalizedCandidate = candidate.toLowerCase();
|
|
294
|
-
const normalizedContainer = container.toLowerCase();
|
|
295
|
-
if (normalizedCandidate === normalizedContainer || normalizedContainer === "*") {
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
if (normalizedCandidate === "*" || !normalizedContainer.startsWith("*")) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
const containerSuffix = normalizedContainer.slice(1);
|
|
302
|
-
return normalizedCandidate.startsWith("*") ? normalizedCandidate.slice(1).endsWith(containerSuffix) : normalizedCandidate.endsWith(containerSuffix);
|
|
187
|
+
if (accessPolicy.allowedCIDRs.length === 0 && accessPolicy.deniedCIDRs.length === 0) return {};
|
|
188
|
+
return { subnets: {
|
|
189
|
+
...accessPolicy.allowedCIDRs.length === 0 ? {} : { allow: [...accessPolicy.allowedCIDRs] },
|
|
190
|
+
...accessPolicy.deniedCIDRs.length === 0 ? {} : { deny: [...accessPolicy.deniedCIDRs] }
|
|
191
|
+
} };
|
|
192
|
+
}
|
|
193
|
+
function getActiveHostPatterns({ allowedHosts, ruleHost }) {
|
|
194
|
+
return minimizeHostPatterns(allowedHosts.flatMap((allowedHost) => {
|
|
195
|
+
const intersection = intersectHostPatterns({
|
|
196
|
+
first: allowedHost,
|
|
197
|
+
second: ruleHost
|
|
198
|
+
});
|
|
199
|
+
return intersection == null ? [] : [intersection];
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
function intersectHostPatterns({ first, second }) {
|
|
203
|
+
if (isHostPatternSubset({
|
|
204
|
+
candidate: first,
|
|
205
|
+
container: second
|
|
206
|
+
})) return first;
|
|
207
|
+
if (isHostPatternSubset({
|
|
208
|
+
candidate: second,
|
|
209
|
+
container: first
|
|
210
|
+
})) return second;
|
|
211
|
+
}
|
|
212
|
+
function isHostPatternSubset({ candidate, container }) {
|
|
213
|
+
const normalizedCandidate = candidate.toLowerCase();
|
|
214
|
+
const normalizedContainer = container.toLowerCase();
|
|
215
|
+
if (normalizedCandidate === normalizedContainer || normalizedContainer === "*") return true;
|
|
216
|
+
if (normalizedCandidate === "*" || !normalizedContainer.startsWith("*")) return false;
|
|
217
|
+
const containerSuffix = normalizedContainer.slice(1);
|
|
218
|
+
return normalizedCandidate.startsWith("*") ? normalizedCandidate.slice(1).endsWith(containerSuffix) : normalizedCandidate.endsWith(containerSuffix);
|
|
303
219
|
}
|
|
304
220
|
function minimizeHostPatterns(patterns) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
);
|
|
221
|
+
const uniquePatterns = [...new Set(patterns)];
|
|
222
|
+
return uniquePatterns.filter((pattern) => !uniquePatterns.some((other) => other !== pattern && isHostPatternSubset({
|
|
223
|
+
candidate: pattern,
|
|
224
|
+
container: other
|
|
225
|
+
})));
|
|
311
226
|
}
|
|
312
|
-
function appendRule({
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}) {
|
|
317
|
-
const rules = rulesByHost.get(host) ?? [];
|
|
318
|
-
rules.push(rule);
|
|
319
|
-
rulesByHost.set(host, rules);
|
|
227
|
+
function appendRule({ rulesByHost, host, rule }) {
|
|
228
|
+
const rules = rulesByHost.get(host) ?? [];
|
|
229
|
+
rules.push(rule);
|
|
230
|
+
rulesByHost.set(host, rules);
|
|
320
231
|
}
|
|
321
232
|
function toVercelRequestTransformationRule(transformation) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
...headers == null ? {} : { headers: headers.map(cloneKeyValueMatcher) }
|
|
334
|
-
};
|
|
335
|
-
return {
|
|
336
|
-
...Object.keys(match).length === 0 ? {} : { match },
|
|
337
|
-
transform: [{ headers: { ...transformation.transform.headers } }]
|
|
338
|
-
};
|
|
233
|
+
const { host: _host, method, path, queryString, headers } = transformation.match;
|
|
234
|
+
const match = {
|
|
235
|
+
...path == null ? {} : { path: cloneMatcher(path) },
|
|
236
|
+
...method == null ? {} : { method: [...method] },
|
|
237
|
+
...queryString == null ? {} : { queryString: queryString.map(cloneKeyValueMatcher) },
|
|
238
|
+
...headers == null ? {} : { headers: headers.map(cloneKeyValueMatcher) }
|
|
239
|
+
};
|
|
240
|
+
return {
|
|
241
|
+
...Object.keys(match).length === 0 ? {} : { match },
|
|
242
|
+
transform: [{ headers: { ...transformation.transform.headers } }]
|
|
243
|
+
};
|
|
339
244
|
}
|
|
340
245
|
function hasRequestTransformation(rule) {
|
|
341
|
-
|
|
342
|
-
(transformation) => transformation.headers != null && Object.keys(transformation.headers).length > 0
|
|
343
|
-
) ?? false;
|
|
246
|
+
return rule.transform?.some((transformation) => transformation.headers != null && Object.keys(transformation.headers).length > 0) ?? false;
|
|
344
247
|
}
|
|
345
248
|
function getRequestTransformationHosts(policy) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
function mergeRequestTransformations({
|
|
373
|
-
existing,
|
|
374
|
-
incoming
|
|
375
|
-
}) {
|
|
376
|
-
const merged = existing.map(cloneRequestTransformation);
|
|
377
|
-
const identities = new Map(
|
|
378
|
-
merged.map((transformation, index) => [
|
|
379
|
-
getRequestTransformationIdentity(transformation),
|
|
380
|
-
index
|
|
381
|
-
])
|
|
382
|
-
);
|
|
383
|
-
for (const transformation of incoming) {
|
|
384
|
-
const clonedTransformation = cloneRequestTransformation(transformation);
|
|
385
|
-
const identity = getRequestTransformationIdentity(clonedTransformation);
|
|
386
|
-
const existingIndex = identities.get(identity);
|
|
387
|
-
if (existingIndex == null) {
|
|
388
|
-
identities.set(identity, merged.length);
|
|
389
|
-
merged.push(clonedTransformation);
|
|
390
|
-
} else {
|
|
391
|
-
merged[existingIndex] = clonedTransformation;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return merged;
|
|
249
|
+
if (policy === "allow-all" || policy === "deny-all" || policy.allow == null || Array.isArray(policy.allow)) return [];
|
|
250
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
251
|
+
for (const [host, rules] of Object.entries(policy.allow)) for (const rule of rules) if (hasRequestTransformation(rule)) hosts.add(host.toLowerCase());
|
|
252
|
+
return [...hosts];
|
|
253
|
+
}
|
|
254
|
+
function isHostSetSubset({ subset, superset }) {
|
|
255
|
+
const allowedHosts = new Set(superset.map((host) => host.toLowerCase()));
|
|
256
|
+
return subset.every((host) => allowedHosts.has(host.toLowerCase()));
|
|
257
|
+
}
|
|
258
|
+
function areNetworkPoliciesEqual({ first, second }) {
|
|
259
|
+
return stableSerialize(first) === stableSerialize(second);
|
|
260
|
+
}
|
|
261
|
+
function mergeRequestTransformations({ existing, incoming }) {
|
|
262
|
+
const merged = existing.map(cloneRequestTransformation);
|
|
263
|
+
const identities = new Map(merged.map((transformation, index) => [getRequestTransformationIdentity(transformation), index]));
|
|
264
|
+
for (const transformation of incoming) {
|
|
265
|
+
const clonedTransformation = cloneRequestTransformation(transformation);
|
|
266
|
+
const identity = getRequestTransformationIdentity(clonedTransformation);
|
|
267
|
+
const existingIndex = identities.get(identity);
|
|
268
|
+
if (existingIndex == null) {
|
|
269
|
+
identities.set(identity, merged.length);
|
|
270
|
+
merged.push(clonedTransformation);
|
|
271
|
+
} else merged[existingIndex] = clonedTransformation;
|
|
272
|
+
}
|
|
273
|
+
return merged;
|
|
395
274
|
}
|
|
396
275
|
function getRequestTransformationIdentity(transformation) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
276
|
+
return stableSerialize({
|
|
277
|
+
match: transformation.match,
|
|
278
|
+
transformedHeaderNames: Object.keys(transformation.transform.headers).map((name) => name.toLowerCase()).sort()
|
|
279
|
+
});
|
|
401
280
|
}
|
|
402
281
|
function stableSerialize(value) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (value != null && typeof value === "object") {
|
|
407
|
-
return `{${Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([firstKey], [secondKey]) => firstKey.localeCompare(secondKey)).map(
|
|
408
|
-
([key, entryValue]) => `${JSON.stringify(key)}:${stableSerialize(entryValue)}`
|
|
409
|
-
).join(",")}}`;
|
|
410
|
-
}
|
|
411
|
-
return JSON.stringify(value);
|
|
282
|
+
if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
|
|
283
|
+
if (value != null && typeof value === "object") return `{${Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([firstKey], [secondKey]) => firstKey.localeCompare(secondKey)).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableSerialize(entryValue)}`).join(",")}}`;
|
|
284
|
+
return JSON.stringify(value);
|
|
412
285
|
}
|
|
413
286
|
function cloneRequestTransformation(transformation) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
...transformation.match.headers == null ? {} : {
|
|
425
|
-
headers: transformation.match.headers.map(
|
|
426
|
-
cloneHarnessKeyValueMatcher
|
|
427
|
-
)
|
|
428
|
-
}
|
|
429
|
-
},
|
|
430
|
-
transform: { headers: { ...transformation.transform.headers } }
|
|
431
|
-
};
|
|
287
|
+
return {
|
|
288
|
+
match: {
|
|
289
|
+
host: transformation.match.host,
|
|
290
|
+
...transformation.match.path == null ? {} : { path: cloneHarnessMatcher(transformation.match.path) },
|
|
291
|
+
...transformation.match.method == null ? {} : { method: [...transformation.match.method] },
|
|
292
|
+
...transformation.match.queryString == null ? {} : { queryString: transformation.match.queryString.map(cloneHarnessKeyValueMatcher) },
|
|
293
|
+
...transformation.match.headers == null ? {} : { headers: transformation.match.headers.map(cloneHarnessKeyValueMatcher) }
|
|
294
|
+
},
|
|
295
|
+
transform: { headers: { ...transformation.transform.headers } }
|
|
296
|
+
};
|
|
432
297
|
}
|
|
433
298
|
function cloneForwardRule(rule) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
299
|
+
return {
|
|
300
|
+
host: rule.host,
|
|
301
|
+
...rule.match == null ? {} : { match: cloneMatch(rule.match) },
|
|
302
|
+
forwardURL: rule.forwardURL
|
|
303
|
+
};
|
|
439
304
|
}
|
|
440
305
|
function cloneNetworkAccessPolicy(policy) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
deniedCIDRs: [...policy.deniedCIDRs]
|
|
449
|
-
};
|
|
306
|
+
if (policy.mode === "allow-all" || policy.mode === "deny-all") return { mode: policy.mode };
|
|
307
|
+
return {
|
|
308
|
+
mode: "custom",
|
|
309
|
+
allowedHosts: [...policy.allowedHosts],
|
|
310
|
+
allowedCIDRs: [...policy.allowedCIDRs],
|
|
311
|
+
deniedCIDRs: [...policy.deniedCIDRs]
|
|
312
|
+
};
|
|
450
313
|
}
|
|
451
314
|
function cloneNetworkPolicy(policy) {
|
|
452
|
-
|
|
315
|
+
return structuredClone(policy);
|
|
453
316
|
}
|
|
454
317
|
function cloneMatch(match) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
318
|
+
return {
|
|
319
|
+
...match.path == null ? {} : { path: cloneMatcher(match.path) },
|
|
320
|
+
...match.method == null ? {} : { method: [...match.method] },
|
|
321
|
+
...match.queryString == null ? {} : { queryString: match.queryString.map(cloneKeyValueMatcher) },
|
|
322
|
+
...match.headers == null ? {} : { headers: match.headers.map(cloneKeyValueMatcher) }
|
|
323
|
+
};
|
|
461
324
|
}
|
|
462
325
|
function cloneKeyValueMatcher(matcher) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
326
|
+
return {
|
|
327
|
+
...matcher.key == null ? {} : { key: cloneMatcher(matcher.key) },
|
|
328
|
+
...matcher.value == null ? {} : { value: cloneMatcher(matcher.value) }
|
|
329
|
+
};
|
|
467
330
|
}
|
|
468
331
|
function cloneMatcher(matcher) {
|
|
469
|
-
|
|
332
|
+
return { ...matcher };
|
|
470
333
|
}
|
|
471
334
|
function cloneHarnessKeyValueMatcher(matcher) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
335
|
+
return {
|
|
336
|
+
...matcher.key == null ? {} : { key: cloneHarnessMatcher(matcher.key) },
|
|
337
|
+
...matcher.value == null ? {} : { value: cloneHarnessMatcher(matcher.value) }
|
|
338
|
+
};
|
|
476
339
|
}
|
|
477
340
|
function cloneHarnessMatcher(matcher) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if ("startsWith" in matcher) {
|
|
482
|
-
return { startsWith: matcher.startsWith };
|
|
483
|
-
}
|
|
484
|
-
return { regex: matcher.regex };
|
|
341
|
+
if ("exact" in matcher) return { exact: matcher.exact };
|
|
342
|
+
if ("startsWith" in matcher) return { startsWith: matcher.startsWith };
|
|
343
|
+
return { regex: matcher.regex };
|
|
485
344
|
}
|
|
486
345
|
function createPolicyConflictError(message) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
346
|
+
return new HarnessCapabilityUnsupportedError({
|
|
347
|
+
harnessId: VERCEL_PROVIDER_ID$2,
|
|
348
|
+
message
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/vercel-sandbox-session.ts
|
|
353
|
+
/**
|
|
354
|
+
* `Experimental_SandboxSession` implementation backed by a `@vercel/sandbox`
|
|
355
|
+
* `Sandbox` instance. This is the tool-safe surface (file I/O, exec, spawn);
|
|
356
|
+
* it is what `VercelNetworkSandboxSession.restricted()` returns and is not
|
|
357
|
+
* constructed directly by consumers. The network sandbox session owns the
|
|
358
|
+
* lifetime of the underlying sandbox.
|
|
359
|
+
*/
|
|
498
360
|
var VercelSandboxSession = class {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
abortSignal
|
|
594
|
-
}) {
|
|
595
|
-
const bytes = await collectStream(content);
|
|
596
|
-
await this.writeBinaryFile({ path, content: bytes, abortSignal });
|
|
597
|
-
}
|
|
598
|
-
async writeBinaryFile({
|
|
599
|
-
path,
|
|
600
|
-
content,
|
|
601
|
-
abortSignal
|
|
602
|
-
}) {
|
|
603
|
-
abortSignal?.throwIfAborted();
|
|
604
|
-
const parent = posix.dirname(path);
|
|
605
|
-
if (parent && parent !== "." && parent !== "/") {
|
|
606
|
-
await this.sandbox.runCommand({
|
|
607
|
-
cmd: "mkdir",
|
|
608
|
-
args: ["-p", parent],
|
|
609
|
-
...abortSignal !== void 0 ? { signal: abortSignal } : {}
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
await this.sandbox.writeFiles(
|
|
613
|
-
[{ path, content }],
|
|
614
|
-
abortSignal ? { signal: abortSignal } : void 0
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
async writeTextFile({
|
|
618
|
-
path,
|
|
619
|
-
content,
|
|
620
|
-
encoding = "utf-8",
|
|
621
|
-
abortSignal
|
|
622
|
-
}) {
|
|
623
|
-
const buffer = Buffer.from(content, encoding);
|
|
624
|
-
await this.writeBinaryFile({
|
|
625
|
-
path,
|
|
626
|
-
content: new Uint8Array(
|
|
627
|
-
buffer.buffer,
|
|
628
|
-
buffer.byteOffset,
|
|
629
|
-
buffer.byteLength
|
|
630
|
-
),
|
|
631
|
-
abortSignal
|
|
632
|
-
});
|
|
633
|
-
}
|
|
361
|
+
constructor(sandbox) {
|
|
362
|
+
this.sandbox = sandbox;
|
|
363
|
+
}
|
|
364
|
+
get description() {
|
|
365
|
+
return [`Vercel Sandbox (name: ${this.sandbox.name}).`, "Filesystem changes persist for the lifetime of the sandbox."].join("\n");
|
|
366
|
+
}
|
|
367
|
+
async run({ command, workingDirectory, env, abortSignal }) {
|
|
368
|
+
abortSignal?.throwIfAborted();
|
|
369
|
+
const finished = await this.sandbox.runCommand({
|
|
370
|
+
cmd: "bash",
|
|
371
|
+
args: ["-c", command],
|
|
372
|
+
...workingDirectory !== void 0 ? { cwd: workingDirectory } : {},
|
|
373
|
+
...env !== void 0 ? { env } : {},
|
|
374
|
+
...abortSignal !== void 0 ? { signal: abortSignal } : {}
|
|
375
|
+
});
|
|
376
|
+
const [stdout, stderr] = await Promise.all([finished.stdout(), finished.stderr()]);
|
|
377
|
+
return {
|
|
378
|
+
exitCode: finished.exitCode,
|
|
379
|
+
stdout,
|
|
380
|
+
stderr
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
async spawn({ command, workingDirectory, env, abortSignal }) {
|
|
384
|
+
abortSignal?.throwIfAborted();
|
|
385
|
+
return createSandboxProcess(await this.sandbox.runCommand({
|
|
386
|
+
cmd: "bash",
|
|
387
|
+
args: ["-c", command],
|
|
388
|
+
detached: true,
|
|
389
|
+
...workingDirectory !== void 0 ? { cwd: workingDirectory } : {},
|
|
390
|
+
...env !== void 0 ? { env } : {},
|
|
391
|
+
...abortSignal !== void 0 ? { signal: abortSignal } : {}
|
|
392
|
+
}), abortSignal);
|
|
393
|
+
}
|
|
394
|
+
async readFile({ path, abortSignal }) {
|
|
395
|
+
const bytes = await this.readBinaryFile({
|
|
396
|
+
path,
|
|
397
|
+
abortSignal
|
|
398
|
+
});
|
|
399
|
+
if (bytes == null) return null;
|
|
400
|
+
return bytesToStream(bytes);
|
|
401
|
+
}
|
|
402
|
+
async readBinaryFile({ path, abortSignal }) {
|
|
403
|
+
abortSignal?.throwIfAborted();
|
|
404
|
+
try {
|
|
405
|
+
const buffer = await this.sandbox.readFileToBuffer({ path }, abortSignal ? { signal: abortSignal } : void 0);
|
|
406
|
+
if (buffer == null) return null;
|
|
407
|
+
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (isFileNotFoundError(error)) return null;
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async readTextFile({ path, encoding = "utf-8", startLine, endLine, abortSignal }) {
|
|
414
|
+
const bytes = await this.readBinaryFile({
|
|
415
|
+
path,
|
|
416
|
+
abortSignal
|
|
417
|
+
});
|
|
418
|
+
if (bytes == null) return null;
|
|
419
|
+
const text = Buffer.from(bytes).toString(encoding);
|
|
420
|
+
return extractLines({
|
|
421
|
+
text,
|
|
422
|
+
startLine,
|
|
423
|
+
endLine
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
async writeFile({ path, content, abortSignal }) {
|
|
427
|
+
const bytes = await collectStream(content);
|
|
428
|
+
await this.writeBinaryFile({
|
|
429
|
+
path,
|
|
430
|
+
content: bytes,
|
|
431
|
+
abortSignal
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
async writeBinaryFile({ path, content, abortSignal }) {
|
|
435
|
+
abortSignal?.throwIfAborted();
|
|
436
|
+
const parent = posix.dirname(path);
|
|
437
|
+
if (parent && parent !== "." && parent !== "/") await this.sandbox.runCommand({
|
|
438
|
+
cmd: "mkdir",
|
|
439
|
+
args: ["-p", parent],
|
|
440
|
+
...abortSignal !== void 0 ? { signal: abortSignal } : {}
|
|
441
|
+
});
|
|
442
|
+
await this.sandbox.writeFiles([{
|
|
443
|
+
path,
|
|
444
|
+
content
|
|
445
|
+
}], abortSignal ? { signal: abortSignal } : void 0);
|
|
446
|
+
}
|
|
447
|
+
async writeTextFile({ path, content, encoding = "utf-8", abortSignal }) {
|
|
448
|
+
const buffer = Buffer.from(content, encoding);
|
|
449
|
+
await this.writeBinaryFile({
|
|
450
|
+
path,
|
|
451
|
+
content: new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength),
|
|
452
|
+
abortSignal
|
|
453
|
+
});
|
|
454
|
+
}
|
|
634
455
|
};
|
|
635
456
|
function createSandboxProcess(command, abortSignal) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (abortSignal?.aborted) {
|
|
669
|
-
throw abortSignal.reason ?? new DOMException("Aborted", "AbortError");
|
|
670
|
-
}
|
|
671
|
-
return { exitCode: finished.exitCode };
|
|
672
|
-
},
|
|
673
|
-
async kill() {
|
|
674
|
-
await command.kill();
|
|
675
|
-
}
|
|
676
|
-
};
|
|
457
|
+
const encoder = new TextEncoder();
|
|
458
|
+
const controllers = {};
|
|
459
|
+
const stdout = new ReadableStream({ start(controller) {
|
|
460
|
+
controllers.stdout = controller;
|
|
461
|
+
} });
|
|
462
|
+
const stderr = new ReadableStream({ start(controller) {
|
|
463
|
+
controllers.stderr = controller;
|
|
464
|
+
} });
|
|
465
|
+
const drained = (async () => {
|
|
466
|
+
try {
|
|
467
|
+
const iterator = abortSignal ? command.logs({ signal: abortSignal }) : command.logs();
|
|
468
|
+
for await (const message of iterator) (message.stream === "stdout" ? controllers.stdout : controllers.stderr)?.enqueue(encoder.encode(message.data));
|
|
469
|
+
controllers.stdout?.close();
|
|
470
|
+
controllers.stderr?.close();
|
|
471
|
+
} catch (error) {
|
|
472
|
+
controllers.stdout?.error(error);
|
|
473
|
+
controllers.stderr?.error(error);
|
|
474
|
+
}
|
|
475
|
+
})();
|
|
476
|
+
return {
|
|
477
|
+
stdout,
|
|
478
|
+
stderr,
|
|
479
|
+
async wait() {
|
|
480
|
+
const finished = await command.wait();
|
|
481
|
+
await drained;
|
|
482
|
+
if (abortSignal?.aborted) throw abortSignal.reason ?? new DOMException("Aborted", "AbortError");
|
|
483
|
+
return { exitCode: finished.exitCode };
|
|
484
|
+
},
|
|
485
|
+
async kill() {
|
|
486
|
+
await command.kill();
|
|
487
|
+
}
|
|
488
|
+
};
|
|
677
489
|
}
|
|
678
490
|
function bytesToStream(bytes) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
});
|
|
491
|
+
return new ReadableStream({ start(controller) {
|
|
492
|
+
controller.enqueue(bytes);
|
|
493
|
+
controller.close();
|
|
494
|
+
} });
|
|
685
495
|
}
|
|
686
496
|
async function collectStream(stream) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
497
|
+
const reader = stream.getReader();
|
|
498
|
+
const chunks = [];
|
|
499
|
+
let total = 0;
|
|
500
|
+
while (true) {
|
|
501
|
+
const { value, done } = await reader.read();
|
|
502
|
+
if (done) break;
|
|
503
|
+
if (value) {
|
|
504
|
+
chunks.push(value);
|
|
505
|
+
total += value.byteLength;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const out = new Uint8Array(total);
|
|
509
|
+
let offset = 0;
|
|
510
|
+
for (const chunk of chunks) {
|
|
511
|
+
out.set(chunk, offset);
|
|
512
|
+
offset += chunk.byteLength;
|
|
513
|
+
}
|
|
514
|
+
return out;
|
|
705
515
|
}
|
|
706
516
|
function isFileNotFoundError(error) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
517
|
+
if (error == null || typeof error !== "object") return false;
|
|
518
|
+
if (error.code === "ENOENT") return true;
|
|
519
|
+
const message = error.message;
|
|
520
|
+
return typeof message === "string" && /no such file|not found|does not exist|ENOENT/i.test(message);
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region src/vercel-network-sandbox-session.ts
|
|
524
|
+
const VERCEL_PROVIDER_ID$1 = "vercel-sandbox";
|
|
525
|
+
/**
|
|
526
|
+
* `HarnessV1NetworkSandboxSession` backed by a `@vercel/sandbox` `Sandbox`. The
|
|
527
|
+
* native adaptation and sandbox creation functions return one of these. It
|
|
528
|
+
* extends {@link VercelSandboxSession} with ports, lifecycle, and network
|
|
529
|
+
* policy. Explicit `stop()` and `destroy()` calls delegate to the native
|
|
530
|
+
* sandbox unless the session was wrapped through the compatibility provider's
|
|
531
|
+
* existing-sandbox path, where those calls are intentionally no-ops.
|
|
532
|
+
*/
|
|
716
533
|
var VercelNetworkSandboxSession = class extends VercelSandboxSession {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
this.id = input.sandbox.name;
|
|
776
|
-
this.defaultWorkingDirectory = input.sandbox.currentSession().cwd;
|
|
777
|
-
this.#networkPolicyManager = new VercelNetworkPolicyManager({
|
|
778
|
-
sandbox: input.sandbox
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
#networkPolicyManager;
|
|
782
|
-
get ports() {
|
|
783
|
-
return this.sandbox.routes.map((route) => route.port);
|
|
784
|
-
}
|
|
785
|
-
restricted() {
|
|
786
|
-
return new VercelSandboxSession(this.sandbox);
|
|
787
|
-
}
|
|
534
|
+
#networkPolicyManager;
|
|
535
|
+
constructor(input) {
|
|
536
|
+
super(input.sandbox);
|
|
537
|
+
this.getPortEndpoint = async (options) => {
|
|
538
|
+
const exposedPorts = this.ports;
|
|
539
|
+
if (!exposedPorts.includes(options.port)) throw new HarnessCapabilityUnsupportedError({
|
|
540
|
+
harnessId: VERCEL_PROVIDER_ID$1,
|
|
541
|
+
message: `Port ${options.port} is not exposed on this sandbox. Exposed ports: [${exposedPorts.join(", ")}].`
|
|
542
|
+
});
|
|
543
|
+
const protocol = options.protocol ?? "https";
|
|
544
|
+
const url = new URL(this.sandbox.domain(options.port));
|
|
545
|
+
const isSecure = url.protocol === "https:";
|
|
546
|
+
switch (protocol) {
|
|
547
|
+
case "http":
|
|
548
|
+
url.protocol = isSecure ? "https:" : "http:";
|
|
549
|
+
break;
|
|
550
|
+
case "https":
|
|
551
|
+
url.protocol = "https:";
|
|
552
|
+
break;
|
|
553
|
+
case "ws": url.protocol = isSecure ? "wss:" : "ws:";
|
|
554
|
+
}
|
|
555
|
+
return { url: url.toString() };
|
|
556
|
+
};
|
|
557
|
+
this.getPortUrl = async (options) => {
|
|
558
|
+
return (await this.getPortEndpoint(options)).url;
|
|
559
|
+
};
|
|
560
|
+
this.setNetworkPolicy = async (policy) => {
|
|
561
|
+
await this.#networkPolicyManager.setNetworkPolicy(policy);
|
|
562
|
+
};
|
|
563
|
+
this.setRequestTransformations = async (transformations) => {
|
|
564
|
+
await this.#networkPolicyManager.setRequestTransformations(transformations);
|
|
565
|
+
};
|
|
566
|
+
this.addRequestTransformations = async (transformations) => {
|
|
567
|
+
await this.#networkPolicyManager.addRequestTransformations(transformations);
|
|
568
|
+
};
|
|
569
|
+
this.setPorts = async (ports, options) => {
|
|
570
|
+
await this.sandbox.update({ ports: [...ports] }, options?.abortSignal ? { signal: options.abortSignal } : void 0);
|
|
571
|
+
};
|
|
572
|
+
this.stop = async () => {
|
|
573
|
+
if (!this.ownsLifecycle) return;
|
|
574
|
+
await this.sandbox.stop();
|
|
575
|
+
};
|
|
576
|
+
this.destroy = async () => {
|
|
577
|
+
if (!this.ownsLifecycle) return;
|
|
578
|
+
await this.sandbox.stop().catch(() => {});
|
|
579
|
+
await this.sandbox.delete();
|
|
580
|
+
};
|
|
581
|
+
this.ownsLifecycle = input.ownsLifecycle;
|
|
582
|
+
this.id = input.sandbox.name;
|
|
583
|
+
this.defaultWorkingDirectory = input.sandbox.currentSession().cwd;
|
|
584
|
+
this.#networkPolicyManager = new VercelNetworkPolicyManager({ sandbox: input.sandbox });
|
|
585
|
+
}
|
|
586
|
+
get ports() {
|
|
587
|
+
return this.sandbox.routes.map((route) => route.port);
|
|
588
|
+
}
|
|
589
|
+
restricted() {
|
|
590
|
+
return new VercelSandboxSession(this.sandbox);
|
|
591
|
+
}
|
|
788
592
|
};
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
var DEFAULT_SANDBOX_RUNTIME = "node24";
|
|
796
|
-
var SNAPSHOT_POLL_INTERVAL_MS = 500;
|
|
797
|
-
var SNAPSHOT_POLL_TIMEOUT_MS = 3e4;
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/utils.ts
|
|
595
|
+
const VERCEL_PROVIDER_ID = "vercel-sandbox";
|
|
596
|
+
const DEFAULT_SANDBOX_RUNTIME = "node24";
|
|
597
|
+
const SNAPSHOT_POLL_INTERVAL_MS = 500;
|
|
598
|
+
const SNAPSHOT_POLL_TIMEOUT_MS = 3e4;
|
|
798
599
|
function getSandboxLookupParams(settings) {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
}
|
|
824
|
-
throw new HarnessSandboxAuthenticationError({
|
|
825
|
-
message: VERCEL_SANDBOX_AUTHENTICATION_MESSAGE,
|
|
826
|
-
sandboxProviderId: VERCEL_PROVIDER_ID3,
|
|
827
|
-
cause: error
|
|
828
|
-
});
|
|
829
|
-
}
|
|
600
|
+
if ("sandbox" in settings && settings.sandbox != null) return {};
|
|
601
|
+
const { fetch, projectId, teamId, token } = settings;
|
|
602
|
+
return {
|
|
603
|
+
...fetch ? { fetch } : {},
|
|
604
|
+
...projectId ? { projectId } : {},
|
|
605
|
+
...teamId ? { teamId } : {},
|
|
606
|
+
...token ? { token } : {}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const VERCEL_SANDBOX_AUTHENTICATION_MESSAGE = "Vercel Sandbox authentication failed. Set VERCEL_OIDC_TOKEN, or pass token, teamId, and projectId to createVercelSandbox(), then verify that they can access Vercel Sandbox.";
|
|
610
|
+
async function withVercelSandboxAuthenticationError({ settings, operation }) {
|
|
611
|
+
try {
|
|
612
|
+
return await operation();
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (!isVercelSandboxAuthenticationFailure({
|
|
615
|
+
error,
|
|
616
|
+
hasConfiguredCredentials: hasConfiguredCredentials(settings)
|
|
617
|
+
})) throw error;
|
|
618
|
+
throw new HarnessSandboxAuthenticationError({
|
|
619
|
+
message: VERCEL_SANDBOX_AUTHENTICATION_MESSAGE,
|
|
620
|
+
sandboxProviderId: VERCEL_PROVIDER_ID,
|
|
621
|
+
cause: error
|
|
622
|
+
});
|
|
623
|
+
}
|
|
830
624
|
}
|
|
831
625
|
function hasConfiguredCredentials(settings) {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
function isVercelSandboxAuthenticationFailure({
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
break;
|
|
863
|
-
}
|
|
864
|
-
return false;
|
|
865
|
-
}
|
|
866
|
-
var VERCEL_AUTHENTICATION_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
867
|
-
"AccessTokenMissingError",
|
|
868
|
-
"LocalOidcContextError",
|
|
869
|
-
"OAuthError",
|
|
870
|
-
"RefreshAccessTokenFailedError",
|
|
871
|
-
"VercelOidcContextError",
|
|
872
|
-
"VercelOidcTokenError"
|
|
626
|
+
if (process.env.VERCEL_OIDC_TOKEN) return true;
|
|
627
|
+
if ("sandbox" in settings && settings.sandbox != null) return true;
|
|
628
|
+
const { token, teamId, projectId } = getSandboxLookupParams(settings);
|
|
629
|
+
return Boolean(token && teamId && projectId);
|
|
630
|
+
}
|
|
631
|
+
function isVercelSandboxAuthenticationFailure({ error, hasConfiguredCredentials }) {
|
|
632
|
+
const seen = /* @__PURE__ */ new Set();
|
|
633
|
+
let current = error;
|
|
634
|
+
while (current != null && !seen.has(current)) {
|
|
635
|
+
seen.add(current);
|
|
636
|
+
if (typeof current === "object") {
|
|
637
|
+
const candidate = current;
|
|
638
|
+
if (candidate.response?.status === 401 || candidate.response?.status === 403 || candidate.response?.statusCode === 401 || candidate.response?.statusCode === 403) return true;
|
|
639
|
+
if (typeof candidate.name === "string" && VERCEL_AUTHENTICATION_ERROR_NAMES.has(candidate.name)) return true;
|
|
640
|
+
if (typeof candidate.message === "string" && VERCEL_AUTHENTICATION_ERROR_MESSAGE.test(candidate.message)) return true;
|
|
641
|
+
if (!hasConfiguredCredentials && candidate.code === "ERR_INVALID_ARG_TYPE" && typeof candidate.message === "string" && candidate.message.includes("\"path\" argument") && candidate.message.includes("Received undefined")) return true;
|
|
642
|
+
current = candidate.cause;
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
const VERCEL_AUTHENTICATION_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
650
|
+
"AccessTokenMissingError",
|
|
651
|
+
"LocalOidcContextError",
|
|
652
|
+
"OAuthError",
|
|
653
|
+
"RefreshAccessTokenFailedError",
|
|
654
|
+
"VercelOidcContextError",
|
|
655
|
+
"VercelOidcTokenError"
|
|
873
656
|
]);
|
|
874
|
-
|
|
657
|
+
const VERCEL_AUTHENTICATION_ERROR_MESSAGE = /Could not get credentials from OIDC context|No authentication found|Failed to (?:retrieve|refresh) authentication token|Missing credentials parameters to access the Vercel API|Authentication failed/i;
|
|
875
658
|
function hasExplicitSandboxEnvironment(params) {
|
|
876
|
-
|
|
659
|
+
return "runtime" in params && params.runtime != null || "image" in params && params.image != null || "source" in params && typeof params.source === "object" && params.source != null && "type" in params.source && params.source.type === "snapshot";
|
|
877
660
|
}
|
|
878
661
|
function withDefaultSandboxSettings(createParams) {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
885
|
-
async function ensureTemplateSnapshot({
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
...liveName != null ? { name: liveName } : {},
|
|
936
|
-
...abortSignal ? { signal: abortSignal } : {}
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
async function pollForTemplateSnapshot({
|
|
940
|
-
name,
|
|
941
|
-
lookupParams,
|
|
942
|
-
abortSignal
|
|
943
|
-
}) {
|
|
944
|
-
const deadline = Date.now() + SNAPSHOT_POLL_TIMEOUT_MS;
|
|
945
|
-
while (Date.now() < deadline) {
|
|
946
|
-
abortSignal?.throwIfAborted();
|
|
947
|
-
const refreshed = await Sandbox.get({
|
|
948
|
-
...lookupParams,
|
|
949
|
-
name,
|
|
950
|
-
resume: false,
|
|
951
|
-
...abortSignal ? { signal: abortSignal } : {}
|
|
952
|
-
});
|
|
953
|
-
if (refreshed.currentSnapshotId) {
|
|
954
|
-
return refreshed.currentSnapshotId;
|
|
955
|
-
}
|
|
956
|
-
await new Promise(
|
|
957
|
-
(resolve) => setTimeout(resolve, SNAPSHOT_POLL_INTERVAL_MS)
|
|
958
|
-
);
|
|
959
|
-
}
|
|
960
|
-
throw new Error(
|
|
961
|
-
`Timed out waiting for snapshot of template "${name}" to publish.`
|
|
962
|
-
);
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// src/vercel-legacy-sandbox-provider.ts
|
|
966
|
-
var SESSION_NAME_PREFIX = "ai-sdk-harness-session";
|
|
662
|
+
return {
|
|
663
|
+
...hasExplicitSandboxEnvironment(createParams) ? {} : { runtime: DEFAULT_SANDBOX_RUNTIME },
|
|
664
|
+
...createParams,
|
|
665
|
+
timeout: createParams.timeout ?? 18e5
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
async function ensureTemplateSnapshot({ baseParams, templateName, onCreate, lookupParams, abortSignal, snapshotCache }) {
|
|
669
|
+
const cachedSnapshotId = snapshotCache?.get(templateName);
|
|
670
|
+
if (cachedSnapshotId != null) return cachedSnapshotId;
|
|
671
|
+
const prepared = await Sandbox.getOrCreate({
|
|
672
|
+
...baseParams,
|
|
673
|
+
name: templateName,
|
|
674
|
+
persistent: true,
|
|
675
|
+
snapshotExpiration: baseParams.snapshotExpiration ?? 0,
|
|
676
|
+
onCreate,
|
|
677
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
678
|
+
});
|
|
679
|
+
let snapshotId = prepared.currentSnapshotId;
|
|
680
|
+
if (snapshotId == null) snapshotId = (await prepared.stop(abortSignal ? { signal: abortSignal } : void 0)).snapshot?.id ?? await pollForTemplateSnapshot({
|
|
681
|
+
name: templateName,
|
|
682
|
+
lookupParams,
|
|
683
|
+
abortSignal
|
|
684
|
+
});
|
|
685
|
+
snapshotCache?.set(templateName, snapshotId);
|
|
686
|
+
return snapshotId;
|
|
687
|
+
}
|
|
688
|
+
function createLiveSandboxFromSnapshot({ baseParams, snapshotId, liveName, abortSignal }) {
|
|
689
|
+
const { runtime: _runtime, image: _image, source: _source, persistent: _persistent, ...forkParams } = baseParams;
|
|
690
|
+
return Sandbox.create({
|
|
691
|
+
...forkParams,
|
|
692
|
+
source: {
|
|
693
|
+
type: "snapshot",
|
|
694
|
+
snapshotId
|
|
695
|
+
},
|
|
696
|
+
...liveName != null ? { name: liveName } : {},
|
|
697
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
async function pollForTemplateSnapshot({ name, lookupParams, abortSignal }) {
|
|
701
|
+
const deadline = Date.now() + SNAPSHOT_POLL_TIMEOUT_MS;
|
|
702
|
+
while (Date.now() < deadline) {
|
|
703
|
+
abortSignal?.throwIfAborted();
|
|
704
|
+
const refreshed = await Sandbox.get({
|
|
705
|
+
...lookupParams,
|
|
706
|
+
name,
|
|
707
|
+
resume: false,
|
|
708
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
709
|
+
});
|
|
710
|
+
if (refreshed.currentSnapshotId) return refreshed.currentSnapshotId;
|
|
711
|
+
await new Promise((resolve) => setTimeout(resolve, SNAPSHOT_POLL_INTERVAL_MS));
|
|
712
|
+
}
|
|
713
|
+
throw new Error(`Timed out waiting for snapshot of template "${name}" to publish.`);
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/vercel-legacy-sandbox-provider.ts
|
|
717
|
+
const SESSION_NAME_PREFIX = "ai-sdk-harness-session";
|
|
967
718
|
function sessionSandboxName(sessionId) {
|
|
968
|
-
|
|
719
|
+
return `${SESSION_NAME_PREFIX}-${sessionId}`;
|
|
969
720
|
}
|
|
721
|
+
/** @deprecated Use `createVercelNetworkSandboxSession` instead. */
|
|
970
722
|
function createVercelSandbox(settings = {}) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
723
|
+
console.warn("createVercelSandbox is deprecated. Use createVercelNetworkSandboxSession and HarnessAgent.createSession({ sandboxSession }) instead.");
|
|
724
|
+
return new VercelSandboxProvider(settings);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* `HarnessV1SandboxProvider` implementation backed by `@vercel/sandbox`.
|
|
728
|
+
* Construct one via {@link createVercelSandbox} at module scope and pass it
|
|
729
|
+
* to a `HarnessAgent` (or call `createSession()` directly if you want raw
|
|
730
|
+
* access to a network sandbox session).
|
|
731
|
+
*/
|
|
732
|
+
/** @deprecated Use `createVercelNetworkSandboxSession` instead. */
|
|
976
733
|
var VercelSandboxProvider = class {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
const sandbox = await withVercelSandboxAuthenticationError({
|
|
1051
|
-
settings: this.settings,
|
|
1052
|
-
operation: () => Sandbox2.get({
|
|
1053
|
-
...getSandboxLookupParams(this.settings),
|
|
1054
|
-
name: sessionSandboxName(options.sessionId),
|
|
1055
|
-
...options.abortSignal ? { signal: options.abortSignal } : {}
|
|
1056
|
-
})
|
|
1057
|
-
});
|
|
1058
|
-
return new VercelNetworkSandboxSession({ sandbox, ownsLifecycle: true });
|
|
1059
|
-
};
|
|
1060
|
-
console.warn(
|
|
1061
|
-
"VercelSandboxProvider is deprecated. Use createVercelNetworkSandboxSession instead."
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
734
|
+
constructor(settings) {
|
|
735
|
+
this.settings = settings;
|
|
736
|
+
this.specificationVersion = "harness-sandbox-v1";
|
|
737
|
+
this.providerId = VERCEL_PROVIDER_ID;
|
|
738
|
+
this.createSession = async (options) => {
|
|
739
|
+
options?.abortSignal?.throwIfAborted();
|
|
740
|
+
if ("sandbox" in this.settings && this.settings.sandbox != null) return new VercelNetworkSandboxSession({
|
|
741
|
+
sandbox: this.settings.sandbox,
|
|
742
|
+
ownsLifecycle: false
|
|
743
|
+
});
|
|
744
|
+
const { sandbox: _ignoredSandbox, name: explicitName, ...createParams } = this.settings;
|
|
745
|
+
const baseParams = withDefaultSandboxSettings(createParams);
|
|
746
|
+
const identity = options?.identity;
|
|
747
|
+
const onFirstCreate = options?.onFirstCreate;
|
|
748
|
+
const sessionNameOverride = options?.sessionId ? { name: sessionSandboxName(options.sessionId) } : {};
|
|
749
|
+
if (identity == null || onFirstCreate == null) return new VercelNetworkSandboxSession({
|
|
750
|
+
sandbox: await withVercelSandboxAuthenticationError({
|
|
751
|
+
settings: this.settings,
|
|
752
|
+
operation: () => Sandbox.create({
|
|
753
|
+
...baseParams,
|
|
754
|
+
...sessionNameOverride,
|
|
755
|
+
...options?.abortSignal ? { signal: options.abortSignal } : {}
|
|
756
|
+
})
|
|
757
|
+
}),
|
|
758
|
+
ownsLifecycle: true
|
|
759
|
+
});
|
|
760
|
+
const templateName = explicitName ?? `ai-sdk-harness-${identity}`;
|
|
761
|
+
const snapshotId = await withVercelSandboxAuthenticationError({
|
|
762
|
+
settings: this.settings,
|
|
763
|
+
operation: () => ensureTemplateSnapshot({
|
|
764
|
+
baseParams,
|
|
765
|
+
templateName,
|
|
766
|
+
lookupParams: getSandboxLookupParams(baseParams),
|
|
767
|
+
abortSignal: options?.abortSignal,
|
|
768
|
+
snapshotCache: getSnapshotCache(),
|
|
769
|
+
onCreate: async (sbx) => {
|
|
770
|
+
await onFirstCreate(new VercelSandboxSession(sbx), { abortSignal: options?.abortSignal });
|
|
771
|
+
}
|
|
772
|
+
})
|
|
773
|
+
});
|
|
774
|
+
return new VercelNetworkSandboxSession({
|
|
775
|
+
sandbox: await withVercelSandboxAuthenticationError({
|
|
776
|
+
settings: this.settings,
|
|
777
|
+
operation: () => createLiveSandboxFromSnapshot({
|
|
778
|
+
baseParams,
|
|
779
|
+
snapshotId,
|
|
780
|
+
liveName: sessionNameOverride.name,
|
|
781
|
+
abortSignal: options?.abortSignal
|
|
782
|
+
})
|
|
783
|
+
}),
|
|
784
|
+
ownsLifecycle: true
|
|
785
|
+
});
|
|
786
|
+
};
|
|
787
|
+
this.resumeSession = async (options) => {
|
|
788
|
+
options.abortSignal?.throwIfAborted();
|
|
789
|
+
if ("sandbox" in this.settings && this.settings.sandbox != null) return new VercelNetworkSandboxSession({
|
|
790
|
+
sandbox: this.settings.sandbox,
|
|
791
|
+
ownsLifecycle: false
|
|
792
|
+
});
|
|
793
|
+
return new VercelNetworkSandboxSession({
|
|
794
|
+
sandbox: await withVercelSandboxAuthenticationError({
|
|
795
|
+
settings: this.settings,
|
|
796
|
+
operation: () => Sandbox.get({
|
|
797
|
+
...getSandboxLookupParams(this.settings),
|
|
798
|
+
name: sessionSandboxName(options.sessionId),
|
|
799
|
+
...options.abortSignal ? { signal: options.abortSignal } : {}
|
|
800
|
+
})
|
|
801
|
+
}),
|
|
802
|
+
ownsLifecycle: true
|
|
803
|
+
});
|
|
804
|
+
};
|
|
805
|
+
console.warn("VercelSandboxProvider is deprecated. Use createVercelNetworkSandboxSession instead.");
|
|
806
|
+
}
|
|
1064
807
|
};
|
|
1065
|
-
|
|
1066
|
-
"ai-sdk.harness.vercel-template-snapshots"
|
|
1067
|
-
);
|
|
808
|
+
const SNAPSHOT_CACHE_KEY = Symbol.for("ai-sdk.harness.vercel-template-snapshots");
|
|
1068
809
|
function getSnapshotCache() {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
import { Sandbox as Sandbox3 } from "@vercel/sandbox";
|
|
810
|
+
const globals = globalThis;
|
|
811
|
+
let cache = globals[SNAPSHOT_CACHE_KEY];
|
|
812
|
+
if (cache == null) {
|
|
813
|
+
cache = /* @__PURE__ */ new Map();
|
|
814
|
+
globals[SNAPSHOT_CACHE_KEY] = cache;
|
|
815
|
+
}
|
|
816
|
+
return cache;
|
|
817
|
+
}
|
|
818
|
+
//#endregion
|
|
819
|
+
//#region src/vercel-sandbox.ts
|
|
1080
820
|
async function createVercelNetworkSandboxSession(options = {}) {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
await template.prepare({
|
|
1132
|
-
session: createVercelSandboxSessionFromNativeSandbox(sandbox),
|
|
1133
|
-
abortSignal: effectiveSignal
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
})
|
|
1137
|
-
});
|
|
1138
|
-
const liveSandbox = await withVercelSandboxAuthenticationError({
|
|
1139
|
-
settings,
|
|
1140
|
-
operation: () => createLiveSandboxFromSnapshot({
|
|
1141
|
-
baseParams,
|
|
1142
|
-
snapshotId,
|
|
1143
|
-
liveName
|
|
1144
|
-
})
|
|
1145
|
-
});
|
|
1146
|
-
return createVercelNetworkSandboxSessionFromNativeSandbox(liveSandbox);
|
|
821
|
+
if ("sandbox" in options) throw new Error("createVercelNetworkSandboxSession: use createVercelNetworkSandboxSessionFromNativeSandbox for an existing sandbox.");
|
|
822
|
+
const { template, abortSignal, sandboxId, sandbox: _sandbox, ...nativeOptions } = options;
|
|
823
|
+
const effectiveSignal = abortSignal ?? nativeOptions.signal;
|
|
824
|
+
effectiveSignal?.throwIfAborted();
|
|
825
|
+
const { name, ...creationOptions } = nativeOptions;
|
|
826
|
+
if (sandboxId != null && name != null && sandboxId !== name) throw new Error("createVercelNetworkSandboxSession: sandboxId and name must match when both are provided.");
|
|
827
|
+
const liveName = sandboxId ?? name;
|
|
828
|
+
const baseParams = withDefaultSandboxSettings({
|
|
829
|
+
...creationOptions,
|
|
830
|
+
...effectiveSignal ? { signal: effectiveSignal } : {}
|
|
831
|
+
});
|
|
832
|
+
const settings = options;
|
|
833
|
+
if (template == null) return createVercelNetworkSandboxSessionFromNativeSandbox(await withVercelSandboxAuthenticationError({
|
|
834
|
+
settings,
|
|
835
|
+
operation: () => Sandbox.create({
|
|
836
|
+
...baseParams,
|
|
837
|
+
...liveName != null ? { name: liveName } : {}
|
|
838
|
+
})
|
|
839
|
+
}));
|
|
840
|
+
const selection = "source" in baseParams && baseParams.source != null ? baseParams.source : "image" in baseParams && baseParams.image != null ? { image: baseParams.image } : { runtime: baseParams.runtime };
|
|
841
|
+
const material = JSON.stringify([
|
|
842
|
+
2,
|
|
843
|
+
template.identity,
|
|
844
|
+
selection
|
|
845
|
+
]);
|
|
846
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(material)));
|
|
847
|
+
const templateName = `ai-sdk-harness-v2-${Array.from(digest.slice(0, 12), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
848
|
+
const snapshotId = await withVercelSandboxAuthenticationError({
|
|
849
|
+
settings,
|
|
850
|
+
operation: () => ensureTemplateSnapshot({
|
|
851
|
+
baseParams,
|
|
852
|
+
templateName,
|
|
853
|
+
lookupParams: getSandboxLookupParams(baseParams),
|
|
854
|
+
abortSignal: effectiveSignal,
|
|
855
|
+
onCreate: async (sandbox) => {
|
|
856
|
+
await template.prepare({
|
|
857
|
+
session: createVercelSandboxSessionFromNativeSandbox(sandbox),
|
|
858
|
+
abortSignal: effectiveSignal
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
})
|
|
862
|
+
});
|
|
863
|
+
return createVercelNetworkSandboxSessionFromNativeSandbox(await withVercelSandboxAuthenticationError({
|
|
864
|
+
settings,
|
|
865
|
+
operation: () => createLiveSandboxFromSnapshot({
|
|
866
|
+
baseParams,
|
|
867
|
+
snapshotId,
|
|
868
|
+
liveName
|
|
869
|
+
})
|
|
870
|
+
}));
|
|
1147
871
|
}
|
|
1148
872
|
async function resumeVercelNetworkSandboxSession(options) {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
operation: () => Sandbox3.get({
|
|
1162
|
-
...lookupOptions,
|
|
1163
|
-
name: sandboxId,
|
|
1164
|
-
resume: true,
|
|
1165
|
-
...effectiveSignal ? { signal: effectiveSignal } : {}
|
|
1166
|
-
})
|
|
1167
|
-
});
|
|
1168
|
-
return createVercelNetworkSandboxSessionFromNativeSandbox(sandbox);
|
|
873
|
+
const { sandboxId, abortSignal, signal, name: _name, resume: _resume, ...lookupOptions } = options;
|
|
874
|
+
const effectiveSignal = abortSignal ?? signal;
|
|
875
|
+
effectiveSignal?.throwIfAborted();
|
|
876
|
+
return createVercelNetworkSandboxSessionFromNativeSandbox(await withVercelSandboxAuthenticationError({
|
|
877
|
+
settings: options,
|
|
878
|
+
operation: () => Sandbox.get({
|
|
879
|
+
...lookupOptions,
|
|
880
|
+
name: sandboxId,
|
|
881
|
+
resume: true,
|
|
882
|
+
...effectiveSignal ? { signal: effectiveSignal } : {}
|
|
883
|
+
})
|
|
884
|
+
}));
|
|
1169
885
|
}
|
|
1170
886
|
function createVercelSandboxSessionFromNativeSandbox(nativeSandbox) {
|
|
1171
|
-
|
|
887
|
+
return new VercelSandboxSession(nativeSandbox);
|
|
1172
888
|
}
|
|
1173
889
|
function createVercelNetworkSandboxSessionFromNativeSandbox(nativeSandbox) {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
890
|
+
return new VercelNetworkSandboxSession({
|
|
891
|
+
sandbox: nativeSandbox,
|
|
892
|
+
ownsLifecycle: true
|
|
893
|
+
});
|
|
1178
894
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
createVercelNetworkSandboxSessionFromNativeSandbox,
|
|
1183
|
-
createVercelSandbox,
|
|
1184
|
-
createVercelSandboxSessionFromNativeSandbox,
|
|
1185
|
-
resumeVercelNetworkSandboxSession
|
|
1186
|
-
};
|
|
895
|
+
//#endregion
|
|
896
|
+
export { VercelSandboxProvider, createVercelNetworkSandboxSession, createVercelNetworkSandboxSessionFromNativeSandbox, createVercelSandbox, createVercelSandboxSessionFromNativeSandbox, resumeVercelNetworkSandboxSession };
|
|
897
|
+
|
|
1187
898
|
//# sourceMappingURL=index.js.map
|