From 15c2a6447e33961beef8489351aa32fa7bdda576 Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Fri, 26 Jun 2026 10:31:34 +0200 Subject: [PATCH 1/2] patch(sandbox): send an HTTP header with eve + version Signed-off-by: Marc Codina --- .changeset/sandbox-eve-client-header.md | 5 ++ .../bindings/vercel-client-header.test.ts | 85 +++++++++++++++++++ .../sandbox/bindings/vercel-client-header.ts | 29 +++++++ .../sandbox/bindings/vercel-create-sdk.ts | 2 + .../sandbox/bindings/vercel-credentials.ts | 3 +- 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 .changeset/sandbox-eve-client-header.md create mode 100644 packages/eve/src/execution/sandbox/bindings/vercel-client-header.test.ts create mode 100644 packages/eve/src/execution/sandbox/bindings/vercel-client-header.ts diff --git a/.changeset/sandbox-eve-client-header.md b/.changeset/sandbox-eve-client-header.md new file mode 100644 index 000000000..bf43dd934 --- /dev/null +++ b/.changeset/sandbox-eve-client-header.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Sandbox API requests now send an `x-eve-client: eve/` header. diff --git a/packages/eve/src/execution/sandbox/bindings/vercel-client-header.test.ts b/packages/eve/src/execution/sandbox/bindings/vercel-client-header.test.ts new file mode 100644 index 000000000..daa35d01b --- /dev/null +++ b/packages/eve/src/execution/sandbox/bindings/vercel-client-header.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it, vi } from "vitest"; + +import { + EVE_SANDBOX_CLIENT_HEADER, + withEveSandboxClientHeader, +} from "#execution/sandbox/bindings/vercel-client-header.js"; +import { getVercelSandboxFetch } from "#execution/sandbox/bindings/vercel-credentials.js"; +import { createVercelEveImageSandbox } from "#execution/sandbox/bindings/vercel-create-sdk.js"; +import type { VercelSandboxCreateParams } from "#execution/sandbox/bindings/vercel-create-sdk.js"; +import type { VercelCreateOptions } from "#execution/sandbox/bindings/vercel-sdk-types.js"; + +function headerOf(init: RequestInit | undefined): string | null { + return new Headers(init?.headers).get(EVE_SANDBOX_CLIENT_HEADER); +} + +describe("withEveSandboxClientHeader", () => { + it("stamps the eve client header as eve/", async () => { + const inner = vi.fn().mockResolvedValue(new Response()); + const wrapped = withEveSandboxClientHeader(inner); + + await wrapped("https://api.vercel.com/sandboxes"); + + const [, init] = inner.mock.calls[0]!; + expect(headerOf(init)).toMatch(/^eve\/.+/); + }); + + it("preserves existing headers", async () => { + const inner = vi.fn().mockResolvedValue(new Response()); + const wrapped = withEveSandboxClientHeader(inner); + + await wrapped("https://api.vercel.com/sandboxes", { + headers: { "user-agent": "vercel/sandbox/1.0.0" }, + }); + + const [, init] = inner.mock.calls[0]!; + const headers = new Headers(init?.headers); + expect(headers.get("user-agent")).toBe("vercel/sandbox/1.0.0"); + expect(headers.get(EVE_SANDBOX_CLIENT_HEADER)).toMatch(/^eve\/.+/); + }); + + it("delegates to globalThis.fetch when no inner fetch is supplied", () => { + const wrapped = withEveSandboxClientHeader(); + expect(typeof wrapped).toBe("function"); + }); +}); + +describe("getVercelSandboxFetch", () => { + it("returns a fetch that injects the eve client header (lookup/get path)", async () => { + const override = vi.fn().mockResolvedValue(new Response()); + const createOptions: VercelCreateOptions = { fetch: override }; + const fetchImpl = getVercelSandboxFetch(createOptions); + + await fetchImpl("https://api.vercel.com/sandboxes/foo"); + + const [, init] = override.mock.calls[0]!; + expect(headerOf(init)).toMatch(/^eve\/.+/); + }); +}); + +describe("createVercelEveImageSandbox", () => { + it("passes a header-injecting fetch through to Sandbox.create", async () => { + const override = vi.fn().mockResolvedValue(new Response()); + let capturedFetch: typeof globalThis.fetch | undefined; + const sandboxModule = { + Sandbox: { + create: vi.fn(async (options: VercelSandboxCreateParams) => { + capturedFetch = (options as { fetch?: typeof globalThis.fetch }).fetch; + return {}; + }), + }, + }; + const createOptions: VercelSandboxCreateParams = { + name: "agent", + persistent: false, + fetch: override, + }; + + await createVercelEveImageSandbox({ createOptions, sandboxModule: sandboxModule as never }); + + expect(capturedFetch).toBeDefined(); + await capturedFetch!("https://api.vercel.com/sandboxes"); + const [, init] = override.mock.calls[0]!; + expect(headerOf(init)).toMatch(/^eve\/.+/); + }); +}); diff --git a/packages/eve/src/execution/sandbox/bindings/vercel-client-header.ts b/packages/eve/src/execution/sandbox/bindings/vercel-client-header.ts new file mode 100644 index 000000000..48a9a222e --- /dev/null +++ b/packages/eve/src/execution/sandbox/bindings/vercel-client-header.ts @@ -0,0 +1,29 @@ +import { resolveInstalledPackageInfo } from "#internal/application/package.js"; + +/** + * Request header eve stamps on every Vercel Sandbox API call so the sandbox + * control plane can attribute traffic to eve and its version. + */ +export const EVE_SANDBOX_CLIENT_HEADER = "x-eve-client"; + +/** + * Wraps a `fetch` implementation so every request carries the + * {@link EVE_SANDBOX_CLIENT_HEADER} identifying the eve client and version. + */ +export function withEveSandboxClientHeader( + inner: typeof globalThis.fetch = globalThis.fetch, +): typeof globalThis.fetch { + const { name, version } = resolveInstalledPackageInfo(); + const clientId = `${name}/${version}`; + + return (input, init) => { + const headers = new Headers( + init?.headers ?? + (typeof input === "object" && input !== null && "headers" in input + ? (input as Request).headers + : undefined), + ); + headers.set(EVE_SANDBOX_CLIENT_HEADER, clientId); + return inner(input, { ...init, headers }); + }; +} diff --git a/packages/eve/src/execution/sandbox/bindings/vercel-create-sdk.ts b/packages/eve/src/execution/sandbox/bindings/vercel-create-sdk.ts index 80a6933b2..f3992508f 100644 --- a/packages/eve/src/execution/sandbox/bindings/vercel-create-sdk.ts +++ b/packages/eve/src/execution/sandbox/bindings/vercel-create-sdk.ts @@ -1,3 +1,4 @@ +import { getVercelSandboxFetch } from "#execution/sandbox/bindings/vercel-credentials.js"; import type { VercelCreateOptions, VercelModule, @@ -27,6 +28,7 @@ export async function createVercelEveImageSandbox(input: { const createOptions: VercelSandboxCreateParams = { ...input.createOptions, __image: VERCEL_EVE_SANDBOX_IMAGE, + fetch: getVercelSandboxFetch(input.createOptions), }; return await input.sandboxModule.Sandbox.create(createOptions); } diff --git a/packages/eve/src/execution/sandbox/bindings/vercel-credentials.ts b/packages/eve/src/execution/sandbox/bindings/vercel-credentials.ts index c0b5578f4..f6b051135 100644 --- a/packages/eve/src/execution/sandbox/bindings/vercel-credentials.ts +++ b/packages/eve/src/execution/sandbox/bindings/vercel-credentials.ts @@ -1,9 +1,10 @@ import { getVercelOidcToken } from "#compiled/@vercel/oidc/index.js"; +import { withEveSandboxClientHeader } from "#execution/sandbox/bindings/vercel-client-header.js"; import type { VercelCreateOptions } from "#execution/sandbox/bindings/vercel-sdk-types.js"; export function getVercelSandboxFetch(createOptions: VercelCreateOptions): typeof globalThis.fetch { const fetchOverride = (createOptions as { readonly fetch?: typeof globalThis.fetch }).fetch; - return fetchOverride ?? globalThis.fetch; + return withEveSandboxClientHeader(fetchOverride ?? globalThis.fetch); } export async function getVercelSandboxCredentials( From 11890e70ba18a75ba9edc772b8e319f8ced54d9a Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Fri, 26 Jun 2026 10:38:01 +0200 Subject: [PATCH 2/2] remove non useful tests Signed-off-by: Marc Codina --- .../bindings/vercel-client-header.test.ts | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/packages/eve/src/execution/sandbox/bindings/vercel-client-header.test.ts b/packages/eve/src/execution/sandbox/bindings/vercel-client-header.test.ts index daa35d01b..f443e4783 100644 --- a/packages/eve/src/execution/sandbox/bindings/vercel-client-header.test.ts +++ b/packages/eve/src/execution/sandbox/bindings/vercel-client-header.test.ts @@ -4,10 +4,6 @@ import { EVE_SANDBOX_CLIENT_HEADER, withEveSandboxClientHeader, } from "#execution/sandbox/bindings/vercel-client-header.js"; -import { getVercelSandboxFetch } from "#execution/sandbox/bindings/vercel-credentials.js"; -import { createVercelEveImageSandbox } from "#execution/sandbox/bindings/vercel-create-sdk.js"; -import type { VercelSandboxCreateParams } from "#execution/sandbox/bindings/vercel-create-sdk.js"; -import type { VercelCreateOptions } from "#execution/sandbox/bindings/vercel-sdk-types.js"; function headerOf(init: RequestInit | undefined): string | null { return new Headers(init?.headers).get(EVE_SANDBOX_CLIENT_HEADER); @@ -43,43 +39,3 @@ describe("withEveSandboxClientHeader", () => { expect(typeof wrapped).toBe("function"); }); }); - -describe("getVercelSandboxFetch", () => { - it("returns a fetch that injects the eve client header (lookup/get path)", async () => { - const override = vi.fn().mockResolvedValue(new Response()); - const createOptions: VercelCreateOptions = { fetch: override }; - const fetchImpl = getVercelSandboxFetch(createOptions); - - await fetchImpl("https://api.vercel.com/sandboxes/foo"); - - const [, init] = override.mock.calls[0]!; - expect(headerOf(init)).toMatch(/^eve\/.+/); - }); -}); - -describe("createVercelEveImageSandbox", () => { - it("passes a header-injecting fetch through to Sandbox.create", async () => { - const override = vi.fn().mockResolvedValue(new Response()); - let capturedFetch: typeof globalThis.fetch | undefined; - const sandboxModule = { - Sandbox: { - create: vi.fn(async (options: VercelSandboxCreateParams) => { - capturedFetch = (options as { fetch?: typeof globalThis.fetch }).fetch; - return {}; - }), - }, - }; - const createOptions: VercelSandboxCreateParams = { - name: "agent", - persistent: false, - fetch: override, - }; - - await createVercelEveImageSandbox({ createOptions, sandboxModule: sandboxModule as never }); - - expect(capturedFetch).toBeDefined(); - await capturedFetch!("https://api.vercel.com/sandboxes"); - const [, init] = override.mock.calls[0]!; - expect(headerOf(init)).toMatch(/^eve\/.+/); - }); -});