@cloudflare/sandbox 0.10.3 → 0.11.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/dist/bridge/index.js +187 -3
- package/dist/bridge/index.js.map +1 -1
- package/dist/{contexts-D_shbnJs.d.ts → contexts-XHAo64dB.d.ts} +1 -5
- package/dist/contexts-XHAo64dB.d.ts.map +1 -0
- package/dist/{errors-8Hvune8K.js → errors-COsTRno_.js} +3 -3
- package/dist/errors-COsTRno_.js.map +1 -0
- package/dist/index.d.ts +3 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +63 -4
- package/dist/index.js.map +1 -0
- package/dist/openai/index.d.ts +1 -1
- package/dist/opencode/index.d.ts +2 -2
- package/dist/opencode/index.js +1 -1
- package/dist/{sandbox-CwcSm_60.d.ts → sandbox-B9LOT0cg.d.ts} +138 -90
- package/dist/sandbox-B9LOT0cg.d.ts.map +1 -0
- package/dist/{sandbox-B-MUmsli.js → sandbox-DQxTkLyY.js} +1484 -323
- package/dist/sandbox-DQxTkLyY.js.map +1 -0
- package/package.json +2 -2
- package/dist/contexts-D_shbnJs.d.ts.map +0 -1
- package/dist/errors-8Hvune8K.js.map +0 -1
- package/dist/sandbox-B-MUmsli.js.map +0 -1
- package/dist/sandbox-CwcSm_60.d.ts.map +0 -1
package/dist/bridge/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../dist-B_eXrP83.js";
|
|
2
|
-
import "../errors-
|
|
3
|
-
import {
|
|
2
|
+
import "../errors-COsTRno_.js";
|
|
3
|
+
import { C as streamFile, b as validatePort, n as getSandbox, v as SandboxSecurityError, x as validateTunnelName } from "../sandbox-DQxTkLyY.js";
|
|
4
4
|
import { DurableObject, env } from "cloudflare:workers";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
|
|
@@ -267,6 +267,50 @@ const OPENAPI_SCHEMA = {
|
|
|
267
267
|
example: "/mnt/data"
|
|
268
268
|
} }
|
|
269
269
|
},
|
|
270
|
+
TunnelRequest: {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: { name: {
|
|
273
|
+
type: "string",
|
|
274
|
+
description: "Subdomain prefix for a named tunnel, such as `app`. Do not pass a full hostname. Omit to create or reuse an ephemeral tunnel.",
|
|
275
|
+
example: "app"
|
|
276
|
+
} }
|
|
277
|
+
},
|
|
278
|
+
Tunnel: {
|
|
279
|
+
type: "object",
|
|
280
|
+
required: [
|
|
281
|
+
"id",
|
|
282
|
+
"port",
|
|
283
|
+
"url",
|
|
284
|
+
"hostname",
|
|
285
|
+
"createdAt"
|
|
286
|
+
],
|
|
287
|
+
properties: {
|
|
288
|
+
id: { type: "string" },
|
|
289
|
+
port: {
|
|
290
|
+
type: "integer",
|
|
291
|
+
description: "Container port served by the tunnel.",
|
|
292
|
+
example: 8080
|
|
293
|
+
},
|
|
294
|
+
url: {
|
|
295
|
+
type: "string",
|
|
296
|
+
format: "uri",
|
|
297
|
+
example: "https://app.example.com"
|
|
298
|
+
},
|
|
299
|
+
hostname: {
|
|
300
|
+
type: "string",
|
|
301
|
+
example: "app.example.com"
|
|
302
|
+
},
|
|
303
|
+
createdAt: {
|
|
304
|
+
type: "string",
|
|
305
|
+
format: "date-time"
|
|
306
|
+
},
|
|
307
|
+
name: {
|
|
308
|
+
type: "string",
|
|
309
|
+
description: "Present for named tunnels only.",
|
|
310
|
+
example: "app"
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
270
314
|
ErrorResponse: {
|
|
271
315
|
type: "object",
|
|
272
316
|
required: ["error", "code"],
|
|
@@ -290,7 +334,8 @@ const OPENAPI_SCHEMA = {
|
|
|
290
334
|
"pool_error",
|
|
291
335
|
"mount_error",
|
|
292
336
|
"unmount_error",
|
|
293
|
-
"session_error"
|
|
337
|
+
"session_error",
|
|
338
|
+
"tunnel_error"
|
|
294
339
|
]
|
|
295
340
|
}
|
|
296
341
|
}
|
|
@@ -439,6 +484,103 @@ const OPENAPI_SCHEMA = {
|
|
|
439
484
|
}
|
|
440
485
|
}
|
|
441
486
|
} },
|
|
487
|
+
"/v1/sandbox/{id}/tunnel/{port}": {
|
|
488
|
+
post: {
|
|
489
|
+
operationId: "createTunnel",
|
|
490
|
+
summary: "Create or reuse a tunnel for a sandbox port",
|
|
491
|
+
description: "Returns an existing tunnel for the port when one is already recorded, or provisions one when needed. The service must already be listening inside the sandbox. Omit `name` for an ephemeral `*.trycloudflare.com` tunnel, or pass `name` to choose the subdomain prefix for a named tunnel. Use a value such as `app`, not a full hostname.",
|
|
492
|
+
"x-codeSamples": [{
|
|
493
|
+
lang: "curl",
|
|
494
|
+
label: "Ephemeral tunnel",
|
|
495
|
+
source: "curl -X POST https://$HOST/v1/sandbox/my-sandbox/tunnel/8080 \\\n -H \"Authorization: Bearer $SANDBOX_API_KEY\""
|
|
496
|
+
}, {
|
|
497
|
+
lang: "curl",
|
|
498
|
+
label: "Named tunnel",
|
|
499
|
+
source: "curl -X POST https://$HOST/v1/sandbox/my-sandbox/tunnel/8080 \\\n -H \"Authorization: Bearer $SANDBOX_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"name\":\"app\"}'"
|
|
500
|
+
}],
|
|
501
|
+
parameters: [{
|
|
502
|
+
name: "id",
|
|
503
|
+
in: "path",
|
|
504
|
+
required: true,
|
|
505
|
+
schema: { type: "string" },
|
|
506
|
+
description: "Sandbox instance name."
|
|
507
|
+
}, {
|
|
508
|
+
name: "port",
|
|
509
|
+
in: "path",
|
|
510
|
+
required: true,
|
|
511
|
+
schema: {
|
|
512
|
+
type: "integer",
|
|
513
|
+
minimum: 1024,
|
|
514
|
+
maximum: 65535
|
|
515
|
+
},
|
|
516
|
+
description: "Container port to tunnel. Port 3000 is reserved."
|
|
517
|
+
}],
|
|
518
|
+
requestBody: {
|
|
519
|
+
required: false,
|
|
520
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/TunnelRequest" } } }
|
|
521
|
+
},
|
|
522
|
+
responses: {
|
|
523
|
+
"200": {
|
|
524
|
+
description: "Tunnel created or reused.",
|
|
525
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Tunnel" } } }
|
|
526
|
+
},
|
|
527
|
+
"400": { $ref: "#/components/responses/InvalidRequest" },
|
|
528
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
529
|
+
"502": {
|
|
530
|
+
description: "Tunnel creation failed.",
|
|
531
|
+
content: { "application/json": {
|
|
532
|
+
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
533
|
+
example: {
|
|
534
|
+
error: "tunnel failed: cloudflared could not be found",
|
|
535
|
+
code: "tunnel_error"
|
|
536
|
+
}
|
|
537
|
+
} }
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
delete: {
|
|
542
|
+
operationId: "deleteTunnel",
|
|
543
|
+
summary: "Delete the tunnel for a sandbox port",
|
|
544
|
+
description: "Stops the tunnel process for the port and removes any named-tunnel Cloudflare resources tracked by the sandbox.",
|
|
545
|
+
"x-codeSamples": [{
|
|
546
|
+
lang: "curl",
|
|
547
|
+
label: "Delete tunnel",
|
|
548
|
+
source: "curl -X DELETE https://$HOST/v1/sandbox/my-sandbox/tunnel/8080 \\\n -H \"Authorization: Bearer $SANDBOX_API_KEY\""
|
|
549
|
+
}],
|
|
550
|
+
parameters: [{
|
|
551
|
+
name: "id",
|
|
552
|
+
in: "path",
|
|
553
|
+
required: true,
|
|
554
|
+
schema: { type: "string" },
|
|
555
|
+
description: "Sandbox instance name."
|
|
556
|
+
}, {
|
|
557
|
+
name: "port",
|
|
558
|
+
in: "path",
|
|
559
|
+
required: true,
|
|
560
|
+
schema: {
|
|
561
|
+
type: "integer",
|
|
562
|
+
minimum: 1024,
|
|
563
|
+
maximum: 65535
|
|
564
|
+
},
|
|
565
|
+
description: "Container port whose tunnel should be deleted. Port 3000 is reserved."
|
|
566
|
+
}],
|
|
567
|
+
responses: {
|
|
568
|
+
"204": { description: "Tunnel deleted or already absent." },
|
|
569
|
+
"400": { $ref: "#/components/responses/InvalidRequest" },
|
|
570
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
571
|
+
"502": {
|
|
572
|
+
description: "Tunnel deletion failed.",
|
|
573
|
+
content: { "application/json": {
|
|
574
|
+
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
|
575
|
+
example: {
|
|
576
|
+
error: "tunnel failed: cleanup failed",
|
|
577
|
+
code: "tunnel_error"
|
|
578
|
+
}
|
|
579
|
+
} }
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
},
|
|
442
584
|
"/v1/sandbox/{id}/file/{path}": {
|
|
443
585
|
get: {
|
|
444
586
|
operationId: "readFile",
|
|
@@ -1632,6 +1774,24 @@ function hasCredentials(options) {
|
|
|
1632
1774
|
function isStringArray(value) {
|
|
1633
1775
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1634
1776
|
}
|
|
1777
|
+
function parseTunnelOptions(rawBody) {
|
|
1778
|
+
if (!rawBody.trim()) return void 0;
|
|
1779
|
+
let body;
|
|
1780
|
+
try {
|
|
1781
|
+
body = JSON.parse(rawBody);
|
|
1782
|
+
} catch {
|
|
1783
|
+
return errorJson("Invalid JSON body", "invalid_request", 400);
|
|
1784
|
+
}
|
|
1785
|
+
if (!body || typeof body !== "object" || Array.isArray(body) || body.name === void 0) return;
|
|
1786
|
+
if (typeof body.name !== "string") return errorJson("name must be a string when provided", "invalid_request", 400);
|
|
1787
|
+
try {
|
|
1788
|
+
validateTunnelName(body.name);
|
|
1789
|
+
} catch (err) {
|
|
1790
|
+
if (err instanceof SandboxSecurityError) return errorJson(err.message, "invalid_request", 400);
|
|
1791
|
+
throw err;
|
|
1792
|
+
}
|
|
1793
|
+
return { name: body.name };
|
|
1794
|
+
}
|
|
1635
1795
|
function validateMountOptions(options, binding) {
|
|
1636
1796
|
if ("endpoint" in options && !hasEndpoint(options)) return errorJson("options.endpoint must be a string when provided", "invalid_request", 400);
|
|
1637
1797
|
if (binding !== void 0 && typeof binding !== "string") return errorJson("binding must be a string when provided", "invalid_request", 400);
|
|
@@ -1865,6 +2025,30 @@ function createBridgeApp(config) {
|
|
|
1865
2025
|
return errorJson(`write failed: ${err instanceof Error ? err.message : String(err)}`, "workspace_archive_write_error", 502);
|
|
1866
2026
|
}
|
|
1867
2027
|
});
|
|
2028
|
+
app.post(`${apiPrefix}/sandbox/:id/tunnel/:port`, async (c) => {
|
|
2029
|
+
const port = Number(c.req.param("port"));
|
|
2030
|
+
if (!validatePort(port)) return errorJson("Invalid port", "invalid_request", 400);
|
|
2031
|
+
const options = parseTunnelOptions(await c.req.text());
|
|
2032
|
+
if (options instanceof Response) return options;
|
|
2033
|
+
const sandbox = getSandbox$1(getSandboxNs(c.env), c.get("containerUUID"));
|
|
2034
|
+
try {
|
|
2035
|
+
const tunnel = await sandbox.tunnels.get(port, options);
|
|
2036
|
+
return c.json(tunnel);
|
|
2037
|
+
} catch (err) {
|
|
2038
|
+
return errorJson(`tunnel failed: ${err instanceof Error ? err.message : String(err)}`, "tunnel_error", 502);
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
app.delete(`${apiPrefix}/sandbox/:id/tunnel/:port`, async (c) => {
|
|
2042
|
+
const port = Number(c.req.param("port"));
|
|
2043
|
+
if (!validatePort(port)) return errorJson("Invalid port", "invalid_request", 400);
|
|
2044
|
+
const sandbox = getSandbox$1(getSandboxNs(c.env), c.get("containerUUID"));
|
|
2045
|
+
try {
|
|
2046
|
+
await sandbox.tunnels.destroy(port);
|
|
2047
|
+
return c.body(null, 204);
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
return errorJson(`tunnel failed: ${err instanceof Error ? err.message : String(err)}`, "tunnel_error", 502);
|
|
2050
|
+
}
|
|
2051
|
+
});
|
|
1868
2052
|
app.get(`${apiPrefix}/sandbox/:id/running`, async (c) => {
|
|
1869
2053
|
const sandbox = getSandbox$1(getSandboxNs(c.env), c.get("containerUUID"));
|
|
1870
2054
|
try {
|