@alibaba-group/opensandbox 0.1.4 → 0.1.6

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/src/api/execd.ts CHANGED
@@ -17,6 +17,13 @@
17
17
  * Do not make direct changes to the file.
18
18
  */
19
19
 
20
+
21
+ /**
22
+ * NOTE: The session-related path types and operations in this file (e.g. /session, runInSession)
23
+ * are generated from the execd OpenAPI spec. They are not the recommended runtime entry point.
24
+ * Use `sandbox.commands.createSession()`, `sandbox.commands.runInSession()`, and
25
+ * `sandbox.commands.deleteSession()` instead.
26
+ */
20
27
  export interface paths {
21
28
  "/ping": {
22
29
  parameters: {
@@ -143,6 +150,71 @@ export interface paths {
143
150
  patch?: never;
144
151
  trace?: never;
145
152
  };
153
+ "/session": {
154
+ parameters: {
155
+ query?: never;
156
+ header?: never;
157
+ path?: never;
158
+ cookie?: never;
159
+ };
160
+ get?: never;
161
+ put?: never;
162
+ /**
163
+ * Create bash session (create_session)
164
+ * @description Creates a new bash session and returns a session ID for subsequent run_in_session requests.
165
+ * The session maintains shell state (e.g. working directory, environment) across multiple
166
+ * code executions. Request body is optional; an empty body uses default options (no cwd override).
167
+ */
168
+ post: operations["createSession"];
169
+ delete?: never;
170
+ options?: never;
171
+ head?: never;
172
+ patch?: never;
173
+ trace?: never;
174
+ };
175
+ "/session/{sessionId}/run": {
176
+ parameters: {
177
+ query?: never;
178
+ header?: never;
179
+ path?: never;
180
+ cookie?: never;
181
+ };
182
+ get?: never;
183
+ put?: never;
184
+ /**
185
+ * Run command in bash session (run_in_session)
186
+ * @description Executes a shell command in an existing bash session and streams the output in real-time via SSE
187
+ * (Server-Sent Events). The session must have been created by create_session. Supports
188
+ * optional working directory override and timeout (milliseconds).
189
+ */
190
+ post: operations["runInSession"];
191
+ delete?: never;
192
+ options?: never;
193
+ head?: never;
194
+ patch?: never;
195
+ trace?: never;
196
+ };
197
+ "/session/{sessionId}": {
198
+ parameters: {
199
+ query?: never;
200
+ header?: never;
201
+ path?: never;
202
+ cookie?: never;
203
+ };
204
+ get?: never;
205
+ put?: never;
206
+ post?: never;
207
+ /**
208
+ * Delete bash session (delete_session)
209
+ * @description Deletes an existing bash session by ID. Terminates the underlying shell process
210
+ * and releases resources. The session ID must have been returned by create_session.
211
+ */
212
+ delete: operations["deleteSession"];
213
+ options?: never;
214
+ head?: never;
215
+ patch?: never;
216
+ trace?: never;
217
+ };
146
218
  "/command": {
147
219
  parameters: {
148
220
  query?: never;
@@ -158,7 +230,8 @@ export interface paths {
158
230
  * The command can run in foreground or background mode. The response includes stdout, stderr,
159
231
  * execution status, and completion events.
160
232
  * Optionally specify `timeout` (milliseconds) to enforce a maximum runtime; the server will
161
- * terminate the process when the timeout is reached.
233
+ * terminate the process when the timeout is reached. You can also pass `uid`/`gid` to run
234
+ * with specific user/group IDs, and `envs` to inject environment variables.
162
235
  */
163
236
  post: operations["runCommand"];
164
237
  /**
@@ -471,6 +544,41 @@ export interface paths {
471
544
  export type webhooks = Record<string, never>;
472
545
  export interface components {
473
546
  schemas: {
547
+ /** @description Request to create a bash session (optional body; empty treated as defaults) */
548
+ CreateSessionRequest: {
549
+ /**
550
+ * @description Working directory for the session (optional)
551
+ * @example /workspace
552
+ */
553
+ cwd?: string;
554
+ };
555
+ /** @description Response for create_session */
556
+ CreateSessionResponse: {
557
+ /**
558
+ * @description Unique session ID for run_in_session and delete_session
559
+ * @example session-abc123
560
+ */
561
+ session_id: string;
562
+ };
563
+ /** @description Request to run a command in an existing bash session */
564
+ RunInSessionRequest: {
565
+ /**
566
+ * @description Shell command to execute in the session
567
+ * @example echo "Hello"
568
+ */
569
+ command: string;
570
+ /**
571
+ * @description Working directory override for this run (optional)
572
+ * @example /workspace
573
+ */
574
+ cwd?: string;
575
+ /**
576
+ * Format: int64
577
+ * @description Maximum execution time in milliseconds (optional; server may not enforce if omitted)
578
+ * @example 30000
579
+ */
580
+ timeout?: number;
581
+ };
474
582
  /** @description Request to create a code execution context */
475
583
  CodeContextRequest: {
476
584
  /**
@@ -527,6 +635,28 @@ export interface components {
527
635
  * @example 60000
528
636
  */
529
637
  timeout?: number;
638
+ /**
639
+ * Format: int32
640
+ * @description Unix user ID used to run the command. If `gid` is provided, `uid` is required.
641
+ * @example 1000
642
+ */
643
+ uid?: number;
644
+ /**
645
+ * Format: int32
646
+ * @description Unix group ID used to run the command. Requires `uid` to be provided.
647
+ * @example 1000
648
+ */
649
+ gid?: number;
650
+ /**
651
+ * @description Environment variables injected into the command process.
652
+ * @example {
653
+ * "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
654
+ * "PYTHONUNBUFFERED": "1"
655
+ * }
656
+ */
657
+ envs?: {
658
+ [key: string]: string;
659
+ };
530
660
  };
531
661
  /** @description Command execution status (foreground or background) */
532
662
  CommandStatusResponse: {
@@ -1046,6 +1176,91 @@ export interface operations {
1046
1176
  500: components["responses"]["InternalServerError"];
1047
1177
  };
1048
1178
  };
1179
+ createSession: {
1180
+ parameters: {
1181
+ query?: never;
1182
+ header?: never;
1183
+ path?: never;
1184
+ cookie?: never;
1185
+ };
1186
+ requestBody?: {
1187
+ content: {
1188
+ "application/json": components["schemas"]["CreateSessionRequest"];
1189
+ };
1190
+ };
1191
+ responses: {
1192
+ /** @description Session created successfully */
1193
+ 200: {
1194
+ headers: {
1195
+ [name: string]: unknown;
1196
+ };
1197
+ content: {
1198
+ "application/json": components["schemas"]["CreateSessionResponse"];
1199
+ };
1200
+ };
1201
+ 400: components["responses"]["BadRequest"];
1202
+ 500: components["responses"]["InternalServerError"];
1203
+ };
1204
+ };
1205
+ runInSession: {
1206
+ parameters: {
1207
+ query?: never;
1208
+ header?: never;
1209
+ path: {
1210
+ /**
1211
+ * @description Session ID returned by create_session
1212
+ * @example session-abc123
1213
+ */
1214
+ sessionId: string;
1215
+ };
1216
+ cookie?: never;
1217
+ };
1218
+ requestBody: {
1219
+ content: {
1220
+ "application/json": components["schemas"]["RunInSessionRequest"];
1221
+ };
1222
+ };
1223
+ responses: {
1224
+ /** @description Stream of execution events */
1225
+ 200: {
1226
+ headers: {
1227
+ [name: string]: unknown;
1228
+ };
1229
+ content: {
1230
+ "text/event-stream": components["schemas"]["ServerStreamEvent"];
1231
+ };
1232
+ };
1233
+ 400: components["responses"]["BadRequest"];
1234
+ 500: components["responses"]["InternalServerError"];
1235
+ };
1236
+ };
1237
+ deleteSession: {
1238
+ parameters: {
1239
+ query?: never;
1240
+ header?: never;
1241
+ path: {
1242
+ /**
1243
+ * @description Session ID to delete
1244
+ * @example session-abc123
1245
+ */
1246
+ sessionId: string;
1247
+ };
1248
+ cookie?: never;
1249
+ };
1250
+ requestBody?: never;
1251
+ responses: {
1252
+ /** @description Session deleted successfully */
1253
+ 200: {
1254
+ headers: {
1255
+ [name: string]: unknown;
1256
+ };
1257
+ content?: never;
1258
+ };
1259
+ 400: components["responses"]["BadRequest"];
1260
+ 404: components["responses"]["NotFound"];
1261
+ 500: components["responses"]["InternalServerError"];
1262
+ };
1263
+ };
1049
1264
  runCommand: {
1050
1265
  parameters: {
1051
1266
  query?: never;
@@ -470,9 +470,9 @@ export interface components {
470
470
  };
471
471
  /**
472
472
  * Format: date-time
473
- * @description Timestamp when sandbox will auto-terminate
473
+ * @description Timestamp when sandbox will auto-terminate. Omitted when manual cleanup is enabled.
474
474
  */
475
- expiresAt: string;
475
+ expiresAt?: string;
476
476
  /**
477
477
  * Format: date-time
478
478
  * @description Sandbox creation timestamp
@@ -503,9 +503,9 @@ export interface components {
503
503
  entrypoint: string[];
504
504
  /**
505
505
  * Format: date-time
506
- * @description Timestamp when sandbox will auto-terminate
506
+ * @description Timestamp when sandbox will auto-terminate. Omitted when manual cleanup is enabled.
507
507
  */
508
- expiresAt: string;
508
+ expiresAt?: string;
509
509
  /**
510
510
  * Format: date-time
511
511
  * @description Sandbox creation timestamp
@@ -588,9 +588,12 @@ export interface components {
588
588
  image: components["schemas"]["ImageSpec"];
589
589
  /**
590
590
  * @description Sandbox timeout in seconds. The sandbox will automatically terminate after this duration.
591
- * SDK clients should provide a default value (e.g., 3600 seconds / 1 hour).
591
+ * The maximum is controlled by the server configuration (`server.max_sandbox_timeout_seconds`).
592
+ * Omit this field or set it to null to disable automatic expiration and require explicit cleanup.
593
+ * Note: manual cleanup support is runtime-dependent; Kubernetes providers may reject
594
+ * omitted or null timeout when the underlying workload provider does not support non-expiring sandboxes.
592
595
  */
593
- timeout: number;
596
+ timeout?: number | null;
594
597
  /**
595
598
  * @description Runtime resource constraints for the sandbox instance.
596
599
  * SDK clients should provide sensible defaults (e.g., cpu: "500m", memory: "512Mi").
@@ -659,6 +662,9 @@ export interface components {
659
662
  * **Best Practices**:
660
663
  * - **Namespacing**: Use prefixed keys (e.g., `storage.id`) to prevent collisions.
661
664
  * - **Pass-through**: SDKs and middleware must treat this object as opaque and pass it through transparently.
665
+ *
666
+ * **Well-known keys**:
667
+ * - `access.renew.extend.seconds` (optional): Decimal integer string from **300** to **86400** (5 minutes to 24 hours inclusive). Opts the sandbox into OSEP-0009 renew-on-access and sets per-renewal extension seconds. Omit to disable. Invalid values are rejected at creation with HTTP 400 (validated on the lifecycle create endpoint via `validate_extensions` in server `src/extensions/validation.py`).
662
668
  */
663
669
  extensions?: {
664
670
  [key: string]: string;
@@ -758,7 +764,7 @@ export interface components {
758
764
  /**
759
765
  * @description Storage mount definition for a sandbox. Each volume entry contains:
760
766
  * - A unique name identifier
761
- * - Exactly one backend struct (host, pvc, etc.) with backend-specific fields
767
+ * - Exactly one backend struct (host, pvc, ossfs, etc.) with backend-specific fields
762
768
  * - Common mount settings (mountPath, readOnly, subPath)
763
769
  */
764
770
  Volume: {
@@ -769,6 +775,7 @@ export interface components {
769
775
  name: string;
770
776
  host?: components["schemas"]["Host"];
771
777
  pvc?: components["schemas"]["PVC"];
778
+ ossfs?: components["schemas"]["OSSFS"];
772
779
  /**
773
780
  * @description Absolute path inside the container where the volume is mounted.
774
781
  * Must start with '/'.
@@ -781,6 +788,7 @@ export interface components {
781
788
  readOnly: boolean;
782
789
  /**
783
790
  * @description Optional subdirectory under the backend path to mount.
791
+ * For `ossfs` backend, this field is used as the bucket prefix.
784
792
  * Must be a relative path without '..' components.
785
793
  */
786
794
  subPath?: string;
@@ -795,23 +803,61 @@ export interface components {
795
803
  Host: {
796
804
  /**
797
805
  * @description Absolute path on the host filesystem to mount.
798
- * Must start with '/' and be under an allowed prefix.
806
+ * Must start with '/' (Unix) or a drive letter such as 'C:\' or 'D:/'
807
+ * (Windows), and be under an allowed prefix.
799
808
  */
800
809
  path: string;
801
810
  };
802
811
  /**
803
- * @description Kubernetes PersistentVolumeClaim mount backend. References an existing
804
- * PVC in the same namespace as the sandbox pod.
812
+ * @description Platform-managed named volume backend. A runtime-neutral abstraction
813
+ * for referencing a pre-existing, platform-managed named volume.
814
+ *
815
+ * - Kubernetes: maps to a PersistentVolumeClaim in the same namespace.
816
+ * - Docker: maps to a Docker named volume (created via `docker volume create`).
805
817
  *
806
- * Only available in Kubernetes runtime.
818
+ * The volume must already exist on the target platform before sandbox
819
+ * creation.
807
820
  */
808
821
  PVC: {
809
822
  /**
810
- * @description Name of the PersistentVolumeClaim in the same namespace.
811
- * Must be a valid Kubernetes resource name.
823
+ * @description Name of the volume on the target platform.
824
+ * In Kubernetes this is the PVC name; in Docker this is the named
825
+ * volume name. Must be a valid DNS label.
812
826
  */
813
827
  claimName: string;
814
828
  };
829
+ /**
830
+ * @description Alibaba Cloud OSS mount backend via ossfs.
831
+ *
832
+ * The runtime mounts a host-side OSS path under `storage.ossfs_mount_root`
833
+ * and bind-mounts the resolved path into the sandbox container.
834
+ * Prefix selection is expressed via `Volume.subPath`.
835
+ * In Docker runtime, OSSFS backend requires OpenSandbox Server to run on a Linux host with FUSE support.
836
+ */
837
+ OSSFS: {
838
+ /** @description OSS bucket name. */
839
+ bucket: string;
840
+ /** @description OSS endpoint (e.g., `oss-cn-hangzhou.aliyuncs.com`). */
841
+ endpoint: string;
842
+ /**
843
+ * @description ossfs major version used by runtime mount integration.
844
+ * @default 2.0
845
+ * @enum {string}
846
+ */
847
+ version: "1.0" | "2.0";
848
+ /**
849
+ * @description Additional ossfs mount options.
850
+ * Runtime encodes options by `version`:
851
+ * - `1.0`: mounts with `ossfs ... -o <option>`
852
+ * - `2.0`: mounts with `ossfs2 mount ... -c <config-file>` and encodes options as `--<option>` lines in the config file
853
+ * Option values must be provided as raw payloads without leading `-`.
854
+ */
855
+ options?: string[];
856
+ /** @description OSS access key ID for inline credentials mode. */
857
+ accessKeyId: string;
858
+ /** @description OSS access key secret for inline credentials mode. */
859
+ accessKeySecret: string;
860
+ };
815
861
  };
816
862
  responses: {
817
863
  /** @description Error response envelope */
@@ -13,6 +13,7 @@
13
13
  // limitations under the License.
14
14
 
15
15
  export const DEFAULT_EXECD_PORT = 44772;
16
+ export const DEFAULT_EGRESS_PORT = 18080;
16
17
 
17
18
  export const DEFAULT_ENTRYPOINT: string[] = ["tail", "-f", "/dev/null"];
18
19
 
@@ -26,4 +27,4 @@ export const DEFAULT_READY_TIMEOUT_SECONDS = 30;
26
27
  export const DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS = 200;
27
28
 
28
29
  export const DEFAULT_REQUEST_TIMEOUT_SECONDS = 30;
29
- export const DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.4";
30
+ export const DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.6";
@@ -44,6 +44,7 @@ interface SandboxExceptionOpts {
44
44
  message?: string;
45
45
  cause?: unknown;
46
46
  error?: SandboxError;
47
+ requestId?: string;
47
48
  }
48
49
 
49
50
  /**
@@ -55,32 +56,32 @@ export class SandboxException extends Error {
55
56
  readonly name: string = "SandboxException";
56
57
  readonly error: SandboxError;
57
58
  readonly cause?: unknown;
59
+ readonly requestId?: string;
58
60
 
59
61
  constructor(opts: SandboxExceptionOpts = {}) {
60
62
  super(opts.message);
61
63
  this.cause = opts.cause;
62
64
  this.error = opts.error ?? new SandboxError(SandboxError.INTERNAL_UNKNOWN_ERROR);
65
+ this.requestId = opts.requestId;
63
66
  }
64
67
  }
65
68
 
66
69
  export class SandboxApiException extends SandboxException {
67
70
  readonly name: string = "SandboxApiException";
68
71
  readonly statusCode?: number;
69
- readonly requestId?: string;
70
72
  readonly rawBody?: unknown;
71
73
 
72
74
  constructor(opts: SandboxExceptionOpts & {
73
75
  statusCode?: number;
74
- requestId?: string;
75
76
  rawBody?: unknown;
76
77
  }) {
77
78
  super({
78
79
  message: opts.message,
79
80
  cause: opts.cause,
80
81
  error: opts.error ?? new SandboxError(SandboxError.UNEXPECTED_RESPONSE, opts.message),
82
+ requestId: opts.requestId,
81
83
  });
82
84
  this.statusCode = opts.statusCode;
83
- this.requestId = opts.requestId;
84
85
  this.rawBody = opts.rawBody;
85
86
  }
86
87
  }
@@ -131,4 +132,4 @@ export class InvalidArgumentException extends SandboxException {
131
132
  error: new SandboxError(SandboxError.INVALID_ARGUMENT, opts.message),
132
133
  });
133
134
  }
134
- }
135
+ }
@@ -14,6 +14,7 @@
14
14
 
15
15
  import type { ConnectionConfig } from "../config/connection.js";
16
16
  import type { SandboxFiles } from "../services/filesystem.js";
17
+ import type { Egress } from "../services/egress.js";
17
18
  import type { ExecdCommands } from "../services/execdCommands.js";
18
19
  import type { ExecdHealth } from "../services/execdHealth.js";
19
20
  import type { ExecdMetrics } from "../services/execdMetrics.js";
@@ -41,6 +42,16 @@ export interface ExecdStack {
41
42
  metrics: ExecdMetrics;
42
43
  }
43
44
 
45
+ export interface CreateEgressStackOptions {
46
+ connectionConfig: ConnectionConfig;
47
+ egressBaseUrl: string;
48
+ endpointHeaders?: Record<string, string>;
49
+ }
50
+
51
+ export interface EgressStack {
52
+ egress: Egress;
53
+ }
54
+
44
55
  /**
45
56
  * Factory abstraction to keep `Sandbox` and `SandboxManager` decoupled from concrete adapter implementations.
46
57
  *
@@ -49,4 +60,5 @@ export interface ExecdStack {
49
60
  export interface AdapterFactory {
50
61
  createLifecycleStack(opts: CreateLifecycleStackOptions): LifecycleStack;
51
62
  createExecdStack(opts: CreateExecdStackOptions): ExecdStack;
52
- }
63
+ createEgressStack(opts: CreateEgressStackOptions): EgressStack;
64
+ }
@@ -13,15 +13,25 @@
13
13
  // limitations under the License.
14
14
 
15
15
  import { createExecdClient } from "../openapi/execdClient.js";
16
+ import { createEgressClient } from "../openapi/egressClient.js";
16
17
  import { createLifecycleClient } from "../openapi/lifecycleClient.js";
17
18
 
18
19
  import { CommandsAdapter } from "../adapters/commandsAdapter.js";
20
+ import { EgressAdapter } from "../adapters/egressAdapter.js";
19
21
  import { FilesystemAdapter } from "../adapters/filesystemAdapter.js";
20
22
  import { HealthAdapter } from "../adapters/healthAdapter.js";
21
23
  import { MetricsAdapter } from "../adapters/metricsAdapter.js";
22
24
  import { SandboxesAdapter } from "../adapters/sandboxesAdapter.js";
23
25
 
24
- import type { AdapterFactory, CreateExecdStackOptions, CreateLifecycleStackOptions, ExecdStack, LifecycleStack } from "./adapterFactory.js";
26
+ import type {
27
+ AdapterFactory,
28
+ CreateEgressStackOptions,
29
+ CreateExecdStackOptions,
30
+ CreateLifecycleStackOptions,
31
+ EgressStack,
32
+ ExecdStack,
33
+ LifecycleStack,
34
+ } from "./adapterFactory.js";
25
35
 
26
36
  export class DefaultAdapterFactory implements AdapterFactory {
27
37
  createLifecycleStack(opts: CreateLifecycleStackOptions): LifecycleStack {
@@ -66,8 +76,23 @@ export class DefaultAdapterFactory implements AdapterFactory {
66
76
  metrics,
67
77
  };
68
78
  }
79
+
80
+ createEgressStack(opts: CreateEgressStackOptions): EgressStack {
81
+ const headers: Record<string, string> = {
82
+ ...(opts.connectionConfig.headers ?? {}),
83
+ ...(opts.endpointHeaders ?? {}),
84
+ };
85
+ const egressClient = createEgressClient({
86
+ baseUrl: opts.egressBaseUrl,
87
+ headers,
88
+ fetch: opts.connectionConfig.fetch,
89
+ });
90
+ return {
91
+ egress: new EgressAdapter(egressClient),
92
+ };
93
+ }
69
94
  }
70
95
 
71
96
  export function createDefaultAdapterFactory(): AdapterFactory {
72
97
  return new DefaultAdapterFactory();
73
- }
98
+ }
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export type {
39
39
  NetworkPolicy,
40
40
  NetworkRule,
41
41
  NetworkRuleAction,
42
+ OSSFS,
42
43
  PVC,
43
44
  RenewSandboxExpirationRequest,
44
45
  RenewSandboxExpirationResponse,
@@ -91,6 +92,7 @@ export { ExecutionEventDispatcher } from "./models/executionEventDispatcher.js";
91
92
 
92
93
  export {
93
94
  DEFAULT_ENTRYPOINT,
95
+ DEFAULT_EGRESS_PORT,
94
96
  DEFAULT_EXECD_PORT,
95
97
  DEFAULT_RESOURCE_LIMITS,
96
98
  DEFAULT_TIMEOUT_SECONDS,
@@ -112,4 +114,4 @@ export type {
112
114
  SetPermissionEntry,
113
115
  WriteEntry,
114
116
  } from "./models/filesystem.js";
115
- export type { SandboxFiles } from "./services/filesystem.js";
117
+ export type { SandboxFiles } from "./services/filesystem.js";
@@ -63,6 +63,18 @@ export interface RunCommandOpts {
63
63
  * If omitted, the server will not enforce any timeout.
64
64
  */
65
65
  timeoutSeconds?: number;
66
+ /**
67
+ * Unix user ID used to run the command process.
68
+ */
69
+ uid?: number;
70
+ /**
71
+ * Unix group ID used to run the command process. Requires `uid`.
72
+ */
73
+ gid?: number;
74
+ /**
75
+ * Environment variables injected into the command process.
76
+ */
77
+ envs?: Record<string, string>;
66
78
  }
67
79
 
68
80
  export interface CommandStatus {
@@ -54,6 +54,7 @@ export interface Execution {
54
54
  result: ExecutionResult[];
55
55
  error?: ExecutionError;
56
56
  complete?: ExecutionComplete;
57
+ exitCode?: number | null;
57
58
  }
58
59
 
59
60
  export interface ExecutionHandlers {
@@ -68,4 +69,4 @@ export interface ExecutionHandlers {
68
69
  onExecutionComplete?: (c: ExecutionComplete) => void | Promise<void>;
69
70
  onError?: (err: ExecutionError) => void | Promise<void>;
70
71
  onInit?: (init: ExecutionInit) => void | Promise<void>;
71
- }
72
+ }