From 4e76038a0da49b5d27095bcbfc4099ba0c2ab382 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 17 Mar 2026 15:25:21 -0700 Subject: [PATCH 1/2] feat(providers): add base image support and improve forward compatibility Add support for configuring base images across all compute providers: - E2B: Accept optional `template` parameter to select custom templates - Modal: Accept optional `image` parameter (string or Image object) for base images - ComputeSDK: Expand `create` override to accept full CreateSandboxOptions payload (image, templateId, etc.) - Daytona: Improve type safety for `image` option Improve forward compatibility by making all `create` overrides accept full Partial SDK types, allowing any new provider fields to flow through without code changes. Fix Modal provider bug where `encryptedPorts` was hardcoded and would clobber user-provided values; now merges additional ports instead. Update docs and examples to demonstrate base image configuration for E2B, Modal, and ComputeSDK. Add comprehensive provider lifecycle tests for Modal and ComputeSDK, including template and image passthrough verification. Co-Authored-By: Claude Haiku 4.5 --- docs/deploy/computesdk.mdx | 7 +- docs/deploy/e2b.mdx | 7 +- docs/deploy/modal.mdx | 3 + examples/e2b/src/e2b.ts | 2 + examples/e2b/src/index.ts | 3 +- sdks/typescript/src/providers/computesdk.ts | 18 +- sdks/typescript/src/providers/daytona.ts | 2 +- sdks/typescript/src/providers/e2b.ts | 17 +- sdks/typescript/src/providers/modal.ts | 51 ++++-- .../tests/provider-lifecycle.test.ts | 166 +++++++++++++++++- 10 files changed, 242 insertions(+), 34 deletions(-) diff --git a/docs/deploy/computesdk.mdx b/docs/deploy/computesdk.mdx index 1adfffe..65000a6 100644 --- a/docs/deploy/computesdk.mdx +++ b/docs/deploy/computesdk.mdx @@ -27,7 +27,11 @@ if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY const sdk = await SandboxAgent.start({ sandbox: computesdk({ - create: { envs }, + create: { + envs, + image: process.env.COMPUTESDK_IMAGE, + templateId: process.env.COMPUTESDK_TEMPLATE_ID, + }, }), }); @@ -43,6 +47,7 @@ try { ``` The `computesdk` provider handles sandbox creation, Sandbox Agent installation, agent setup, and server startup automatically. ComputeSDK routes to your configured provider behind the scenes. +The `create` option now forwards the full ComputeSDK sandbox-create payload, including provider-specific fields such as `image` and `templateId` when the selected provider supports them. Before calling `SandboxAgent.start()`, configure ComputeSDK with your provider: diff --git a/docs/deploy/e2b.mdx b/docs/deploy/e2b.mdx index e6465f2..30354a0 100644 --- a/docs/deploy/e2b.mdx +++ b/docs/deploy/e2b.mdx @@ -21,9 +21,11 @@ import { e2b } from "sandbox-agent/e2b"; const envs: Record = {}; if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY; +const template = process.env.E2B_TEMPLATE; const sdk = await SandboxAgent.start({ sandbox: e2b({ + template, create: { envs }, }), }); @@ -41,7 +43,10 @@ try { The `e2b` provider handles sandbox creation, Sandbox Agent installation, agent setup, and server startup automatically. Sandboxes pause by default instead of being deleted, and reconnecting with the same `sandboxId` resumes them automatically. +Pass `template` when you want to start from a custom E2B template alias or template ID. E2B base-image selection happens when you build the template, then `sandbox-agent/e2b` uses that template at sandbox creation time. + ## Faster cold starts For faster startup, create a custom E2B template with Sandbox Agent and target agents pre-installed. -See [E2B Custom Templates](https://e2b.dev/docs/sandbox-template). +Build System 2.0 also lets you choose the template's base image in code. +See [E2B Custom Templates](https://e2b.dev/docs/sandbox-template) and [E2B Base Images](https://e2b.dev/docs/template/base-image). diff --git a/docs/deploy/modal.mdx b/docs/deploy/modal.mdx index 02a3828..dffd622 100644 --- a/docs/deploy/modal.mdx +++ b/docs/deploy/modal.mdx @@ -21,9 +21,11 @@ import { modal } from "sandbox-agent/modal"; const secrets: Record = {}; if (process.env.ANTHROPIC_API_KEY) secrets.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; if (process.env.OPENAI_API_KEY) secrets.OPENAI_API_KEY = process.env.OPENAI_API_KEY; +const baseImage = process.env.MODAL_BASE_IMAGE ?? "node:22-slim"; const sdk = await SandboxAgent.start({ sandbox: modal({ + image: baseImage, create: { secrets }, }), }); @@ -40,6 +42,7 @@ try { ``` The `modal` provider handles app creation, image building, sandbox provisioning, agent installation, server startup, and tunnel networking automatically. +Set `image` to change the base Docker image before Sandbox Agent and its agent binaries are layered on top. You can also pass a prebuilt Modal `Image` object. ## Faster cold starts diff --git a/examples/e2b/src/e2b.ts b/examples/e2b/src/e2b.ts index bfd5bda..17762a2 100644 --- a/examples/e2b/src/e2b.ts +++ b/examples/e2b/src/e2b.ts @@ -17,8 +17,10 @@ export async function setupE2BSandboxAgent(): Promise<{ token?: string; cleanup: () => Promise; }> { + const template = process.env.E2B_TEMPLATE; const client = await SandboxAgent.start({ sandbox: e2b({ + template, create: { envs: collectEnvVars() }, }), }); diff --git a/examples/e2b/src/index.ts b/examples/e2b/src/index.ts index c20ebaa..6eb79b7 100644 --- a/examples/e2b/src/index.ts +++ b/examples/e2b/src/index.ts @@ -5,10 +5,11 @@ import { detectAgent } from "@sandbox-agent/example-shared"; const envs: Record = {}; if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY; +const template = process.env.E2B_TEMPLATE; const client = await SandboxAgent.start({ // ✨ NEW ✨ - sandbox: e2b({ create: { envs } }), + sandbox: e2b({ template, create: { envs } }), }); const session = await client.createSession({ diff --git a/sdks/typescript/src/providers/computesdk.ts b/sdks/typescript/src/providers/computesdk.ts index 7bca7ca..8cbef53 100644 --- a/sdks/typescript/src/providers/computesdk.ts +++ b/sdks/typescript/src/providers/computesdk.ts @@ -1,25 +1,31 @@ -import { compute } from "computesdk"; +import { compute, type CreateSandboxOptions } from "computesdk"; import type { SandboxProvider } from "./types.ts"; import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT } from "./shared.ts"; const DEFAULT_AGENT_PORT = 3000; +type ComputeCreateOverrides = Partial; + export interface ComputeSdkProviderOptions { - create?: { - envs?: Record; - }; + create?: ComputeCreateOverrides | (() => ComputeCreateOverrides | Promise); agentPort?: number; } +async function resolveCreateOptions(value: ComputeSdkProviderOptions["create"]): Promise { + if (!value) return {}; + return typeof value === "function" ? await value() : value; +} + export function computesdk(options: ComputeSdkProviderOptions = {}): SandboxProvider { const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT; return { name: "computesdk", async create(): Promise { - const envs = options.create?.envs; + const createOpts = await resolveCreateOptions(options.create); const sandbox = await compute.sandbox.create({ - envs: envs && Object.keys(envs).length > 0 ? envs : undefined, + ...createOpts, + envs: createOpts.envs && Object.keys(createOpts.envs).length > 0 ? createOpts.envs : undefined, }); const run = async (cmd: string, runOptions?: { background?: boolean }) => { diff --git a/sdks/typescript/src/providers/daytona.ts b/sdks/typescript/src/providers/daytona.ts index 19026de..f614faf 100644 --- a/sdks/typescript/src/providers/daytona.ts +++ b/sdks/typescript/src/providers/daytona.ts @@ -11,7 +11,7 @@ type DaytonaCreateOverrides = Partial; export interface DaytonaProviderOptions { create?: DaytonaCreateOverrides | (() => DaytonaCreateOverrides | Promise); - image?: string; + image?: DaytonaCreateParams["image"]; agentPort?: number; previewTtlSeconds?: number; deleteTimeoutSeconds?: number; diff --git a/sdks/typescript/src/providers/e2b.ts b/sdks/typescript/src/providers/e2b.ts index 8e99c64..54d2e28 100644 --- a/sdks/typescript/src/providers/e2b.ts +++ b/sdks/typescript/src/providers/e2b.ts @@ -8,10 +8,12 @@ const DEFAULT_TIMEOUT_MS = 3_600_000; type E2BCreateOverrides = Omit, "timeoutMs" | "autoPause">; type E2BConnectOverrides = Omit, "timeoutMs">; +type E2BTemplateOverride = string | (() => string | Promise); export interface E2BProviderOptions { create?: E2BCreateOverrides | (() => E2BCreateOverrides | Promise); connect?: E2BConnectOverrides | ((sandboxId: string) => E2BConnectOverrides | Promise); + template?: E2BTemplateOverride; agentPort?: number; timeoutMs?: number; autoPause?: boolean; @@ -28,6 +30,11 @@ async function resolveOptions(value: E2BProviderOptions["create"] | E2BProviderO return value; } +async function resolveTemplate(value: E2BTemplateOverride | undefined): Promise { + if (!value) return undefined; + return typeof value === "function" ? await value() : value; +} + export function e2b(options: E2BProviderOptions = {}): SandboxProvider { const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT; const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS; @@ -37,8 +44,16 @@ export function e2b(options: E2BProviderOptions = {}): SandboxProvider { name: "e2b", async create(): Promise { const createOpts = await resolveOptions(options.create); + const rawTemplate = typeof createOpts.template === "string" ? createOpts.template : undefined; + const restCreateOpts = { ...createOpts }; + delete restCreateOpts.template; + const template = (await resolveTemplate(options.template)) ?? rawTemplate; // eslint-disable-next-line @typescript-eslint/no-explicit-any - const sandbox = await Sandbox.betaCreate({ allowInternetAccess: true, ...createOpts, timeoutMs, autoPause } as any); + const sandbox = template + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + await Sandbox.betaCreate(template, { allowInternetAccess: true, ...restCreateOpts, timeoutMs, autoPause } as any) + : // eslint-disable-next-line @typescript-eslint/no-explicit-any + await Sandbox.betaCreate({ allowInternetAccess: true, ...restCreateOpts, timeoutMs, autoPause } as any); await sandbox.commands.run(`curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`).then((r) => { if (r.exitCode !== 0) throw new Error(`e2b install failed:\n${r.stderr}`); diff --git a/sdks/typescript/src/providers/modal.ts b/sdks/typescript/src/providers/modal.ts index 394272b..4e193c8 100644 --- a/sdks/typescript/src/providers/modal.ts +++ b/sdks/typescript/src/providers/modal.ts @@ -1,49 +1,64 @@ -import { ModalClient } from "modal"; +import { ModalClient, type Image, type SandboxCreateParams } from "modal"; import type { SandboxProvider } from "./types.ts"; import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT } from "./shared.ts"; const DEFAULT_AGENT_PORT = 3000; const DEFAULT_APP_NAME = "sandbox-agent"; const DEFAULT_MEMORY_MIB = 2048; +const DEFAULT_BASE_IMAGE = "node:22-slim"; + +type ModalCreateOverrides = Omit, "secrets" | "encryptedPorts"> & { + secrets?: Record; + encryptedPorts?: number[]; + appName?: string; +}; export interface ModalProviderOptions { - create?: { - secrets?: Record; - appName?: string; - memoryMiB?: number; - }; + create?: ModalCreateOverrides | (() => ModalCreateOverrides | Promise); + image?: string | Image; agentPort?: number; } +async function resolveCreateOptions(value: ModalProviderOptions["create"]): Promise { + if (!value) return {}; + return typeof value === "function" ? await value() : value; +} + export function modal(options: ModalProviderOptions = {}): SandboxProvider { const agentPort = options.agentPort ?? DEFAULT_AGENT_PORT; - const appName = options.create?.appName ?? DEFAULT_APP_NAME; - const memoryMiB = options.create?.memoryMiB ?? DEFAULT_MEMORY_MIB; const client = new ModalClient(); return { name: "modal", async create(): Promise { + const createOpts = await resolveCreateOptions(options.create); + const appName = createOpts.appName ?? DEFAULT_APP_NAME; + const baseImage = options.image ?? DEFAULT_BASE_IMAGE; const app = await client.apps.fromName(appName, { createIfMissing: true }); // Pre-install sandbox-agent and agents in the image so they are cached // across sandbox creates and don't need to be installed at runtime. const installAgentCmds = DEFAULT_AGENTS.map((agent) => `RUN sandbox-agent install-agent ${agent}`); - const image = client.images - .fromRegistry("node:22-slim") - .dockerfileCommands([ - "RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*", - `RUN curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`, - ...installAgentCmds, - ]); + const image = (typeof baseImage === "string" ? client.images.fromRegistry(baseImage) : baseImage).dockerfileCommands([ + "RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*", + `RUN curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`, + ...installAgentCmds, + ]); - const envVars = options.create?.secrets ?? {}; + const envVars = createOpts.secrets ?? {}; const secrets = Object.keys(envVars).length > 0 ? [await client.secrets.fromObject(envVars)] : []; + const sandboxCreateOpts = { ...createOpts }; + delete sandboxCreateOpts.appName; + delete sandboxCreateOpts.secrets; + + const extraPorts = createOpts.encryptedPorts ?? []; + delete sandboxCreateOpts.encryptedPorts; const sb = await client.sandboxes.create(app, image, { - encryptedPorts: [agentPort], + ...sandboxCreateOpts, + encryptedPorts: [agentPort, ...extraPorts], secrets, - memoryMiB, + memoryMiB: sandboxCreateOpts.memoryMiB ?? DEFAULT_MEMORY_MIB, }); // Start the server as a long-running exec process. We intentionally diff --git a/sdks/typescript/tests/provider-lifecycle.test.ts b/sdks/typescript/tests/provider-lifecycle.test.ts index 06c85f5..8c3d397 100644 --- a/sdks/typescript/tests/provider-lifecycle.test.ts +++ b/sdks/typescript/tests/provider-lifecycle.test.ts @@ -16,6 +16,19 @@ const e2bMocks = vi.hoisted(() => { }; }); +const modalMocks = vi.hoisted(() => ({ + appsFromName: vi.fn(), + imageFromRegistry: vi.fn(), + secretFromObject: vi.fn(), + sandboxCreate: vi.fn(), + sandboxFromId: vi.fn(), +})); + +const computeSdkMocks = vi.hoisted(() => ({ + create: vi.fn(), + getById: vi.fn(), +})); + vi.mock("@e2b/code-interpreter", () => ({ NotFoundError: e2bMocks.MockNotFoundError, Sandbox: { @@ -24,7 +37,30 @@ vi.mock("@e2b/code-interpreter", () => ({ }, })); +vi.mock("modal", () => ({ + ModalClient: class MockModalClient { + apps = { fromName: modalMocks.appsFromName }; + images = { fromRegistry: modalMocks.imageFromRegistry }; + secrets = { fromObject: modalMocks.secretFromObject }; + sandboxes = { + create: modalMocks.sandboxCreate, + fromId: modalMocks.sandboxFromId, + }; + }, +})); + +vi.mock("computesdk", () => ({ + compute: { + sandbox: { + create: computeSdkMocks.create, + getById: computeSdkMocks.getById, + }, + }, +})); + import { e2b } from "../src/providers/e2b.ts"; +import { modal } from "../src/providers/modal.ts"; +import { computesdk } from "../src/providers/computesdk.ts"; function createFetch(): typeof fetch { return async () => new Response(null, { status: 200 }); @@ -56,6 +92,26 @@ function createMockSandbox() { }; } +function createMockModalImage() { + return { + dockerfileCommands: vi.fn(function dockerfileCommands() { + return this; + }), + }; +} + +beforeEach(() => { + e2bMocks.betaCreate.mockReset(); + e2bMocks.connect.mockReset(); + modalMocks.appsFromName.mockReset(); + modalMocks.imageFromRegistry.mockReset(); + modalMocks.secretFromObject.mockReset(); + modalMocks.sandboxCreate.mockReset(); + modalMocks.sandboxFromId.mockReset(); + computeSdkMocks.create.mockReset(); + computeSdkMocks.getById.mockReset(); +}); + describe("SandboxAgent provider lifecycle", () => { it("reconnects an existing sandbox before ensureServer", async () => { const order: string[] = []; @@ -124,11 +180,6 @@ describe("SandboxAgent provider lifecycle", () => { }); describe("e2b provider", () => { - beforeEach(() => { - e2bMocks.betaCreate.mockReset(); - e2bMocks.connect.mockReset(); - }); - it("creates sandboxes with betaCreate, autoPause, and the default timeout", async () => { const sandbox = createMockSandbox(); e2bMocks.betaCreate.mockResolvedValue(sandbox); @@ -190,4 +241,109 @@ describe("e2b provider", () => { await expect(provider.reconnect?.("missing-sandbox")).rejects.toBeInstanceOf(SandboxDestroyedError); }); + + it("passes a configured template to betaCreate", async () => { + const sandbox = createMockSandbox(); + e2bMocks.betaCreate.mockResolvedValue(sandbox); + + const provider = e2b({ + template: "my-template", + create: { envs: { ANTHROPIC_API_KEY: "test" } }, + }); + + await provider.create(); + + expect(e2bMocks.betaCreate).toHaveBeenCalledWith( + "my-template", + expect.objectContaining({ + allowInternetAccess: true, + envs: { ANTHROPIC_API_KEY: "test" }, + timeoutMs: 3_600_000, + }), + ); + }); + + it("accepts legacy create.template values from plain JavaScript", async () => { + const sandbox = createMockSandbox(); + e2bMocks.betaCreate.mockResolvedValue(sandbox); + + const provider = e2b({ + create: { template: "legacy-template" } as never, + }); + + await provider.create(); + + expect(e2bMocks.betaCreate).toHaveBeenCalledWith( + "legacy-template", + expect.objectContaining({ + allowInternetAccess: true, + timeoutMs: 3_600_000, + }), + ); + }); +}); + +describe("modal provider", () => { + it("uses the configured base image when building the sandbox image", async () => { + const app = { appId: "app-123" }; + const image = createMockModalImage(); + const sandbox = { + sandboxId: "sbx-modal", + exec: vi.fn(), + }; + + modalMocks.appsFromName.mockResolvedValue(app); + modalMocks.imageFromRegistry.mockReturnValue(image); + modalMocks.sandboxCreate.mockResolvedValue(sandbox); + + const provider = modal({ + image: "python:3.12-slim", + create: { + appName: "custom-app", + secrets: { OPENAI_API_KEY: "test" }, + }, + }); + + await expect(provider.create()).resolves.toBe("sbx-modal"); + + expect(modalMocks.appsFromName).toHaveBeenCalledWith("custom-app", { createIfMissing: true }); + expect(modalMocks.imageFromRegistry).toHaveBeenCalledWith("python:3.12-slim"); + expect(image.dockerfileCommands).toHaveBeenCalled(); + expect(modalMocks.sandboxCreate).toHaveBeenCalledWith( + app, + image, + expect.objectContaining({ + encryptedPorts: [3000], + memoryMiB: 2048, + }), + ); + }); +}); + +describe("computesdk provider", () => { + it("passes image and template options through to compute.sandbox.create", async () => { + const sandbox = { + sandboxId: "sbx-compute", + runCommand: vi.fn(async () => ({ exitCode: 0, stderr: "" })), + }; + computeSdkMocks.create.mockResolvedValue(sandbox); + + const provider = computesdk({ + create: { + envs: { ANTHROPIC_API_KEY: "test" }, + image: "ghcr.io/example/sandbox-agent:latest", + templateId: "tmpl-123", + }, + }); + + await expect(provider.create()).resolves.toBe("sbx-compute"); + + expect(computeSdkMocks.create).toHaveBeenCalledWith( + expect.objectContaining({ + envs: { ANTHROPIC_API_KEY: "test" }, + image: "ghcr.io/example/sandbox-agent:latest", + templateId: "tmpl-123", + }), + ); + }); }); From 524f40ec02b9e0fbd634cc6887d2b5deee425498 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 17 Mar 2026 16:54:20 -0700 Subject: [PATCH 2/2] feat(providers): simplify modal to use published base image The `-full` base image already includes sandbox-agent and all agents pre-installed. Remove redundant apt-get, install script, and install-agent dockerfile commands from the Modal provider. Also allow overriding the default image via SANDBOX_AGENT_IMAGE env var across all providers for testing with different published versions. Co-Authored-By: Claude Opus 4.6 --- sdks/typescript/src/providers/modal.ts | 16 +++++----------- sdks/typescript/src/providers/shared.ts | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/sdks/typescript/src/providers/modal.ts b/sdks/typescript/src/providers/modal.ts index 4e193c8..4d5b39f 100644 --- a/sdks/typescript/src/providers/modal.ts +++ b/sdks/typescript/src/providers/modal.ts @@ -1,11 +1,10 @@ import { ModalClient, type Image, type SandboxCreateParams } from "modal"; import type { SandboxProvider } from "./types.ts"; -import { DEFAULT_AGENTS, SANDBOX_AGENT_INSTALL_SCRIPT } from "./shared.ts"; +import { DEFAULT_SANDBOX_AGENT_IMAGE } from "./shared.ts"; const DEFAULT_AGENT_PORT = 3000; const DEFAULT_APP_NAME = "sandbox-agent"; const DEFAULT_MEMORY_MIB = 2048; -const DEFAULT_BASE_IMAGE = "node:22-slim"; type ModalCreateOverrides = Omit, "secrets" | "encryptedPorts"> & { secrets?: Record; @@ -33,17 +32,12 @@ export function modal(options: ModalProviderOptions = {}): SandboxProvider { async create(): Promise { const createOpts = await resolveCreateOptions(options.create); const appName = createOpts.appName ?? DEFAULT_APP_NAME; - const baseImage = options.image ?? DEFAULT_BASE_IMAGE; + const baseImage = options.image ?? DEFAULT_SANDBOX_AGENT_IMAGE; const app = await client.apps.fromName(appName, { createIfMissing: true }); - // Pre-install sandbox-agent and agents in the image so they are cached - // across sandbox creates and don't need to be installed at runtime. - const installAgentCmds = DEFAULT_AGENTS.map((agent) => `RUN sandbox-agent install-agent ${agent}`); - const image = (typeof baseImage === "string" ? client.images.fromRegistry(baseImage) : baseImage).dockerfileCommands([ - "RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*", - `RUN curl -fsSL ${SANDBOX_AGENT_INSTALL_SCRIPT} | sh`, - ...installAgentCmds, - ]); + // The default `-full` base image already includes sandbox-agent and all + // agents pre-installed, so no additional dockerfile commands are needed. + const image = typeof baseImage === "string" ? client.images.fromRegistry(baseImage) : baseImage; const envVars = createOpts.secrets ?? {}; const secrets = Object.keys(envVars).length > 0 ? [await client.secrets.fromObject(envVars)] : []; diff --git a/sdks/typescript/src/providers/shared.ts b/sdks/typescript/src/providers/shared.ts index c0f7b1c..100b707 100644 --- a/sdks/typescript/src/providers/shared.ts +++ b/sdks/typescript/src/providers/shared.ts @@ -1,4 +1,4 @@ -export const DEFAULT_SANDBOX_AGENT_IMAGE = "rivetdev/sandbox-agent:0.5.0-rc.1-full"; +export const DEFAULT_SANDBOX_AGENT_IMAGE = process.env.SANDBOX_AGENT_IMAGE ?? "rivetdev/sandbox-agent:0.5.0-rc.1-full"; export const SANDBOX_AGENT_INSTALL_SCRIPT = "https://releases.rivet.dev/sandbox-agent/0.3.x/install.sh"; export const DEFAULT_AGENTS = ["claude", "codex"] as const;