Add Agent Computer provider

Add an Agent Computer provider to the TypeScript SDK, wire in provider-specific inspector URLs, and document the new deploy flow with an example package.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Harivansh Rathi 2026-03-26 14:59:01 -04:00
parent bf484e7c96
commit e7d31de44f
17 changed files with 729 additions and 6 deletions

View file

@ -83,12 +83,24 @@ vi.mock("@fly/sprites", () => ({
import { e2b } from "../src/providers/e2b.ts";
import { modal } from "../src/providers/modal.ts";
import { computesdk } from "../src/providers/computesdk.ts";
import { agentcomputer } from "../src/providers/agentcomputer.ts";
import { sprites } from "../src/providers/sprites.ts";
function createFetch(): typeof fetch {
return async () => new Response(null, { status: 200 });
}
function jsonResponse(body: unknown, init?: ResponseInit): Response {
return new Response(JSON.stringify(body), {
status: 200,
...init,
headers: {
"content-type": "application/json",
...(init?.headers ?? {}),
},
});
}
function createBaseProvider(overrides: Partial<SandboxProvider> = {}): SandboxProvider {
return {
name: "mock",
@ -203,6 +215,27 @@ describe("SandboxAgent provider lifecycle", () => {
await killed.killSandbox();
expect(kill).toHaveBeenCalledWith("created");
});
it("uses provider-specific inspector URLs when provided", async () => {
const provider = createBaseProvider({
async getUrl(): Promise<string> {
return "https://sandbox.example";
},
async getInspectorUrl(): Promise<string> {
return "https://sandbox.example/ui/?access_token=test-token";
},
});
const sdk = await SandboxAgent.start({
sandbox: provider,
skipHealthCheck: true,
fetch: createFetch(),
});
expect(sdk.inspectorUrl).toBe("https://sandbox.example/ui/?access_token=test-token");
await sdk.killSandbox();
});
});
describe("e2b provider", () => {
@ -374,6 +407,85 @@ describe("computesdk provider", () => {
});
});
describe("agentcomputer provider", () => {
it("creates managed-worker computers with platform defaults and exposes an auth-ready inspector URL", async () => {
const fetchMock = vi.fn<typeof fetch>();
fetchMock
.mockResolvedValueOnce(jsonResponse({ id: "cmp_123", status: "pending" }, { status: 201 }))
.mockResolvedValueOnce(jsonResponse({ id: "cmp_123", status: "pending" }))
.mockResolvedValueOnce(jsonResponse({ id: "cmp_123", status: "starting" }))
.mockResolvedValueOnce(jsonResponse({ connection: { web_url: "https://box.agentcomputer.example" } }))
.mockResolvedValueOnce(
jsonResponse({
access_url: "https://box.agentcomputer.example?access_token=browser-token",
expires_at: "2099-01-01T00:00:00Z",
}),
)
.mockResolvedValueOnce(new Response(JSON.stringify({ status: "ok" }), { status: 200, headers: { "content-type": "application/json" } }))
.mockResolvedValueOnce(new Response(JSON.stringify({ status: "ok" }), { status: 200, headers: { "content-type": "application/json" } }))
.mockResolvedValueOnce(new Response(null, { status: 204 }));
const sdk = await SandboxAgent.start({
sandbox: agentcomputer({
apiKey: "ac_live_test",
fetch: fetchMock,
pollIntervalMs: 0,
}),
});
expect(sdk.sandboxId).toBe("agentcomputer/cmp_123");
expect(sdk.inspectorUrl).toBe("https://box.agentcomputer.example/ui/?access_token=browser-token");
const health = await sdk.getHealth();
expect(health.status).toBe("ok");
expect(fetchMock).toHaveBeenNthCalledWith(
1,
"https://api.computer.agentcomputer.ai/v1/computers",
expect.objectContaining({
method: "POST",
body: JSON.stringify({
runtime_family: "managed-worker",
use_platform_default: true,
}),
}),
);
expect(fetchMock).toHaveBeenNthCalledWith(
4,
"https://api.computer.agentcomputer.ai/v1/computers/cmp_123/connection",
expect.objectContaining({
headers: expect.any(Headers),
}),
);
const browserAccessRequest = fetchMock.mock.calls[4];
expect(browserAccessRequest?.[0]).toBe("https://api.computer.agentcomputer.ai/v1/computers/cmp_123/access/browser");
const sandboxHealthRequest = fetchMock.mock.calls[5]?.[0];
expect(sandboxHealthRequest).toBeInstanceOf(Request);
const sandboxHealthHeaders = new Headers((sandboxHealthRequest as Request).headers);
expect(sandboxHealthHeaders.get("cookie")).toContain("agentcomputer_access_session=browser-token");
await sdk.killSandbox();
});
it("maps missing computers to SandboxDestroyedError during reconnect", async () => {
const fetchMock = vi.fn<typeof fetch>().mockResolvedValue(
new Response(JSON.stringify({ error: "not found" }), {
status: 404,
headers: { "content-type": "application/json" },
}),
);
const provider = agentcomputer({
apiKey: "ac_live_test",
fetch: fetchMock,
});
await expect(provider.reconnect?.("cmp_missing")).rejects.toBeInstanceOf(SandboxDestroyedError);
});
});
describe("sprites provider", () => {
it("creates a sprite, installs sandbox-agent, and configures the managed service", async () => {
const sprite = {

View file

@ -12,6 +12,7 @@ import { local } from "../src/providers/local.ts";
import { docker } from "../src/providers/docker.ts";
import { e2b } from "../src/providers/e2b.ts";
import { daytona } from "../src/providers/daytona.ts";
import { agentcomputer } from "../src/providers/agentcomputer.ts";
import { vercel } from "../src/providers/vercel.ts";
import { modal } from "../src/providers/modal.ts";
import { computesdk } from "../src/providers/computesdk.ts";
@ -212,6 +213,21 @@ function buildProviders(): ProviderEntry[] {
});
}
// --- agentcomputer ---
{
entries.push({
name: "agentcomputer",
skipReasons: missingAnyEnvVars("COMPUTER_API_KEY", "AGENTCOMPUTER_API_KEY"),
agent: "claude",
startTimeoutMs: 300_000,
canVerifyDestroyedSandbox: false,
sessionTestsEnabled: false,
createProvider() {
return agentcomputer();
},
});
}
// --- vercel ---
{
entries.push({