Refactor Foundry GitHub and sandbox flows

This commit is contained in:
Nathan Flurry 2026-03-12 10:51:33 -07:00
parent 4bccd5fc8d
commit ec8e816d0d
112 changed files with 4026 additions and 2715 deletions

View file

@ -1,11 +1,24 @@
import { describe, expect, it } from "vitest";
import { afterEach, describe, expect, it } from "vitest";
import type { DaytonaClientLike, DaytonaDriver } from "../src/driver.js";
import type { DaytonaCreateSandboxOptions } from "../src/integrations/daytona/client.js";
import { DaytonaProvider } from "../src/providers/daytona/index.js";
interface RecordedFetchCall {
url: string;
method: string;
headers: Record<string, string>;
bodyText?: string;
}
class RecordingDaytonaClient implements DaytonaClientLike {
createSandboxCalls: DaytonaCreateSandboxOptions[] = [];
executedCommands: string[] = [];
getPreviewEndpointCalls: Array<{ sandboxId: string; port: number }> = [];
executeCommandCalls: Array<{
sandboxId: string;
command: string;
env?: Record<string, string>;
timeoutSeconds?: number;
}> = [];
async createSandbox(options: DaytonaCreateSandboxOptions) {
this.createSandboxCalls.push(options);
@ -32,17 +45,21 @@ class RecordingDaytonaClient implements DaytonaClientLike {
async deleteSandbox(_sandboxId: string) {}
async executeCommand(_sandboxId: string, command: string) {
this.executedCommands.push(command);
return { exitCode: 0, result: "" };
}
async getPreviewEndpoint(sandboxId: string, port: number) {
this.getPreviewEndpointCalls.push({ sandboxId, port });
return {
url: `https://preview.example/sandbox/${sandboxId}/port/${port}`,
token: "preview-token",
};
}
async executeCommand(sandboxId: string, command: string, env?: Record<string, string>, timeoutSeconds?: number) {
this.executeCommandCalls.push({ sandboxId, command, env, timeoutSeconds });
return {
exitCode: 0,
result: "",
};
}
}
function createProviderWithClient(client: DaytonaClientLike): DaytonaProvider {
@ -59,79 +76,159 @@ function createProviderWithClient(client: DaytonaClientLike): DaytonaProvider {
);
}
function withFetchStub(implementation: (call: RecordedFetchCall) => Response | Promise<Response>): () => void {
const previous = globalThis.fetch;
globalThis.fetch = (async (input, init) => {
const headers = new Headers(init?.headers);
const headerRecord: Record<string, string> = {};
headers.forEach((value, key) => {
headerRecord[key] = value;
});
const bodyText = typeof init?.body === "string" ? init.body : init?.body instanceof Uint8Array ? Buffer.from(init.body).toString("utf8") : undefined;
return await implementation({
url: typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url,
method: init?.method ?? "GET",
headers: headerRecord,
bodyText,
});
}) as typeof fetch;
return () => {
globalThis.fetch = previous;
};
}
afterEach(() => {
delete process.env.HF_SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS;
delete process.env.HF_DAYTONA_REQUEST_TIMEOUT_MS;
});
describe("daytona provider snapshot image behavior", () => {
it("creates sandboxes using a snapshot-capable image recipe", async () => {
it("creates sandboxes using a snapshot-capable image recipe and clones via sandbox-agent process api", async () => {
const client = new RecordingDaytonaClient();
const provider = createProviderWithClient(client);
const fetchCalls: RecordedFetchCall[] = [];
const restoreFetch = withFetchStub(async (call) => {
fetchCalls.push(call);
const handle = await provider.createSandbox({
workspaceId: "default",
repoId: "repo-1",
repoRemote: "https://github.com/acme/repo.git",
branchName: "feature/test",
taskId: "task-1",
if (call.url.endsWith("/v1/health")) {
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (call.url.endsWith("/v1/processes/run")) {
return new Response(JSON.stringify({ exitCode: 0, stdout: "", stderr: "" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${call.method} ${call.url}`);
});
expect(client.createSandboxCalls).toHaveLength(1);
const createCall = client.createSandboxCalls[0];
if (!createCall) {
throw new Error("expected create sandbox call");
try {
const handle = await provider.createSandbox({
workspaceId: "default",
repoId: "repo-1",
repoRemote: "https://github.com/acme/repo.git",
branchName: "feature/test",
taskId: "task-1",
githubToken: "github-token",
});
expect(client.createSandboxCalls).toHaveLength(1);
const createCall = client.createSandboxCalls[0];
if (!createCall) {
throw new Error("expected create sandbox call");
}
expect(typeof createCall.image).not.toBe("string");
if (typeof createCall.image === "string") {
throw new Error("expected daytona image recipe object");
}
const dockerfile = createCall.image.dockerfile;
expect(dockerfile).toContain("apt-get install -y curl ca-certificates git openssh-client");
expect(dockerfile).toContain("deb.nodesource.com/setup_20.x");
expect(dockerfile).toContain("apt-get install -y nodejs");
expect(dockerfile).toContain("sandbox-agent/0.3.0/install.sh");
expect(dockerfile).toContain("sandbox-agent install-agent codex; sandbox-agent install-agent claude");
expect(dockerfile).not.toContain("|| true");
expect(dockerfile).not.toContain("ENTRYPOINT [");
expect(client.getPreviewEndpointCalls).toEqual([{ sandboxId: "sandbox-1", port: 2468 }]);
expect(client.executeCommandCalls).toHaveLength(1);
expect(client.executeCommandCalls[0]?.sandboxId).toBe("sandbox-1");
expect(client.executeCommandCalls[0]?.command).toContain("nohup sandbox-agent server --no-token --host 0.0.0.0 --port 2468");
expect(fetchCalls.map((call) => `${call.method} ${call.url}`)).toEqual([
"GET https://preview.example/sandbox/sandbox-1/port/2468/v1/health",
"POST https://preview.example/sandbox/sandbox-1/port/2468/v1/processes/run",
]);
const runCall = fetchCalls[1];
if (!runCall?.bodyText) {
throw new Error("expected process run request body");
}
const runBody = JSON.parse(runCall.bodyText) as {
command: string;
args: string[];
env?: Record<string, string>;
};
expect(runBody.command).toBe("bash");
expect(runBody.args).toHaveLength(2);
expect(runBody.args[0]).toBe("-lc");
expect(runBody.env).toEqual({
GITHUB_TOKEN: "github-token",
});
expect(runBody.args[1]).toContain("GIT_TERMINAL_PROMPT=0");
expect(runBody.args[1]).toContain('AUTH_REMOTE="$REMOTE"');
expect(runBody.args[1]).toContain('git clone "$AUTH_REMOTE"');
expect(runBody.args[1]).toContain('AUTH_HEADER="$(printf');
expect(handle.metadata.snapshot).toBe("snapshot-foundry");
expect(handle.metadata.image).toBe("ubuntu:24.04");
expect(handle.metadata.cwd).toBe("/home/daytona/foundry/default/repo-1/task-1/repo");
} finally {
restoreFetch();
}
expect(typeof createCall.image).not.toBe("string");
if (typeof createCall.image === "string") {
throw new Error("expected daytona image recipe object");
}
const dockerfile = createCall.image.dockerfile;
expect(dockerfile).toContain("apt-get install -y curl ca-certificates git openssh-client nodejs npm");
expect(dockerfile).toContain("sandbox-agent/0.3.0/install.sh");
const installAgentLines = dockerfile.match(/sandbox-agent install-agent [a-z0-9-]+/gi) ?? [];
expect(installAgentLines.length).toBeGreaterThanOrEqual(2);
const commands = client.executedCommands.join("\n");
expect(commands).toContain("GIT_TERMINAL_PROMPT=0");
expect(commands).toContain("GIT_ASKPASS=/bin/echo");
expect(handle.metadata.snapshot).toBe("snapshot-foundry");
expect(handle.metadata.image).toBe("ubuntu:24.04");
expect(handle.metadata.cwd).toBe("/home/daytona/foundry/default/repo-1/task-1/repo");
expect(client.executedCommands.length).toBeGreaterThan(0);
});
it("starts sandbox-agent with ACP timeout env override", async () => {
const previous = process.env.HF_SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS;
it("ensures sandbox-agent by checking health through the preview endpoint", async () => {
process.env.HF_SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS = "240000";
try {
const client = new RecordingDaytonaClient();
const provider = createProviderWithClient(client);
const client = new RecordingDaytonaClient();
const provider = createProviderWithClient(client);
const fetchCalls: RecordedFetchCall[] = [];
const restoreFetch = withFetchStub(async (call) => {
fetchCalls.push(call);
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
});
await provider.ensureSandboxAgent({
try {
const endpoint = await provider.ensureSandboxAgent({
workspaceId: "default",
sandboxId: "sandbox-1",
});
const startCommand = client.executedCommands.find((command) =>
command.includes("nohup env SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS=240000 sandbox-agent server"),
);
const joined = client.executedCommands.join("\n");
expect(joined).toContain("sandbox-agent/0.3.0/install.sh");
expect(joined).toContain("SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS=240000");
expect(joined).toContain("apt-get install -y nodejs npm");
expect(joined).toContain("sandbox-agent server --no-token --host 0.0.0.0 --port 2468");
expect(startCommand).toBeTruthy();
expect(endpoint).toEqual({
endpoint: "https://preview.example/sandbox/sandbox-1/port/2468",
token: "preview-token",
});
expect(client.executeCommandCalls).toHaveLength(1);
expect(client.executeCommandCalls[0]?.command).toContain("nohup sandbox-agent server --no-token --host 0.0.0.0 --port 2468");
expect(client.getPreviewEndpointCalls).toEqual([{ sandboxId: "sandbox-1", port: 2468 }]);
expect(fetchCalls.map((call) => `${call.method} ${call.url}`)).toEqual(["GET https://preview.example/sandbox/sandbox-1/port/2468/v1/health"]);
} finally {
if (previous === undefined) {
delete process.env.HF_SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS;
} else {
process.env.HF_SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS = previous;
}
restoreFetch();
}
});
it("fails with explicit timeout when daytona createSandbox hangs", async () => {
const previous = process.env.HF_DAYTONA_REQUEST_TIMEOUT_MS;
process.env.HF_DAYTONA_REQUEST_TIMEOUT_MS = "120";
const hangingClient: DaytonaClientLike = {
@ -140,13 +237,20 @@ describe("daytona provider snapshot image behavior", () => {
startSandbox: async () => {},
stopSandbox: async () => {},
deleteSandbox: async () => {},
executeCommand: async () => ({ exitCode: 0, result: "" }),
getPreviewEndpoint: async (sandboxId, port) => ({
url: `https://preview.example/sandbox/${sandboxId}/port/${port}`,
token: "preview-token",
}),
executeCommand: async () => ({
exitCode: 0,
result: "",
}),
};
const restoreFetch = withFetchStub(async () => {
throw new Error("unexpected fetch");
});
try {
const provider = createProviderWithClient(hangingClient);
await expect(
@ -159,26 +263,64 @@ describe("daytona provider snapshot image behavior", () => {
}),
).rejects.toThrow("daytona create sandbox timed out after 120ms");
} finally {
if (previous === undefined) {
delete process.env.HF_DAYTONA_REQUEST_TIMEOUT_MS;
} else {
process.env.HF_DAYTONA_REQUEST_TIMEOUT_MS = previous;
}
restoreFetch();
}
});
it("executes backend-managed sandbox commands through provider API", async () => {
it("executes backend-managed sandbox commands through sandbox-agent process api", async () => {
const client = new RecordingDaytonaClient();
const provider = createProviderWithClient(client);
const fetchCalls: RecordedFetchCall[] = [];
const restoreFetch = withFetchStub(async (call) => {
fetchCalls.push(call);
const result = await provider.executeCommand({
workspaceId: "default",
sandboxId: "sandbox-1",
command: "echo backend-push",
label: "manual push",
if (call.url.endsWith("/v1/health")) {
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (call.url.endsWith("/v1/processes/run")) {
return new Response(JSON.stringify({ exitCode: 0, stdout: "backend-push\n", stderr: "" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${call.method} ${call.url}`);
});
expect(result.exitCode).toBe(0);
expect(client.executedCommands).toContain("echo backend-push");
try {
const result = await provider.executeCommand({
workspaceId: "default",
sandboxId: "sandbox-1",
command: "echo backend-push",
env: { GITHUB_TOKEN: "user-token" },
label: "manual push",
});
expect(result.exitCode).toBe(0);
expect(result.result).toBe("backend-push\n");
expect(fetchCalls.map((call) => `${call.method} ${call.url}`)).toEqual([
"GET https://preview.example/sandbox/sandbox-1/port/2468/v1/health",
"POST https://preview.example/sandbox/sandbox-1/port/2468/v1/processes/run",
]);
const runCall = fetchCalls[1];
if (!runCall?.bodyText) {
throw new Error("expected process run body");
}
const runBody = JSON.parse(runCall.bodyText) as {
command: string;
args: string[];
env?: Record<string, string>;
};
expect(runBody.command).toBe("bash");
expect(runBody.args).toEqual(["-lc", "echo backend-push"]);
expect(runBody.env).toEqual({ GITHUB_TOKEN: "user-token" });
} finally {
restoreFetch();
}
});
});