@sandbox-kit/modal 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sandbox-kit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,56 @@
1
- # Temporary Holding Version
1
+ # Modal provider for Sandbox Kit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Independent package over `modal@0.11.1`, published separately from the core
4
+ `@sandbox-kit/sandbox` client. Use it through that shared API.
5
+
6
+ ```sh
7
+ npm install @sandbox-kit/sandbox @sandbox-kit/modal
8
+ ```
9
+
10
+ ## Usage
11
+
12
+ ```ts
13
+ import { modal } from "@sandbox-kit/modal";
14
+ import { Client } from "@sandbox-kit/sandbox";
15
+
16
+ await using client = new Client({
17
+ provider: modal(),
18
+ auth: {
19
+ tokenPair: { id: "...", secret: "..." },
20
+ },
21
+ scope: { appName: "my-app", environment: "main" },
22
+ });
23
+
24
+ const sandbox = await client.create({
25
+ source: { image: { reference: "alpine:3.21" } },
26
+ provisioning: { timeout: 120_000 },
27
+ });
28
+ console.log(sandbox.id);
29
+ ```
30
+
31
+ Closing the client releases local SDK resources only. The Modal app must already
32
+ exist.
33
+
34
+ ## Examples
35
+
36
+ | Example | Project |
37
+ | ---------- | ---------------------------------------------------------------------------------------------------------- |
38
+ | Creation | [create-modal-sandbox](../../../../examples/typescript/modal/create-modal-sandbox/README.md) |
39
+ | Resources | [create-modal-with-resources](../../../../examples/typescript/modal/create-modal-with-resources/README.md) |
40
+ | Attach | [attach-modal-sandbox](../../../../examples/typescript/modal/attach-modal-sandbox/README.md) |
41
+ | Full index | [TypeScript examples](../../../../examples/typescript/README.md) |
42
+
43
+ ## Capabilities
44
+
45
+ Modal supports image creation, attach, terminate, and local handle detachment.
46
+ The creation adapter covers runtime command/workdir/PTY, gVisor or VM isolation,
47
+ CPU and memory reservations and limits, placement, network policy and ports,
48
+ secrets, OIDC workload identity, readiness, timeouts, labels, and verbose mode.
49
+ Unsupported shared intent is rejected before contacting Modal.
50
+
51
+ ## Implementation notes
52
+
53
+ Checks and portable field mappings are handwritten in `checks/provider_checks.ts`,
54
+ `mappings/provider_mappings.ts`, and `config/provider_config.ts`, kept in sync with
55
+ `specs/providers/modal.yaml`. `src/modal.ts` owns SDK call ordering;
56
+ `src/mappings.ts` owns remaining composition those helpers do not cover.
@@ -0,0 +1,2 @@
1
+ import type { CreateOptions } from "@sandbox-kit/sandbox";
2
+ export declare function validateProviderCreate(request: CreateOptions): void;
@@ -0,0 +1,106 @@
1
+ import { ErrorKind, sandboxError, CreateFieldEnvironment, CreateFieldNetworkInboundCidrsEntries, CreateFieldNetworkOutboundCidrsEntries, CreateFieldNetworkOutboundDomainsEntries, CreateFieldPlacementCloud, CreateFieldResourcesCpuCores, CreateFieldResourcesMemoryMib, CreateFieldRuntimeWorkingDirectory } from "@sandbox-kit/sandbox";
2
+ // validateProviderCreate rejects creation settings this provider cannot honor.
3
+ // Rules are inventoried in specs/providers/modal.yaml checks.
4
+ export function validateProviderCreate(request) {
5
+ const cpuCores = request.resources?.cpuCores;
6
+ if (cpuCores !== undefined && cpuCores < 0.125) {
7
+ fail(ErrorKind.InvalidArgument, CreateFieldResourcesCpuCores, "resources.cpu_cores is outside supported values");
8
+ }
9
+ const memoryMib = request.resources?.memoryMib;
10
+ if (memoryMib !== undefined && memoryMib < BigInt("128")) {
11
+ fail(ErrorKind.InvalidArgument, CreateFieldResourcesMemoryMib, "resources.memory_mib is outside supported values");
12
+ }
13
+ const workingDirectory = request.runtime?.workingDirectory;
14
+ if (workingDirectory !== undefined && !workingDirectory.startsWith("/")) {
15
+ fail(ErrorKind.InvalidArgument, CreateFieldRuntimeWorkingDirectory, "runtime.working_directory is outside supported values");
16
+ }
17
+ const cloud = request.placement?.cloud;
18
+ if (cloud !== undefined &&
19
+ cloud !== "aws" &&
20
+ cloud !== "gcp" &&
21
+ cloud !== "oci" &&
22
+ cloud !== "auto") {
23
+ fail(ErrorKind.Unsupported, CreateFieldPlacementCloud, "placement.cloud is outside supported values");
24
+ }
25
+ for (const value of request.network?.outboundCidrs ?? []) {
26
+ if (value !== "" && !validCidr(value)) {
27
+ fail(ErrorKind.InvalidArgument, CreateFieldNetworkOutboundCidrsEntries, "network.outbound_cidrs.entries is outside supported values");
28
+ }
29
+ }
30
+ for (const value of request.network?.inboundCidrs ?? []) {
31
+ if (!validCidr(value)) {
32
+ fail(ErrorKind.InvalidArgument, CreateFieldNetworkInboundCidrsEntries, "network.inbound_cidrs.entries is outside supported values");
33
+ }
34
+ }
35
+ for (const value of request.network?.outboundDomains ?? []) {
36
+ if (value !== "" && !validDomain(value)) {
37
+ fail(ErrorKind.InvalidArgument, CreateFieldNetworkOutboundDomainsEntries, "network.outbound_domains.entries is outside supported values");
38
+ }
39
+ }
40
+ for (const value of Object.keys(request.environment ?? {})) {
41
+ if (!validEnvName(value)) {
42
+ fail(ErrorKind.InvalidArgument, CreateFieldEnvironment, "environment is outside supported values");
43
+ }
44
+ }
45
+ }
46
+ function validEnvName(value) {
47
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
48
+ }
49
+ function validDomain(value) {
50
+ const domain = value.startsWith("*.") ? value.slice(2) : value;
51
+ return (domain.length <= 253 &&
52
+ /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.?$/i.test(domain));
53
+ }
54
+ function validCidr(value) {
55
+ const separator = value.lastIndexOf("/");
56
+ if (separator <= 0)
57
+ return false;
58
+ const address = value.slice(0, separator);
59
+ const prefix = Number(value.slice(separator + 1));
60
+ if (!Number.isInteger(prefix))
61
+ return false;
62
+ return address.includes(":")
63
+ ? prefix >= 0 && prefix <= 128 && validIpv6(address)
64
+ : prefix >= 0 && prefix <= 32 && validIpv4(address);
65
+ }
66
+ function validIpv4(value) {
67
+ const parts = value.split(".");
68
+ return (parts.length === 4 &&
69
+ parts.every((part) => /^(0|[1-9][0-9]{0,2})$/.test(part) && Number(part) <= 255));
70
+ }
71
+ function validIpv6(value) {
72
+ if ((value.match(/::/g)?.length ?? 0) > 1)
73
+ return false;
74
+ const halves = value.split("::");
75
+ if (halves.length > 2)
76
+ return false;
77
+ const count = (part) => {
78
+ if (part === "")
79
+ return 0;
80
+ const groups = part.split(":");
81
+ let total = 0;
82
+ for (const group of groups) {
83
+ if (/^[0-9a-f]{1,4}$/i.test(group))
84
+ total++;
85
+ else if (validIpv4(group))
86
+ total += 2;
87
+ else
88
+ return undefined;
89
+ }
90
+ return total;
91
+ };
92
+ const left = count(halves[0] ?? "");
93
+ const right = count(halves[1] ?? "");
94
+ return (left !== undefined &&
95
+ right !== undefined &&
96
+ (halves.length === 2 ? left + right < 8 : left === 8));
97
+ }
98
+ function fail(kind, field, message) {
99
+ throw sandboxError({
100
+ kind,
101
+ provider: "modal",
102
+ operation: "create",
103
+ field,
104
+ message: "sandbox-kit modal: " + message,
105
+ });
106
+ }
@@ -0,0 +1,8 @@
1
+ import type { ModalClientParams as NativeClientConfig } from "modal";
2
+ import { type Config, type Scope } from "@sandbox-kit/sandbox";
3
+ export declare function mapGeneratedClientConfig(config: Config): NativeClientConfig;
4
+ export interface ProviderState {
5
+ readonly scope?: Scope;
6
+ readonly region?: string;
7
+ }
8
+ export declare function captureProviderState(config: Config): ProviderState;
@@ -0,0 +1,46 @@
1
+ // Client configuration composition for the native SDK constructor.
2
+ // Kept in sync with specs/providers/modal.yaml client: rules (not emitted).
3
+ import { ErrorKind, sandboxError, validateConfig, ConfigFieldAuth, ConfigFieldEndpoint, ConfigFieldScopeProjectId } from "@sandbox-kit/sandbox";
4
+ import { mapClientScope, mapOAuthAuth, mapTokenPairCredentials, } from "#mappings/provider_mappings.js";
5
+ export function mapGeneratedClientConfig(config) {
6
+ const source = config;
7
+ validateConfig(source, "modal", "initialize");
8
+ const target = {};
9
+ if (source.endpoint !== undefined)
10
+ unsupported(ConfigFieldEndpoint, "Endpoint cannot be configured through this SDK's public client constructor");
11
+ if (source.scope !== undefined) {
12
+ const mapped = mapClientScope(source.scope, "initialize");
13
+ Object.assign(target, mapped);
14
+ if (source.scope.organizationId !== undefined)
15
+ unsupported("scope.organizationId", "unsupported client configuration field");
16
+ if (source.scope.projectId !== undefined)
17
+ unsupported(ConfigFieldScopeProjectId, "unsupported client configuration field");
18
+ }
19
+ if (source.auth?.tokenPair !== undefined) {
20
+ Object.assign(target, mapTokenPairCredentials(source.auth.tokenPair, "initialize"));
21
+ }
22
+ else if (source.auth?.oauthRefresh !== undefined) {
23
+ Object.assign(target, mapOAuthAuth(source.auth.oauthRefresh, "initialize"));
24
+ }
25
+ else if (source.auth !== undefined) {
26
+ unsupported(ConfigFieldAuth, "unsupported authentication mode");
27
+ }
28
+ return target;
29
+ }
30
+ export function captureProviderState(config) {
31
+ return {
32
+ ...(config.scope === undefined
33
+ ? {}
34
+ : { scope: structuredClone(config.scope) }),
35
+ ...(config.region === undefined ? {} : { region: config.region }),
36
+ };
37
+ }
38
+ function unsupported(field, message) {
39
+ throw sandboxError({
40
+ kind: ErrorKind.Unsupported,
41
+ provider: "modal",
42
+ operation: "initialize",
43
+ field,
44
+ message: "sandbox-kit modal: " + message,
45
+ });
46
+ }
@@ -0,0 +1,2 @@
1
+ import { SandboxError } from "@sandbox-kit/sandbox";
2
+ export declare function mapModalError(error: unknown, operation: string): SandboxError;
package/dist/errors.js ADDED
@@ -0,0 +1,34 @@
1
+ // Native Modal error translation is handwritten.
2
+ // Classification order: existing SandboxError, abort/timeout DOMException,
3
+ // then native Modal types, then unknown. Cause stays on SandboxError.cause.
4
+ import { AlreadyExistsError, ClientClosedError, ConflictError, InvalidError, NotFoundError, ResourceExhaustedError, SandboxTimeoutError, TimeoutError } from "modal";
5
+ import { ErrorKind, SandboxError, sandboxError } from "@sandbox-kit/sandbox";
6
+ export function mapModalError(error, operation) {
7
+ if (error instanceof SandboxError)
8
+ return error;
9
+ if (error instanceof DOMException && error.name === "AbortError")
10
+ return create(ErrorKind.Canceled, error, operation);
11
+ if (error instanceof DOMException && error.name === "TimeoutError")
12
+ return create(ErrorKind.Timeout, error, operation);
13
+ let kind = ErrorKind.Unknown;
14
+ if (kind === ErrorKind.Unknown && error instanceof InvalidError)
15
+ kind = ErrorKind.InvalidArgument;
16
+ if (kind === ErrorKind.Unknown && error instanceof NotFoundError)
17
+ kind = ErrorKind.NotFound;
18
+ if (kind === ErrorKind.Unknown && error instanceof AlreadyExistsError)
19
+ kind = ErrorKind.AlreadyExists;
20
+ if (kind === ErrorKind.Unknown && error instanceof ConflictError)
21
+ kind = ErrorKind.Conflict;
22
+ if (kind === ErrorKind.Unknown && error instanceof ResourceExhaustedError)
23
+ kind = ErrorKind.ResourceExhausted;
24
+ if (kind === ErrorKind.Unknown && error instanceof TimeoutError)
25
+ kind = ErrorKind.Timeout;
26
+ if (kind === ErrorKind.Unknown && error instanceof SandboxTimeoutError)
27
+ kind = ErrorKind.Timeout;
28
+ if (kind === ErrorKind.Unknown && error instanceof ClientClosedError)
29
+ kind = ErrorKind.FailedPrecondition;
30
+ return create(kind, error, operation);
31
+ }
32
+ function create(kind, error, operation, statusCode, providerCode, providerSource) {
33
+ return sandboxError({ kind, provider: "modal", operation, field: "", message: error instanceof Error ? error.message : "sandbox-kit modal: provider operation failed", ...(statusCode === undefined ? {} : { statusCode }), ...(providerCode === undefined ? {} : { providerCode }), ...(providerSource === undefined ? {} : { providerSource }) }, error);
34
+ }
@@ -0,0 +1,2 @@
1
+ export { providerName } from "./provider.js";
2
+ export { modal } from "./modal.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { providerName } from "./provider.js";
2
+ export { modal } from "./modal.js";
@@ -0,0 +1,16 @@
1
+ import { type ModalClientParams, type Sandbox, type SandboxCreateParams } from "modal";
2
+ import { type Config, type CreateOptions, type SandboxInfo } from "@sandbox-kit/sandbox";
3
+ import type { ProviderState } from "#config/provider_config.js";
4
+ export interface ModalCreatePlan {
5
+ readonly app: string;
6
+ readonly environment?: string;
7
+ readonly image: string;
8
+ readonly params: SandboxCreateParams;
9
+ readonly secretNames: readonly string[];
10
+ readonly proxyName?: string;
11
+ readonly waitUntilReady: boolean;
12
+ readonly readyTimeoutMs?: number;
13
+ }
14
+ export declare function mapClientConfig(config: Config): ModalClientParams;
15
+ export declare function mapCreateRequest(request: CreateOptions, config: ProviderState): ModalCreatePlan;
16
+ export declare function mapSandboxInfo(value: Sandbox, request?: CreateOptions): SandboxInfo;
@@ -0,0 +1,317 @@
1
+ // Provider-specific orchestration around generated portable field mappings.
2
+ import { Probe, } from "modal";
3
+ import { EgressMode, ErrorKind, IsolationKind, PolicyMode, PortTransport, SecretInjection, SandboxState, ValueOrigin, WaitCondition, sandboxError, CreateFieldIsolation, CreateFieldIsolationKind, CreateFieldLifetime, CreateFieldLifetimeIdleTerminateAfter, CreateFieldLifetimeIdleTerminateMode, CreateFieldNetwork, CreateFieldNetworkEgress, CreateFieldNetworkPortsPort, CreateFieldNetworkPortsTransport, CreateFieldPlacementTarget, CreateFieldProvisioningTimeout, CreateFieldProvisioningWaitFor, CreateFieldReadiness, CreateFieldRuntime, CreateFieldSecuritySecretsEnvironmentVariable, CreateFieldSecuritySecretsInjection, CreateFieldSource, ConfigFieldScopeAppName, CreateFieldName, CreateFieldNetworkInboundCidrs, CreateFieldNetworkProxyReference, CreateFieldProvisioningQueueTimeout, CreateFieldResourcesCpuLimitCores, CreateFieldResourcesDiskMib, CreateFieldResourcesMemoryLimitMib, CreateFieldResourcesMemoryMib, CreateFieldSecurityPublicAccess, CreateFieldSecuritySecretsReference, CreateFieldSourceImageReference } from "@sandbox-kit/sandbox";
4
+ import { mapCommonCreateFields as generatedMapCommonCreateFields, mapIdleDuration as generatedMapIdleDuration, mapLifetimeDuration as generatedMapLifetimeDuration, mapResources as generatedMapResources, mapSandboxIdentity as generatedMapSandboxIdentity, } from "./provider_mappings.js";
5
+ import { validateProviderCreate } from "#checks/provider_checks.js";
6
+ import { mapGeneratedClientConfig } from "#config/provider_config.js";
7
+ export function mapClientConfig(config) {
8
+ const result = mapGeneratedClientConfig(config);
9
+ if (config.timeout !== undefined)
10
+ result.timeoutMs = finiteDurationMs(config.timeout, "timeout", true);
11
+ return result;
12
+ }
13
+ export function mapCreateRequest(request, config) {
14
+ validateProviderCreate(request);
15
+ const app = config.scope?.appName === undefined
16
+ ? invalid(ConfigFieldScopeAppName, "Modal app name is required")
17
+ : nonblank(config.scope.appName, ConfigFieldScopeAppName);
18
+ if (request.source?.image === undefined)
19
+ unsupported(CreateFieldSource, "Modal requires an explicit image source");
20
+ const params = generatedMapCommonCreateFields(request);
21
+ if (request.name !== undefined)
22
+ params.name = nonblank(request.name, CreateFieldName);
23
+ if (config.region !== undefined)
24
+ params.regions = [nonblankConfig(config.region, "region")];
25
+ mapRuntime(request, params);
26
+ mapIsolation(request, params);
27
+ mapResources(request, params);
28
+ mapPlacement(request, params);
29
+ mapNetwork(request, params);
30
+ const secretNames = mapSecurity(request, params);
31
+ mapLifetime(request, params);
32
+ mapReadiness(request, params);
33
+ const wait = mapProvisioning(request, params);
34
+ mapObservability(request, params);
35
+ if (request.providerOptions !== undefined)
36
+ unsupported("providerOptions", "Modal provider options are not mapped");
37
+ return {
38
+ app,
39
+ ...(config.scope?.environment === undefined
40
+ ? {}
41
+ : { environment: config.scope.environment }),
42
+ image: nonblank(request.source.image.reference, CreateFieldSourceImageReference),
43
+ params,
44
+ secretNames,
45
+ ...(request.network?.proxyReference === undefined
46
+ ? {}
47
+ : {
48
+ proxyName: nonblank(request.network.proxyReference, CreateFieldNetworkProxyReference),
49
+ }),
50
+ ...wait,
51
+ };
52
+ }
53
+ function mapRuntime(r, p) {
54
+ const v = r.runtime;
55
+ if (!v)
56
+ return;
57
+ if (v.workingDirectory !== undefined)
58
+ p.workdir = v.workingDirectory;
59
+ if (v.entrypoint !== undefined)
60
+ p.command = [...v.entrypoint];
61
+ if (v.pty !== undefined)
62
+ p.pty = v.pty;
63
+ if (v.language !== undefined ||
64
+ v.version !== undefined ||
65
+ v.user !== undefined)
66
+ unsupported(CreateFieldRuntime, "language, version, and user must come from the Modal image");
67
+ }
68
+ function mapIsolation(r, p) {
69
+ const v = r.isolation;
70
+ if (!v)
71
+ return;
72
+ const kind = v.kind ?? IsolationKind.Default;
73
+ if (kind === IsolationKind.LinuxVm)
74
+ p.runtime = "vm";
75
+ else if (kind === IsolationKind.Container)
76
+ p.runtime = "gvisor";
77
+ else if (kind !== IsolationKind.Default)
78
+ unsupported(CreateFieldIsolationKind, "requested Modal isolation is unsupported");
79
+ if (v.providerKind !== undefined || v.nestedVirtualization !== undefined)
80
+ unsupported(CreateFieldIsolation, "provider kind and nested virtualization are unsupported");
81
+ }
82
+ function mapResources(r, p) {
83
+ const v = r.resources;
84
+ if (!v)
85
+ return;
86
+ if (v.cpuLimitCores !== undefined)
87
+ positive(v.cpuLimitCores, CreateFieldResourcesCpuLimitCores);
88
+ if (v.memoryMib !== undefined)
89
+ integer(v.memoryMib, CreateFieldResourcesMemoryMib, 128);
90
+ if (v.memoryLimitMib !== undefined)
91
+ integer(v.memoryLimitMib, CreateFieldResourcesMemoryLimitMib, 1);
92
+ if (v.diskMib !== undefined)
93
+ unsupported(CreateFieldResourcesDiskMib, "Modal disk override is unsupported");
94
+ Object.assign(p, generatedMapResources(v));
95
+ }
96
+ function mapPlacement(r, p) {
97
+ const v = r.placement;
98
+ if (!v)
99
+ return;
100
+ if (v.cloud !== undefined)
101
+ p.cloud = v.cloud;
102
+ if (v.regions !== undefined)
103
+ p.regions = [...v.regions];
104
+ if (v.target !== undefined)
105
+ unsupported(CreateFieldPlacementTarget, "Modal placement target is unsupported");
106
+ }
107
+ function mapNetwork(r, p) {
108
+ const v = r.network;
109
+ if (!v)
110
+ return;
111
+ const egress = v.egress ?? EgressMode.Default;
112
+ if (egress === EgressMode.BlockAll)
113
+ p.blockNetwork = true;
114
+ else if (egress !== EgressMode.Default &&
115
+ egress !== EgressMode.AllowAll &&
116
+ egress !== EgressMode.Restricted)
117
+ invalid(CreateFieldNetworkEgress, "unknown egress mode");
118
+ if (v.outboundCidrs !== undefined)
119
+ p.outboundCidrAllowlist = [...v.outboundCidrs];
120
+ if (v.outboundDomains !== undefined)
121
+ p.outboundDomainAllowlist = [...v.outboundDomains];
122
+ if (v.inboundCidrs !== undefined) {
123
+ if (!v.inboundCidrs.length)
124
+ unsupported(CreateFieldNetworkInboundCidrs, "empty inbound deny-all is not representable");
125
+ p.inboundCidrAllowlist = [...v.inboundCidrs];
126
+ }
127
+ if (v.privateNetwork !== undefined)
128
+ p.i6pn = v.privateNetwork;
129
+ if (v.customDomain !== undefined)
130
+ p.customDomain = v.customDomain;
131
+ if (v.ports) {
132
+ p.encryptedPorts = [];
133
+ p.h2Ports = [];
134
+ p.unencryptedPorts = [];
135
+ for (const x of v.ports) {
136
+ const port = x.port;
137
+ if (port === undefined)
138
+ invalid(CreateFieldNetworkPortsPort, "port is required");
139
+ const transport = x.transport ?? PortTransport.Default;
140
+ if (transport === PortTransport.Tls)
141
+ p.encryptedPorts.push(port);
142
+ else if (transport === PortTransport.Http2Tls)
143
+ p.h2Ports.push(port);
144
+ else if (transport === PortTransport.Plain)
145
+ p.unencryptedPorts.push(port);
146
+ else
147
+ invalid(CreateFieldNetworkPortsTransport, "explicit transport is required");
148
+ }
149
+ }
150
+ if (v.linkedSandbox !== undefined || v.outboundProxyUrl !== undefined)
151
+ unsupported(CreateFieldNetwork, "linked sandbox and outbound proxy URL mappings are unsupported");
152
+ }
153
+ function mapSecurity(r, p) {
154
+ const v = r.security;
155
+ if (!v)
156
+ return [];
157
+ if (v.workloadIdentity !== undefined)
158
+ p.includeOidcIdentityToken = v.workloadIdentity;
159
+ if (v.publicAccess !== undefined)
160
+ unsupported(CreateFieldSecurityPublicAccess, "Modal public access is represented by ports");
161
+ const names = [];
162
+ for (const s of v.secrets ?? []) {
163
+ if (s.environmentVariable !== undefined)
164
+ unsupported(CreateFieldSecuritySecretsEnvironmentVariable, "per-variable secret renaming is unsupported");
165
+ const injection = s.injection ?? SecretInjection.Default;
166
+ if (injection !== SecretInjection.Default &&
167
+ injection !== SecretInjection.Environment)
168
+ unsupported(CreateFieldSecuritySecretsInjection, "egress-placeholder injection is unsupported");
169
+ names.push(nonblank(s.reference, CreateFieldSecuritySecretsReference));
170
+ }
171
+ return names;
172
+ }
173
+ function mapLifetime(r, p) {
174
+ const v = r.lifetime;
175
+ if (!v)
176
+ return;
177
+ if (v.maximumLifetime !== undefined)
178
+ finiteDurationMs(v.maximumLifetime, "lifetime.maximumLifetime", false);
179
+ Object.assign(p, generatedMapLifetimeDuration(v));
180
+ if (v.idleTerminate) {
181
+ const mode = v.idleTerminate.mode ?? PolicyMode.Default;
182
+ if (mode === PolicyMode.Disabled)
183
+ p.idleTimeoutMs = 0;
184
+ else if (mode === PolicyMode.After) {
185
+ if (v.idleTerminate.after === undefined)
186
+ invalid(CreateFieldLifetimeIdleTerminateAfter, "duration is required");
187
+ finiteDurationMs(v.idleTerminate.after, "lifetime.idleTerminate.after", false);
188
+ Object.assign(p, generatedMapIdleDuration(v.idleTerminate));
189
+ }
190
+ else if (mode !== PolicyMode.Default)
191
+ invalid(CreateFieldLifetimeIdleTerminateMode, "unknown policy mode");
192
+ }
193
+ if (v.ephemeral !== undefined ||
194
+ v.idleStop !== undefined ||
195
+ v.idlePause !== undefined ||
196
+ v.stoppedArchive !== undefined ||
197
+ v.stoppedDelete !== undefined)
198
+ unsupported(CreateFieldLifetime, "requested lifecycle policy is unsupported by Modal");
199
+ }
200
+ function mapReadiness(r, p) {
201
+ const v = r.readiness;
202
+ if (!v)
203
+ return;
204
+ if (v.tcpPort !== undefined) {
205
+ p.readinessProbe = Probe.withTcp(v.tcpPort);
206
+ }
207
+ else if (v.command !== undefined)
208
+ p.readinessProbe = Probe.withExec([...(v.command.argv ?? [])]);
209
+ else
210
+ invalid(CreateFieldReadiness, "probe is required");
211
+ }
212
+ function mapProvisioning(r, _p) {
213
+ const v = r.provisioning;
214
+ if (!v)
215
+ return { waitUntilReady: false };
216
+ if (v.queueTimeout !== undefined)
217
+ unsupported(CreateFieldProvisioningQueueTimeout, "Modal queue timeout is unsupported");
218
+ const waitFor = v.waitFor ?? WaitCondition.Default;
219
+ if (waitFor === WaitCondition.Default || waitFor === WaitCondition.Scheduled)
220
+ return { waitUntilReady: false };
221
+ if (waitFor === WaitCondition.Ready) {
222
+ if (v.timeout === undefined || v.timeout <= 0)
223
+ invalid(CreateFieldProvisioningTimeout, "ready wait requires a positive timeout");
224
+ if (!r.readiness)
225
+ invalid(CreateFieldReadiness, "ready wait requires a probe");
226
+ return { waitUntilReady: true, readyTimeoutMs: v.timeout };
227
+ }
228
+ unsupported(CreateFieldProvisioningWaitFor, "requested wait condition is unsupported");
229
+ }
230
+ function mapObservability(r, p) {
231
+ const v = r.observability;
232
+ if (!v)
233
+ return;
234
+ if (v.verbose !== undefined)
235
+ p.verbose = v.verbose;
236
+ if (v.telemetryEndpoint !== undefined)
237
+ unsupported("observability.telemetryEndpoint", "per-sandbox telemetry endpoint is unsupported");
238
+ }
239
+ export function mapSandboxInfo(value, request) {
240
+ const mapped = generatedMapSandboxIdentity(value);
241
+ const origins = { ...(mapped.origins ?? {}) };
242
+ const result = {
243
+ id: mapped.id ?? value.sandboxId,
244
+ provider: "modal",
245
+ state: SandboxState.Running,
246
+ labels: {},
247
+ origins,
248
+ };
249
+ if (request?.name !== undefined) {
250
+ mutable(result).name = request.name;
251
+ origins.name = ValueOrigin.Request;
252
+ }
253
+ if (request?.labels !== undefined) {
254
+ mutable(result).labels = { ...request.labels };
255
+ origins.labels = ValueOrigin.Request;
256
+ }
257
+ if (request?.isolation !== undefined) {
258
+ mutable(result).isolation = { ...request.isolation };
259
+ origins.isolation = ValueOrigin.Request;
260
+ }
261
+ return result;
262
+ }
263
+ function mutable(v) {
264
+ return v;
265
+ }
266
+ function nonblank(v, f) {
267
+ if (!v.trim())
268
+ invalid(f, "must not be blank");
269
+ return v;
270
+ }
271
+ function nonblankConfig(v, f) {
272
+ if (!v.trim())
273
+ configInvalid(f, "must not be blank");
274
+ return v;
275
+ }
276
+ function positive(v, f) {
277
+ if (!Number.isFinite(v) || v <= 0)
278
+ invalid(f, "must be positive");
279
+ return v;
280
+ }
281
+ function integer(v, f, min) {
282
+ if (v < BigInt(min) || v > 2147483647n)
283
+ invalid(f, "is outside the supported range");
284
+ return Number(v);
285
+ }
286
+ function finiteDurationMs(v, f, zero) {
287
+ if (!Number.isFinite(v) || v < 0 || (!zero && v === 0))
288
+ invalid(f, "must be a finite positive duration");
289
+ return v;
290
+ }
291
+ function invalid(field, message) {
292
+ throw sandboxError({
293
+ kind: ErrorKind.InvalidArgument,
294
+ provider: "modal",
295
+ operation: "create",
296
+ field,
297
+ message: "sandbox-kit modal: " + message,
298
+ });
299
+ }
300
+ function unsupported(field, message) {
301
+ throw sandboxError({
302
+ kind: ErrorKind.Unsupported,
303
+ provider: "modal",
304
+ operation: "create",
305
+ field,
306
+ message: "sandbox-kit modal: " + message,
307
+ });
308
+ }
309
+ function configInvalid(field, message) {
310
+ throw sandboxError({
311
+ kind: ErrorKind.InvalidArgument,
312
+ provider: "modal",
313
+ operation: "initialize",
314
+ field,
315
+ message: "sandbox-kit modal: " + message,
316
+ });
317
+ }
@@ -0,0 +1,14 @@
1
+ import { type SandboxInfo, type Scope } from "@sandbox-kit/sandbox";
2
+ import type { CreateOptions, OAuthCredentials, Resources, TokenPairCredentials } from "@sandbox-kit/sandbox";
3
+ import type { ModalClientParams as NativeModalClientParams, Sandbox as NativeSandbox, SandboxCreateParams as NativeSandboxCreateParams } from "modal";
4
+ type PublicLifetime = NonNullable<CreateOptions["lifetime"]>;
5
+ type PublicIdleTerminate = NonNullable<PublicLifetime["idleTerminate"]>;
6
+ export declare function mapSandboxIdentity(source: NativeSandbox | undefined, operation?: string): Partial<SandboxInfo>;
7
+ export declare function mapClientScope(source: Scope | undefined, operation?: string): NativeModalClientParams;
8
+ export declare function mapCommonCreateFields(source: CreateOptions | undefined, operation?: string): NativeSandboxCreateParams;
9
+ export declare function mapResources(source: Resources | undefined, operation?: string): NativeSandboxCreateParams;
10
+ export declare function mapLifetimeDuration(source: PublicLifetime | undefined, operation?: string): NativeSandboxCreateParams;
11
+ export declare function mapIdleDuration(source: PublicIdleTerminate | undefined, operation?: string): NativeSandboxCreateParams;
12
+ export declare function mapTokenPairCredentials(source: TokenPairCredentials | undefined, operation?: string): NativeModalClientParams;
13
+ export declare function mapOAuthAuth(source: OAuthCredentials | undefined, operation?: string): NativeModalClientParams;
14
+ export {};