@alibaba-group/opensandbox 0.1.0-dev2 → 0.1.0-dev4

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/README.md CHANGED
@@ -52,6 +52,7 @@ try {
52
52
 
53
53
  // Optional but recommended: terminate the remote instance when you are done.
54
54
  await sandbox.kill();
55
+ await sandbox.close();
55
56
  } catch (err) {
56
57
  if (err instanceof SandboxException) {
57
58
  console.error(`Sandbox Error: [${err.error.code}] ${err.error.message ?? ""}`);
@@ -157,6 +158,7 @@ import { SandboxManager } from "@alibaba-group/opensandbox";
157
158
  const manager = SandboxManager.create({ connectionConfig: config });
158
159
  const list = await manager.listSandboxInfos({ states: ["Running"], pageSize: 10 });
159
160
  console.log(list.items.map((s) => s.id));
161
+ await manager.close();
160
162
  ```
161
163
 
162
164
  ## Configuration
@@ -165,6 +167,10 @@ console.log(list.items.map((s) => s.id));
165
167
 
166
168
  The `ConnectionConfig` class manages API server connection settings.
167
169
 
170
+ Runtime notes:
171
+ - In browsers, the SDK uses the global `fetch` implementation.
172
+ - In Node.js, every `Sandbox` and `SandboxManager` clones the base `ConnectionConfig` via `withTransportIfMissing()`, so each instance gets an isolated `undici` keep-alive pool. Call `sandbox.close()` or `manager.close()` when you are done so the SDK can release the associated agent.
173
+
168
174
  | Parameter | Description | Default | Environment Variable |
169
175
  | --- | --- | --- | --- |
170
176
  | `apiKey` | API key for authentication | Optional | `OPEN_SANDBOX_API_KEY` |
@@ -210,6 +216,13 @@ const config2 = new ConnectionConfig({
210
216
  | `readyTimeoutSeconds` | Max time to wait for readiness | 30 seconds |
211
217
  | `healthCheckPollingInterval` | Poll interval while waiting (milliseconds) | 200 ms |
212
218
 
219
+ ### 3. Resource cleanup
220
+
221
+ Both `Sandbox` and `SandboxManager` own a scoped HTTP agent when running on Node.js
222
+ so you can safely reuse the same `ConnectionConfig`. Once you are finished interacting
223
+ with the sandbox or administration APIs, call `sandbox.close()` / `manager.close()` to
224
+ release the underlying agent.
225
+
213
226
  ## Browser Notes
214
227
 
215
228
  - The SDK can run in browsers, but **streaming file uploads are Node-only**.
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/adapters/sse.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,wBAAuB,oBAAoB,CAAC,CAAC,EAC3C,GAAG,EAAE,QAAQ,EACb,IAAI,CAAC,EAAE;IAAE,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,aAAa,CAAC,CAAC,CAAC,CA2DlB"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/adapters/sse.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,wBAAuB,oBAAoB,CAAC,CAAC,EAC3C,GAAG,EAAE,QAAQ,EACb,IAAI,CAAC,EAAE;IAAE,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,aAAa,CAAC,CAAC,CAAC,CA8DlB"}
@@ -73,6 +73,8 @@ export async function* parseJsonEventStream(res, opts) {
73
73
  yield parsed;
74
74
  }
75
75
  }
76
+ // Flush any buffered UTF-8 bytes from the decoder.
77
+ buf += decoder.decode();
76
78
  // flush last line if exists
77
79
  const last = buf.trim();
78
80
  if (last) {
@@ -33,18 +33,14 @@ export declare class ConnectionConfig {
33
33
  readonly domain: string;
34
34
  readonly apiKey?: string;
35
35
  readonly headers: Record<string, string>;
36
- readonly fetch: typeof fetch;
37
- /**
38
- * Fetch function intended for long-lived streaming requests (SSE).
39
- *
40
- * This is separate from {@link fetch} so callers can supply a different implementation
41
- * (e.g. Node undici with bodyTimeout disabled) and so the SDK can apply distinct
42
- * timeout semantics for streaming vs normal HTTP calls.
43
- */
44
- readonly sseFetch: typeof fetch;
36
+ private _fetch;
37
+ private _sseFetch;
45
38
  readonly requestTimeoutSeconds: number;
46
39
  readonly debug: boolean;
47
40
  readonly userAgent: string;
41
+ private _closeTransport;
42
+ private _closePromise;
43
+ private _transportInitialized;
48
44
  /**
49
45
  * Create a connection configuration.
50
46
  *
@@ -53,6 +49,21 @@ export declare class ConnectionConfig {
53
49
  * - `OPEN_SANDBOX_API_KEY`
54
50
  */
55
51
  constructor(opts?: ConnectionConfigOptions);
52
+ get fetch(): typeof fetch;
53
+ get sseFetch(): typeof fetch;
56
54
  getBaseUrl(): string;
55
+ private initializeTransport;
56
+ /**
57
+ * Ensure this configuration has transport helpers (fetch/SSE) allocated.
58
+ *
59
+ * On Node.js this creates a dedicated `undici` dispatcher; on browsers it
60
+ * simply reuses the global fetch. Returns either `this` or a cloned config
61
+ * with the transport initialized.
62
+ */
63
+ withTransportIfMissing(): ConnectionConfig;
64
+ /**
65
+ * Close the Node.js agent owned by this configuration.
66
+ */
67
+ closeTransport(): Promise<void>;
57
68
  }
58
69
  //# sourceMappingURL=connection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/config/connection.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,OAAO,CAAC;AAElD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAkGD,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,KAAK,CAAC;IAChC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAA8B;IAExD;;;;;;OAMG;gBACS,IAAI,GAAE,uBAA4B;IAmD9C,UAAU,IAAI,MAAM;CAOrB"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/config/connection.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,OAAO,CAAC;AAElD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAsMD,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,SAAS,CAAsB;IACvC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAA8B;IACxD,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,qBAAqB,CAAS;IAEtC;;;;;;OAMG;gBACS,IAAI,GAAE,uBAA4B;IAsC9C,IAAI,KAAK,IAAI,OAAO,KAAK,CAExB;IAED,IAAI,QAAQ,IAAI,OAAO,KAAK,CAE3B;IAED,UAAU,IAAI,MAAM;IAWpB,OAAO,CAAC,mBAAmB;IAqB3B;;;;;;OAMG;IACH,sBAAsB,IAAI,gBAAgB;IAiB1C;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAKtC"}
@@ -1,11 +1,11 @@
1
1
  // Copyright 2026 Alibaba Group Holding Ltd.
2
- //
2
+ //
3
3
  // Licensed under the Apache License, Version 2.0 (the "License");
4
4
  // you may not use this file except in compliance with the License.
5
5
  // You may obtain a copy of the License at
6
- //
6
+ //
7
7
  // http://www.apache.org/licenses/LICENSE-2.0
8
- //
8
+ //
9
9
  // Unless required by applicable law or agreed to in writing, software
10
10
  // distributed under the License is distributed on an "AS IS" BASIS,
11
11
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -13,7 +13,7 @@
13
13
  // limitations under the License.
14
14
  function isNodeRuntime() {
15
15
  const p = globalThis?.process;
16
- return !!(p?.versions?.node);
16
+ return !!p?.versions?.node;
17
17
  }
18
18
  function redactHeaders(headers) {
19
19
  const out = { ...headers };
@@ -35,6 +35,7 @@ function stripV1Suffix(s) {
35
35
  const trimmed = stripTrailingSlashes(s);
36
36
  return trimmed.endsWith("/v1") ? trimmed.slice(0, -3) : trimmed;
37
37
  }
38
+ const DEFAULT_KEEPALIVE_TIMEOUT_MS = 30_000;
38
39
  function normalizeDomainBase(input) {
39
40
  // Accept a full URL and preserve its path prefix (if any).
40
41
  if (input.startsWith("http://") || input.startsWith("https://")) {
@@ -47,6 +48,64 @@ function normalizeDomainBase(input) {
47
48
  // No scheme: treat as "host[:port]" or "host[:port]/prefix" and normalize trailing "/v1" or "/".
48
49
  return { domainBase: stripV1Suffix(input) };
49
50
  }
51
+ function createNodeFetch() {
52
+ if (!isNodeRuntime()) {
53
+ return {
54
+ fetch,
55
+ close: async () => {
56
+ // Browser fetch has no pooled dispatcher to close.
57
+ },
58
+ };
59
+ }
60
+ const baseFetch = fetch;
61
+ let dispatcher;
62
+ let dispatcherPromise = null;
63
+ const nodeFetch = async (input, init) => {
64
+ dispatcherPromise ??= (async () => {
65
+ try {
66
+ const mod = await import("undici");
67
+ const Agent = mod.Agent;
68
+ if (!Agent) {
69
+ return undefined;
70
+ }
71
+ dispatcher = new Agent({
72
+ keepAliveTimeout: DEFAULT_KEEPALIVE_TIMEOUT_MS,
73
+ keepAliveMaxTimeout: DEFAULT_KEEPALIVE_TIMEOUT_MS,
74
+ });
75
+ return dispatcher;
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ })();
81
+ if (dispatcherPromise) {
82
+ await dispatcherPromise;
83
+ }
84
+ if (dispatcher) {
85
+ const mergedInit = { ...(init ?? {}), dispatcher };
86
+ return baseFetch(input, mergedInit);
87
+ }
88
+ return baseFetch(input, init);
89
+ };
90
+ return {
91
+ fetch: nodeFetch,
92
+ close: async () => {
93
+ if (dispatcherPromise) {
94
+ await dispatcherPromise.catch(() => undefined);
95
+ }
96
+ if (dispatcher &&
97
+ typeof dispatcher === "object" &&
98
+ typeof dispatcher.close === "function") {
99
+ try {
100
+ await dispatcher.close();
101
+ }
102
+ catch {
103
+ // swallow close errors
104
+ }
105
+ }
106
+ },
107
+ };
108
+ }
50
109
  function createTimedFetch(opts) {
51
110
  const baseFetch = opts.baseFetch;
52
111
  const timeoutSeconds = opts.timeoutSeconds;
@@ -55,7 +114,9 @@ function createTimedFetch(opts) {
55
114
  const label = opts.label;
56
115
  return async (input, init) => {
57
116
  const method = init?.method ?? "GET";
58
- const url = typeof input === "string" ? input : input?.toString?.() ?? String(input);
117
+ const url = typeof input === "string"
118
+ ? input
119
+ : input?.toString?.() ?? String(input);
59
120
  const ac = new AbortController();
60
121
  const timeoutMs = Math.floor(timeoutSeconds * 1000);
61
122
  const t = Number.isFinite(timeoutMs) && timeoutMs > 0
@@ -73,7 +134,10 @@ function createTimedFetch(opts) {
73
134
  signal: ac.signal,
74
135
  };
75
136
  if (debug) {
76
- const mergedHeaders = { ...defaultHeaders, ...(init?.headers ?? {}) };
137
+ const mergedHeaders = {
138
+ ...defaultHeaders,
139
+ ...(init?.headers ?? {}),
140
+ };
77
141
  // eslint-disable-next-line no-console
78
142
  console.log(`[opensandbox:${label}] ->`, method, url, redactHeaders(mergedHeaders));
79
143
  }
@@ -98,18 +162,14 @@ export class ConnectionConfig {
98
162
  domain;
99
163
  apiKey;
100
164
  headers;
101
- fetch;
102
- /**
103
- * Fetch function intended for long-lived streaming requests (SSE).
104
- *
105
- * This is separate from {@link fetch} so callers can supply a different implementation
106
- * (e.g. Node undici with bodyTimeout disabled) and so the SDK can apply distinct
107
- * timeout semantics for streaming vs normal HTTP calls.
108
- */
109
- sseFetch;
165
+ _fetch;
166
+ _sseFetch;
110
167
  requestTimeoutSeconds;
111
168
  debug;
112
169
  userAgent = "OpenSandbox-JS-SDK/0.1.0";
170
+ _closeTransport;
171
+ _closePromise = null;
172
+ _transportInitialized = false;
113
173
  /**
114
174
  * Create a connection configuration.
115
175
  *
@@ -126,9 +186,10 @@ export class ConnectionConfig {
126
186
  this.protocol = normalized.protocol ?? opts.protocol ?? "http";
127
187
  this.domain = normalized.domainBase;
128
188
  this.apiKey = opts.apiKey ?? envApiKey;
129
- this.requestTimeoutSeconds = typeof opts.requestTimeoutSeconds === "number"
130
- ? opts.requestTimeoutSeconds
131
- : 30;
189
+ this.requestTimeoutSeconds =
190
+ typeof opts.requestTimeoutSeconds === "number"
191
+ ? opts.requestTimeoutSeconds
192
+ : 30;
132
193
  this.debug = !!opts.debug;
133
194
  const headers = { ...(opts.headers ?? {}) };
134
195
  // Attach API key via header unless the user already provided one.
@@ -136,36 +197,82 @@ export class ConnectionConfig {
136
197
  headers["OPEN-SANDBOX-API-KEY"] = this.apiKey;
137
198
  }
138
199
  // Best-effort user-agent (Node only).
139
- if (isNodeRuntime() && this.userAgent && !headers["user-agent"] && !headers["User-Agent"]) {
200
+ if (isNodeRuntime() &&
201
+ this.userAgent &&
202
+ !headers["user-agent"] &&
203
+ !headers["User-Agent"]) {
140
204
  headers["user-agent"] = this.userAgent;
141
205
  }
142
206
  this.headers = headers;
143
- // Node SDK: do not expose custom fetch in ConnectionConfigOptions.
144
- // Use the runtime's global fetch (Node >= 20).
145
- const baseFetch = fetch;
146
- const baseSseFetch = fetch;
147
- // Normal HTTP calls: apply requestTimeoutSeconds.
148
- this.fetch = createTimedFetch({
207
+ this._fetch = null;
208
+ this._sseFetch = null;
209
+ this._closeTransport = async () => { };
210
+ this._transportInitialized = false;
211
+ }
212
+ get fetch() {
213
+ return this._fetch ?? fetch;
214
+ }
215
+ get sseFetch() {
216
+ return this._sseFetch ?? fetch;
217
+ }
218
+ getBaseUrl() {
219
+ // If `domain` already contains a scheme, treat it as a full base URL prefix.
220
+ if (this.domain.startsWith("http://") ||
221
+ this.domain.startsWith("https://")) {
222
+ return `${stripV1Suffix(this.domain)}/v1`;
223
+ }
224
+ return `${this.protocol}://${stripV1Suffix(this.domain)}/v1`;
225
+ }
226
+ initializeTransport() {
227
+ if (this._transportInitialized)
228
+ return;
229
+ const { fetch: baseFetch, close } = createNodeFetch();
230
+ this._fetch = createTimedFetch({
149
231
  baseFetch,
150
232
  timeoutSeconds: this.requestTimeoutSeconds,
151
233
  debug: this.debug,
152
234
  defaultHeaders: this.headers,
153
235
  label: "http",
154
236
  });
155
- // Streaming calls (SSE): do not apply SDK-side timeouts; do not proactively disconnect.
156
- this.sseFetch = createTimedFetch({
157
- baseFetch: baseSseFetch,
237
+ this._sseFetch = createTimedFetch({
238
+ baseFetch,
158
239
  timeoutSeconds: 0,
159
240
  debug: this.debug,
160
241
  defaultHeaders: this.headers,
161
242
  label: "sse",
162
243
  });
244
+ this._closeTransport = close;
245
+ this._transportInitialized = true;
163
246
  }
164
- getBaseUrl() {
165
- // If `domain` already contains a scheme, treat it as a full base URL prefix.
166
- if (this.domain.startsWith("http://") || this.domain.startsWith("https://")) {
167
- return `${stripV1Suffix(this.domain)}/v1`;
247
+ /**
248
+ * Ensure this configuration has transport helpers (fetch/SSE) allocated.
249
+ *
250
+ * On Node.js this creates a dedicated `undici` dispatcher; on browsers it
251
+ * simply reuses the global fetch. Returns either `this` or a cloned config
252
+ * with the transport initialized.
253
+ */
254
+ withTransportIfMissing() {
255
+ if (this._transportInitialized) {
256
+ return this;
168
257
  }
169
- return `${this.protocol}://${stripV1Suffix(this.domain)}/v1`;
258
+ const clone = new ConnectionConfig({
259
+ domain: this.domain,
260
+ protocol: this.protocol,
261
+ apiKey: this.apiKey,
262
+ headers: { ...this.headers },
263
+ requestTimeoutSeconds: this.requestTimeoutSeconds,
264
+ debug: this.debug,
265
+ });
266
+ clone.initializeTransport();
267
+ return clone;
268
+ }
269
+ /**
270
+ * Close the Node.js agent owned by this configuration.
271
+ */
272
+ async closeTransport() {
273
+ if (!this._transportInitialized)
274
+ return;
275
+ this._closePromise ??= this._closeTransport();
276
+ await this._closePromise;
170
277
  }
171
278
  }
@@ -1 +1 @@
1
- {"version":3,"file":"defaultAdapterFactory.d.ts","sourceRoot":"","sources":["../../src/factory/defaultAdapterFactory.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE5I,qBAAa,qBAAsB,YAAW,cAAc;IAC1D,oBAAoB,CAAC,IAAI,EAAE,2BAA2B,GAAG,cAAc;IAWvE,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,UAAU;CA4B5D;AAED,wBAAgB,2BAA2B,IAAI,cAAc,CAE5D"}
1
+ {"version":3,"file":"defaultAdapterFactory.d.ts","sourceRoot":"","sources":["../../src/factory/defaultAdapterFactory.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE5I,qBAAa,qBAAsB,YAAW,cAAc;IAC1D,oBAAoB,CAAC,IAAI,EAAE,2BAA2B,GAAG,cAAc;IAWvE,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,UAAU;CA2B5D;AAED,wBAAgB,2BAA2B,IAAI,cAAc,CAE5D"}
@@ -44,7 +44,6 @@ export class DefaultAdapterFactory {
44
44
  });
45
45
  const commands = new CommandsAdapter(execdClient, {
46
46
  baseUrl: opts.execdBaseUrl,
47
- // Streaming calls (SSE) use a dedicated fetch, aligned with Kotlin/Python SDKs.
48
47
  fetch: opts.connectionConfig.sseFetch,
49
48
  headers: opts.connectionConfig.headers,
50
49
  });
package/dist/manager.d.ts CHANGED
@@ -18,6 +18,7 @@ export interface SandboxFilter {
18
18
  */
19
19
  export declare class SandboxManager {
20
20
  private readonly sandboxes;
21
+ private readonly connectionConfig;
21
22
  private constructor();
22
23
  static create(opts?: SandboxManagerOptions): SandboxManager;
23
24
  listSandboxInfos(filter?: SandboxFilter): Promise<ListSandboxesResponse>;
@@ -30,10 +31,11 @@ export declare class SandboxManager {
30
31
  */
31
32
  renewSandbox(sandboxId: SandboxId, timeoutSeconds: number): Promise<void>;
32
33
  /**
33
- * No-op for now (fetch-based implementation doesn't own a pooled transport).
34
+ * Release the HTTP agent resources allocated for this manager instance.
34
35
  *
35
- * This method exists so callers can consistently release resources when using
36
- * a custom {@link AdapterFactory} implementation.
36
+ * Each manager clone owns a scoped `ConnectionConfig` clone.
37
+ *
38
+ * This mirrors the Python SDK's default transport lifecycle.
37
39
  */
38
40
  close(): Promise<void>;
39
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAExF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG3F,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,uBAAuB,CAAC;IAC9D,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IAEtC,OAAO;IAIP,MAAM,CAAC,MAAM,CAAC,IAAI,GAAE,qBAA0B,GAAG,cAAc;IAU/D,gBAAgB,CAAC,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAS5E,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAI1D,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/E;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAExF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG3F,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,uBAAuB,CAAC;IAC9D,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IAEpD,OAAO;IAKP,MAAM,CAAC,MAAM,CAAC,IAAI,GAAE,qBAA0B,GAAG,cAAc;IAoB/D,gBAAgB,CAAC,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAS5E,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAI1D,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/E;;;;;;OAMG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
package/dist/manager.js CHANGED
@@ -20,17 +20,30 @@ import { createDefaultAdapterFactory } from "./factory/defaultAdapterFactory.js"
20
20
  */
21
21
  export class SandboxManager {
22
22
  sandboxes;
23
+ connectionConfig;
23
24
  constructor(opts) {
24
25
  this.sandboxes = opts.sandboxes;
26
+ this.connectionConfig = opts.connectionConfig;
25
27
  }
26
28
  static create(opts = {}) {
27
- const connectionConfig = opts.connectionConfig instanceof ConnectionConfig
29
+ const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig
28
30
  ? opts.connectionConfig
29
31
  : new ConnectionConfig(opts.connectionConfig);
32
+ const connectionConfig = baseConnectionConfig.withTransportIfMissing();
30
33
  const lifecycleBaseUrl = connectionConfig.getBaseUrl();
31
34
  const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
32
- const { sandboxes } = adapterFactory.createLifecycleStack({ connectionConfig, lifecycleBaseUrl });
33
- return new SandboxManager({ sandboxes });
35
+ let sandboxes;
36
+ try {
37
+ sandboxes = adapterFactory.createLifecycleStack({
38
+ connectionConfig,
39
+ lifecycleBaseUrl,
40
+ }).sandboxes;
41
+ }
42
+ catch (err) {
43
+ void connectionConfig.closeTransport().catch(() => undefined);
44
+ throw err;
45
+ }
46
+ return new SandboxManager({ sandboxes, connectionConfig });
34
47
  }
35
48
  listSandboxInfos(filter = {}) {
36
49
  return this.sandboxes.listSandboxes({
@@ -60,12 +73,13 @@ export class SandboxManager {
60
73
  await this.sandboxes.renewSandboxExpiration(sandboxId, { expiresAt });
61
74
  }
62
75
  /**
63
- * No-op for now (fetch-based implementation doesn't own a pooled transport).
76
+ * Release the HTTP agent resources allocated for this manager instance.
64
77
  *
65
- * This method exists so callers can consistently release resources when using
66
- * a custom {@link AdapterFactory} implementation.
78
+ * Each manager clone owns a scoped `ConnectionConfig` clone.
79
+ *
80
+ * This mirrors the Python SDK's default transport lifecycle.
67
81
  */
68
82
  async close() {
69
- // no-op
83
+ await this.connectionConfig.closeTransport();
70
84
  }
71
85
  }
package/dist/sandbox.d.ts CHANGED
@@ -111,6 +111,10 @@ export declare class Sandbox {
111
111
  */
112
112
  static resume(opts: SandboxConnectOptions): Promise<Sandbox>;
113
113
  kill(): Promise<void>;
114
+ /**
115
+ * Release any client-side resources (e.g. Node.js HTTP agents) owned by this Sandbox instance.
116
+ */
117
+ close(): Promise<void>;
114
118
  /**
115
119
  * Renew expiration by setting expiresAt to now + timeoutSeconds.
116
120
  */
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
package/dist/sandbox.js CHANGED
@@ -61,15 +61,23 @@ export class Sandbox {
61
61
  this.metrics = opts.metrics;
62
62
  }
63
63
  static async create(opts) {
64
- const connectionConfig = opts.connectionConfig instanceof ConnectionConfig
64
+ const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig
65
65
  ? opts.connectionConfig
66
66
  : new ConnectionConfig(opts.connectionConfig);
67
+ const connectionConfig = baseConnectionConfig.withTransportIfMissing();
67
68
  const lifecycleBaseUrl = connectionConfig.getBaseUrl();
68
69
  const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
69
- const { sandboxes } = adapterFactory.createLifecycleStack({
70
- connectionConfig,
71
- lifecycleBaseUrl,
72
- });
70
+ let sandboxes;
71
+ try {
72
+ sandboxes = adapterFactory.createLifecycleStack({
73
+ connectionConfig,
74
+ lifecycleBaseUrl,
75
+ }).sandboxes;
76
+ }
77
+ catch (err) {
78
+ await connectionConfig.closeTransport();
79
+ throw err;
80
+ }
73
81
  const req = {
74
82
  image: toImageSpec(opts.image),
75
83
  entrypoint: opts.entrypoint ?? DEFAULT_ENTRYPOINT,
@@ -120,46 +128,61 @@ export class Sandbox {
120
128
  // Ignore cleanup failure; surface original error.
121
129
  }
122
130
  }
131
+ await connectionConfig.closeTransport();
123
132
  throw err;
124
133
  }
125
134
  }
126
135
  static async connect(opts) {
127
- const connectionConfig = opts.connectionConfig instanceof ConnectionConfig
136
+ const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig
128
137
  ? opts.connectionConfig
129
138
  : new ConnectionConfig(opts.connectionConfig);
139
+ const connectionConfig = baseConnectionConfig.withTransportIfMissing();
130
140
  const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
131
141
  const lifecycleBaseUrl = connectionConfig.getBaseUrl();
132
- const { sandboxes } = adapterFactory.createLifecycleStack({
133
- connectionConfig,
134
- lifecycleBaseUrl,
135
- });
136
- const endpoint = await sandboxes.getSandboxEndpoint(opts.sandboxId, DEFAULT_EXECD_PORT);
137
- const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
138
- const { commands, files, health, metrics } = adapterFactory.createExecdStack({
139
- connectionConfig,
140
- execdBaseUrl,
141
- });
142
- const sbx = new Sandbox({
143
- id: opts.sandboxId,
144
- connectionConfig,
145
- adapterFactory,
146
- lifecycleBaseUrl,
147
- execdBaseUrl,
148
- sandboxes,
149
- commands,
150
- files,
151
- health,
152
- metrics,
153
- });
154
- if (!(opts.skipHealthCheck ?? false)) {
155
- await sbx.waitUntilReady({
156
- readyTimeoutSeconds: opts.readyTimeoutSeconds ?? DEFAULT_READY_TIMEOUT_SECONDS,
157
- pollingIntervalMillis: opts.healthCheckPollingInterval ??
158
- DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
159
- healthCheck: opts.healthCheck,
142
+ let sandboxes;
143
+ try {
144
+ sandboxes = adapterFactory.createLifecycleStack({
145
+ connectionConfig,
146
+ lifecycleBaseUrl,
147
+ }).sandboxes;
148
+ }
149
+ catch (err) {
150
+ await connectionConfig.closeTransport();
151
+ throw err;
152
+ }
153
+ try {
154
+ const endpoint = await sandboxes.getSandboxEndpoint(opts.sandboxId, DEFAULT_EXECD_PORT);
155
+ const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
156
+ const { commands, files, health, metrics } = adapterFactory.createExecdStack({
157
+ connectionConfig,
158
+ execdBaseUrl,
160
159
  });
160
+ const sbx = new Sandbox({
161
+ id: opts.sandboxId,
162
+ connectionConfig,
163
+ adapterFactory,
164
+ lifecycleBaseUrl,
165
+ execdBaseUrl,
166
+ sandboxes,
167
+ commands,
168
+ files,
169
+ health,
170
+ metrics,
171
+ });
172
+ if (!(opts.skipHealthCheck ?? false)) {
173
+ await sbx.waitUntilReady({
174
+ readyTimeoutSeconds: opts.readyTimeoutSeconds ?? DEFAULT_READY_TIMEOUT_SECONDS,
175
+ pollingIntervalMillis: opts.healthCheckPollingInterval ??
176
+ DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
177
+ healthCheck: opts.healthCheck,
178
+ });
179
+ }
180
+ return sbx;
181
+ }
182
+ catch (err) {
183
+ await connectionConfig.closeTransport();
184
+ throw err;
161
185
  }
162
- return sbx;
163
186
  }
164
187
  async getInfo() {
165
188
  return await this.sandboxes.getSandbox(this.id);
@@ -199,21 +222,36 @@ export class Sandbox {
199
222
  * Resume a paused sandbox by id, then connect to its execd endpoint.
200
223
  */
201
224
  static async resume(opts) {
202
- const connectionConfig = opts.connectionConfig instanceof ConnectionConfig
225
+ const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig
203
226
  ? opts.connectionConfig
204
227
  : new ConnectionConfig(opts.connectionConfig);
205
228
  const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
206
- const lifecycleBaseUrl = connectionConfig.getBaseUrl();
207
- const { sandboxes } = adapterFactory.createLifecycleStack({
208
- connectionConfig,
209
- lifecycleBaseUrl,
210
- });
211
- await sandboxes.resumeSandbox(opts.sandboxId);
212
- return await Sandbox.connect({ ...opts, connectionConfig, adapterFactory });
229
+ const resumeConnectionConfig = baseConnectionConfig.withTransportIfMissing();
230
+ const lifecycleBaseUrl = resumeConnectionConfig.getBaseUrl();
231
+ let sandboxes;
232
+ try {
233
+ sandboxes = adapterFactory.createLifecycleStack({
234
+ connectionConfig: resumeConnectionConfig,
235
+ lifecycleBaseUrl,
236
+ }).sandboxes;
237
+ await sandboxes.resumeSandbox(opts.sandboxId);
238
+ }
239
+ catch (err) {
240
+ await resumeConnectionConfig.closeTransport();
241
+ throw err;
242
+ }
243
+ await resumeConnectionConfig.closeTransport();
244
+ return await Sandbox.connect({ ...opts, connectionConfig: baseConnectionConfig, adapterFactory });
213
245
  }
214
246
  async kill() {
215
247
  await this.sandboxes.deleteSandbox(this.id);
216
248
  }
249
+ /**
250
+ * Release any client-side resources (e.g. Node.js HTTP agents) owned by this Sandbox instance.
251
+ */
252
+ async close() {
253
+ await this.connectionConfig.closeTransport();
254
+ }
217
255
  /**
218
256
  * Renew expiration by setting expiresAt to now + timeoutSeconds.
219
257
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alibaba-group/opensandbox",
3
- "version": "0.1.0-dev2",
3
+ "version": "0.1.0-dev4",
4
4
  "description": "OpenSandbox TypeScript/JavaScript SDK (sandbox lifecycle + execd APIs)",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -33,7 +33,8 @@
33
33
  "node": ">=20"
34
34
  },
35
35
  "dependencies": {
36
- "openapi-fetch": "^0.13.8"
36
+ "openapi-fetch": "^0.13.8",
37
+ "undici": "^7.18.2"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@eslint/js": "^9.39.2",