···3636 "deno",
3737 "sprites",
3838 "modal",
3939- "e2b"
3939+ "e2b",
4040+ "blaxel",
4141+ "hopx",
4242+ "runloop"
4043 ]
4144 },
4245 "topics": {
···152155 "redactedE2bApiKey": {
153156 "type": "string",
154157 "description": "A redacted API KEY for accessing E2B resources"
158158+ },
159159+ "hopxApiKey": {
160160+ "type": "string",
161161+ "description": "An API KEY (encrypted) for accessing hopx resources"
162162+ },
163163+ "redactedHopxApiKey": {
164164+ "type": "string",
165165+ "description": "A redacted API KEY for accessing hopx resources"
166166+ },
167167+ "runloopApiKey": {
168168+ "type": "string",
169169+ "description": "An API KEY (encrypted) for accessing Runloop resources"
170170+ },
171171+ "redactedRunloopApiKey": {
172172+ "type": "string",
173173+ "description": "A redacted API KEY for accessing Runloop resources"
155174 }
156175 }
157176 }
+16
apps/api/lexicons/sandbox/defs.json
···354354 "redactedE2bApiKey": {
355355 "type": "string",
356356 "description": "The redacted API KEY for E2B, returned in API responses when the sandbox provider is E2B. This can be used to identify which E2B API KEY is being used without exposing the actual API KEY."
357357+ },
358358+ "hopxApiKey": {
359359+ "type": "string",
360360+ "description": "The API KEY for hopx, if the sandbox provider is hopx. This is used to determine which hopx API KEY to use when creating the sandbox."
361361+ },
362362+ "redactedHopxApiKey": {
363363+ "type": "string",
364364+ "description": "The redacted API KEY for hopx, returned in API responses when the sandbox provider is hopx. This can be used to identify which hopx API KEY is being used without exposing the actual API KEY."
365365+ },
366366+ "runloopApiKey": {
367367+ "type": "string",
368368+ "description": "The API KEY for Runloop, if the sandbox provider is Runloop. This is used to determine which Runloop API KEY to use when creating the sandbox."
369369+ },
370370+ "redactedRunloopApiKey": {
371371+ "type": "string",
372372+ "description": "The redacted API KEY for Runloop, returned in API responses when the sandbox provider is Runloop. This can be used to identify which Runloop API KEY is being used without exposing the actual API KEY."
357373 }
358374 }
359375 },
+29-1
apps/api/pkl/defs/sandbox/createSandbox.pkl
···2828 }
2929 ["provider"] = new StringType {
3030 type = "string"
3131- enum = List("daytona", "vercel", "cloudflare", "deno", "sprites", "modal", "e2b")
3131+ enum =
3232+ List(
3333+ "daytona",
3434+ "vercel",
3535+ "cloudflare",
3636+ "deno",
3737+ "sprites",
3838+ "modal",
3939+ "e2b",
4040+ "blaxel",
4141+ "hopx",
4242+ "runloop",
4343+ )
3244 description =
3345 "The provider to create the sandbox on, e.g. 'daytona', 'vercel', 'cloudflare', etc."
3446 }
···147159 ["redactedE2bApiKey"] = new StringType {
148160 type = "string"
149161 description = "A redacted API KEY for accessing E2B resources"
162162+ }
163163+ ["hopxApiKey"] = new StringType {
164164+ type = "string"
165165+ description = "An API KEY (encrypted) for accessing hopx resources"
166166+ }
167167+ ["redactedHopxApiKey"] = new StringType {
168168+ type = "string"
169169+ description = "A redacted API KEY for accessing hopx resources"
170170+ }
171171+ ["runloopApiKey"] = new StringType {
172172+ type = "string"
173173+ description = "An API KEY (encrypted) for accessing Runloop resources"
174174+ }
175175+ ["redactedRunloopApiKey"] = new StringType {
176176+ type = "string"
177177+ description = "A redacted API KEY for accessing Runloop resources"
150178 }
151179 }
152180 }
+20
apps/api/pkl/defs/sandbox/defs.pkl
···365365 description =
366366 "The redacted API KEY for E2B, returned in API responses when the sandbox provider is E2B. This can be used to identify which E2B API KEY is being used without exposing the actual API KEY."
367367 }
368368+ ["hopxApiKey"] = new StringType {
369369+ type = "string"
370370+ description =
371371+ "The API KEY for hopx, if the sandbox provider is hopx. This is used to determine which hopx API KEY to use when creating the sandbox."
372372+ }
373373+ ["redactedHopxApiKey"] = new StringType {
374374+ type = "string"
375375+ description =
376376+ "The redacted API KEY for hopx, returned in API responses when the sandbox provider is hopx. This can be used to identify which hopx API KEY is being used without exposing the actual API KEY."
377377+ }
378378+ ["runloopApiKey"] = new StringType {
379379+ type = "string"
380380+ description =
381381+ "The API KEY for Runloop, if the sandbox provider is Runloop. This is used to determine which Runloop API KEY to use when creating the sandbox."
382382+ }
383383+ ["redactedRunloopApiKey"] = new StringType {
384384+ type = "string"
385385+ description =
386386+ "The redacted API KEY for Runloop, returned in API responses when the sandbox provider is Runloop. This can be used to identify which Runloop API KEY is being used without exposing the actual API KEY."
387387+ }
368388 }
369389 }
370390 ["preferences"] = new Array {
···585585 "sprites",
586586 "modal",
587587 "e2b",
588588+ "blaxel",
589589+ "hopx",
590590+ "runloop",
588591 ],
589592 },
590593 topics: {
···715718 redactedE2bApiKey: {
716719 type: "string",
717720 description: "A redacted API KEY for accessing E2B resources",
721721+ },
722722+ hopxApiKey: {
723723+ type: "string",
724724+ description:
725725+ "An API KEY (encrypted) for accessing hopx resources",
726726+ },
727727+ redactedHopxApiKey: {
728728+ type: "string",
729729+ description: "A redacted API KEY for accessing hopx resources",
730730+ },
731731+ runloopApiKey: {
732732+ type: "string",
733733+ description:
734734+ "An API KEY (encrypted) for accessing Runloop resources",
735735+ },
736736+ redactedRunloopApiKey: {
737737+ type: "string",
738738+ description:
739739+ "A redacted API KEY for accessing Runloop resources",
718740 },
719741 },
720742 },
···11071129 type: "string",
11081130 description:
11091131 "The redacted API KEY for E2B, returned in API responses when the sandbox provider is E2B. This can be used to identify which E2B API KEY is being used without exposing the actual API KEY.",
11321132+ },
11331133+ hopxApiKey: {
11341134+ type: "string",
11351135+ description:
11361136+ "The API KEY for hopx, if the sandbox provider is hopx. This is used to determine which hopx API KEY to use when creating the sandbox.",
11371137+ },
11381138+ redactedHopxApiKey: {
11391139+ type: "string",
11401140+ description:
11411141+ "The redacted API KEY for hopx, returned in API responses when the sandbox provider is hopx. This can be used to identify which hopx API KEY is being used without exposing the actual API KEY.",
11421142+ },
11431143+ runloopApiKey: {
11441144+ type: "string",
11451145+ description:
11461146+ "The API KEY for Runloop, if the sandbox provider is Runloop. This is used to determine which Runloop API KEY to use when creating the sandbox.",
11471147+ },
11481148+ redactedRunloopApiKey: {
11491149+ type: "string",
11501150+ description:
11511151+ "The redacted API KEY for Runloop, returned in API responses when the sandbox provider is Runloop. This can be used to identify which Runloop API KEY is being used without exposing the actual API KEY.",
11101152 },
11111153 },
11121154 },
···2626 | "deno"
2727 | "sprites"
2828 | "modal"
2929- | "e2b";
2929+ | "e2b"
3030+ | "blaxel"
3131+ | "hopx"
3232+ | "runloop";
3033 /** A list of topics/tags to associate with the sandbox */
3134 topics?: string[];
3235 /** A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo. */
···7780 e2bApiKey?: string;
7881 /** A redacted API KEY for accessing E2B resources */
7982 redactedE2bApiKey?: string;
8383+ /** An API KEY (encrypted) for accessing hopx resources */
8484+ hopxApiKey?: string;
8585+ /** A redacted API KEY for accessing hopx resources */
8686+ redactedHopxApiKey?: string;
8787+ /** An API KEY (encrypted) for accessing Runloop resources */
8888+ runloopApiKey?: string;
8989+ /** A redacted API KEY for accessing Runloop resources */
9090+ redactedRunloopApiKey?: string;
8091 [k: string]: unknown;
8192}
8293
···241241 e2bApiKey?: string;
242242 /** The redacted API KEY for E2B, returned in API responses when the sandbox provider is E2B. This can be used to identify which E2B API KEY is being used without exposing the actual API KEY. */
243243 redactedE2bApiKey?: string;
244244+ /** The API KEY for hopx, if the sandbox provider is hopx. This is used to determine which hopx API KEY to use when creating the sandbox. */
245245+ hopxApiKey?: string;
246246+ /** The redacted API KEY for hopx, returned in API responses when the sandbox provider is hopx. This can be used to identify which hopx API KEY is being used without exposing the actual API KEY. */
247247+ redactedHopxApiKey?: string;
248248+ /** The API KEY for Runloop, if the sandbox provider is Runloop. This is used to determine which Runloop API KEY to use when creating the sandbox. */
249249+ runloopApiKey?: string;
250250+ /** The redacted API KEY for Runloop, returned in API responses when the sandbox provider is Runloop. This can be used to identify which Runloop API KEY is being used without exposing the actual API KEY. */
251251+ redactedRunloopApiKey?: string;
244252 [k: string]: unknown;
245253}
246254
+5-1
apps/api/src/pty/e2b/index.ts
···66import decrypt from "lib/decrypt";
77import type { Message } from "pty/pty-tunnel/messages";
8899-export async function createTerminalSession(ctx: Context, id: string, key = id): Promise<Session> {
99+export async function createTerminalSession(
1010+ ctx: Context,
1111+ id: string,
1212+ key = id,
1313+): Promise<Session> {
1014 const [record] = await ctx.db
1115 .select()
1216 .from(schema.sandboxes)
+30-8
apps/api/src/pty/modal/index.ts
···22import { eq, or } from "drizzle-orm";
33import schema from "schema";
44import { consola } from "consola";
55-import { ContainerProcess, ModalClient, Sandbox } from "modal";
55+import { type ContainerProcess, ModalClient, type Sandbox } from "modal";
66import decrypt from "lib/decrypt";
77import { createListener } from "pty/pty-tunnel";
88import chalk from "chalk";
···4040 const sandbox = await modal.sandboxes.fromId(options.id);
4141 consola.info("Modal: sandbox fetched", chalk.greenBright(options.id));
42424343- consola.info("Modal: checking pty-tunnel-server", chalk.greenBright(options.id));
4343+ consola.info(
4444+ "Modal: checking pty-tunnel-server",
4545+ chalk.greenBright(options.id),
4646+ );
4447 if (!(await checkIfServerInstalled(sandbox))) {
4548 await $`bash -c "type /tmp/${SERVER_BIN_NAME} || curl -L ${PTY_SERVER_DOWNLOAD_URL} | tar xz -C /tmp"`;
4649···9295 },
9396 );
94979595- consola.info("Modal: pty-tunnel-server process started", chalk.greenBright(options.id));
9898+ consola.info(
9999+ "Modal: pty-tunnel-server process started",
100100+ chalk.greenBright(options.id),
101101+ );
9610297103 return { sandbox, cmd };
98104}
99105100100-export async function createTerminalSession(ctx: Context, id: string, key = id) {
106106+export async function createTerminalSession(
107107+ ctx: Context,
108108+ id: string,
109109+ key = id,
110110+) {
101111 const [record] = await ctx.db
102112 .select()
103113 .from(schema.sandboxes)
···161171 throw new Error(`PTY port ${PTY_PORT} not found in sandbox tunnels`);
162172 }
163173164164- consola.info("Modal: awaiting pty-tunnel connection info", chalk.greenBright(id));
174174+ consola.info(
175175+ "Modal: awaiting pty-tunnel connection info",
176176+ chalk.greenBright(id),
177177+ );
165178 const details = await listener.connection;
166166- consola.info("Modal: pty-tunnel connection info received", chalk.greenBright(id));
179179+ consola.info(
180180+ "Modal: pty-tunnel connection info received",
181181+ chalk.greenBright(id),
182182+ );
167183168184 const url = `wss://${port.url.replace(/^https?:\/\//, "")}` as const;
169185 consola.info("Connecting to WebSocket URL:", url);
···196212 session.wsClients.clear();
197213 });
198214199199- consola.info("Modal: waiting for pty-tunnel socket to open", chalk.greenBright(id));
215215+ consola.info(
216216+ "Modal: waiting for pty-tunnel socket to open",
217217+ chalk.greenBright(id),
218218+ );
200219 await socket.waitForOpen();
201201- consola.info("Modal: pty-tunnel socket open, sending ready", chalk.greenBright(id));
220220+ consola.info(
221221+ "Modal: pty-tunnel socket open, sending ready",
222222+ chalk.greenBright(id),
223223+ );
202224 socket.sendMessage({ type: "ready" });
203225204226 ctx.sessions.set(key, session);
···11+CREATE TABLE "hopx_auth" (
22+ "id" text PRIMARY KEY DEFAULT xata_id() NOT NULL,
33+ "sandbox_id" text NOT NULL,
44+ "user_id" text NOT NULL,
55+ "api_key" text NOT NULL,
66+ "redacted_api_key" text NOT NULL,
77+ "created_at" timestamp DEFAULT now() NOT NULL
88+);
99+--> statement-breakpoint
1010+CREATE TABLE "runloop_auth" (
1111+ "id" text PRIMARY KEY DEFAULT xata_id() NOT NULL,
1212+ "sandbox_id" text NOT NULL,
1313+ "user_id" text NOT NULL,
1414+ "api_key" text NOT NULL,
1515+ "redacted_api_key" text NOT NULL,
1616+ "created_at" timestamp DEFAULT now() NOT NULL
1717+);
1818+--> statement-breakpoint
1919+ALTER TABLE "hopx_auth" ADD CONSTRAINT "hopx_auth_sandbox_id_sandboxes_id_fk" FOREIGN KEY ("sandbox_id") REFERENCES "public"."sandboxes"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
2020+ALTER TABLE "hopx_auth" ADD CONSTRAINT "hopx_auth_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
2121+ALTER TABLE "runloop_auth" ADD CONSTRAINT "runloop_auth_sandbox_id_sandboxes_id_fk" FOREIGN KEY ("sandbox_id") REFERENCES "public"."sandboxes"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
2222+ALTER TABLE "runloop_auth" ADD CONSTRAINT "runloop_auth_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
2323+CREATE UNIQUE INDEX "unique_hopx_auth" ON "hopx_auth" USING btree ("sandbox_id","user_id");--> statement-breakpoint
2424+CREATE UNIQUE INDEX "unique_runloop_auth" ON "runloop_auth" USING btree ("sandbox_id","user_id");