@ai-sdk/sandbox-vercel 1.0.123 → 1.0.126
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 +26 -0
- package/README.md +13 -68
- package/dist/index.d.ts +60 -32
- package/dist/index.js +381 -275
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +10 -1
- package/src/utils.ts +313 -0
- package/src/vercel-legacy-sandbox-provider.ts +221 -0
- package/src/vercel-network-sandbox-session.ts +5 -5
- package/src/vercel-sandbox-session.ts +5 -5
- package/src/vercel-sandbox.ts +156 -430
package/src/vercel-sandbox.ts
CHANGED
|
@@ -1,455 +1,181 @@
|
|
|
1
1
|
import {
|
|
2
|
-
HarnessSandboxAuthenticationError,
|
|
3
2
|
type HarnessV1NetworkSandboxSession,
|
|
4
|
-
type
|
|
3
|
+
type HarnessV1SandboxSessionCreateOptions,
|
|
4
|
+
type HarnessV1SandboxSessionResumeOptions,
|
|
5
5
|
} from '@ai-sdk/harness';
|
|
6
6
|
import type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';
|
|
7
7
|
import { Sandbox } from '@vercel/sandbox';
|
|
8
8
|
import { VercelNetworkSandboxSession } from './vercel-network-sandbox-session';
|
|
9
9
|
import { VercelSandboxSession } from './vercel-sandbox-session';
|
|
10
|
+
import {
|
|
11
|
+
createLiveSandboxFromSnapshot,
|
|
12
|
+
ensureTemplateSnapshot,
|
|
13
|
+
getSandboxLookupParams,
|
|
14
|
+
withVercelSandboxAuthenticationError,
|
|
15
|
+
withDefaultSandboxSettings,
|
|
16
|
+
type BaseCreateSandboxParams,
|
|
17
|
+
type DistributiveOmit,
|
|
18
|
+
type Prettify,
|
|
19
|
+
type VercelSandboxCreateParams,
|
|
20
|
+
} from './utils';
|
|
21
|
+
|
|
22
|
+
export type VercelNativeSandboxSession = Sandbox;
|
|
23
|
+
|
|
24
|
+
type VercelSandboxCreationSettings = VercelSandboxCreateParams & {
|
|
25
|
+
sandbox?: never;
|
|
26
|
+
name?: string;
|
|
27
|
+
};
|
|
10
28
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Distributes `Omit` across each member of a union instead of collapsing the
|
|
20
|
-
* union to its common keys. `Sandbox.create`'s parameter is a union (a
|
|
21
|
-
* git/tarball/no-source create variant and a snapshot-source create variant),
|
|
22
|
-
* so a plain `Omit` would discard keys absent from any one member (e.g.
|
|
23
|
-
* `runtime`, which the snapshot variant lacks) and merge the `source` shapes.
|
|
24
|
-
* Applying `Omit` per-member preserves every variant intact; the `Prettify`
|
|
25
|
-
* wrapper collapses each member's intersections into a readable object shape.
|
|
26
|
-
*/
|
|
27
|
-
type DistributiveOmit<T, K extends keyof any> = T extends unknown
|
|
28
|
-
? Prettify<Omit<T, K>>
|
|
29
|
-
: never;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Parameters forwarded to `@vercel/sandbox`'s `Sandbox.create` when creating
|
|
33
|
-
* a sandbox from scratch. Aliased directly from the underlying SDK so the
|
|
34
|
-
* full surface — every option Vercel supports, including its native
|
|
35
|
-
* `NetworkPolicy` — is available without us re-declaring it.
|
|
36
|
-
*/
|
|
37
|
-
type VercelSandboxCreateParams = DistributiveOmit<
|
|
38
|
-
NonNullable<Parameters<typeof Sandbox.create>[0]>,
|
|
39
|
-
'onResume'
|
|
29
|
+
type VercelCreationSettings = DistributiveOmit<
|
|
30
|
+
VercelSandboxCreationSettings,
|
|
31
|
+
'sandbox'
|
|
32
|
+
>;
|
|
33
|
+
export type VercelNetworkSandboxSessionCreateOptions = Prettify<
|
|
34
|
+
HarnessV1SandboxSessionCreateOptions<VercelCreationSettings> & {
|
|
35
|
+
sandbox?: never;
|
|
36
|
+
}
|
|
40
37
|
>;
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* sandbox. When the adapter declares a bootstrap recipe the provider uses
|
|
50
|
-
* `Sandbox.getOrCreate` to maintain a persistent named template snapshot
|
|
51
|
-
* keyed by the recipe identity, and forks an ephemeral sandbox per session
|
|
52
|
-
* from the snapshot. Use `name` to override the auto-derived template name.
|
|
53
|
-
*/
|
|
54
|
-
export type VercelSandboxSettings =
|
|
55
|
-
| { sandbox: Sandbox }
|
|
56
|
-
| (VercelSandboxCreateParams & {
|
|
57
|
-
sandbox?: never;
|
|
58
|
-
name?: string;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 30 minutes. The `@vercel/sandbox` SDK defaults to 5 minutes which is
|
|
63
|
-
* too short for multi-step workflows — the VM expires between steps.
|
|
64
|
-
*/
|
|
65
|
-
const DEFAULT_SANDBOX_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
66
|
-
const DEFAULT_SANDBOX_RUNTIME = 'node24';
|
|
67
|
-
|
|
68
|
-
const VERCEL_PROVIDER_ID = 'vercel-sandbox';
|
|
69
|
-
const TEMPLATE_NAME_PREFIX = 'ai-sdk-harness';
|
|
70
|
-
const SESSION_NAME_PREFIX = 'ai-sdk-harness-session';
|
|
71
|
-
const SNAPSHOT_POLL_INTERVAL_MS = 500;
|
|
72
|
-
const SNAPSHOT_POLL_TIMEOUT_MS = 30_000;
|
|
73
|
-
|
|
74
|
-
function sessionSandboxName(sessionId: string): string {
|
|
75
|
-
return `${SESSION_NAME_PREFIX}-${sessionId}`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function hasExplicitSandboxEnvironment(params: object): boolean {
|
|
79
|
-
return (
|
|
80
|
-
('runtime' in params && params.runtime != null) ||
|
|
81
|
-
('image' in params && params.image != null) ||
|
|
82
|
-
('source' in params &&
|
|
83
|
-
typeof params.source === 'object' &&
|
|
84
|
-
params.source != null &&
|
|
85
|
-
'type' in params.source &&
|
|
86
|
-
params.source.type === 'snapshot')
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function createVercelSandbox(
|
|
91
|
-
settings: VercelSandboxSettings = {} as VercelSandboxSettings,
|
|
92
|
-
): HarnessV1SandboxProvider {
|
|
93
|
-
return new VercelSandboxProvider(settings);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* `HarnessV1SandboxProvider` implementation backed by `@vercel/sandbox`.
|
|
98
|
-
* Construct one via {@link createVercelSandbox} at module scope and pass it
|
|
99
|
-
* to a `HarnessAgent` (or call `createSession()` directly if you want raw
|
|
100
|
-
* access to a network sandbox session).
|
|
101
|
-
*/
|
|
102
|
-
export class VercelSandboxProvider implements HarnessV1SandboxProvider {
|
|
103
|
-
readonly specificationVersion = 'harness-sandbox-v1' as const;
|
|
104
|
-
readonly providerId = VERCEL_PROVIDER_ID;
|
|
105
|
-
|
|
106
|
-
constructor(private readonly settings: VercelSandboxSettings) {}
|
|
107
|
-
|
|
108
|
-
createSession = async (options?: {
|
|
109
|
-
sessionId?: string;
|
|
110
|
-
abortSignal?: AbortSignal;
|
|
111
|
-
identity?: string;
|
|
112
|
-
onFirstCreate?: (
|
|
113
|
-
session: SandboxSession,
|
|
114
|
-
opts: { abortSignal?: AbortSignal },
|
|
115
|
-
) => Promise<void>;
|
|
116
|
-
}): Promise<HarnessV1NetworkSandboxSession> => {
|
|
117
|
-
options?.abortSignal?.throwIfAborted();
|
|
118
|
-
|
|
119
|
-
if ('sandbox' in this.settings && this.settings.sandbox != null) {
|
|
120
|
-
return new VercelNetworkSandboxSession({
|
|
121
|
-
sandbox: this.settings.sandbox,
|
|
122
|
-
ownsLifecycle: false,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
type CreateNewBranch = BaseCreateSandboxParams & {
|
|
127
|
-
sandbox?: never;
|
|
128
|
-
name?: string;
|
|
129
|
-
};
|
|
130
|
-
const settings = this.settings as CreateNewBranch;
|
|
131
|
-
const {
|
|
132
|
-
sandbox: _ignoredSandbox,
|
|
133
|
-
name: explicitName,
|
|
134
|
-
...createParams
|
|
135
|
-
} = settings;
|
|
136
|
-
// Sandbox v3 changed its implicit default from the Node 24 runtime to the
|
|
137
|
-
// Universal managed image. Keep this adapter's existing default stable while
|
|
138
|
-
// allowing callers to opt into managed images explicitly.
|
|
139
|
-
const baseParams = {
|
|
140
|
-
...(hasExplicitSandboxEnvironment(createParams)
|
|
141
|
-
? {}
|
|
142
|
-
: { runtime: DEFAULT_SANDBOX_RUNTIME }),
|
|
143
|
-
...createParams,
|
|
144
|
-
timeout: createParams.timeout ?? DEFAULT_SANDBOX_TIMEOUT_MS,
|
|
145
|
-
} as BaseCreateSandboxParams;
|
|
146
|
-
|
|
147
|
-
const identity = options?.identity;
|
|
148
|
-
const onFirstCreate = options?.onFirstCreate;
|
|
149
|
-
|
|
150
|
-
// When sessionId is supplied, name the per-session sandbox deterministically
|
|
151
|
-
// so a future `resumeSession({ sessionId })` can locate it via
|
|
152
|
-
// `Sandbox.get({ name })`. Absent sessionId (e.g. prewarm), fall back to
|
|
153
|
-
// Vercel's auto-naming.
|
|
154
|
-
const sessionNameOverride = options?.sessionId
|
|
155
|
-
? { name: sessionSandboxName(options.sessionId) }
|
|
156
|
-
: {};
|
|
157
|
-
|
|
158
|
-
if (identity == null || onFirstCreate == null) {
|
|
159
|
-
const sandbox = await withVercelSandboxAuthenticationError({
|
|
160
|
-
settings: this.settings,
|
|
161
|
-
operation: () =>
|
|
162
|
-
Sandbox.create({
|
|
163
|
-
...baseParams,
|
|
164
|
-
...sessionNameOverride,
|
|
165
|
-
...(options?.abortSignal ? { signal: options.abortSignal } : {}),
|
|
166
|
-
}),
|
|
167
|
-
});
|
|
168
|
-
return new VercelNetworkSandboxSession({ sandbox, ownsLifecycle: true });
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const templateName = explicitName ?? `${TEMPLATE_NAME_PREFIX}-${identity}`;
|
|
172
|
-
const cache = getSnapshotCache();
|
|
173
|
-
let snapshotId = cache.get(templateName);
|
|
174
|
-
|
|
175
|
-
if (snapshotId == null) {
|
|
176
|
-
const template = await withVercelSandboxAuthenticationError({
|
|
177
|
-
settings: this.settings,
|
|
178
|
-
operation: () =>
|
|
179
|
-
Sandbox.getOrCreate({
|
|
180
|
-
...baseParams,
|
|
181
|
-
name: templateName,
|
|
182
|
-
persistent: true,
|
|
183
|
-
snapshotExpiration: baseParams.snapshotExpiration ?? 0,
|
|
184
|
-
onCreate: async sbx => {
|
|
185
|
-
await onFirstCreate(new VercelSandboxSession(sbx), {
|
|
186
|
-
abortSignal: options?.abortSignal,
|
|
187
|
-
});
|
|
188
|
-
},
|
|
189
|
-
...(options?.abortSignal ? { signal: options.abortSignal } : {}),
|
|
190
|
-
}),
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
let resolvedId: string | undefined = template.currentSnapshotId;
|
|
194
|
-
if (resolvedId == null) {
|
|
195
|
-
const stopResult = await withVercelSandboxAuthenticationError({
|
|
196
|
-
settings: this.settings,
|
|
197
|
-
operation: () =>
|
|
198
|
-
template.stop(
|
|
199
|
-
options?.abortSignal
|
|
200
|
-
? { signal: options.abortSignal }
|
|
201
|
-
: undefined,
|
|
202
|
-
),
|
|
203
|
-
});
|
|
204
|
-
resolvedId = stopResult.snapshot?.id;
|
|
205
|
-
if (resolvedId == null) {
|
|
206
|
-
resolvedId = await withVercelSandboxAuthenticationError({
|
|
207
|
-
settings: this.settings,
|
|
208
|
-
operation: () =>
|
|
209
|
-
pollForTemplateSnapshot({
|
|
210
|
-
name: templateName,
|
|
211
|
-
lookupParams: getSandboxLookupParams(baseParams),
|
|
212
|
-
abortSignal: options?.abortSignal,
|
|
213
|
-
}),
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
cache.set(templateName, resolvedId);
|
|
219
|
-
snapshotId = resolvedId;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const {
|
|
223
|
-
runtime: _ignoredRuntime,
|
|
224
|
-
image: _ignoredImage,
|
|
225
|
-
source: _ignoredSource,
|
|
226
|
-
persistent: _ignoredPersistent,
|
|
227
|
-
...forkParams
|
|
228
|
-
} = baseParams;
|
|
229
|
-
|
|
230
|
-
const fork = await withVercelSandboxAuthenticationError({
|
|
231
|
-
settings: this.settings,
|
|
232
|
-
operation: () =>
|
|
233
|
-
Sandbox.create({
|
|
234
|
-
...forkParams,
|
|
235
|
-
source: { type: 'snapshot', snapshotId },
|
|
236
|
-
...sessionNameOverride,
|
|
237
|
-
...(options?.abortSignal ? { signal: options.abortSignal } : {}),
|
|
238
|
-
}),
|
|
239
|
-
});
|
|
240
|
-
return new VercelNetworkSandboxSession({
|
|
241
|
-
sandbox: fork,
|
|
242
|
-
ownsLifecycle: true,
|
|
243
|
-
});
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
resumeSession = async (options: {
|
|
247
|
-
sessionId: string;
|
|
248
|
-
abortSignal?: AbortSignal;
|
|
249
|
-
}): Promise<HarnessV1NetworkSandboxSession> => {
|
|
250
|
-
options.abortSignal?.throwIfAborted();
|
|
39
|
+
type VercelLookupSettings = DistributiveOmit<
|
|
40
|
+
NonNullable<Parameters<typeof Sandbox.get>[0]>,
|
|
41
|
+
'name' | 'resume'
|
|
42
|
+
> & {
|
|
43
|
+
name?: never;
|
|
44
|
+
resume?: never;
|
|
45
|
+
};
|
|
251
46
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
sandbox: this.settings.sandbox,
|
|
256
|
-
ownsLifecycle: false,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
47
|
+
export type VercelNetworkSandboxSessionResumeOptions = Prettify<
|
|
48
|
+
HarnessV1SandboxSessionResumeOptions<VercelLookupSettings>
|
|
49
|
+
>;
|
|
259
50
|
|
|
51
|
+
export {
|
|
52
|
+
DEFAULT_SANDBOX_TIMEOUT_MS,
|
|
53
|
+
DEFAULT_SANDBOX_RUNTIME,
|
|
54
|
+
VERCEL_PROVIDER_ID,
|
|
55
|
+
getSandboxLookupParams,
|
|
56
|
+
hasExplicitSandboxEnvironment,
|
|
57
|
+
pollForTemplateSnapshot,
|
|
58
|
+
withVercelSandboxAuthenticationError,
|
|
59
|
+
} from './utils';
|
|
60
|
+
export type { BaseCreateSandboxParams } from './utils';
|
|
61
|
+
|
|
62
|
+
export async function createVercelNetworkSandboxSession(
|
|
63
|
+
options: VercelNetworkSandboxSessionCreateOptions = {},
|
|
64
|
+
): Promise<HarnessV1NetworkSandboxSession> {
|
|
65
|
+
if ('sandbox' in options) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
'createVercelNetworkSandboxSession: use createVercelNetworkSandboxSessionFromNativeSandbox for an existing sandbox.',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const {
|
|
71
|
+
template,
|
|
72
|
+
abortSignal,
|
|
73
|
+
sandboxId,
|
|
74
|
+
sandbox: _sandbox,
|
|
75
|
+
...nativeOptions
|
|
76
|
+
} = options;
|
|
77
|
+
const effectiveSignal = abortSignal ?? nativeOptions.signal;
|
|
78
|
+
effectiveSignal?.throwIfAborted();
|
|
79
|
+
const { name, ...creationOptions } = nativeOptions;
|
|
80
|
+
if (sandboxId != null && name != null && sandboxId !== name) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
'createVercelNetworkSandboxSession: sandboxId and name must match when both are provided.',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const liveName = sandboxId ?? name;
|
|
86
|
+
const baseParams = withDefaultSandboxSettings({
|
|
87
|
+
...creationOptions,
|
|
88
|
+
...(effectiveSignal ? { signal: effectiveSignal } : {}),
|
|
89
|
+
} as BaseCreateSandboxParams);
|
|
90
|
+
const settings = options;
|
|
91
|
+
if (template == null) {
|
|
260
92
|
const sandbox = await withVercelSandboxAuthenticationError({
|
|
261
|
-
settings
|
|
93
|
+
settings,
|
|
262
94
|
operation: () =>
|
|
263
|
-
Sandbox.
|
|
264
|
-
...
|
|
265
|
-
name:
|
|
266
|
-
...(options.abortSignal ? { signal: options.abortSignal } : {}),
|
|
95
|
+
Sandbox.create({
|
|
96
|
+
...baseParams,
|
|
97
|
+
...(liveName != null ? { name: liveName } : {}),
|
|
267
98
|
}),
|
|
268
99
|
});
|
|
269
|
-
return
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Base shape of `Sandbox.create` params extracted from the union (excludes
|
|
275
|
-
* the `source: { type: 'snapshot' }` variant) so all create-time fields
|
|
276
|
-
* are typed as present.
|
|
277
|
-
*/
|
|
278
|
-
type BaseCreateSandboxParams = Exclude<
|
|
279
|
-
VercelSandboxCreateParams,
|
|
280
|
-
{ source: { type: 'snapshot'; snapshotId: string } }
|
|
281
|
-
>;
|
|
282
|
-
|
|
283
|
-
const SNAPSHOT_CACHE_KEY = Symbol.for(
|
|
284
|
-
'ai-sdk.harness.vercel-template-snapshots',
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
type SnapshotCache = Map<string, string>;
|
|
288
|
-
type SandboxLookupParams = {
|
|
289
|
-
fetch?: typeof fetch;
|
|
290
|
-
projectId?: string;
|
|
291
|
-
teamId?: string;
|
|
292
|
-
token?: string;
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
function getSandboxLookupParams(
|
|
296
|
-
settings: VercelSandboxSettings,
|
|
297
|
-
): SandboxLookupParams {
|
|
298
|
-
if ('sandbox' in settings && settings.sandbox != null) {
|
|
299
|
-
return {};
|
|
100
|
+
return createVercelNetworkSandboxSessionFromNativeSandbox(sandbox);
|
|
300
101
|
}
|
|
301
102
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
hasConfiguredCredentials: hasConfiguredCredentials(settings),
|
|
340
|
-
})
|
|
341
|
-
) {
|
|
342
|
-
throw error;
|
|
343
|
-
}
|
|
344
|
-
throw new HarnessSandboxAuthenticationError({
|
|
345
|
-
message: VERCEL_SANDBOX_AUTHENTICATION_MESSAGE,
|
|
346
|
-
sandboxProviderId: VERCEL_PROVIDER_ID,
|
|
347
|
-
cause: error,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
103
|
+
const selection =
|
|
104
|
+
'source' in baseParams && baseParams.source != null
|
|
105
|
+
? baseParams.source
|
|
106
|
+
: 'image' in baseParams && baseParams.image != null
|
|
107
|
+
? { image: baseParams.image }
|
|
108
|
+
: { runtime: baseParams.runtime };
|
|
109
|
+
const material = JSON.stringify([2, template.identity, selection]);
|
|
110
|
+
const digest = new Uint8Array(
|
|
111
|
+
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(material)),
|
|
112
|
+
);
|
|
113
|
+
const templateName = `ai-sdk-harness-v2-${Array.from(digest.slice(0, 12), byte => byte.toString(16).padStart(2, '0')).join('')}`;
|
|
114
|
+
const snapshotId = await withVercelSandboxAuthenticationError({
|
|
115
|
+
settings,
|
|
116
|
+
operation: () =>
|
|
117
|
+
ensureTemplateSnapshot({
|
|
118
|
+
baseParams,
|
|
119
|
+
templateName,
|
|
120
|
+
lookupParams: getSandboxLookupParams(baseParams),
|
|
121
|
+
abortSignal: effectiveSignal,
|
|
122
|
+
onCreate: async sandbox => {
|
|
123
|
+
await template.prepare({
|
|
124
|
+
session: createVercelSandboxSessionFromNativeSandbox(sandbox),
|
|
125
|
+
abortSignal: effectiveSignal,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
const liveSandbox = await withVercelSandboxAuthenticationError({
|
|
131
|
+
settings,
|
|
132
|
+
operation: () =>
|
|
133
|
+
createLiveSandboxFromSnapshot({
|
|
134
|
+
baseParams,
|
|
135
|
+
snapshotId,
|
|
136
|
+
liveName,
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
return createVercelNetworkSandboxSessionFromNativeSandbox(liveSandbox);
|
|
350
140
|
}
|
|
351
141
|
|
|
352
|
-
function
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const {
|
|
356
|
-
|
|
142
|
+
export async function resumeVercelNetworkSandboxSession(
|
|
143
|
+
options: VercelNetworkSandboxSessionResumeOptions,
|
|
144
|
+
): Promise<HarnessV1NetworkSandboxSession> {
|
|
145
|
+
const {
|
|
146
|
+
sandboxId,
|
|
147
|
+
abortSignal,
|
|
148
|
+
signal,
|
|
149
|
+
name: _name,
|
|
150
|
+
resume: _resume,
|
|
151
|
+
...lookupOptions
|
|
152
|
+
} = options;
|
|
153
|
+
const effectiveSignal = abortSignal ?? signal;
|
|
154
|
+
effectiveSignal?.throwIfAborted();
|
|
155
|
+
const sandbox = await withVercelSandboxAuthenticationError({
|
|
156
|
+
settings: options,
|
|
157
|
+
operation: () =>
|
|
158
|
+
Sandbox.get({
|
|
159
|
+
...lookupOptions,
|
|
160
|
+
name: sandboxId,
|
|
161
|
+
resume: true,
|
|
162
|
+
...(effectiveSignal ? { signal: effectiveSignal } : {}),
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
return createVercelNetworkSandboxSessionFromNativeSandbox(sandbox);
|
|
357
166
|
}
|
|
358
167
|
|
|
359
|
-
function
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
error: unknown;
|
|
364
|
-
hasConfiguredCredentials: boolean;
|
|
365
|
-
}): boolean {
|
|
366
|
-
const seen = new Set<unknown>();
|
|
367
|
-
let current = error;
|
|
368
|
-
while (current != null && !seen.has(current)) {
|
|
369
|
-
seen.add(current);
|
|
370
|
-
if (typeof current === 'object') {
|
|
371
|
-
const candidate = current as {
|
|
372
|
-
name?: unknown;
|
|
373
|
-
message?: unknown;
|
|
374
|
-
code?: unknown;
|
|
375
|
-
cause?: unknown;
|
|
376
|
-
response?: { status?: unknown; statusCode?: unknown };
|
|
377
|
-
};
|
|
378
|
-
if (
|
|
379
|
-
candidate.response?.status === 401 ||
|
|
380
|
-
candidate.response?.status === 403 ||
|
|
381
|
-
candidate.response?.statusCode === 401 ||
|
|
382
|
-
candidate.response?.statusCode === 403
|
|
383
|
-
) {
|
|
384
|
-
return true;
|
|
385
|
-
}
|
|
386
|
-
if (
|
|
387
|
-
typeof candidate.name === 'string' &&
|
|
388
|
-
VERCEL_AUTHENTICATION_ERROR_NAMES.has(candidate.name)
|
|
389
|
-
) {
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
if (
|
|
393
|
-
typeof candidate.message === 'string' &&
|
|
394
|
-
VERCEL_AUTHENTICATION_ERROR_MESSAGE.test(candidate.message)
|
|
395
|
-
) {
|
|
396
|
-
return true;
|
|
397
|
-
}
|
|
398
|
-
if (
|
|
399
|
-
!hasConfiguredCredentials &&
|
|
400
|
-
candidate.code === 'ERR_INVALID_ARG_TYPE' &&
|
|
401
|
-
typeof candidate.message === 'string' &&
|
|
402
|
-
candidate.message.includes('"path" argument') &&
|
|
403
|
-
candidate.message.includes('Received undefined')
|
|
404
|
-
) {
|
|
405
|
-
return true;
|
|
406
|
-
}
|
|
407
|
-
current = candidate.cause;
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
return false;
|
|
168
|
+
export function createVercelSandboxSessionFromNativeSandbox(
|
|
169
|
+
nativeSandbox: VercelNativeSandboxSession,
|
|
170
|
+
): SandboxSession {
|
|
171
|
+
return new VercelSandboxSession(nativeSandbox);
|
|
413
172
|
}
|
|
414
173
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
]);
|
|
423
|
-
|
|
424
|
-
const VERCEL_AUTHENTICATION_ERROR_MESSAGE =
|
|
425
|
-
/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;
|
|
426
|
-
|
|
427
|
-
async function pollForTemplateSnapshot({
|
|
428
|
-
name,
|
|
429
|
-
lookupParams,
|
|
430
|
-
abortSignal,
|
|
431
|
-
}: {
|
|
432
|
-
name: string;
|
|
433
|
-
lookupParams: ReturnType<typeof getSandboxLookupParams>;
|
|
434
|
-
abortSignal: AbortSignal | undefined;
|
|
435
|
-
}): Promise<string> {
|
|
436
|
-
const deadline = Date.now() + SNAPSHOT_POLL_TIMEOUT_MS;
|
|
437
|
-
while (Date.now() < deadline) {
|
|
438
|
-
abortSignal?.throwIfAborted();
|
|
439
|
-
const refreshed = await Sandbox.get({
|
|
440
|
-
...lookupParams,
|
|
441
|
-
name,
|
|
442
|
-
resume: false,
|
|
443
|
-
...(abortSignal ? { signal: abortSignal } : {}),
|
|
444
|
-
});
|
|
445
|
-
if (refreshed.currentSnapshotId) {
|
|
446
|
-
return refreshed.currentSnapshotId;
|
|
447
|
-
}
|
|
448
|
-
await new Promise<void>(resolve =>
|
|
449
|
-
setTimeout(resolve, SNAPSHOT_POLL_INTERVAL_MS),
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
throw new Error(
|
|
453
|
-
`Timed out waiting for snapshot of template "${name}" to publish.`,
|
|
454
|
-
);
|
|
174
|
+
export function createVercelNetworkSandboxSessionFromNativeSandbox(
|
|
175
|
+
nativeSandbox: VercelNativeSandboxSession,
|
|
176
|
+
): HarnessV1NetworkSandboxSession {
|
|
177
|
+
return new VercelNetworkSandboxSession({
|
|
178
|
+
sandbox: nativeSandbox,
|
|
179
|
+
ownsLifecycle: true,
|
|
180
|
+
});
|
|
455
181
|
}
|