diff --git a/.changeset/quiet-sandboxes-trace.md b/.changeset/quiet-sandboxes-trace.md new file mode 100644 index 000000000..1599e3cd4 --- /dev/null +++ b/.changeset/quiet-sandboxes-trace.md @@ -0,0 +1,5 @@ +--- +"braintrust": minor +--- + +Add automatic tracing for E2B sandbox creation, command execution, and Code Interpreter execution, including execution errors and an `e2b` integration opt-out. diff --git a/js/README.md b/js/README.md index 9c5e70094..de7713072 100644 --- a/js/README.md +++ b/js/README.md @@ -104,6 +104,14 @@ If you use TypeScript or other transpilation plugins, place the Braintrust plugi For deeper details, see the [auto-instrumentation architecture docs](src/auto-instrumentations/README.md). +### E2B + +Auto-instrumentation supports `e2b` 2.9+ and `@e2b/code-interpreter` 2.2+ within their 2.x release lines. With the runtime hook or a bundler plugin enabled, `Sandbox.create()`, `sandbox.commands.run()`, and `sandbox.runCode()` produce tool spans. Initialize a Braintrust logger as usual before making these calls. + +Spans capture command/code inputs, execution outputs and errors, and selected operation metadata. Connection credentials, environment variables, and callbacks are not logged. Background command spans finish when the SDK returns the command handle; they do not track the background process to completion. + +Set `BRAINTRUST_DISABLE_INSTRUMENTATION=e2b` to disable both E2B packages, or use `configureInstrumentation({ integrations: { e2b: false } })` before initializing the SDK. + ## Migration Guides ### Upgrading from 2.x to 3.x diff --git a/js/package.json b/js/package.json index a72758597..e74a6f7d5 100644 --- a/js/package.json +++ b/js/package.json @@ -190,6 +190,7 @@ "license": "MIT", "devDependencies": { "@anthropic-ai/sdk": "^0.60.0", + "@e2b/code-interpreter": "2.2.0", "@nodelib/fs.walk": "^1.2.8", "@types/argparse": "^2.0.14", "@types/async": "^3.2.24", @@ -207,6 +208,7 @@ "ai": "^6.0.0", "async": "^3.2.5", "cross-env": "^7.0.3", + "e2b": "2.9.0", "eslint": "^9.39.2", "eslint-plugin-es-x": "9.7.0", "eslint-plugin-n": "^18.2.1", diff --git a/js/src/auto-instrumentations/configs/all.ts b/js/src/auto-instrumentations/configs/all.ts index deb7aad8f..6c16736f0 100644 --- a/js/src/auto-instrumentations/configs/all.ts +++ b/js/src/auto-instrumentations/configs/all.ts @@ -37,6 +37,7 @@ import { strandsAgentSDKConfigs } from "./strands-agent-sdk"; import { elevenLabsConfigs } from "./elevenlabs"; import { voyageAIConfigs } from "./voyageai"; import { typeSafeConfigs } from "./typesafe"; +import { e2bConfigs } from "./e2b"; interface InstrumentationConfigGroup { integrations: readonly (keyof InstrumentationIntegrationsConfig)[]; @@ -46,6 +47,7 @@ interface InstrumentationConfigGroup { const defaultInstrumentationConfigGroups: readonly InstrumentationConfigGroup[] = [ { integrations: ["openai"], configs: openaiConfigs }, + { integrations: ["e2b"], configs: e2bConfigs }, { integrations: ["openaiCodexSDK"], configs: openAICodexConfigs, diff --git a/js/src/auto-instrumentations/configs/e2b.ts b/js/src/auto-instrumentations/configs/e2b.ts new file mode 100644 index 000000000..5c6cb5a7a --- /dev/null +++ b/js/src/auto-instrumentations/configs/e2b.ts @@ -0,0 +1,39 @@ +import type { InstrumentationConfig } from "../orchestrion-js"; +import { + e2bChannels, + e2bCodeInterpreterChannels, +} from "../../instrumentation/plugins/e2b-channels"; + +export const e2bConfigs: InstrumentationConfig[] = [ + "dist/index.js", + "dist/index.mjs", +].flatMap((filePath) => [ + { + channelName: e2bChannels.create.channelName, + module: { name: "e2b", versionRange: ">=2.9.0 <3.0.0", filePath }, + functionQuery: { + className: "Sandbox", + methodName: "create", + kind: "Async", + }, + }, + { + channelName: e2bChannels.run.channelName, + module: { name: "e2b", versionRange: ">=2.9.0 <3.0.0", filePath }, + functionQuery: { className: "Commands", methodName: "run", kind: "Async" }, + }, + { + // Code Interpreter inherits create() and commands from e2b. + channelName: e2bCodeInterpreterChannels.runCode.channelName, + module: { + name: "@e2b/code-interpreter", + versionRange: ">=2.2.0 <3.0.0", + filePath, + }, + functionQuery: { + className: "Sandbox", + methodName: "runCode", + kind: "Async", + }, + }, +]); diff --git a/js/src/instrumentation/braintrust-plugin.test.ts b/js/src/instrumentation/braintrust-plugin.test.ts index f6510cbe2..05a98cc10 100644 --- a/js/src/instrumentation/braintrust-plugin.test.ts +++ b/js/src/instrumentation/braintrust-plugin.test.ts @@ -24,6 +24,7 @@ import { PiCodingAgentPlugin } from "./plugins/pi-coding-agent-plugin"; import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin"; import { VoyageAIPlugin } from "./plugins/voyageai-plugin"; import { TypeSafePlugin } from "./plugins/typesafe-plugin"; +import { E2BPlugin } from "./plugins/e2b-plugin"; import { CloudflareAIChatPlugin } from "./plugins/cloudflare-ai-chat-plugin"; import { CloudflareAgentsPlugin } from "./plugins/cloudflare-agents-plugin"; @@ -143,6 +144,10 @@ vi.mock("./plugins/typesafe-plugin", () => ({ TypeSafePlugin: createPluginClassMock(), })); +vi.mock("./plugins/e2b-plugin", () => ({ + E2BPlugin: createPluginClassMock(), +})); + vi.mock("./plugins/cloudflare-ai-chat-plugin", () => ({ CloudflareAIChatPlugin: createPluginClassMock(), })); @@ -157,6 +162,23 @@ describe("BraintrustPlugin", () => { }); describe("sub-plugin creation (default behavior)", () => { + it("enables and disables E2B with the parent plugin", () => { + const plugin = new BraintrustPlugin(); + plugin.enable(); + expect(E2BPlugin).toHaveBeenCalledTimes(1); + const instance = vi.mocked(E2BPlugin).mock.results[0].value; + expect(instance.enable).toHaveBeenCalledTimes(1); + plugin.disable(); + expect(instance.disable).toHaveBeenCalledTimes(1); + }); + + it("respects the E2B integration opt-out", () => { + const plugin = new BraintrustPlugin({ integrations: { e2b: false } }); + plugin.enable(); + expect(E2BPlugin).not.toHaveBeenCalled(); + plugin.disable(); + }); + it("should create and enable OpenAI plugin by default", () => { const plugin = new BraintrustPlugin(); plugin.enable(); diff --git a/js/src/instrumentation/braintrust-plugin.ts b/js/src/instrumentation/braintrust-plugin.ts index 93015d351..62abce83e 100644 --- a/js/src/instrumentation/braintrust-plugin.ts +++ b/js/src/instrumentation/braintrust-plugin.ts @@ -30,6 +30,7 @@ import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin"; import { ElevenLabsPlugin } from "./plugins/elevenlabs-plugin"; import { VoyageAIPlugin } from "./plugins/voyageai-plugin"; import { TypeSafePlugin } from "./plugins/typesafe-plugin"; +import { E2BPlugin } from "./plugins/e2b-plugin"; import { CloudflareAIChatPlugin } from "./plugins/cloudflare-ai-chat-plugin"; import { CloudflareAgentsPlugin } from "./plugins/cloudflare-agents-plugin"; import type { InstrumentationIntegrationsConfig } from "./config"; @@ -91,6 +92,7 @@ export class BraintrustPlugin extends BasePlugin { private elevenLabsPlugin: ElevenLabsPlugin | null = null; private voyageAIPlugin: VoyageAIPlugin | null = null; private typeSafePlugin: TypeSafePlugin | null = null; + private e2bPlugin: E2BPlugin | null = null; private cloudflareAIChatPlugin: CloudflareAIChatPlugin | null = null; private cloudflareAgentsPlugin: CloudflareAgentsPlugin | null = null; @@ -217,6 +219,10 @@ export class BraintrustPlugin extends BasePlugin { this.typeSafePlugin = new TypeSafePlugin(); this.typeSafePlugin.enable(); } + if (integrations.e2b !== false) { + this.e2bPlugin = new E2BPlugin(); + this.e2bPlugin.enable(); + } if (integrations.groq !== false) { this.groqPlugin = new GroqPlugin(); @@ -395,6 +401,10 @@ export class BraintrustPlugin extends BasePlugin { this.typeSafePlugin.disable(); this.typeSafePlugin = null; } + if (this.e2bPlugin) { + this.e2bPlugin.disable(); + this.e2bPlugin = null; + } if (this.groqPlugin) { this.groqPlugin.disable(); diff --git a/js/src/instrumentation/config.ts b/js/src/instrumentation/config.ts index 057191b68..2578c1bda 100644 --- a/js/src/instrumentation/config.ts +++ b/js/src/instrumentation/config.ts @@ -57,6 +57,7 @@ export interface InstrumentationIntegrationsConfig { langsmith?: boolean; voyageai?: boolean; typesafe?: boolean; + e2b?: boolean; elevenlabs?: boolean; } @@ -161,6 +162,8 @@ const envIntegrationAliases: Record< "voyage-ai": "voyageai", voyageai: "voyageai", typesafe: "typesafe", + e2b: "e2b", + "@e2b/code-interpreter": "e2b", "typesafe-ai": "typesafe", "@typesafe-ai/sdk": "typesafe", elevenlabs: "elevenlabs", @@ -207,6 +210,7 @@ export function getDefaultInstrumentationIntegrations(): Record< langsmith: true, voyageai: true, typesafe: true, + e2b: true, elevenlabs: true, piCodingAgent: true, strandsAgentSDK: true, diff --git a/js/src/instrumentation/plugins/e2b-channels.ts b/js/src/instrumentation/plugins/e2b-channels.ts new file mode 100644 index 000000000..ef668f4ce --- /dev/null +++ b/js/src/instrumentation/plugins/e2b-channels.ts @@ -0,0 +1,28 @@ +import { INSTRUMENTATION_NAMES } from "../../span-origin"; +import { channel, defineChannels } from "../core/channel-definitions"; + +export const e2bChannels = defineChannels( + "e2b", + { + create: channel<[templateOrOptions?: unknown, options?: unknown], unknown>({ + channelName: "Sandbox.create", + kind: "async", + }), + run: channel<[command: string, options?: unknown], unknown>({ + channelName: "Commands.run", + kind: "async", + }), + }, + { instrumentationName: INSTRUMENTATION_NAMES.E2B }, +); + +export const e2bCodeInterpreterChannels = defineChannels( + "@e2b/code-interpreter", + { + runCode: channel<[code: string, options?: unknown], unknown>({ + channelName: "Sandbox.runCode", + kind: "async", + }), + }, + { instrumentationName: INSTRUMENTATION_NAMES.E2B }, +); diff --git a/js/src/instrumentation/plugins/e2b-plugin.ts b/js/src/instrumentation/plugins/e2b-plugin.ts new file mode 100644 index 000000000..034836ac8 --- /dev/null +++ b/js/src/instrumentation/plugins/e2b-plugin.ts @@ -0,0 +1,94 @@ +import { isObject, SpanTypeAttribute } from "../../../util/index"; +import { BasePlugin } from "../core"; +import { + traceAsyncChannel, + traceStreamingChannel, + unsubscribeAll, +} from "../core/channel-tracing"; +import { e2bChannels, e2bCodeInterpreterChannels } from "./e2b-channels"; + +// Select public operation data instead of serializing connection options, +// callbacks, environment variables, or credential-bearing SDK instances. +function fields(value: unknown, keys: string[]): Record { + if (!isObject(value)) return {}; + return Object.fromEntries( + keys + .filter((key) => value[key] !== undefined) + .map((key) => [key, value[key]]), + ); +} + +export class E2BPlugin extends BasePlugin { + protected onEnable(): void { + this.unsubscribers.push( + traceAsyncChannel(e2bChannels.create, { + name: "e2b.Sandbox.create", + type: SpanTypeAttribute.TOOL, + extractInput: ([templateOrOptions, options]) => ({ + input: { + ...(typeof templateOrOptions === "string" + ? { template: templateOrOptions } + : {}), + ...fields( + typeof templateOrOptions === "string" + ? options + : templateOrOptions, + ["timeoutMs"], + ), + }, + metadata: { provider: "e2b" }, + }), + extractOutput: (result) => fields(result, ["sandboxId"]), + extractMetrics: () => ({}), + }), + traceAsyncChannel(e2bChannels.run, { + name: "e2b.commands.run", + type: SpanTypeAttribute.TOOL, + extractInput: ([command, options]) => ({ + input: { command }, + metadata: { + provider: "e2b", + ...fields(options, ["cwd", "user", "background", "timeoutMs"]), + }, + }), + // Background calls return a CommandHandle, not a finished command. + extractOutput: (result) => + fields(result, ["pid", "stdout", "stderr", "exitCode"]), + extractMetrics: () => ({}), + }), + traceStreamingChannel(e2bCodeInterpreterChannels.runCode, { + name: "e2b.runCode", + type: SpanTypeAttribute.TOOL, + extractInput: ([code, options], event) => ({ + input: { code }, + metadata: { + provider: "e2b", + ...fields(event.self, ["sandboxId"]), + ...fields(options, ["language", "timeoutMs"]), + ...(isObject(options) && isObject(options.context) + ? { context: fields(options.context, ["id", "language"]) } + : {}), + }, + }), + extractOutput: (result) => fields(result, ["logs", "results", "error"]), + extractMetrics: () => ({}), + onComplete: ({ result, span }) => { + // Execution errors resolve successfully at the HTTP layer. + if (isObject(result) && isObject(result.error)) { + const { name, value, traceback } = result.error; + span.log({ + error: + typeof traceback === "string" && traceback + ? traceback + : `${String(name ?? "ExecutionError")}: ${String(value ?? "")}`, + }); + } + }, + }), + ); + } + + protected onDisable(): void { + this.unsubscribers = unsubscribeAll(this.unsubscribers); + } +} diff --git a/js/src/span-origin.ts b/js/src/span-origin.ts index 755fc43f5..83a97aada 100644 --- a/js/src/span-origin.ts +++ b/js/src/span-origin.ts @@ -20,6 +20,7 @@ export const INSTRUMENTATION_NAMES = { CURSOR_SDK: "cursor-sdk", DEEPSEEK_HARNESS: "deepseek-harness", EVE: "eve", + E2B: "e2b", FLUE: "flue", GENKIT: "genkit", GITHUB_COPILOT: "github-copilot", diff --git a/js/tests/auto-instrumentations/e2b.test.ts b/js/tests/auto-instrumentations/e2b.test.ts new file mode 100644 index 000000000..4b30dfad4 --- /dev/null +++ b/js/tests/auto-instrumentations/e2b.test.ts @@ -0,0 +1,283 @@ +import { readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { transformSync } from "esbuild"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import { create } from "../../src/auto-instrumentations/orchestrion-js"; +import { getDefaultInstrumentationConfigs } from "../../src/auto-instrumentations/configs/all"; +import { _exportsForTestingOnly, initLogger } from "../../src/logger"; +import { configureNode } from "../../src/node/config"; +import { runWithAutoInstrumentationSuppressed } from "../../src/instrumentation/auto-instrumentation-suppression"; + +configureNode(); + +// Execute the published SDK implementations through the same transformer used +// by the loader/bundlers. Only provider I/O is stubbed: these are hermetic SDK +// contract tests, not recordings of live E2B sandboxes. +function loadSDK( + name: string, + format: "cjs" | "esm", + dependencies: Record = {}, + disabled = false, +) { + const require = createRequire(import.meta.url); + const root = dirname(dirname(require.resolve(name))); + const { version } = JSON.parse( + readFileSync(join(root, "package.json"), "utf8"), + ); + const filePath = format === "cjs" ? "dist/index.js" : "dist/index.mjs"; + const source = readFileSync(join(root, filePath), "utf8"); + const transformer = create( + getDefaultInstrumentationConfigs({ + disabledIntegrationConfig: disabled ? { e2b: false } : undefined, + }), + ).getTransformer(name, version, filePath); + const transformed = transformer?.transform(source, format).code ?? source; + const code = + format === "esm" + ? transformSync(transformed, { format: "cjs", platform: "node" }).code + : transformed; + const module = { exports: {} }; + const sdkRequire = createRequire(join(root, filePath)); + new Function("require", "module", "exports", code)( + (id: string) => dependencies[id] ?? sdkRequire(id), + module, + module.exports, + ); + return module.exports as typeof import("@e2b/code-interpreter"); +} + +describe.each(["cjs", "esm"] as const)("E2B published %s SDK", (format) => { + let backgroundLogger: ReturnType< + typeof _exportsForTestingOnly.useTestBackgroundLogger + >; + + beforeAll(async () => { + await _exportsForTestingOnly.simulateLoginForTests(); + }); + beforeEach(() => { + backgroundLogger = _exportsForTestingOnly.useTestBackgroundLogger(); + initLogger({ projectName: "e2b.test.ts", projectId: "test-project-id" }); + }); + afterEach(() => { + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + _exportsForTestingOnly.clearTestBackgroundLogger(); + }); + + function sdks(disabled = false) { + const core = loadSDK("e2b", format, {}, disabled); + const interpreter = loadSDK( + "@e2b/code-interpreter", + format, + { e2b: core }, + disabled, + ); + return { core, interpreter }; + } + + function executionResponse(messages: unknown[]) { + vi.stubGlobal( + "fetch", + vi + .fn() + .mockResolvedValue( + new Response( + messages.map((message) => JSON.stringify(message)).join("\n") + + "\n", + { status: 200 }, + ), + ), + ); + } + + it("traces both create overloads, preserves subclasses, and omits credentials", async () => { + const { core, interpreter } = sdks(); + const coreSandbox = await core.Sandbox.create({ + debug: true, + apiKey: "secret-api-key", + timeoutMs: 1000, + }); + const sandbox = await interpreter.Sandbox.create("code-interpreter", { + debug: true, + apiKey: "secret-api-key", + envs: { TOKEN: "secret-environment" }, + timeoutMs: 1000, + }); + expect(coreSandbox).toBeInstanceOf(core.Sandbox); + expect(sandbox).toBeInstanceOf(interpreter.Sandbox); + expect(typeof sandbox.runCode).toBe("function"); + const spans = await backgroundLogger.drain(); + expect(spans).toHaveLength(2); + expect(spans[0]).toMatchObject({ + span_attributes: { name: "e2b.Sandbox.create", type: "tool" }, + input: { timeoutMs: 1000 }, + output: { sandboxId: coreSandbox.sandboxId }, + }); + expect(spans[1]).toMatchObject({ + input: { template: "code-interpreter", timeoutMs: 1000 }, + output: { sandboxId: sandbox.sandboxId }, + }); + expect(JSON.stringify(spans)).not.toContain("secret-"); + }); + + it("preserves foreground results and background command handles", async () => { + const { core } = sdks(); + const sandbox = await core.Sandbox.create({ debug: true }); + await backgroundLogger.drain(); + const result = { stdout: "hello\n", stderr: "", exitCode: 0 }; + const handle = { + pid: 42, + wait: vi.fn().mockResolvedValue(result), + kill: vi.fn(), + }; + // start() owns the transport; run() is the actual instrumented SDK method. + const start = vi + .spyOn(sandbox.commands as any, "start") + .mockResolvedValue(handle); + const options = { + cwd: "/tmp", + envs: { TOKEN: "secret-environment" }, + onStdout: vi.fn(), + }; + expect(await sandbox.commands.run("echo hello", options)).toBe(result); + expect(start).toHaveBeenCalledWith("echo hello", options); + expect(await sandbox.commands.run("sleep 1", { background: true })).toBe( + handle, + ); + expect(handle.wait).toHaveBeenCalledTimes(1); + const spans = await backgroundLogger.drain(); + expect(spans).toHaveLength(2); + expect(spans[0]).toMatchObject({ + input: { command: "echo hello" }, + output: result, + metadata: { cwd: "/tmp" }, + }); + expect(spans[1]).toMatchObject({ + metadata: { background: true }, + output: { pid: 42 }, + }); + expect(JSON.stringify(spans)).not.toContain("secret-environment"); + }); + + it("captures execution results and preserves streaming callbacks and parentage", async () => { + const { interpreter } = sdks(); + const sandbox = await interpreter.Sandbox.create({ debug: true }); + await backgroundLogger.drain(); + executionResponse([ + { type: "stdout", text: "hello\n" }, + { type: "stderr", text: "warning\n" }, + { type: "result", text: "2", is_main_result: true }, + ]); + const onStdout = vi.fn(); + const onStderr = vi.fn(); + const onResult = vi.fn(); + const logger = initLogger({ + projectName: "e2b.test.ts", + projectId: "test-project-id", + }); + const result = await logger.traced( + () => + sandbox.runCode("print('hello'); 1 + 1", { + language: "python", + onStdout, + onStderr, + onResult, + envs: { TOKEN: "secret-environment" }, + }), + { name: "agent" }, + ); + expect(result.text).toBe("2"); + expect(onStdout).toHaveBeenCalledOnce(); + expect(onStderr).toHaveBeenCalledOnce(); + expect(onResult).toHaveBeenCalledWith(result.results[0]); + const spans = await backgroundLogger.drain(); + const child = spans.find( + (span: any) => span.span_attributes?.name === "e2b.runCode", + ); + const parent = spans.find( + (span: any) => span.span_attributes?.name === "agent", + ) as Record | undefined; + expect(child).toMatchObject({ + span_attributes: { type: "tool" }, + input: { code: "print('hello'); 1 + 1" }, + output: { + logs: { stdout: ["hello\n"], stderr: ["warning\n"] }, + results: [{ text: "2" }], + }, + metadata: { + provider: "e2b", + language: "python", + sandboxId: sandbox.sandboxId, + }, + span_parents: [parent!.span_id], + }); + expect(JSON.stringify(spans)).not.toContain("secret-environment"); + }); + + it("logs resolved execution errors without turning them into rejections", async () => { + const { interpreter } = sdks(); + const sandbox = await interpreter.Sandbox.create({ debug: true }); + await backgroundLogger.drain(); + executionResponse([ + { + type: "error", + name: "ZeroDivisionError", + value: "division by zero", + traceback: "ZeroDivisionError: division by zero", + }, + ]); + const result = await sandbox.runCode("1 / 0"); + expect(result.error?.name).toBe("ZeroDivisionError"); + const spans = await backgroundLogger.drain(); + expect(spans).toHaveLength(1); + expect(spans[0]).toMatchObject({ + error: "ZeroDivisionError: division by zero", + output: { error: { name: "ZeroDivisionError" } }, + }); + }); + + it("preserves rejected errors and ends their spans", async () => { + const { interpreter } = sdks(); + const sandbox = await interpreter.Sandbox.create({ debug: true }); + await backgroundLogger.drain(); + const error = new Error("command failed"); + vi.spyOn(sandbox.commands as any, "start").mockRejectedValue(error); + await expect(sandbox.commands.run("false")).rejects.toBe(error); + await expect( + // @ts-expect-error Exercise the SDK's runtime validation of incompatible options. + sandbox.runCode("1", { + language: "python", + context: { id: "context", language: "python", cwd: "/tmp" }, + }), + ).rejects.toThrow("context or language"); + const spans = await backgroundLogger.drain(); + expect(spans).toHaveLength(2); + for (const span of spans) { + expect(span).toMatchObject({ + error: expect.any(String), + metrics: { end: expect.any(Number) }, + }); + } + }); + + it("does not emit spans when disabled or suppressed", async () => { + const { interpreter } = sdks(true); + const sandbox = await interpreter.Sandbox.create({ debug: true }); + executionResponse([{ type: "stdout", text: "hello" }]); + await sandbox.runCode("print('hello')"); + const enabled = sdks(); + await runWithAutoInstrumentationSuppressed(() => + enabled.core.Sandbox.create({ debug: true }), + ); + expect(await backgroundLogger.drain()).toEqual([]); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e69fe489..c6fd16474 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -449,6 +449,9 @@ importers: '@anthropic-ai/sdk': specifier: ^0.60.0 version: 0.60.0 + '@e2b/code-interpreter': + specifier: 2.2.0 + version: 2.2.0 '@nodelib/fs.walk': specifier: ^1.2.8 version: 1.2.8 @@ -500,6 +503,9 @@ importers: cross-env: specifier: ^7.0.3 version: 7.0.3 + e2b: + specifier: 2.9.0 + version: 2.9.0 eslint: specifier: ^9.39.2 version: 9.39.4(jiti@2.6.1) @@ -669,6 +675,9 @@ packages: cpu: [x64] os: [win32] + '@bufbuild/protobuf@2.16.0': + resolution: {integrity: sha512-FWa0sPlqYGJgpTs6OxBRcL/AW4JT0OIO+W1ajerluoTVh8CV0B7XM1qsNCRgnkSCkRCgOICKHRQlY17jrsm0+Q==} + '@cfworker/json-schema@4.0.3': resolution: {integrity: sha512-ZykIcDTVv5UNmKWSTLAs3VukO6NDJkkSKxrgUTDPBkAlORVT3H9n5DbRjRl8xIotklscHdbLIa0b9+y3mQq73g==} @@ -742,6 +751,17 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@connectrpc/connect-web@2.0.0-rc.3': + resolution: {integrity: sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw==} + peerDependencies: + '@bufbuild/protobuf': ^2.2.0 + '@connectrpc/connect': 2.0.0-rc.3 + + '@connectrpc/connect@2.0.0-rc.3': + resolution: {integrity: sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ==} + peerDependencies: + '@bufbuild/protobuf': ^2.2.0 + '@deepseek-ai/cordis@4.0.1': resolution: {integrity: sha512-YBdskTU2Po1kru3GgcUWUbkTsPMA9LkSQDAY8rBkFJeajdgcQad3QPJZE26JyK99Xb6HaASvoXg2DSUTeN/0Nw==} hasBin: true @@ -871,6 +891,10 @@ packages: '@deepseek-ai/schemastery@3.18.2': resolution: {integrity: sha512-njDtZsznjYxok7KLLlHOPyuv2efdWVbSflAHgztSfbMsg+CVraEoRe2DjOCgClYv3ZCSm7WXoaUkbB/+RY7tWQ==} + '@e2b/code-interpreter@2.2.0': + resolution: {integrity: sha512-j2XIhuuV+MvjCMJbwRCL3hZjkOTv4Jk1FygI4dCwaU9PggtAW+TXhyaVzzVPZroE9fqxLfrXGqY08P06Ei7MQA==} + engines: {node: '>=20'} + '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} @@ -1157,6 +1181,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -2486,6 +2514,9 @@ packages: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2574,6 +2605,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dockerfile-ast@0.7.1: + resolution: {integrity: sha512-oX/A4I0EhSkGqrFv0YuvPkBUSYp1XiY8O8zAKc8Djglx8ocz+JfOr8gP0ryRMC2myqvDLagmnZaU9ot1vG2ijw==} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -2586,6 +2620,10 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + e2b@2.9.0: + resolution: {integrity: sha512-3fot3N+ZOoxQF0XwQO2CZ1X1bUb6m3tpGw4CEkjEL1DgKfMiB6hml0DYvf3lt3MRtsDZcZ43fCYo3DvvHfJAPg==} + engines: {node: '>=20'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2959,6 +2997,12 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} + engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -3120,6 +3164,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} + engines: {node: 20 || >=22} + jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} @@ -3334,6 +3382,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} @@ -3563,6 +3615,12 @@ packages: zod: optional: true + openapi-fetch@0.14.1: + resolution: {integrity: sha512-l7RarRHxlEZYjMLd/PR0slfMVse2/vvIAGm75/F7J6MlQ8/b9uUQmUF2kCPrQhJqMXSxmYWObVgeYXbFYzZR+A==} + + openapi-typescript-helpers@0.0.15: + resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3650,6 +3708,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -3698,6 +3760,9 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -4407,6 +4472,12 @@ packages: jsdom: optional: true + vscode-languageserver-textdocument@1.0.15: + resolution: {integrity: sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw==} + + vscode-languageserver-types@3.18.4: + resolution: {integrity: sha512-nQsacoiijvl0fRf2q28uFDrAVwRhdCa3QveiF7/CX0r18mLR5FU/1WskSsCKOjmEbtteKUnY8x8Yzxop+k0zwg==} + vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} @@ -4595,6 +4666,8 @@ snapshots: '@braintrust/bt-win32-x64@0.12.0': optional: true + '@bufbuild/protobuf@2.16.0': {} + '@cfworker/json-schema@4.0.3': {} '@changesets/apply-release-plan@7.1.0': @@ -4762,6 +4835,15 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@connectrpc/connect-web@2.0.0-rc.3(@bufbuild/protobuf@2.16.0)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.16.0))': + dependencies: + '@bufbuild/protobuf': 2.16.0 + '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.16.0) + + '@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.16.0)': + dependencies: + '@bufbuild/protobuf': 2.16.0 + '@deepseek-ai/cordis@4.0.1': dependencies: '@deepseek-ai/cosmokit': 1.8.2 @@ -4878,6 +4960,10 @@ snapshots: '@deepseek-ai/cosmokit': 1.8.3 '@standard-schema/spec': 1.1.0 + '@e2b/code-interpreter@2.2.0': + dependencies: + e2b: 2.9.0 + '@emnapi/core@1.11.1': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -5120,6 +5206,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/cliui@9.0.0': {} + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -6476,6 +6564,8 @@ snapshots: commander@5.1.0: {} + compare-versions@6.1.1: {} + concat-map@0.0.1: {} confbox@0.1.8: {} @@ -6535,6 +6625,11 @@ snapshots: dependencies: path-type: 4.0.0 + dockerfile-ast@0.7.1: + dependencies: + vscode-languageserver-textdocument: 1.0.15 + vscode-languageserver-types: 3.18.4 + dotenv@16.4.5: {} dotenv@17.3.1: {} @@ -6545,6 +6640,19 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + e2b@2.9.0: + dependencies: + '@bufbuild/protobuf': 2.16.0 + '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.16.0) + '@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.16.0)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.16.0)) + chalk: 5.6.2 + compare-versions: 6.1.1 + dockerfile-ast: 0.7.1 + glob: 11.1.0 + openapi-fetch: 0.14.1 + platform: 1.3.6 + tar: 7.5.22 + eastasianwidth@0.2.0: {} ee-first@1.1.1: {} @@ -6975,6 +7083,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.2.3 + minimatch: 10.2.5 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -7115,6 +7232,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.2.3: + dependencies: + '@isaacs/cliui': 9.0.0 + jest-worker@27.5.1: dependencies: '@types/node': 22.19.1 @@ -7305,6 +7426,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.5.3: {} + lunr@2.3.9: {} magic-string@0.30.21: @@ -7594,6 +7717,12 @@ snapshots: zod: 3.25.76 optional: true + openapi-fetch@0.14.1: + dependencies: + openapi-typescript-helpers: 0.0.15 + + openapi-typescript-helpers@0.0.15: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -7696,6 +7825,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.3 + minipass: 7.1.3 + path-to-regexp@6.3.0: {} path-to-regexp@8.4.0: {} @@ -7727,6 +7861,8 @@ snapshots: mlly: 1.8.0 pathe: 2.0.3 + platform@1.3.6: {} + pluralize@8.0.0: {} postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.26)(tsx@4.21.0)(yaml@2.9.0): @@ -8568,6 +8704,10 @@ snapshots: transitivePeerDependencies: - msw + vscode-languageserver-textdocument@1.0.15: {} + + vscode-languageserver-types@3.18.4: {} + vscode-oniguruma@1.7.0: {} vscode-textmate@8.0.0: {}