@alibaba-group/opensandbox 0.1.0-dev4 → 0.1.1-dev0

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.
Files changed (92) hide show
  1. package/dist/adapters/commandsAdapter.js +1 -0
  2. package/dist/adapters/commandsAdapter.js.map +1 -0
  3. package/dist/adapters/filesystemAdapter.js +1 -0
  4. package/dist/adapters/filesystemAdapter.js.map +1 -0
  5. package/dist/adapters/healthAdapter.js +1 -0
  6. package/dist/adapters/healthAdapter.js.map +1 -0
  7. package/dist/adapters/metricsAdapter.js +1 -0
  8. package/dist/adapters/metricsAdapter.js.map +1 -0
  9. package/dist/adapters/openapiError.js +1 -0
  10. package/dist/adapters/openapiError.js.map +1 -0
  11. package/dist/adapters/sandboxesAdapter.js +1 -0
  12. package/dist/adapters/sandboxesAdapter.js.map +1 -0
  13. package/dist/adapters/sse.js +1 -0
  14. package/dist/adapters/sse.js.map +1 -0
  15. package/dist/api/execd.js +1 -0
  16. package/dist/api/execd.js.map +1 -0
  17. package/dist/api/lifecycle.js +1 -0
  18. package/dist/api/lifecycle.js.map +1 -0
  19. package/dist/config/connection.d.ts.map +1 -1
  20. package/dist/config/connection.js +5 -2
  21. package/dist/config/connection.js.map +1 -0
  22. package/dist/core/constants.js +1 -0
  23. package/dist/core/constants.js.map +1 -0
  24. package/dist/core/exceptions.js +1 -0
  25. package/dist/core/exceptions.js.map +1 -0
  26. package/dist/factory/adapterFactory.js +1 -0
  27. package/dist/factory/adapterFactory.js.map +1 -0
  28. package/dist/factory/defaultAdapterFactory.js +1 -0
  29. package/dist/factory/defaultAdapterFactory.js.map +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/internal.js +1 -0
  33. package/dist/internal.js.map +1 -0
  34. package/dist/manager.js +1 -0
  35. package/dist/manager.js.map +1 -0
  36. package/dist/models/execd.js +1 -0
  37. package/dist/models/execd.js.map +1 -0
  38. package/dist/models/execution.js +1 -0
  39. package/dist/models/execution.js.map +1 -0
  40. package/dist/models/executionEventDispatcher.js +1 -0
  41. package/dist/models/executionEventDispatcher.js.map +1 -0
  42. package/dist/models/filesystem.js +1 -0
  43. package/dist/models/filesystem.js.map +1 -0
  44. package/dist/models/sandboxes.js +1 -0
  45. package/dist/models/sandboxes.js.map +1 -0
  46. package/dist/openapi/execdClient.js +1 -0
  47. package/dist/openapi/execdClient.js.map +1 -0
  48. package/dist/openapi/lifecycleClient.js +1 -0
  49. package/dist/openapi/lifecycleClient.js.map +1 -0
  50. package/dist/sandbox.js +1 -0
  51. package/dist/sandbox.js.map +1 -0
  52. package/dist/services/execdCommands.js +1 -0
  53. package/dist/services/execdCommands.js.map +1 -0
  54. package/dist/services/execdHealth.js +1 -0
  55. package/dist/services/execdHealth.js.map +1 -0
  56. package/dist/services/execdMetrics.js +1 -0
  57. package/dist/services/execdMetrics.js.map +1 -0
  58. package/dist/services/filesystem.js +1 -0
  59. package/dist/services/filesystem.js.map +1 -0
  60. package/dist/services/sandboxes.js +1 -0
  61. package/dist/services/sandboxes.js.map +1 -0
  62. package/package.json +3 -2
  63. package/src/adapters/commandsAdapter.ts +112 -0
  64. package/src/adapters/filesystemAdapter.ts +575 -0
  65. package/src/adapters/healthAdapter.ts +27 -0
  66. package/src/adapters/metricsAdapter.ts +51 -0
  67. package/src/adapters/openapiError.ts +42 -0
  68. package/src/adapters/sandboxesAdapter.ts +187 -0
  69. package/src/adapters/sse.ts +95 -0
  70. package/src/api/execd.ts +1569 -0
  71. package/src/api/lifecycle.ts +801 -0
  72. package/src/config/connection.ts +377 -0
  73. package/src/core/constants.ts +29 -0
  74. package/src/core/exceptions.ts +134 -0
  75. package/src/factory/adapterFactory.ts +51 -0
  76. package/src/factory/defaultAdapterFactory.ts +69 -0
  77. package/src/index.ts +108 -0
  78. package/src/internal.ts +39 -0
  79. package/src/manager.ts +111 -0
  80. package/src/models/execd.ts +90 -0
  81. package/src/models/execution.ts +71 -0
  82. package/src/models/executionEventDispatcher.ts +97 -0
  83. package/src/models/filesystem.ts +103 -0
  84. package/src/models/sandboxes.ts +142 -0
  85. package/src/openapi/execdClient.ts +49 -0
  86. package/src/openapi/lifecycleClient.ts +70 -0
  87. package/src/sandbox.ts +459 -0
  88. package/src/services/execdCommands.ts +35 -0
  89. package/src/services/execdHealth.ts +17 -0
  90. package/src/services/execdMetrics.ts +19 -0
  91. package/src/services/filesystem.ts +47 -0
  92. package/src/services/sandboxes.ts +42 -0
@@ -0,0 +1,377 @@
1
+ // Copyright 2026 Alibaba Group Holding Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ export type ConnectionProtocol = "http" | "https";
16
+
17
+ /**
18
+ * Options for {@link ConnectionConfig}.
19
+ *
20
+ * Most users only need `domain`, `protocol`, and `apiKey`.
21
+ */
22
+ export interface ConnectionConfigOptions {
23
+ /**
24
+ * API server domain (host[:port]) without scheme.
25
+ * Examples:
26
+ * - "localhost:8080"
27
+ * - "api.opensandbox.io"
28
+ *
29
+ * You may also pass a full URL (e.g. "http://localhost:8080" or "https://api.example.com").
30
+ * If the URL includes a path, it will be preserved and `/v1` will be appended automatically.
31
+ */
32
+ domain?: string;
33
+ protocol?: ConnectionProtocol;
34
+ apiKey?: string;
35
+ headers?: Record<string, string>;
36
+
37
+ /**
38
+ * Request timeout applied to all SDK HTTP calls (best-effort; wraps fetch).
39
+ * Defaults to 30 seconds.
40
+ */
41
+ requestTimeoutSeconds?: number;
42
+ /**
43
+ * Enable basic debug logging for HTTP requests (best-effort).
44
+ */
45
+ debug?: boolean;
46
+ }
47
+
48
+ function isNodeRuntime(): boolean {
49
+ const p = (globalThis as any)?.process;
50
+ return !!p?.versions?.node;
51
+ }
52
+
53
+ function redactHeaders(
54
+ headers: Record<string, string>
55
+ ): Record<string, string> {
56
+ const out: Record<string, string> = { ...headers };
57
+ for (const k of Object.keys(out)) {
58
+ if (k.toLowerCase() === "open-sandbox-api-key") out[k] = "***";
59
+ }
60
+ return out;
61
+ }
62
+
63
+ function readEnv(name: string): string | undefined {
64
+ const env = (globalThis as any)?.process?.env;
65
+ const v = env?.[name];
66
+ return typeof v === "string" && v.length ? v : undefined;
67
+ }
68
+
69
+ function stripTrailingSlashes(s: string): string {
70
+ return s.replace(/\/+$/, "");
71
+ }
72
+
73
+ function stripV1Suffix(s: string): string {
74
+ const trimmed = stripTrailingSlashes(s);
75
+ return trimmed.endsWith("/v1") ? trimmed.slice(0, -3) : trimmed;
76
+ }
77
+
78
+ const DEFAULT_KEEPALIVE_TIMEOUT_MS = 30_000;
79
+
80
+ function normalizeDomainBase(input: string): {
81
+ protocol?: ConnectionProtocol;
82
+ domainBase: string;
83
+ } {
84
+ // Accept a full URL and preserve its path prefix (if any).
85
+ if (input.startsWith("http://") || input.startsWith("https://")) {
86
+ const u = new URL(input);
87
+ const proto = u.protocol === "https:" ? "https" : "http";
88
+ // Keep origin + pathname, drop query/hash.
89
+ const base = `${u.origin}${u.pathname}`;
90
+ return { protocol: proto, domainBase: stripV1Suffix(base) };
91
+ }
92
+
93
+ // No scheme: treat as "host[:port]" or "host[:port]/prefix" and normalize trailing "/v1" or "/".
94
+ return { domainBase: stripV1Suffix(input) };
95
+ }
96
+
97
+ function createNodeFetch(): {
98
+ fetch: typeof fetch;
99
+ close: () => Promise<void>;
100
+ } {
101
+ if (!isNodeRuntime()) {
102
+ return {
103
+ fetch,
104
+ close: async () => {
105
+ // Browser fetch has no pooled dispatcher to close.
106
+ },
107
+ };
108
+ }
109
+
110
+ const baseFetch = fetch;
111
+ let dispatcher: unknown | undefined;
112
+ let dispatcherPromise: Promise<unknown> | null = null;
113
+
114
+ const nodeFetch: typeof fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
115
+ dispatcherPromise ??= (async () => {
116
+ try {
117
+ const mod = await import("undici");
118
+ const Agent = (mod as { Agent?: new (...args: any[]) => unknown }).Agent;
119
+ if (!Agent) {
120
+ return undefined;
121
+ }
122
+ dispatcher = new Agent({
123
+ keepAliveTimeout: DEFAULT_KEEPALIVE_TIMEOUT_MS,
124
+ keepAliveMaxTimeout: DEFAULT_KEEPALIVE_TIMEOUT_MS,
125
+ });
126
+ return dispatcher;
127
+ } catch {
128
+ return undefined;
129
+ }
130
+ })();
131
+
132
+ if (dispatcherPromise) {
133
+ await dispatcherPromise;
134
+ }
135
+
136
+ if (dispatcher) {
137
+ const mergedInit = { ...(init ?? {}), dispatcher } as RequestInit & {
138
+ dispatcher?: unknown;
139
+ };
140
+ return baseFetch(input, mergedInit as RequestInit);
141
+ }
142
+
143
+ return baseFetch(input, init);
144
+ };
145
+
146
+ return {
147
+ fetch: nodeFetch,
148
+ close: async () => {
149
+ if (dispatcherPromise) {
150
+ await dispatcherPromise.catch(() => undefined);
151
+ }
152
+ if (
153
+ dispatcher &&
154
+ typeof dispatcher === "object" &&
155
+ typeof (dispatcher as any).close === "function"
156
+ ) {
157
+ try {
158
+ await (dispatcher as any).close();
159
+ } catch {
160
+ // swallow close errors
161
+ }
162
+ }
163
+ },
164
+ };
165
+ }
166
+
167
+ function createTimedFetch(opts: {
168
+ baseFetch: typeof fetch;
169
+ timeoutSeconds: number;
170
+ debug: boolean;
171
+ defaultHeaders?: Record<string, string>;
172
+ label: string;
173
+ }): typeof fetch {
174
+ const baseFetch = opts.baseFetch;
175
+ const timeoutSeconds = opts.timeoutSeconds;
176
+ const debug = opts.debug;
177
+ const defaultHeaders = opts.defaultHeaders ?? {};
178
+ const label = opts.label;
179
+
180
+ return async (input: RequestInfo | URL, init?: RequestInit) => {
181
+ const method = init?.method ?? "GET";
182
+ const url =
183
+ typeof input === "string"
184
+ ? input
185
+ : (input as any)?.toString?.() ?? String(input);
186
+
187
+ const ac = new AbortController();
188
+ const timeoutMs = Math.floor(timeoutSeconds * 1000);
189
+ const t =
190
+ Number.isFinite(timeoutMs) && timeoutMs > 0
191
+ ? setTimeout(
192
+ () =>
193
+ ac.abort(
194
+ new Error(
195
+ `[${label}] Request timed out (timeoutSeconds=${timeoutSeconds})`
196
+ )
197
+ ),
198
+ timeoutMs
199
+ )
200
+ : undefined;
201
+
202
+ const onAbort = () =>
203
+ ac.abort((init?.signal as any)?.reason ?? new Error("Aborted"));
204
+ if (init?.signal) {
205
+ if (init.signal.aborted) onAbort();
206
+ else
207
+ init.signal.addEventListener("abort", onAbort, { once: true } as any);
208
+ }
209
+
210
+ const mergedInit: RequestInit = {
211
+ ...init,
212
+ signal: ac.signal,
213
+ };
214
+
215
+ if (debug) {
216
+ const mergedHeaders = {
217
+ ...defaultHeaders,
218
+ ...((init?.headers ?? {}) as any),
219
+ };
220
+ // eslint-disable-next-line no-console
221
+ console.log(
222
+ `[opensandbox:${label}] ->`,
223
+ method,
224
+ url,
225
+ redactHeaders(mergedHeaders)
226
+ );
227
+ }
228
+
229
+ try {
230
+ const res = await baseFetch(input, mergedInit);
231
+ if (debug) {
232
+ // eslint-disable-next-line no-console
233
+ console.log(`[opensandbox:${label}] <-`, method, url, res.status);
234
+ }
235
+ return res;
236
+ } finally {
237
+ if (t) clearTimeout(t);
238
+ if (init?.signal)
239
+ init.signal.removeEventListener("abort", onAbort as any);
240
+ }
241
+ };
242
+ }
243
+
244
+ export class ConnectionConfig {
245
+ readonly protocol: ConnectionProtocol;
246
+ readonly domain: string;
247
+ readonly apiKey?: string;
248
+ readonly headers: Record<string, string>;
249
+ private _fetch: typeof fetch | null;
250
+ private _sseFetch: typeof fetch | null;
251
+ readonly requestTimeoutSeconds: number;
252
+ readonly debug: boolean;
253
+ readonly userAgent: string = "OpenSandbox-JS-SDK/0.1.1";
254
+ private _closeTransport: () => Promise<void>;
255
+ private _closePromise: Promise<void> | null = null;
256
+ private _transportInitialized = false;
257
+
258
+ /**
259
+ * Create a connection configuration.
260
+ *
261
+ * Environment variables (optional):
262
+ * - `OPEN_SANDBOX_DOMAIN` (default: `localhost:8080`)
263
+ * - `OPEN_SANDBOX_API_KEY`
264
+ */
265
+ constructor(opts: ConnectionConfigOptions = {}) {
266
+ const envDomain = readEnv("OPEN_SANDBOX_DOMAIN");
267
+ const envApiKey = readEnv("OPEN_SANDBOX_API_KEY");
268
+
269
+ const rawDomain = opts.domain ?? envDomain ?? "localhost:8080";
270
+ const normalized = normalizeDomainBase(rawDomain);
271
+
272
+ // If the domain includes a scheme, it overrides `protocol`.
273
+ this.protocol = normalized.protocol ?? opts.protocol ?? "http";
274
+ this.domain = normalized.domainBase;
275
+ this.apiKey = opts.apiKey ?? envApiKey;
276
+ this.requestTimeoutSeconds =
277
+ typeof opts.requestTimeoutSeconds === "number"
278
+ ? opts.requestTimeoutSeconds
279
+ : 30;
280
+ this.debug = !!opts.debug;
281
+
282
+ const headers: Record<string, string> = { ...(opts.headers ?? {}) };
283
+ // Attach API key via header unless the user already provided one.
284
+ if (this.apiKey && !headers["OPEN-SANDBOX-API-KEY"]) {
285
+ headers["OPEN-SANDBOX-API-KEY"] = this.apiKey;
286
+ }
287
+ // Best-effort user-agent (Node only).
288
+ if (
289
+ isNodeRuntime() &&
290
+ this.userAgent &&
291
+ !headers["user-agent"] &&
292
+ !headers["User-Agent"]
293
+ ) {
294
+ headers["user-agent"] = this.userAgent;
295
+ }
296
+ this.headers = headers;
297
+ this._fetch = null;
298
+ this._sseFetch = null;
299
+ this._closeTransport = async () => {
300
+ // Init with empty close call
301
+ };
302
+ this._transportInitialized = false;
303
+ }
304
+
305
+ get fetch(): typeof fetch {
306
+ return this._fetch ?? fetch;
307
+ }
308
+
309
+ get sseFetch(): typeof fetch {
310
+ return this._sseFetch ?? fetch;
311
+ }
312
+
313
+ getBaseUrl(): string {
314
+ // If `domain` already contains a scheme, treat it as a full base URL prefix.
315
+ if (
316
+ this.domain.startsWith("http://") ||
317
+ this.domain.startsWith("https://")
318
+ ) {
319
+ return `${stripV1Suffix(this.domain)}/v1`;
320
+ }
321
+ return `${this.protocol}://${stripV1Suffix(this.domain)}/v1`;
322
+ }
323
+
324
+ private initializeTransport(): void {
325
+ if (this._transportInitialized) return;
326
+
327
+ const { fetch: baseFetch, close } = createNodeFetch();
328
+ this._fetch = createTimedFetch({
329
+ baseFetch,
330
+ timeoutSeconds: this.requestTimeoutSeconds,
331
+ debug: this.debug,
332
+ defaultHeaders: this.headers,
333
+ label: "http",
334
+ });
335
+ this._sseFetch = createTimedFetch({
336
+ baseFetch,
337
+ timeoutSeconds: 0,
338
+ debug: this.debug,
339
+ defaultHeaders: this.headers,
340
+ label: "sse",
341
+ });
342
+ this._closeTransport = close;
343
+ this._transportInitialized = true;
344
+ }
345
+ /**
346
+ * Ensure this configuration has transport helpers (fetch/SSE) allocated.
347
+ *
348
+ * On Node.js this creates a dedicated `undici` dispatcher; on browsers it
349
+ * simply reuses the global fetch. Returns either `this` or a cloned config
350
+ * with the transport initialized.
351
+ */
352
+ withTransportIfMissing(): ConnectionConfig {
353
+ if (this._transportInitialized) {
354
+ return this;
355
+ }
356
+
357
+ const clone = new ConnectionConfig({
358
+ domain: this.domain,
359
+ protocol: this.protocol,
360
+ apiKey: this.apiKey,
361
+ headers: { ...this.headers },
362
+ requestTimeoutSeconds: this.requestTimeoutSeconds,
363
+ debug: this.debug,
364
+ });
365
+ clone.initializeTransport();
366
+ return clone;
367
+ }
368
+
369
+ /**
370
+ * Close the Node.js agent owned by this configuration.
371
+ */
372
+ async closeTransport(): Promise<void> {
373
+ if (!this._transportInitialized) return;
374
+ this._closePromise ??= this._closeTransport();
375
+ await this._closePromise;
376
+ }
377
+ }
@@ -0,0 +1,29 @@
1
+ // Copyright 2026 Alibaba Group Holding Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ export const DEFAULT_EXECD_PORT = 44772;
16
+
17
+ export const DEFAULT_ENTRYPOINT: string[] = ["tail", "-f", "/dev/null"];
18
+
19
+ export const DEFAULT_RESOURCE_LIMITS: Record<string, string> = {
20
+ cpu: "1",
21
+ memory: "2Gi",
22
+ };
23
+
24
+ export const DEFAULT_TIMEOUT_SECONDS = 600;
25
+ export const DEFAULT_READY_TIMEOUT_SECONDS = 30;
26
+ export const DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS = 200;
27
+
28
+ export const DEFAULT_REQUEST_TIMEOUT_SECONDS = 30;
29
+ export const DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.0";
@@ -0,0 +1,134 @@
1
+ // Copyright 2026 Alibaba Group Holding Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ export type SandboxErrorCode =
16
+ | "INTERNAL_UNKNOWN_ERROR"
17
+ | "READY_TIMEOUT"
18
+ | "UNHEALTHY"
19
+ | "INVALID_ARGUMENT"
20
+ | "UNEXPECTED_RESPONSE"
21
+ // Allow server-defined codes as well.
22
+ | (string & {});
23
+
24
+ /**
25
+ * Structured error payload carried by {@link SandboxException}.
26
+ *
27
+ * - `code`: stable programmatic identifier
28
+ * - `message`: optional human-readable message
29
+ */
30
+ export class SandboxError {
31
+ static readonly INTERNAL_UNKNOWN_ERROR: SandboxErrorCode = "INTERNAL_UNKNOWN_ERROR";
32
+ static readonly READY_TIMEOUT: SandboxErrorCode = "READY_TIMEOUT";
33
+ static readonly UNHEALTHY: SandboxErrorCode = "UNHEALTHY";
34
+ static readonly INVALID_ARGUMENT: SandboxErrorCode = "INVALID_ARGUMENT";
35
+ static readonly UNEXPECTED_RESPONSE: SandboxErrorCode = "UNEXPECTED_RESPONSE";
36
+
37
+ constructor(
38
+ readonly code: SandboxErrorCode,
39
+ readonly message?: string,
40
+ ) {}
41
+ }
42
+
43
+ interface SandboxExceptionOpts {
44
+ message?: string;
45
+ cause?: unknown;
46
+ error?: SandboxError;
47
+ }
48
+
49
+ /**
50
+ * Base exception class for all SDK errors.
51
+ *
52
+ * All errors thrown by this SDK are subclasses of {@link SandboxException}.
53
+ */
54
+ export class SandboxException extends Error {
55
+ readonly name: string = "SandboxException";
56
+ readonly error: SandboxError;
57
+ readonly cause?: unknown;
58
+
59
+ constructor(opts: SandboxExceptionOpts = {}) {
60
+ super(opts.message);
61
+ this.cause = opts.cause;
62
+ this.error = opts.error ?? new SandboxError(SandboxError.INTERNAL_UNKNOWN_ERROR);
63
+ }
64
+ }
65
+
66
+ export class SandboxApiException extends SandboxException {
67
+ readonly name: string = "SandboxApiException";
68
+ readonly statusCode?: number;
69
+ readonly requestId?: string;
70
+ readonly rawBody?: unknown;
71
+
72
+ constructor(opts: SandboxExceptionOpts & {
73
+ statusCode?: number;
74
+ requestId?: string;
75
+ rawBody?: unknown;
76
+ }) {
77
+ super({
78
+ message: opts.message,
79
+ cause: opts.cause,
80
+ error: opts.error ?? new SandboxError(SandboxError.UNEXPECTED_RESPONSE, opts.message),
81
+ });
82
+ this.statusCode = opts.statusCode;
83
+ this.requestId = opts.requestId;
84
+ this.rawBody = opts.rawBody;
85
+ }
86
+ }
87
+
88
+ export class SandboxInternalException extends SandboxException {
89
+ readonly name: string = "SandboxInternalException";
90
+
91
+ constructor(opts: { message?: string; cause?: unknown }) {
92
+ super({
93
+ message: opts.message,
94
+ cause: opts.cause,
95
+ error: new SandboxError(SandboxError.INTERNAL_UNKNOWN_ERROR, opts.message),
96
+ });
97
+ }
98
+ }
99
+
100
+ export class SandboxUnhealthyException extends SandboxException {
101
+ readonly name: string = "SandboxUnhealthyException";
102
+
103
+ constructor(opts: { message?: string; cause?: unknown }) {
104
+ super({
105
+ message: opts.message,
106
+ cause: opts.cause,
107
+ error: new SandboxError(SandboxError.UNHEALTHY, opts.message),
108
+ });
109
+ }
110
+ }
111
+
112
+ export class SandboxReadyTimeoutException extends SandboxException {
113
+ readonly name: string = "SandboxReadyTimeoutException";
114
+
115
+ constructor(opts: { message?: string; cause?: unknown }) {
116
+ super({
117
+ message: opts.message,
118
+ cause: opts.cause,
119
+ error: new SandboxError(SandboxError.READY_TIMEOUT, opts.message),
120
+ });
121
+ }
122
+ }
123
+
124
+ export class InvalidArgumentException extends SandboxException {
125
+ readonly name: string = "InvalidArgumentException";
126
+
127
+ constructor(opts: { message?: string; cause?: unknown }) {
128
+ super({
129
+ message: opts.message,
130
+ cause: opts.cause,
131
+ error: new SandboxError(SandboxError.INVALID_ARGUMENT, opts.message),
132
+ });
133
+ }
134
+ }
@@ -0,0 +1,51 @@
1
+ // Copyright 2026 Alibaba Group Holding Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import type { ConnectionConfig } from "../config/connection.js";
16
+ import type { SandboxFiles } from "../services/filesystem.js";
17
+ import type { ExecdCommands } from "../services/execdCommands.js";
18
+ import type { ExecdHealth } from "../services/execdHealth.js";
19
+ import type { ExecdMetrics } from "../services/execdMetrics.js";
20
+ import type { Sandboxes } from "../services/sandboxes.js";
21
+
22
+ export interface CreateLifecycleStackOptions {
23
+ connectionConfig: ConnectionConfig;
24
+ lifecycleBaseUrl: string;
25
+ }
26
+
27
+ export interface LifecycleStack {
28
+ sandboxes: Sandboxes;
29
+ }
30
+
31
+ export interface CreateExecdStackOptions {
32
+ connectionConfig: ConnectionConfig;
33
+ execdBaseUrl: string;
34
+ }
35
+
36
+ export interface ExecdStack {
37
+ commands: ExecdCommands;
38
+ files: SandboxFiles;
39
+ health: ExecdHealth;
40
+ metrics: ExecdMetrics;
41
+ }
42
+
43
+ /**
44
+ * Factory abstraction to keep `Sandbox` and `SandboxManager` decoupled from concrete adapter implementations.
45
+ *
46
+ * This is primarily useful for advanced integrations (custom transports, dependency injection, testing).
47
+ */
48
+ export interface AdapterFactory {
49
+ createLifecycleStack(opts: CreateLifecycleStackOptions): LifecycleStack;
50
+ createExecdStack(opts: CreateExecdStackOptions): ExecdStack;
51
+ }
@@ -0,0 +1,69 @@
1
+ // Copyright 2026 Alibaba Group Holding Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { createExecdClient } from "../openapi/execdClient.js";
16
+ import { createLifecycleClient } from "../openapi/lifecycleClient.js";
17
+
18
+ import { CommandsAdapter } from "../adapters/commandsAdapter.js";
19
+ import { FilesystemAdapter } from "../adapters/filesystemAdapter.js";
20
+ import { HealthAdapter } from "../adapters/healthAdapter.js";
21
+ import { MetricsAdapter } from "../adapters/metricsAdapter.js";
22
+ import { SandboxesAdapter } from "../adapters/sandboxesAdapter.js";
23
+
24
+ import type { AdapterFactory, CreateExecdStackOptions, CreateLifecycleStackOptions, ExecdStack, LifecycleStack } from "./adapterFactory.js";
25
+
26
+ export class DefaultAdapterFactory implements AdapterFactory {
27
+ createLifecycleStack(opts: CreateLifecycleStackOptions): LifecycleStack {
28
+ const lifecycleClient = createLifecycleClient({
29
+ baseUrl: opts.lifecycleBaseUrl,
30
+ apiKey: opts.connectionConfig.apiKey,
31
+ headers: opts.connectionConfig.headers,
32
+ fetch: opts.connectionConfig.fetch,
33
+ });
34
+ const sandboxes = new SandboxesAdapter(lifecycleClient);
35
+ return { sandboxes };
36
+ }
37
+
38
+ createExecdStack(opts: CreateExecdStackOptions): ExecdStack {
39
+ const execdClient = createExecdClient({
40
+ baseUrl: opts.execdBaseUrl,
41
+ headers: opts.connectionConfig.headers,
42
+ fetch: opts.connectionConfig.fetch,
43
+ });
44
+
45
+ const health = new HealthAdapter(execdClient);
46
+ const metrics = new MetricsAdapter(execdClient);
47
+ const files = new FilesystemAdapter(execdClient, {
48
+ baseUrl: opts.execdBaseUrl,
49
+ fetch: opts.connectionConfig.fetch,
50
+ headers: opts.connectionConfig.headers,
51
+ });
52
+ const commands = new CommandsAdapter(execdClient, {
53
+ baseUrl: opts.execdBaseUrl,
54
+ fetch: opts.connectionConfig.sseFetch,
55
+ headers: opts.connectionConfig.headers,
56
+ });
57
+
58
+ return {
59
+ commands,
60
+ files,
61
+ health,
62
+ metrics,
63
+ };
64
+ }
65
+ }
66
+
67
+ export function createDefaultAdapterFactory(): AdapterFactory {
68
+ return new DefaultAdapterFactory();
69
+ }