diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts index 012b6f9f458..eaef45fb2f3 100644 --- a/apps/sim/app/api/function/execute/route.test.ts +++ b/apps/sim/app/api/function/execute/route.test.ts @@ -13,7 +13,7 @@ import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockExecuteInE2B, + mockExecuteInSandbox, mockExecuteInIsolatedVM, mockFetchWorkspaceFileBuffer, mockGetWorkspaceFile, @@ -23,7 +23,7 @@ const { mockValidateWorkspaceFileWriteTarget, mockWriteWorkspaceFileByPath, } = vi.hoisted(() => ({ - mockExecuteInE2B: vi.fn(), + mockExecuteInSandbox: vi.fn(), mockExecuteInIsolatedVM: vi.fn(), mockFetchWorkspaceFileBuffer: vi.fn(), mockGetWorkspaceFile: vi.fn(), @@ -38,9 +38,9 @@ vi.mock('@/lib/execution/isolated-vm', () => ({ executeInIsolatedVM: mockExecuteInIsolatedVM, })) -vi.mock('@/lib/execution/e2b', () => ({ - executeInE2B: mockExecuteInE2B, - executeShellInE2B: vi.fn(), +vi.mock('@/lib/execution/remote-sandbox', () => ({ + executeInSandbox: mockExecuteInSandbox, + executeShellInSandbox: vi.fn(), SIM_RESULT_PREFIX: '__SIM_RESULT__=', })) @@ -124,7 +124,7 @@ describe('Function Execute API Route', () => { mockUploadFile.mockImplementation(async ({ customKey }) => ({ key: customKey })) clearLargeValueCacheForTests() - mockExecuteInE2B.mockResolvedValue({ + mockExecuteInSandbox.mockResolvedValue({ result: 'e2b success', stdout: 'e2b output', sandboxId: 'test-sandbox-id', @@ -351,7 +351,7 @@ describe('Function Execute API Route', () => { it('exports multiple declared sandbox output files', async () => { envFlagsMock.isE2bEnabled = true - mockExecuteInE2B.mockResolvedValueOnce({ + mockExecuteInSandbox.mockResolvedValueOnce({ result: 'done', stdout: 'ok', sandboxId: 'sandbox-123', @@ -388,7 +388,7 @@ describe('Function Execute API Route', () => { expect(response.status).toBe(200) expect(data.success).toBe(true) - expect(mockExecuteInE2B).toHaveBeenCalledWith( + expect(mockExecuteInSandbox).toHaveBeenCalledWith( expect.objectContaining({ outputSandboxPaths: ['/home/user/chart.png', '/home/user/summary.json'], }) @@ -419,7 +419,7 @@ describe('Function Execute API Route', () => { it('prevalidates all sandbox output destinations before writing any files', async () => { envFlagsMock.isE2bEnabled = true - mockExecuteInE2B.mockResolvedValueOnce({ + mockExecuteInSandbox.mockResolvedValueOnce({ result: 'done', stdout: 'ok', sandboxId: 'sandbox-123', @@ -463,7 +463,7 @@ describe('Function Execute API Route', () => { it('rejects duplicate sandbox output destinations before writing files', async () => { envFlagsMock.isE2bEnabled = true - mockExecuteInE2B.mockResolvedValueOnce({ + mockExecuteInSandbox.mockResolvedValueOnce({ result: 'done', stdout: 'ok', sandboxId: 'sandbox-123', @@ -508,7 +508,7 @@ describe('Function Execute API Route', () => { it('returns a targeted error when a declared sandbox output is missing', async () => { envFlagsMock.isE2bEnabled = true - mockExecuteInE2B.mockResolvedValueOnce({ + mockExecuteInSandbox.mockResolvedValueOnce({ result: 'done', stdout: 'ok', sandboxId: 'sandbox-123', @@ -564,7 +564,7 @@ describe('Function Execute API Route', () => { expect(data.success).toBe(false) expect(data.error).toContain('no sandbox filesystem') expect(mockExecuteInIsolatedVM).not.toHaveBeenCalled() - expect(mockExecuteInE2B).not.toHaveBeenCalled() + expect(mockExecuteInSandbox).not.toHaveBeenCalled() expect(mockWriteWorkspaceFileByPath).not.toHaveBeenCalled() }) @@ -590,7 +590,7 @@ describe('Function Execute API Route', () => { it('flags an overwrite export whose bytes are identical to the current file content as unchanged', async () => { envFlagsMock.isE2bEnabled = true const staleContent = '# doc\nunchanged mounted content\n' - mockExecuteInE2B.mockResolvedValueOnce({ + mockExecuteInSandbox.mockResolvedValueOnce({ result: 'done', stdout: 'ok', sandboxId: 'sandbox-123', @@ -637,7 +637,7 @@ describe('Function Execute API Route', () => { it('reports size, previousSize, and sha256 receipts on a successful overwrite export', async () => { envFlagsMock.isE2bEnabled = true const newContent = '# doc\nnew content\n' - mockExecuteInE2B.mockResolvedValueOnce({ + mockExecuteInSandbox.mockResolvedValueOnce({ result: 'done', stdout: 'ok', sandboxId: 'sandbox-123', @@ -681,7 +681,7 @@ describe('Function Execute API Route', () => { expect(data.output.result.message).toContain('sha256:') // The python wrapper prints the marker with a leading \n so it always // starts a fresh line even after non-newline-terminated user output. - const e2bCode = mockExecuteInE2B.mock.calls[0][0].code as string + const e2bCode = mockExecuteInSandbox.mock.calls[0][0].code as string expect(e2bCode).toContain("print('\\n__SIM_RESULT__=' + json.dumps(__sim_result__))") }) diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 20568fd2010..f032abfa943 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -19,7 +19,6 @@ import { import { isE2bEnabled } from '@/lib/core/config/env-flags' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { executeInE2B, executeShellInE2B, SIM_RESULT_PREFIX } from '@/lib/execution/e2b' import { executeInIsolatedVM, type IsolatedVMBrokerHandler } from '@/lib/execution/isolated-vm' import { CodeLanguage, DEFAULT_CODE_LANGUAGE, isValidCodeLanguage } from '@/lib/execution/languages' import { recordMaterializedAccessKeys } from '@/lib/execution/payloads/access-keys' @@ -36,6 +35,11 @@ import { } from '@/lib/execution/payloads/materialization.server' import { compactExecutionPayload } from '@/lib/execution/payloads/serializer' import { materializeLargeValueRef } from '@/lib/execution/payloads/store' +import { + executeInSandbox, + executeShellInSandbox, + SIM_RESULT_PREFIX, +} from '@/lib/execution/remote-sandbox' import { isExecutionResourceLimitError } from '@/lib/execution/resource-errors' import { fetchWorkspaceFileBuffer, @@ -1537,7 +1541,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { error: shellError, exportedFileContent, exportedFiles, - } = await executeShellInE2B({ + } = await executeShellInSandbox({ code: resolvedCode, envs: shellEnvs, timeoutMs: timeout, @@ -1693,7 +1697,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { error: e2bError, exportedFileContent, exportedFiles, - } = await executeInE2B({ + } = await executeInSandbox({ code: codeForE2B, language: CodeLanguage.JavaScript, timeoutMs: timeout, @@ -1781,7 +1785,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { error: e2bError, exportedFileContent, exportedFiles, - } = await executeInE2B({ + } = await executeInSandbox({ code: codeForE2B, language: CodeLanguage.Python, timeoutMs: timeout, diff --git a/apps/sim/executor/handlers/pi/cloud-backend.test.ts b/apps/sim/executor/handlers/pi/cloud-backend.test.ts index ada9859cd39..8107c62bcf3 100644 --- a/apps/sim/executor/handlers/pi/cloud-backend.test.ts +++ b/apps/sim/executor/handlers/pi/cloud-backend.test.ts @@ -13,7 +13,7 @@ const { mockRun, mockReadFile, mockWriteFile, mockExecuteTool, mockProviderEnvVa }) ) -vi.mock('@/lib/execution/e2b', () => ({ +vi.mock('@/lib/execution/remote-sandbox', () => ({ withPiSandbox: (fn: (runner: unknown) => unknown) => fn({ run: mockRun, readFile: mockReadFile, writeFile: mockWriteFile }), })) diff --git a/apps/sim/executor/handlers/pi/cloud-backend.ts b/apps/sim/executor/handlers/pi/cloud-backend.ts index ce91e7c957f..5b46820140d 100644 --- a/apps/sim/executor/handlers/pi/cloud-backend.ts +++ b/apps/sim/executor/handlers/pi/cloud-backend.ts @@ -15,7 +15,7 @@ import { createLogger } from '@sim/logger' import { generateShortId } from '@sim/utils/id' import { truncate } from '@sim/utils/string' -import { withPiSandbox } from '@/lib/execution/e2b' +import { withPiSandbox } from '@/lib/execution/remote-sandbox' import type { PiBackendRun, PiCloudRunParams } from '@/executor/handlers/pi/backend' import { CLONE_TIMEOUT_MS, diff --git a/apps/sim/executor/handlers/pi/cloud-review-backend.test.ts b/apps/sim/executor/handlers/pi/cloud-review-backend.test.ts index 37d22a178dd..adf9e6591a2 100644 --- a/apps/sim/executor/handlers/pi/cloud-review-backend.test.ts +++ b/apps/sim/executor/handlers/pi/cloud-review-backend.test.ts @@ -62,7 +62,7 @@ const mockModelRuntime = { vi.mock('@sim/logger', () => ({ createLogger: () => ({ info: vi.fn(), warn: mockLoggerWarn }), })) -vi.mock('@/lib/execution/e2b', () => ({ +vi.mock('@/lib/execution/remote-sandbox', () => ({ withPiSandbox: (fn: (runner: unknown) => unknown) => fn({ run: mockRun, writeFile: mockWriteFile }), })) diff --git a/apps/sim/executor/handlers/pi/cloud-review-backend.ts b/apps/sim/executor/handlers/pi/cloud-review-backend.ts index f051aec4e13..1b7f2bf91e9 100644 --- a/apps/sim/executor/handlers/pi/cloud-review-backend.ts +++ b/apps/sim/executor/handlers/pi/cloud-review-backend.ts @@ -9,7 +9,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { createLogger } from '@sim/logger' import { truncate } from '@sim/utils/string' -import { withPiSandbox } from '@/lib/execution/e2b' +import { withPiSandbox } from '@/lib/execution/remote-sandbox' import type { PiBackendRun, PiCloudReviewRunParams } from '@/executor/handlers/pi/backend' import { CLOUD_REVIEW_TOOL_NAMES, diff --git a/apps/sim/executor/handlers/pi/cloud-review-tools.test.ts b/apps/sim/executor/handlers/pi/cloud-review-tools.test.ts index c2aded09d48..4cf18a9f3a2 100644 --- a/apps/sim/executor/handlers/pi/cloud-review-tools.test.ts +++ b/apps/sim/executor/handlers/pi/cloud-review-tools.test.ts @@ -8,7 +8,7 @@ import { join } from 'node:path' import { promisify } from 'node:util' import * as sdk from '@earendil-works/pi-coding-agent' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { PiSandboxRunner } from '@/lib/execution/e2b' +import type { PiSandboxRunner } from '@/lib/execution/remote-sandbox' import { CLOUD_REVIEW_TOOL_NAMES, createCloudReviewTools, diff --git a/apps/sim/executor/handlers/pi/cloud-review-tools.ts b/apps/sim/executor/handlers/pi/cloud-review-tools.ts index eb1e8a15da3..192e049bf6a 100644 --- a/apps/sim/executor/handlers/pi/cloud-review-tools.ts +++ b/apps/sim/executor/handlers/pi/cloud-review-tools.ts @@ -1,6 +1,6 @@ import type { ToolDefinition } from '@earendil-works/pi-coding-agent' import { Type } from 'typebox' -import type { PiSandboxRunner } from '@/lib/execution/e2b' +import type { PiSandboxRunner } from '@/lib/execution/remote-sandbox' import { REVIEW_TOOLS_SCRIPT } from '@/executor/handlers/pi/cloud-review-tools-script' import { raceAbort } from '@/executor/handlers/pi/cloud-shared' import type { PiSdk } from '@/executor/handlers/pi/pi-sdk' diff --git a/apps/sim/lib/copilot/tools/server/files/doc-compile.test.ts b/apps/sim/lib/copilot/tools/server/files/doc-compile.test.ts index f65efd94542..18a53134c53 100644 --- a/apps/sim/lib/copilot/tools/server/files/doc-compile.test.ts +++ b/apps/sim/lib/copilot/tools/server/files/doc-compile.test.ts @@ -3,9 +3,9 @@ */ import { describe, expect, it, vi } from 'vitest' -vi.mock('@/lib/execution/e2b', () => ({ - executeInE2B: vi.fn(), - executeShellInE2B: vi.fn(), +vi.mock('@/lib/execution/remote-sandbox', () => ({ + executeInSandbox: vi.fn(), + executeShellInSandbox: vi.fn(), })) vi.mock('@/lib/execution/languages', () => ({ CodeLanguage: { javascript: 'javascript', python: 'python' }, diff --git a/apps/sim/lib/copilot/tools/server/files/doc-compile.ts b/apps/sim/lib/copilot/tools/server/files/doc-compile.ts index b9a6ea0a260..c136450970c 100644 --- a/apps/sim/lib/copilot/tools/server/files/doc-compile.ts +++ b/apps/sim/lib/copilot/tools/server/files/doc-compile.ts @@ -3,8 +3,12 @@ import { sha256Hex } from '@sim/security/hash' import { getErrorMessage } from '@sim/utils/errors' import { isE2BDocEnabled } from '@/lib/core/config/env-flags' import { isFeatureEnabled } from '@/lib/core/config/feature-flags' -import { executeInE2B, executeShellInE2B, type SandboxFile } from '@/lib/execution/e2b' import { CodeLanguage } from '@/lib/execution/languages' +import { + executeInSandbox, + executeShellInSandbox, + type SandboxFile, +} from '@/lib/execution/remote-sandbox' import { runSandboxTask } from '@/lib/execution/sandbox/run-task' import { fetchWorkspaceFileBuffer, @@ -259,7 +263,7 @@ async function compileDocViaE2BPython( // unaffected. Runs only after the user's script succeeds. const code = fmt.ext === 'xlsx' ? `${source}\n${XLSX_RECALC_SNIPPET}` : source - const result = await executeInE2B({ + const result = await executeInSandbox({ code, language: CodeLanguage.Python, timeoutMs: DOC_COMPILE_TIMEOUT_MS, @@ -342,7 +346,7 @@ ${finalize} })().then(() => console.log('__DOC_OK__')).catch((e) => { console.error('__DOC_ERR__' + (e && e.message ? e.message : String(e))); process.exit(1); }); ` - const result = await executeShellInE2B({ + const result = await executeShellInSandbox({ code: 'NODE_PATH=$(npm root -g) node /home/user/script.js', envs: {}, timeoutMs: DOC_COMPILE_TIMEOUT_MS, diff --git a/apps/sim/lib/copilot/tools/server/files/doc-extract.ts b/apps/sim/lib/copilot/tools/server/files/doc-extract.ts index 1674b7c7af7..92230ac871a 100644 --- a/apps/sim/lib/copilot/tools/server/files/doc-extract.ts +++ b/apps/sim/lib/copilot/tools/server/files/doc-extract.ts @@ -1,5 +1,5 @@ -import { executeInE2B } from '@/lib/execution/e2b' import { CodeLanguage } from '@/lib/execution/languages' +import { executeInSandbox } from '@/lib/execution/remote-sandbox' const EXTRACT_TIMEOUT_MS = 120_000 // Bound the text handed back to the agent so a huge document can't blow the @@ -87,7 +87,7 @@ text = "\\n".join(out)[:${MAX_EXTRACT_CHARS + 20000}] print("__SIM_RESULT__=" + json.dumps({"text": text})) `.trim() - const result = await executeInE2B({ + const result = await executeInSandbox({ code: script, language: CodeLanguage.Python, timeoutMs: EXTRACT_TIMEOUT_MS, diff --git a/apps/sim/lib/copilot/tools/server/files/doc-recalc.ts b/apps/sim/lib/copilot/tools/server/files/doc-recalc.ts index dd8c43b3499..c85b9249110 100644 --- a/apps/sim/lib/copilot/tools/server/files/doc-recalc.ts +++ b/apps/sim/lib/copilot/tools/server/files/doc-recalc.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' -import { executeInE2B } from '@/lib/execution/e2b' import { CodeLanguage } from '@/lib/execution/languages' +import { executeInSandbox } from '@/lib/execution/remote-sandbox' import { compileDoc, DocCompileUserError } from './doc-compile' const logger = createLogger('CopilotDocRecalc') @@ -50,7 +50,7 @@ for ws in wb.worksheets: print("__SIM_RESULT__=" + json.dumps({"ok": len(errors) == 0, "errors": errors[:${MAX_REPORTED_ERRORS}]})) `.trim() - const result = await executeInE2B({ + const result = await executeInSandbox({ code: script, language: CodeLanguage.Python, timeoutMs: RECALC_TIMEOUT_MS, diff --git a/apps/sim/lib/copilot/tools/server/files/doc-render.ts b/apps/sim/lib/copilot/tools/server/files/doc-render.ts index 70b3a2a2d97..24262b48825 100644 --- a/apps/sim/lib/copilot/tools/server/files/doc-render.ts +++ b/apps/sim/lib/copilot/tools/server/files/doc-render.ts @@ -1,5 +1,5 @@ -import { executeInE2B } from '@/lib/execution/e2b' import { CodeLanguage } from '@/lib/execution/languages' +import { executeInSandbox } from '@/lib/execution/remote-sandbox' const RENDER_TIMEOUT_MS = 150_000 // Bound the visual-QA cost: cap pages and rasterization DPI so the JPEGs the @@ -84,7 +84,7 @@ else: print("__SIM_RESULT__=" + json.dumps({"grid": base64.b64encode(f.read()).decode(), "pageCount": n})) `.trim() - const result = await executeInE2B({ + const result = await executeInSandbox({ code: script, language: CodeLanguage.Python, timeoutMs: RENDER_TIMEOUT_MS, diff --git a/apps/sim/lib/copilot/tools/server/files/doc-servable.test.ts b/apps/sim/lib/copilot/tools/server/files/doc-servable.test.ts index f72695cb539..3c863771932 100644 --- a/apps/sim/lib/copilot/tools/server/files/doc-servable.test.ts +++ b/apps/sim/lib/copilot/tools/server/files/doc-servable.test.ts @@ -10,9 +10,9 @@ const { e2bFlag, betaFlag, mockLoadCompiledDoc, mockRunSandboxTask } = vi.hoiste mockRunSandboxTask: vi.fn(), })) -vi.mock('@/lib/execution/e2b', () => ({ - executeInE2B: vi.fn(), - executeShellInE2B: vi.fn(), +vi.mock('@/lib/execution/remote-sandbox', () => ({ + executeInSandbox: vi.fn(), + executeShellInSandbox: vi.fn(), })) vi.mock('@/lib/execution/languages', () => ({ CodeLanguage: { javascript: 'javascript', python: 'python' }, diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index d7d51b7b518..2b659669ade 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -454,6 +454,13 @@ export const env = createEnv({ MOTHERSHIP_E2B_DOC_TEMPLATE_ID: z.string().optional(), // Dedicated E2B template with python-pptx/docx/openpyxl/reportlab for document generation; when set (and E2B enabled), docs compile via Python instead of the JS isolated-vm path E2B_PI_TEMPLATE_ID: z.string().optional(), // E2B template ID/alias with the Pi CLI + git baked in (Create PR and Review Code) + // Daytona Remote Code Execution (manual failover for E2B; selected by the sandbox-provider-daytona flag) + DAYTONA_API_KEY: z.string().optional(), // Daytona API key; needs write:snapshots to build images, write:sandboxes to run them + DAYTONA_SHELL_SNAPSHOT_ID: z.string().optional(), // Daytona snapshot mirroring mothership-shell (must carry an explicit tag; latest is rejected) + DAYTONA_DOC_SNAPSHOT_ID: z.string().optional(), // Daytona snapshot mirroring mothership-docs + DAYTONA_PI_SNAPSHOT_ID: z.string().optional(), // Daytona snapshot mirroring the Pi template (Create PR and Review Code) + SANDBOX_PROVIDER_DAYTONA: z.string().optional(), // Fallback for the sandbox-provider-daytona flag when AppConfig is not the source of truth + // Access Control (Permission Groups) - for self-hosted deployments ACCESS_CONTROL_ENABLED: z.boolean().optional(), // Enable access control on self-hosted (bypasses plan requirements) diff --git a/apps/sim/lib/core/config/feature-flags.ts b/apps/sim/lib/core/config/feature-flags.ts index 558707fa587..7981cea2eed 100644 --- a/apps/sim/lib/core/config/feature-flags.ts +++ b/apps/sim/lib/core/config/feature-flags.ts @@ -105,6 +105,16 @@ const FEATURE_FLAGS = { 'self-hosted/local behaviour. Fallback mirrors FORKING_ENABLED for off-AppConfig reads.', fallback: 'FORKING_ENABLED', }, + 'sandbox-provider-daytona': { + description: + 'Route remote sandbox execution (function blocks, shell, doc generation, Pi cloud agent) to ' + + 'Daytona instead of E2B — the manual failover for an E2B outage. Global on/off only: ' + + 'resolved without user/org context at every sandbox entry point so the whole deployment ' + + 'switches together, and resolved ONCE before the sandbox is created so a run is never ' + + 'migrated mid-execution (user code has side effects). Requires the DAYTONA_* snapshot ids; ' + + 'each sandbox kind fails closed when its snapshot is unset.', + fallback: 'SANDBOX_PROVIDER_DAYTONA', + }, 'deploy-as-block': { description: 'Publish a deployed workflow as a reusable, org-wide custom block (custom name/SVG icon/' + diff --git a/apps/sim/lib/execution/e2b.test.ts b/apps/sim/lib/execution/e2b.test.ts deleted file mode 100644 index 0bc92f095f2..00000000000 --- a/apps/sim/lib/execution/e2b.test.ts +++ /dev/null @@ -1,298 +0,0 @@ -/** - * @vitest-environment node - */ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { CodeLanguage } from '@/lib/execution/languages' - -const { mockCreate, mockRunCode, mockCommandsRun, mockFilesWrite, mockKill } = vi.hoisted(() => ({ - mockCreate: vi.fn(), - mockRunCode: vi.fn(), - mockCommandsRun: vi.fn(), - mockFilesWrite: vi.fn(), - mockKill: vi.fn(), -})) - -vi.mock('@e2b/code-interpreter', () => ({ Sandbox: { create: mockCreate } })) -vi.mock('@/lib/core/config/env', () => ({ env: { E2B_API_KEY: 'test-key' } })) - -import { executeInE2B, executeShellInE2B } from '@/lib/execution/e2b' - -describe('e2b sandbox inputs', () => { - beforeEach(() => { - vi.clearAllMocks() - mockCreate.mockResolvedValue({ - sandboxId: 'sb_1', - files: { write: mockFilesWrite }, - commands: { run: mockCommandsRun }, - runCode: mockRunCode, - kill: mockKill, - }) - mockRunCode.mockResolvedValue({ - error: null, - text: '', - logs: { stdout: [], stderr: [] }, - results: [], - }) - // Default: shell code run + any fetch succeed. - mockCommandsRun.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) - }) - - it('fetches a url entry via curl with URL/DST/DIR passed as envs (no inline write)', async () => { - await executeInE2B({ - code: 'x', - language: CodeLanguage.JavaScript, - timeoutMs: 1000, - sandboxFiles: [ - { type: 'url', path: '/home/user/tables/t.csv', url: 'https://s3.example/p?a=1&b=2' }, - ], - }) - - expect(mockCommandsRun).toHaveBeenCalledTimes(1) - const [cmd, opts] = mockCommandsRun.mock.calls[0] - expect(cmd).toContain('curl') - expect(cmd).toContain('mkdir -p') - // URL/path go through envs, never interpolated into the command string. - expect(cmd).not.toContain('https://s3.example') - expect(opts.envs).toEqual({ - URL: 'https://s3.example/p?a=1&b=2', - DST: '/home/user/tables/t.csv', - DIR: '/home/user/tables', - }) - expect(opts.user).toBeUndefined() // code sandbox runs as default user - expect(mockFilesWrite).not.toHaveBeenCalled() - }) - - it('writes a content entry inline (no fetch)', async () => { - await executeInE2B({ - code: 'x', - language: CodeLanguage.JavaScript, - timeoutMs: 1000, - sandboxFiles: [{ path: '/home/user/f.txt', content: 'hi' }], - }) - - expect(mockFilesWrite).toHaveBeenCalledWith('/home/user/f.txt', 'hi') - expect(mockCommandsRun).not.toHaveBeenCalled() - }) - - it('fetches as root in the shell sandbox', async () => { - await executeShellInE2B({ - code: 'echo hi', - envs: {}, - timeoutMs: 1000, - sandboxFiles: [{ type: 'url', path: '/home/user/tables/t.csv', url: 'https://s3.example/p' }], - }) - - const fetchCall = mockCommandsRun.mock.calls.find((c) => c[1]?.envs?.URL) - expect(fetchCall).toBeDefined() - expect(fetchCall?.[0]).toContain('curl') - expect(fetchCall?.[1].user).toBe('root') - }) - - it('passes the requested timeout to shell execution and returns timeout failures', async () => { - mockCommandsRun.mockRejectedValueOnce( - Object.assign(new Error('Execution timed out'), { - stderr: 'Execution timed out', - exitCode: 1, - }) - ) - - const result = await executeShellInE2B({ - code: 'sleep 60', - envs: {}, - timeoutMs: 7000, - }) - - expect(mockCommandsRun).toHaveBeenCalledWith( - 'sleep 60', - expect.objectContaining({ timeoutMs: 7000 }) - ) - expect(result.error).toContain('Execution timed out') - expect(mockKill).toHaveBeenCalled() - }) - - it('throws a clear error and kills the sandbox when the fetch fails', async () => { - mockCommandsRun.mockRejectedValueOnce(new Error('curl: (22) 403')) - - await expect( - executeInE2B({ - code: 'x', - language: CodeLanguage.JavaScript, - timeoutMs: 1000, - sandboxFiles: [ - { type: 'url', path: '/home/user/tables/t.csv', url: 'https://s3.example/p' }, - ], - }) - ).rejects.toThrow(/Failed to fetch mounted file into sandbox/) - - expect(mockKill).toHaveBeenCalled() - expect(mockRunCode).not.toHaveBeenCalled() - }) -}) - -describe('e2b result marker extraction', () => { - beforeEach(() => { - vi.clearAllMocks() - mockCreate.mockResolvedValue({ - sandboxId: 'sb_1', - files: { write: mockFilesWrite }, - commands: { run: mockCommandsRun }, - runCode: mockRunCode, - kill: mockKill, - }) - mockCommandsRun.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) - }) - - it('parses the result marker from a single stdout entry', async () => { - mockRunCode.mockResolvedValue({ - error: null, - text: '', - logs: { stdout: ['before\n', `__SIM_RESULT__=${JSON.stringify({ ok: true })}\n`] }, - results: [], - }) - - const res = await executeInE2B({ - code: 'x', - language: CodeLanguage.Python, - timeoutMs: 1000, - }) - - expect(res.error).toBeUndefined() - expect(res.result).toEqual({ ok: true }) - expect(res.stdout).toBe('before') - }) - - it('reassembles a marker line split across stream chunks (large single-line result)', async () => { - const payload = 'x'.repeat(50_000) - const markerLine = `__SIM_RESULT__=${JSON.stringify(payload)}\n` - // The kernel splits one long line across several stream messages; each - // chunk is NOT newline-terminated except the last. - const chunks = [ - 'log line\n', - markerLine.slice(0, 20_000), - markerLine.slice(20_000, 40_000), - markerLine.slice(40_000), - ] - mockRunCode.mockResolvedValue({ - error: null, - text: '', - logs: { stdout: chunks }, - results: [], - }) - - const res = await executeInE2B({ - code: 'x', - language: CodeLanguage.Python, - timeoutMs: 1000, - }) - - expect(res.error).toBeUndefined() - expect(res.result).toBe(payload) - expect(res.stdout).toBe('log line') - }) - - it('returns an error instead of a truncated fragment when the marker payload is corrupted', async () => { - // A genuinely broken payload (e.g. the tail chunk was lost entirely). - mockRunCode.mockResolvedValue({ - error: null, - text: '', - logs: { stdout: [`__SIM_RESULT__="${'x'.repeat(100)}\n`] }, - results: [], - }) - - const res = await executeInE2B({ - code: 'x', - language: CodeLanguage.Python, - timeoutMs: 1000, - }) - - expect(res.error).toMatch(/corrupted in transport/) - expect(res.result).toBeNull() - }) - - it('parses the shell marker and falls back to the raw string for non-JSON payloads', async () => { - mockCommandsRun.mockResolvedValueOnce({ - stdout: `hello\n__SIM_RESULT__=${JSON.stringify([1, 2])}\n`, - stderr: '', - exitCode: 0, - }) - const ok = await executeShellInE2B({ code: 'x', envs: {}, timeoutMs: 1000 }) - expect(ok.error).toBeUndefined() - expect(ok.result).toEqual([1, 2]) - expect(ok.stdout).toBe('hello') - - // Shell markers are user-authored (`echo "__SIM_RESULT__=$STATUS"`), so a - // plain non-JSON value is a string result, never a transport error. - mockCommandsRun.mockResolvedValueOnce({ - stdout: '__SIM_RESULT__=ok\n', - stderr: '', - exitCode: 0, - }) - const plain = await executeShellInE2B({ code: 'x', envs: {}, timeoutMs: 1000 }) - expect(plain.error).toBeUndefined() - expect(plain.result).toBe('ok') - }) - - it('takes the LAST marker line so user-printed marker lines cannot shadow the real result', async () => { - mockRunCode.mockResolvedValue({ - error: null, - text: '', - logs: { - stdout: [ - '__SIM_RESULT__=user-debug-junk\n', - `__SIM_RESULT__=${JSON.stringify({ real: true })}\n`, - ], - }, - results: [], - }) - - const res = await executeInE2B({ - code: 'x', - language: CodeLanguage.Python, - timeoutMs: 1000, - }) - - expect(res.error).toBeUndefined() - expect(res.result).toEqual({ real: true }) - expect(res.stdout).not.toContain('__SIM_RESULT__') - }) - - it('finds a marker that landed on stderr', async () => { - mockRunCode.mockResolvedValue({ - error: null, - text: '', - logs: { - stdout: ['regular output\n'], - stderr: [`__SIM_RESULT__=${JSON.stringify([1])}\n`], - }, - results: [], - }) - - const res = await executeInE2B({ - code: 'x', - language: CodeLanguage.Python, - timeoutMs: 1000, - }) - - expect(res.error).toBeUndefined() - expect(res.result).toEqual([1]) - expect(res.stdout).not.toContain('__SIM_RESULT__') - }) - - it('keeps separate print lines intact (chunks concatenated verbatim, not newline-joined)', async () => { - mockRunCode.mockResolvedValue({ - error: null, - text: '', - logs: { stdout: ['a\n', 'b\n'], stderr: ['warn\n'] }, - results: [], - }) - - const res = await executeInE2B({ - code: 'x', - language: CodeLanguage.Python, - timeoutMs: 1000, - }) - - expect(res.result).toBeNull() - expect(res.stdout).toBe('a\nb\n\nwarn\n') - }) -}) diff --git a/apps/sim/lib/execution/e2b.ts b/apps/sim/lib/execution/e2b.ts deleted file mode 100644 index 0f9f2e3a553..00000000000 --- a/apps/sim/lib/execution/e2b.ts +++ /dev/null @@ -1,516 +0,0 @@ -import type { Sandbox as E2BSandbox } from '@e2b/code-interpreter' -import { createLogger } from '@sim/logger' -import { getErrorMessage } from '@sim/utils/errors' -import { env } from '@/lib/core/config/env' -import { CodeLanguage } from '@/lib/execution/languages' - -/** - * A sandbox input file. `content` entries are written inline; `url` entries are fetched from inside - * the sandbox (so large mounts never pass their bytes through the web process). - */ -export type SandboxFile = - | { type?: 'content'; path: string; content: string; encoding?: 'base64' } - | { type: 'url'; path: string; url: string } - -export interface E2BExecutionRequest { - code: string - language: CodeLanguage - timeoutMs: number - sandboxFiles?: SandboxFile[] - outputSandboxPath?: string - outputSandboxPaths?: string[] - // Which sandbox template to run in. Defaults to 'code' (mothership-shell). - // Document generation passes 'doc' so it runs in the doc template - // (mothership-docs) that has python-pptx/docx/openpyxl/reportlab installed. - sandboxKind?: 'code' | 'doc' -} - -export interface E2BShellExecutionRequest { - code: string - envs: Record - timeoutMs: number - sandboxFiles?: SandboxFile[] - outputSandboxPath?: string - outputSandboxPaths?: string[] - // Which sandbox template to run in. Defaults to 'shell' (mothership-shell). - // The Node document engines (pptxgenjs/docx + react-icons/sharp) pass 'doc' so - // they run in the doc template (mothership-docs). - sandboxKind?: 'shell' | 'doc' -} - -export interface E2BExecutionResult { - result: unknown - stdout: string - sandboxId?: string - error?: string - exportedFileContent?: string - exportedFiles?: Record - /** Base64-encoded PNG images captured from rich outputs (e.g. matplotlib figures). */ - images?: string[] -} - -const logger = createLogger('E2BExecution') - -/** - * Materializes sandbox input files before user code runs. `content` entries are written inline; - * `url` entries are fetched from inside the sandbox via `curl` — their bytes never pass through the - * web process, so the mount size is bounded by sandbox disk, not web heap. The URL and paths are - * passed as env vars (never interpolated into the shell) so a presigned query string can't break or - * inject. A failed fetch throws so user code never runs against a missing mount. `rootUser` matches - * the shell sandbox's root execution context. - */ -async function writeSandboxInputs( - sandbox: E2BSandbox, - files: SandboxFile[] | undefined, - opts: { sandboxId?: string; rootUser?: boolean } -): Promise { - if (!files?.length) return - const fetchedByUrl: string[] = [] - const writtenInline: string[] = [] - for (const file of files) { - if (file.type === 'url') { - const dir = file.path.slice(0, file.path.lastIndexOf('/')) - try { - await sandbox.commands.run( - 'set -e; [ -n "$DIR" ] && mkdir -p "$DIR"; curl -fsS --retry 3 --retry-connrefused --max-time 300 "$URL" -o "$DST"', - { - envs: { URL: file.url, DST: file.path, DIR: dir }, - ...(opts.rootUser ? { user: 'root' } : {}), - } - ) - fetchedByUrl.push(file.path) - } catch (error) { - throw new Error( - `Failed to fetch mounted file into sandbox at ${file.path}: ${getErrorMessage(error)}` - ) - } - } else if (file.encoding === 'base64') { - const buf = Buffer.from(file.content, 'base64') - await sandbox.files.write( - file.path, - buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) - ) - writtenInline.push(file.path) - } else { - await sandbox.files.write(file.path, file.content) - writtenInline.push(file.path) - } - } - // Split counts so it's visible whether a mount was fetched in-sandbox (by presigned URL, no bytes - // through the web process) or written inline. - logger.info('Materialized sandbox inputs', { - sandboxId: opts.sandboxId, - fetchedByUrlCount: fetchedByUrl.length, - writtenInlineCount: writtenInline.length, - fetchedByUrl, - writtenInline, - }) -} - -async function createE2BSandbox(kind: 'code' | 'shell' | 'doc' | 'pi'): Promise { - const apiKey = env.E2B_API_KEY - if (!apiKey) { - throw new Error('E2B_API_KEY is required when E2B is enabled') - } - - // Document generation uses a dedicated template (python-pptx/docx/openpyxl/ - // reportlab + fonts); shell/code execution use the general shell template. - // Doc fails closed: never run LLM-authored Python in E2B's default template - // (which is not vetted for this) just because the doc template id is unset. - if (kind === 'doc' && !env.MOTHERSHIP_E2B_DOC_TEMPLATE_ID) { - throw new Error('Document compiler not configured (MOTHERSHIP_E2B_DOC_TEMPLATE_ID is unset)') - } - // Pi fails closed for the same reason: the coding agent needs the Pi CLI + git - // baked into a vetted template, never E2B's default image. - if (kind === 'pi' && !env.E2B_PI_TEMPLATE_ID) { - throw new Error('Pi cloud agent not configured (E2B_PI_TEMPLATE_ID is unset)') - } - - const templateName = - kind === 'doc' - ? env.MOTHERSHIP_E2B_DOC_TEMPLATE_ID - : kind === 'pi' - ? env.E2B_PI_TEMPLATE_ID - : env.MOTHERSHIP_E2B_TEMPLATE_ID - logger.info('Creating E2B sandbox', { - kind, - template: templateName || '(default)', - }) - const { Sandbox } = await import('@e2b/code-interpreter') - return templateName ? Sandbox.create(templateName, { apiKey }) : Sandbox.create({ apiKey }) -} - -/** - * Marker prefix for the serialized code result printed to stdout. Emitters - * (the wrapper builders in the function-execute route) interpolate this - * constant so producer and parser cannot drift. - */ -export const SIM_RESULT_PREFIX = '__SIM_RESULT__=' - -/** - * Extracts the `__SIM_RESULT__=` marker line from stdout and parses its JSON - * payload. Takes the LAST marker line: the wrapper prints its marker after all - * user output, so an earlier user-printed line with the same prefix (debug - * output, a grepped log) never shadows the real result. `parseFailed` means - * the last marker's payload was not valid JSON — `rawPayload` carries it so - * callers whose markers are user-authored (shell) can fall back to the plain - * string, while wrapper-backed callers treat it as transport corruption. - */ -function extractSimResult(stdout: string): { - result: unknown - cleanedStdout: string - parseFailed: boolean - rawPayload?: string -} { - const lines = stdout.split('\n') - let markerIndex = -1 - for (let i = lines.length - 1; i >= 0; i--) { - if (lines[i].startsWith(SIM_RESULT_PREFIX)) { - markerIndex = i - break - } - } - if (markerIndex === -1) { - return { result: null, cleanedStdout: stdout, parseFailed: false } - } - const rawPayload = lines[markerIndex].slice(SIM_RESULT_PREFIX.length) - let result: unknown = null - let parseFailed = false - try { - result = JSON.parse(rawPayload) - } catch { - parseFailed = true - } - const filteredLines = lines.filter((l) => !l.startsWith(SIM_RESULT_PREFIX)) - if (filteredLines.length > 0 && filteredLines[filteredLines.length - 1] === '') { - filteredLines.pop() - } - return { result, cleanedStdout: filteredLines.join('\n'), parseFailed, rawPayload } -} - -const SIM_RESULT_CORRUPTED_ERROR = - 'Sandbox result was corrupted in transport (the __SIM_RESULT__ line failed to parse). ' + - "Do not trust or persist this call's output. For large results, write the content to a " + - 'file inside the sandbox and export it via outputs.files[].sandboxPath instead of returning it.' - -function shouldReadSandboxPathAsBase64(outputSandboxPath: string): boolean { - const ext = outputSandboxPath.slice(outputSandboxPath.lastIndexOf('.')).toLowerCase() - const binaryExts = new Set([ - '.png', - '.jpg', - '.jpeg', - '.gif', - '.webp', - '.pdf', - '.zip', - '.mp3', - '.mp4', - '.docx', - '.pptx', - '.xlsx', - ]) - return binaryExts.has(ext) -} - -async function readSandboxOutputFile( - sandbox: E2BSandbox, - outputSandboxPath: string, - options?: { user?: string } -): Promise { - try { - if (shouldReadSandboxPathAsBase64(outputSandboxPath)) { - const b64Result = await sandbox.commands.run(`base64 -w0 "${outputSandboxPath}"`, options) - return b64Result.stdout - } - return await sandbox.files.read(outputSandboxPath) - } catch (error) { - logger.warn('Failed to read requested sandbox output file', { - outputSandboxPath, - error: getErrorMessage(error), - }) - return undefined - } -} - -function requestedOutputSandboxPaths(req: { - outputSandboxPath?: string - outputSandboxPaths?: string[] -}): string[] { - const paths = [...(req.outputSandboxPaths ?? [])] - if (req.outputSandboxPath && !paths.includes(req.outputSandboxPath)) { - paths.push(req.outputSandboxPath) - } - return paths -} - -export async function executeInE2B(req: E2BExecutionRequest): Promise { - const { code, language, timeoutMs } = req - - const sandbox = await createE2BSandbox(req.sandboxKind ?? 'code') - const sandboxId = sandbox.sandboxId - - try { - // Inside the try so a failed mount still kills the sandbox via the finally below. - await writeSandboxInputs(sandbox, req.sandboxFiles, { sandboxId }) - - const execution = await sandbox.runCode(code, { - language: language === CodeLanguage.Python ? 'python' : 'javascript', - timeoutMs, - }) - - if (execution.error) { - const errorMessage = `${execution.error.name}: ${execution.error.value}` - logger.error(`E2B execution error`, { - sandboxId, - error: execution.error, - errorMessage, - }) - - const errorOutput = execution.error.traceback || errorMessage - return { - result: null, - stdout: errorOutput, - error: errorMessage, - sandboxId, - } - } - - // Kernel stream entries are chunks, not lines — each already carries its own - // newlines, and one long line can arrive split across several entries. - // Concatenate each stream verbatim: joining chunks with '\n' injected a - // newline at every chunk boundary, which corrupted large single-line - // __SIM_RESULT__ payloads and silently truncated the persisted result. - // Distinct sources (final-expression text, stdout, stderr) still join with - // '\n' so the marker is found no matter which stream carried it. - const streamStdout = (execution.logs?.stdout ?? []).join('') - const streamStderr = (execution.logs?.stderr ?? []).join('') - const combinedOutput = [execution.text, streamStdout, streamStderr].filter(Boolean).join('\n') - - const extraction = extractSimResult(combinedOutput) - const cleanedStdout = extraction.cleanedStdout - - // The wrapper always emits valid single-line JSON, so a marker that fails - // to parse means the payload was mangled in transport — never persist it. - if (extraction.parseFailed) { - logger.error('E2B result marker failed to parse', { - sandboxId, - stdoutEntryCount: execution.logs?.stdout?.length ?? 0, - stdoutLength: streamStdout.length, - }) - return { - result: null, - stdout: cleanedStdout, - error: SIM_RESULT_CORRUPTED_ERROR, - sandboxId, - } - } - const result = extraction.result - - const images: string[] = [] - if (execution.results?.length) { - for (const r of execution.results) { - if (r.png) { - images.push(r.png) - } else if (r.jpeg) { - images.push(r.jpeg) - } - } - } - - const exportedFiles: Record = {} - for (const outputSandboxPath of requestedOutputSandboxPaths(req)) { - const content = await readSandboxOutputFile(sandbox, outputSandboxPath) - if (content !== undefined) { - exportedFiles[outputSandboxPath] = content - } - } - const exportedFileContent = req.outputSandboxPath - ? exportedFiles[req.outputSandboxPath] - : undefined - - return { - result, - stdout: cleanedStdout, - sandboxId, - exportedFileContent, - exportedFiles: Object.keys(exportedFiles).length ? exportedFiles : undefined, - images: images.length ? images : undefined, - } - } finally { - try { - await sandbox.kill() - } catch {} - } -} - -export async function executeShellInE2B( - req: E2BShellExecutionRequest -): Promise { - const { code, envs, timeoutMs } = req - - const sandbox = await createE2BSandbox(req.sandboxKind ?? 'shell') - const sandboxId = sandbox.sandboxId - - try { - // Inside the try so a failed mount still kills the sandbox via the finally below. - await writeSandboxInputs(sandbox, req.sandboxFiles, { sandboxId, rootUser: true }) - - let result: { stdout: string; stderr: string; exitCode: number } - try { - result = await sandbox.commands.run(code, { - envs: { - ...envs, - PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin', - }, - timeoutMs, - user: 'root', - }) - } catch (cmdError: any) { - const stderr = cmdError?.stderr || cmdError?.message || String(cmdError) - const stdout = cmdError?.stdout || '' - const exitCode = cmdError?.exitCode ?? 1 - logger.error('E2B shell command error', { - sandboxId, - exitCode, - error: stderr.slice(0, 500), - }) - return { - result: null, - stdout: [stdout, stderr].filter(Boolean).join('\n'), - error: stderr || `Command failed with exit code ${exitCode}`, - sandboxId, - } - } - - const stdout = [result.stdout, result.stderr].filter(Boolean).join('\n') - - if (result.exitCode !== 0) { - const errorMessage = result.stderr || `Process exited with code ${result.exitCode}` - logger.error('E2B shell execution error', { - sandboxId, - exitCode: result.exitCode, - stderr: result.stderr?.slice(0, 500), - }) - return { - result: null, - stdout, - error: errorMessage, - sandboxId, - } - } - - // Shell scripts have no wrapper: any __SIM_RESULT__ line is user-authored - // (e.g. `echo "__SIM_RESULT__=$STATUS"`), so a non-JSON payload is a plain - // string result, not transport corruption. commands.run also accumulates - // output into one string, so the chunk-split corruption cannot occur here. - const extraction = extractSimResult(stdout) - const parsed = extraction.parseFailed ? extraction.rawPayload : extraction.result - const cleanedStdout = extraction.cleanedStdout - - const exportedFiles: Record = {} - for (const outputSandboxPath of requestedOutputSandboxPaths(req)) { - const content = await readSandboxOutputFile(sandbox, outputSandboxPath, { - user: 'root', - }) - if (content !== undefined) { - exportedFiles[outputSandboxPath] = content - } - } - const exportedFileContent = req.outputSandboxPath - ? exportedFiles[req.outputSandboxPath] - : undefined - - return { - result: parsed, - stdout: cleanedStdout, - sandboxId, - exportedFileContent, - exportedFiles: Object.keys(exportedFiles).length ? exportedFiles : undefined, - } - } finally { - try { - await sandbox.kill() - } catch {} - } -} - -const PI_SANDBOX_PATH = - '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin' - -/** Result of one command run inside a Pi sandbox. */ -export interface PiSandboxCommandResult { - stdout: string - stderr: string - exitCode: number -} - -/** Runs commands and moves files inside a live Pi sandbox. */ -export interface PiSandboxRunner { - run( - command: string, - options: { - envs?: Record - timeoutMs: number - onStdout?: (chunk: string) => void - onStderr?: (chunk: string) => void - } - ): Promise - readFile(path: string): Promise - /** - * Writes a file via the sandbox filesystem API. Bytes go through the E2B SDK, - * never a shell, so untrusted content (the assembled prompt, a commit message) - * is delivered without any shell parsing — callers reference it by a fixed path. - */ - writeFile(path: string, content: string): Promise -} - -/** - * Creates a Pi sandbox, keeps it alive for the duration of `fn` (so the cloned - * repo persists across the clone -> agent -> push commands), streams command - * output, and always kills the sandbox afterward. Per-command envs are isolated, - * so secrets handed to one command never leak into the next. - */ -export async function withPiSandbox(fn: (runner: PiSandboxRunner) => Promise): Promise { - const sandbox = await createE2BSandbox('pi') - const sandboxId = sandbox.sandboxId - logger.info('Started Pi sandbox', { sandboxId }) - - const runner: PiSandboxRunner = { - run: async (command, options) => { - try { - const result = await sandbox.commands.run(command, { - envs: { ...(options.envs ?? {}), PATH: PI_SANDBOX_PATH }, - timeoutMs: options.timeoutMs, - user: 'root', - onStdout: options.onStdout, - onStderr: options.onStderr, - }) - return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode } - } catch (error) { - const failure = error as { - stdout?: string - stderr?: string - message?: string - exitCode?: number - } - return { - stdout: failure.stdout ?? '', - stderr: failure.stderr ?? failure.message ?? getErrorMessage(error), - exitCode: failure.exitCode ?? 1, - } - } - }, - readFile: (path) => sandbox.files.read(path), - writeFile: async (path, content) => { - await sandbox.files.write(path, content) - }, - } - - try { - return await fn(runner) - } finally { - try { - await sandbox.kill() - } catch {} - } -} diff --git a/apps/sim/lib/execution/remote-sandbox/conformance.test.ts b/apps/sim/lib/execution/remote-sandbox/conformance.test.ts new file mode 100644 index 00000000000..87d3c45d19a --- /dev/null +++ b/apps/sim/lib/execution/remote-sandbox/conformance.test.ts @@ -0,0 +1,342 @@ +/** + * @vitest-environment node + * + * Provider conformance: the same input must produce the same + * `SandboxExecutionResult` on E2B and on Daytona. A divergence here is exactly + * what would surface as a broken failover mid-incident, so every scenario runs + * twice — once per provider — from a single table. + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { CodeLanguage } from '@/lib/execution/languages' + +const { + mockIsFeatureEnabled, + mockE2BCreate, + mockE2BRunCode, + mockE2BCommandsRun, + mockE2BFilesRead, + mockE2BFilesWrite, + mockE2BKill, + mockDaytonaCreate, + mockInterpreterRunCode, + mockProcessCodeRun, + mockExecuteCommand, + mockUploadFile, + mockDownloadFile, + mockDelete, +} = vi.hoisted(() => ({ + mockIsFeatureEnabled: vi.fn(), + mockE2BCreate: vi.fn(), + mockE2BRunCode: vi.fn(), + mockE2BCommandsRun: vi.fn(), + mockE2BFilesRead: vi.fn(), + mockE2BFilesWrite: vi.fn(), + mockE2BKill: vi.fn(), + mockDaytonaCreate: vi.fn(), + mockInterpreterRunCode: vi.fn(), + mockProcessCodeRun: vi.fn(), + mockExecuteCommand: vi.fn(), + mockUploadFile: vi.fn(), + mockDownloadFile: vi.fn(), + mockDelete: vi.fn(), +})) + +vi.mock('@e2b/code-interpreter', () => ({ Sandbox: { create: mockE2BCreate } })) +vi.mock('@daytonaio/sdk', () => ({ + Daytona: class { + create = mockDaytonaCreate + }, +})) +vi.mock('@/lib/core/config/env', () => ({ + env: { + E2B_API_KEY: 'test-key', + MOTHERSHIP_E2B_TEMPLATE_ID: 'mothership-shell', + MOTHERSHIP_E2B_DOC_TEMPLATE_ID: 'mothership-docs', + E2B_PI_TEMPLATE_ID: 'sim-pi', + DAYTONA_API_KEY: 'test-key', + DAYTONA_SHELL_SNAPSHOT_ID: 'mothership-shell:v1', + DAYTONA_DOC_SNAPSHOT_ID: 'mothership-docs:v1', + DAYTONA_PI_SNAPSHOT_ID: 'sim-pi:v1', + }, +})) +vi.mock('@/lib/core/config/feature-flags', () => ({ isFeatureEnabled: mockIsFeatureEnabled })) + +import { + executeInSandbox, + executeShellInSandbox, + SIM_RESULT_PREFIX, +} from '@/lib/execution/remote-sandbox' + +type Provider = 'e2b' | 'daytona' +const PROVIDERS: Provider[] = ['e2b', 'daytona'] + +/** Points the shared layer at one provider and stubs that provider's SDK. */ +function useProvider(provider: Provider) { + mockIsFeatureEnabled.mockResolvedValue(provider === 'daytona') +} + +/** Stubs a code execution that prints `stdout` and emits `result` via the marker. */ +function stubCodeRun(provider: Provider, stdout: string) { + if (provider === 'e2b') { + mockE2BRunCode.mockResolvedValue({ + error: null, + text: '', + logs: { stdout: [stdout], stderr: [] }, + results: [], + }) + } else { + mockInterpreterRunCode.mockResolvedValue({ stdout, stderr: '', error: undefined }) + } +} + +beforeEach(() => { + vi.clearAllMocks() + + mockE2BCreate.mockResolvedValue({ + sandboxId: 'sb_1', + runCode: mockE2BRunCode, + commands: { run: mockE2BCommandsRun }, + files: { read: mockE2BFilesRead, write: mockE2BFilesWrite }, + kill: mockE2BKill, + }) + mockE2BCommandsRun.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + + mockDaytonaCreate.mockResolvedValue({ + id: 'sb_1', + codeInterpreter: { runCode: mockInterpreterRunCode }, + process: { codeRun: mockProcessCodeRun, executeCommand: mockExecuteCommand }, + fs: { uploadFile: mockUploadFile, downloadFile: mockDownloadFile }, + delete: mockDelete, + }) + mockExecuteCommand.mockResolvedValue({ result: '', exitCode: 0 }) +}) + +describe.each(PROVIDERS)('sandbox conformance [%s]', (provider) => { + beforeEach(() => useProvider(provider)) + + it('parses the __SIM_RESULT__ marker and strips it from stdout', async () => { + stubCodeRun(provider, `hello\n${SIM_RESULT_PREFIX}{"ok":true}`) + + const res = await executeInSandbox({ + code: 'x', + language: CodeLanguage.Python, + timeoutMs: 1000, + }) + + expect(res.result).toEqual({ ok: true }) + expect(res.stdout).toBe('hello') + expect(res.error).toBeUndefined() + }) + + it('takes the LAST marker so user output cannot shadow the real result', async () => { + stubCodeRun( + provider, + `${SIM_RESULT_PREFIX}{"decoy":true}\nnoise\n${SIM_RESULT_PREFIX}{"real":true}` + ) + + const res = await executeInSandbox({ + code: 'x', + language: CodeLanguage.Python, + timeoutMs: 1000, + }) + + expect(res.result).toEqual({ real: true }) + }) + + it('refuses to return a corrupted marker payload', async () => { + stubCodeRun(provider, `${SIM_RESULT_PREFIX}{"truncated":`) + + const res = await executeInSandbox({ + code: 'x', + language: CodeLanguage.Python, + timeoutMs: 1000, + }) + + expect(res.result).toBeNull() + expect(res.error).toContain('corrupted in transport') + }) + + it('survives a large single-line payload without chunk corruption', async () => { + const blob = 'x'.repeat(200_000) + stubCodeRun(provider, `${SIM_RESULT_PREFIX}${JSON.stringify({ blob })}`) + + const res = await executeInSandbox({ + code: 'x', + language: CodeLanguage.Python, + timeoutMs: 1000, + }) + + expect((res.result as { blob: string }).blob).toHaveLength(200_000) + }) + + it('normalizes execution errors to the same shape', async () => { + if (provider === 'e2b') { + mockE2BRunCode.mockResolvedValue({ + error: { name: 'ValueError', value: 'boom', traceback: 'Traceback...\nValueError: boom' }, + text: '', + logs: { stdout: [], stderr: [] }, + }) + } else { + mockInterpreterRunCode.mockResolvedValue({ + stdout: '', + stderr: '', + error: { name: 'ValueError', value: 'boom', traceback: 'Traceback...\nValueError: boom' }, + }) + } + + const res = await executeInSandbox({ + code: 'raise ValueError("boom")', + language: CodeLanguage.Python, + timeoutMs: 1000, + }) + + expect(res.error).toBe('ValueError: boom') + expect(res.stdout).toContain('ValueError: boom') + expect(res.result).toBeNull() + }) + + it('fetches url-mounted files inside the sandbox and never inlines the url', async () => { + stubCodeRun(provider, `${SIM_RESULT_PREFIX}null`) + + await executeInSandbox({ + code: 'x', + language: CodeLanguage.Python, + timeoutMs: 1000, + sandboxFiles: [ + { type: 'url', path: '/mnt/data/in.csv', url: 'https://example/presigned?x=1' }, + ], + }) + + const [command, options] = + provider === 'e2b' ? mockE2BCommandsRun.mock.calls[0] : mockExecuteCommand.mock.calls[0] + expect(command).toContain('curl') + // The presigned URL must travel as an env var, never interpolated into the shell. + expect(command).not.toContain('presigned') + const envs = provider === 'e2b' ? options.envs : mockExecuteCommand.mock.calls[0][2] + expect(envs).toMatchObject({ URL: 'https://example/presigned?x=1', DST: '/mnt/data/in.csv' }) + }) + + it('throws rather than running user code against a missing mount', async () => { + if (provider === 'e2b') { + mockE2BCommandsRun.mockRejectedValue(new Error('curl: (22) 404')) + } else { + mockExecuteCommand.mockResolvedValue({ result: 'curl: (22) 404', exitCode: 22 }) + } + + await expect( + executeInSandbox({ + code: 'x', + language: CodeLanguage.Python, + timeoutMs: 1000, + sandboxFiles: [{ type: 'url', path: '/mnt/data/in.csv', url: 'https://example/f' }], + }) + ).rejects.toThrow(/Failed to fetch mounted file/) + }) + + it('treats a non-JSON shell marker as a plain string, not corruption', async () => { + const stdout = `${SIM_RESULT_PREFIX}PLAIN_STATUS` + if (provider === 'e2b') { + mockE2BCommandsRun.mockResolvedValue({ stdout, stderr: '', exitCode: 0 }) + } else { + mockExecuteCommand.mockResolvedValue({ result: stdout, exitCode: 0 }) + } + + const res = await executeShellInSandbox({ code: 'echo hi', envs: {}, timeoutMs: 1000 }) + + expect(res.result).toBe('PLAIN_STATUS') + expect(res.error).toBeUndefined() + }) + + it('surfaces a non-zero shell exit as an error', async () => { + if (provider === 'e2b') { + mockE2BCommandsRun.mockResolvedValue({ stdout: '', stderr: 'bad', exitCode: 3 }) + } else { + mockExecuteCommand.mockResolvedValue({ result: 'bad', exitCode: 3 }) + } + + const res = await executeShellInSandbox({ code: 'false', envs: {}, timeoutMs: 1000 }) + + expect(res.result).toBeNull() + expect(res.error).toBeTruthy() + }) + + it('exports binary output files as base64', async () => { + stubCodeRun(provider, `${SIM_RESULT_PREFIX}null`) + if (provider === 'e2b') { + mockE2BCommandsRun.mockResolvedValue({ stdout: 'QkFTRTY0', stderr: '', exitCode: 0 }) + } else { + mockExecuteCommand.mockResolvedValue({ result: 'QkFTRTY0', exitCode: 0 }) + } + + const res = await executeInSandbox({ + code: 'x', + language: CodeLanguage.Python, + timeoutMs: 1000, + outputSandboxPath: '/out/report.xlsx', + }) + + expect(res.exportedFileContent).toBe('QkFTRTY0') + expect(res.exportedFiles).toEqual({ '/out/report.xlsx': 'QkFTRTY0' }) + }) + + it('always kills the sandbox, even when execution throws', async () => { + if (provider === 'e2b') { + mockE2BRunCode.mockRejectedValue(new Error('kaboom')) + } else { + mockInterpreterRunCode.mockRejectedValue(new Error('kaboom')) + } + + await expect( + executeInSandbox({ code: 'x', language: CodeLanguage.Python, timeoutMs: 1000 }) + ).rejects.toThrow('kaboom') + + expect(provider === 'e2b' ? mockE2BKill : mockDelete).toHaveBeenCalledTimes(1) + }) +}) + +describe('provider selection', () => { + it('routes to E2B when the flag is off and Daytona when it is on', async () => { + stubCodeRun('e2b', `${SIM_RESULT_PREFIX}null`) + stubCodeRun('daytona', `${SIM_RESULT_PREFIX}null`) + + useProvider('e2b') + await executeInSandbox({ code: 'x', language: CodeLanguage.Python, timeoutMs: 1000 }) + expect(mockE2BCreate).toHaveBeenCalledTimes(1) + expect(mockDaytonaCreate).not.toHaveBeenCalled() + + useProvider('daytona') + await executeInSandbox({ code: 'x', language: CodeLanguage.Python, timeoutMs: 1000 }) + expect(mockDaytonaCreate).toHaveBeenCalledTimes(1) + }) + + it('binds language at create time so JS never runs through the Python toolbox', async () => { + useProvider('daytona') + mockProcessCodeRun.mockResolvedValue({ result: `${SIM_RESULT_PREFIX}null`, exitCode: 0 }) + + await executeInSandbox({ code: 'x', language: CodeLanguage.JavaScript, timeoutMs: 1000 }) + + expect(mockDaytonaCreate).toHaveBeenCalledWith( + expect.objectContaining({ language: 'javascript' }) + ) + // JS must go through process.codeRun — CodeInterpreter is Python-only. + expect(mockProcessCodeRun).toHaveBeenCalledTimes(1) + expect(mockInterpreterRunCode).not.toHaveBeenCalled() + }) + + it('fails closed when a Daytona snapshot id is unset', async () => { + useProvider('daytona') + const { env } = await import('@/lib/core/config/env') + const original = env.DAYTONA_DOC_SNAPSHOT_ID + ;(env as { DAYTONA_DOC_SNAPSHOT_ID?: string }).DAYTONA_DOC_SNAPSHOT_ID = undefined + + await expect( + executeInSandbox({ + code: 'x', + language: CodeLanguage.Python, + timeoutMs: 1000, + sandboxKind: 'doc', + }) + ).rejects.toThrow(/DAYTONA_DOC_SNAPSHOT_ID is unset/) + ;(env as { DAYTONA_DOC_SNAPSHOT_ID?: string }).DAYTONA_DOC_SNAPSHOT_ID = original + }) +}) diff --git a/apps/sim/lib/execution/remote-sandbox/daytona.ts b/apps/sim/lib/execution/remote-sandbox/daytona.ts new file mode 100644 index 00000000000..ae2e6b87f87 --- /dev/null +++ b/apps/sim/lib/execution/remote-sandbox/daytona.ts @@ -0,0 +1,210 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { generateShortId } from '@sim/utils/id' +import { env } from '@/lib/core/config/env' +import { CodeLanguage } from '@/lib/execution/languages' +import type { + CreateSandboxOptions, + RunCommandOptions, + SandboxCodeResult, + SandboxCommandResult, + SandboxHandle, + SandboxKind, + SandboxProvider, +} from '@/lib/execution/remote-sandbox/types' + +const logger = createLogger('DaytonaSandboxProvider') + +/** Daytona expresses every timeout in seconds; the rest of Sim works in milliseconds. */ +function toSeconds(timeoutMs: number): number { + return Math.max(1, Math.ceil(timeoutMs / 1000)) +} + +function snapshotFor(kind: SandboxKind): string { + // Mirrors the E2B provider's fail-closed behaviour: never let LLM-authored code + // run in a provider default image just because a snapshot id is unset. + const snapshot = + kind === 'doc' + ? env.DAYTONA_DOC_SNAPSHOT_ID + : kind === 'pi' + ? env.DAYTONA_PI_SNAPSHOT_ID + : env.DAYTONA_SHELL_SNAPSHOT_ID + if (!snapshot) { + const varName = + kind === 'doc' + ? 'DAYTONA_DOC_SNAPSHOT_ID' + : kind === 'pi' + ? 'DAYTONA_PI_SNAPSHOT_ID' + : 'DAYTONA_SHELL_SNAPSHOT_ID' + throw new Error(`Daytona sandbox not configured (${varName} is unset)`) + } + return snapshot +} + +/** Daytona binds `codeRun`'s language to the sandbox, not the call. */ +function toDaytonaLanguage(language: CodeLanguage): string { + return language === CodeLanguage.Python ? 'python' : 'javascript' +} + +class DaytonaSandboxHandle implements SandboxHandle { + constructor( + private readonly sandbox: any, + private readonly language: CodeLanguage + ) {} + + get sandboxId(): string { + return this.sandbox.id + } + + async runCode(code: string, options: { timeoutMs: number }): Promise { + // Python goes through CodeInterpreter because it reports a structured + // `{ name, value, traceback }` error — the same shape E2B returns, which the + // route's line-offset error formatting depends on. CodeInterpreter is + // Python-only, so JS falls back to `process.codeRun`, whose language comes + // from the label bound at sandbox creation. + if (this.language === CodeLanguage.Python) { + const result = await this.sandbox.codeInterpreter.runCode(code, { + timeout: toSeconds(options.timeoutMs), + }) + return { + text: '', + stdout: result.stdout ?? '', + stderr: result.stderr ?? '', + error: result.error + ? { + name: result.error.name, + value: result.error.value ?? result.error.message ?? '', + traceback: result.error.traceback, + } + : undefined, + } + } + + const result = await this.sandbox.process.codeRun(code, undefined, toSeconds(options.timeoutMs)) + const output: string = result.result ?? '' + if (result.exitCode !== 0) { + // `process.codeRun` has no structured error channel — the interpreter's + // stderr lands in `result`. Surface it as the traceback so the shape stays + // identical to the Python and E2B paths. + return { + text: '', + stdout: '', + stderr: output, + error: { name: 'Error', value: lastNonEmptyLine(output), traceback: output }, + } + } + return { text: '', stdout: output, stderr: '' } + } + + async runCommand(command: string, options: RunCommandOptions): Promise { + // `rootUser` needs no handling: Daytona already executes commands as uid 0. + if (options.onStdout || options.onStderr) { + return this.runStreamingCommand(command, options) + } + try { + const result = await this.sandbox.process.executeCommand( + command, + undefined, + options.envs, + toSeconds(options.timeoutMs) + ) + // Daytona merges the two streams into `result`; splitting them back out is + // not possible, so stdout carries everything and callers that join the two + // (every caller today) are unaffected. + return { stdout: result.result ?? '', stderr: '', exitCode: result.exitCode ?? 0 } + } catch (error) { + return { stdout: '', stderr: getErrorMessage(error), exitCode: 1 } + } + } + + /** + * Streaming path (Pi). `SessionExecuteRequest` carries no `env` field, so the + * environment is delivered as a file written through the filesystem API and + * sourced by the command. Secrets therefore never appear in a command line or + * the sandbox process list, and the file is removed before the command runs — + * preserving the per-command isolation the E2B path provides natively. + */ + private async runStreamingCommand( + command: string, + options: RunCommandOptions + ): Promise { + const sessionId = `sim-${generateShortId(12)}` + await this.sandbox.process.createSession(sessionId) + try { + let script = command + if (options.envs && Object.keys(options.envs).length > 0) { + const envPath = `/tmp/.sim-env-${generateShortId(12)}` + const envFile = Object.entries(options.envs) + .map(([key, value]) => `${key}=${shellQuote(value)}`) + .join('\n') + await this.writeFile(envPath, envFile) + script = `set -a; . ${envPath}; set +a; rm -f ${envPath}; ${command}` + } + + const started = await this.sandbox.process.executeSessionCommand( + sessionId, + { command: script, runAsync: true }, + toSeconds(options.timeoutMs) + ) + const commandId: string = started.cmdId ?? started.commandId + await this.sandbox.process.getSessionCommandLogs( + sessionId, + commandId, + (chunk: string) => options.onStdout?.(chunk), + (chunk: string) => options.onStderr?.(chunk) + ) + const finished = await this.sandbox.process.getSessionCommand(sessionId, commandId) + return { stdout: '', stderr: '', exitCode: finished.exitCode ?? 0 } + } catch (error) { + return { stdout: '', stderr: getErrorMessage(error), exitCode: 1 } + } finally { + try { + await this.sandbox.process.deleteSession(sessionId) + } catch {} + } + } + + async readFile(path: string): Promise { + const buffer = await this.sandbox.fs.downloadFile(path) + return buffer.toString('utf-8') + } + + async writeFile(path: string, content: string | ArrayBuffer): Promise { + const buffer = + typeof content === 'string' ? Buffer.from(content, 'utf-8') : Buffer.from(content) + await this.sandbox.fs.uploadFile(buffer, path) + } + + async kill(): Promise { + await this.sandbox.delete() + } +} + +/** Quotes a value for a POSIX `KEY=value` env file. */ +function shellQuote(value: string): string { + return `'${value.replace(/'/g, `'\\''`)}'` +} + +function lastNonEmptyLine(output: string): string { + const lines = output.split('\n').filter((line) => line.trim().length > 0) + return lines.length > 0 ? lines[lines.length - 1] : 'Execution failed' +} + +export const daytonaProvider: SandboxProvider = { + id: 'daytona', + async create(kind: SandboxKind, options?: CreateSandboxOptions): Promise { + const apiKey = env.DAYTONA_API_KEY + if (!apiKey) { + throw new Error('DAYTONA_API_KEY is required when the Daytona sandbox provider is selected') + } + const snapshot = snapshotFor(kind) + const language = options?.language ?? CodeLanguage.Python + logger.info('Creating Daytona sandbox', { kind, snapshot }) + + const { Daytona } = await import('@daytonaio/sdk') + const daytona = new Daytona({ apiKey }) + const sandbox = await daytona.create({ snapshot, language: toDaytonaLanguage(language) } as any) + + return new DaytonaSandboxHandle(sandbox, language) + }, +} diff --git a/apps/sim/lib/execution/remote-sandbox/e2b.ts b/apps/sim/lib/execution/remote-sandbox/e2b.ts new file mode 100644 index 00000000000..f21a06e019d --- /dev/null +++ b/apps/sim/lib/execution/remote-sandbox/e2b.ts @@ -0,0 +1,131 @@ +import type { Sandbox as E2BSandbox } from '@e2b/code-interpreter' +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { env } from '@/lib/core/config/env' +import { CodeLanguage } from '@/lib/execution/languages' +import type { + CreateSandboxOptions, + RunCommandOptions, + SandboxCodeResult, + SandboxCommandResult, + SandboxHandle, + SandboxKind, + SandboxProvider, +} from '@/lib/execution/remote-sandbox/types' + +const logger = createLogger('E2BSandboxProvider') + +function templateFor(kind: SandboxKind): string | undefined { + // Document generation uses a dedicated template (python-pptx/docx/openpyxl/ + // reportlab + fonts); shell/code execution use the general shell template. + // Doc fails closed: never run LLM-authored Python in E2B's default template + // (which is not vetted for this) just because the doc template id is unset. + if (kind === 'doc') { + if (!env.MOTHERSHIP_E2B_DOC_TEMPLATE_ID) { + throw new Error('Document compiler not configured (MOTHERSHIP_E2B_DOC_TEMPLATE_ID is unset)') + } + return env.MOTHERSHIP_E2B_DOC_TEMPLATE_ID + } + // Pi fails closed for the same reason: the coding agent needs the Pi CLI + git + // baked into a vetted template, never E2B's default image. + if (kind === 'pi') { + if (!env.E2B_PI_TEMPLATE_ID) { + throw new Error('Pi cloud agent not configured (E2B_PI_TEMPLATE_ID is unset)') + } + return env.E2B_PI_TEMPLATE_ID + } + return env.MOTHERSHIP_E2B_TEMPLATE_ID +} + +class E2BSandboxHandle implements SandboxHandle { + constructor( + private readonly sandbox: E2BSandbox, + private readonly language: CodeLanguage + ) {} + + get sandboxId(): string { + return this.sandbox.sandboxId + } + + async runCode(code: string, options: { timeoutMs: number }): Promise { + const execution = await this.sandbox.runCode(code, { + language: this.language === CodeLanguage.Python ? 'python' : 'javascript', + timeoutMs: options.timeoutMs, + }) + + // Kernel stream entries are chunks, not lines — each already carries its own + // newlines, and one long line can arrive split across several entries. + // Concatenate each stream verbatim: joining chunks with '\n' injected a + // newline at every chunk boundary, which corrupted large single-line + // __SIM_RESULT__ payloads and silently truncated the persisted result. + return { + text: execution.text ?? '', + stdout: (execution.logs?.stdout ?? []).join(''), + stderr: (execution.logs?.stderr ?? []).join(''), + error: execution.error + ? { + name: execution.error.name, + value: execution.error.value, + traceback: execution.error.traceback, + } + : undefined, + } + } + + async runCommand(command: string, options: RunCommandOptions): Promise { + try { + const result = await this.sandbox.commands.run(command, { + ...(options.envs ? { envs: options.envs } : {}), + timeoutMs: options.timeoutMs, + ...(options.rootUser ? { user: 'root' as const } : {}), + ...(options.onStdout ? { onStdout: options.onStdout } : {}), + ...(options.onStderr ? { onStderr: options.onStderr } : {}), + }) + return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode } + } catch (error) { + // The SDK throws on non-zero exit; callers want the streams, not a throw. + const failure = error as { + stdout?: string + stderr?: string + message?: string + exitCode?: number + } + return { + stdout: failure.stdout ?? '', + stderr: failure.stderr ?? failure.message ?? getErrorMessage(error), + exitCode: failure.exitCode ?? 1, + } + } + } + + readFile(path: string): Promise { + return this.sandbox.files.read(path) + } + + async writeFile(path: string, content: string | ArrayBuffer): Promise { + await this.sandbox.files.write(path, content as string) + } + + async kill(): Promise { + await this.sandbox.kill() + } +} + +export const e2bProvider: SandboxProvider = { + id: 'e2b', + async create(kind: SandboxKind, options?: CreateSandboxOptions): Promise { + const apiKey = env.E2B_API_KEY + if (!apiKey) { + throw new Error('E2B_API_KEY is required when E2B is enabled') + } + const templateName = templateFor(kind) + logger.info('Creating E2B sandbox', { kind, template: templateName || '(default)' }) + + const { Sandbox } = await import('@e2b/code-interpreter') + const sandbox = templateName + ? await Sandbox.create(templateName, { apiKey }) + : await Sandbox.create({ apiKey }) + + return new E2BSandboxHandle(sandbox, options?.language ?? CodeLanguage.Python) + }, +} diff --git a/apps/sim/lib/execution/remote-sandbox/index.ts b/apps/sim/lib/execution/remote-sandbox/index.ts new file mode 100644 index 00000000000..921a6586e3e --- /dev/null +++ b/apps/sim/lib/execution/remote-sandbox/index.ts @@ -0,0 +1,423 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { isFeatureEnabled } from '@/lib/core/config/feature-flags' +import type { CodeLanguage } from '@/lib/execution/languages' +import { daytonaProvider } from '@/lib/execution/remote-sandbox/daytona' +import { e2bProvider } from '@/lib/execution/remote-sandbox/e2b' +import type { + SandboxCommandResult, + SandboxExecutionRequest, + SandboxExecutionResult, + SandboxFile, + SandboxHandle, + SandboxKind, + SandboxProvider, + SandboxShellExecutionRequest, +} from '@/lib/execution/remote-sandbox/types' + +export type { + SandboxExecutionRequest, + SandboxExecutionResult, + SandboxFile, + SandboxShellExecutionRequest, +} from '@/lib/execution/remote-sandbox/types' + +const logger = createLogger('RemoteSandbox') + +/** + * Resolves which provider serves this execution. + * + * Selection is deliberately resolved ONCE, before the sandbox is created, and is + * never revisited mid-execution: user code has side effects (HTTP calls, S3 + * writes, DB mutations), so retrying a partially-executed run on the other + * provider could duplicate them. This is a manual failover — flip the + * `sandbox-provider-daytona` flag and new executions move over. + */ +async function resolveProvider(): Promise { + const useDaytona = await isFeatureEnabled('sandbox-provider-daytona') + return useDaytona ? daytonaProvider : e2bProvider +} + +async function createSandbox( + kind: SandboxKind, + options?: { language?: CodeLanguage } +): Promise { + const provider = await resolveProvider() + const sandbox = await provider.create(kind, options) + logger.info('Created sandbox', { provider: provider.id, kind, sandboxId: sandbox.sandboxId }) + return sandbox +} + +/** + * Materializes sandbox input files before user code runs. `content` entries are written inline; + * `url` entries are fetched from inside the sandbox via `curl` — their bytes never pass through the + * web process, so the mount size is bounded by sandbox disk, not web heap. The URL and paths are + * passed as env vars (never interpolated into the shell) so a presigned query string can't break or + * inject. A failed fetch throws so user code never runs against a missing mount. + */ +async function writeSandboxInputs( + sandbox: SandboxHandle, + files: SandboxFile[] | undefined, + opts: { rootUser?: boolean } +): Promise { + if (!files?.length) return + const fetchedByUrl: string[] = [] + const writtenInline: string[] = [] + for (const file of files) { + if (file.type === 'url') { + const dir = file.path.slice(0, file.path.lastIndexOf('/')) + let result: SandboxCommandResult + try { + result = await sandbox.runCommand( + 'set -e; [ -n "$DIR" ] && mkdir -p "$DIR"; curl -fsS --retry 3 --retry-connrefused --max-time 300 "$URL" -o "$DST"', + { + envs: { URL: file.url, DST: file.path, DIR: dir }, + timeoutMs: 300_000, + rootUser: opts.rootUser, + } + ) + } catch (error) { + throw new Error( + `Failed to fetch mounted file into sandbox at ${file.path}: ${getErrorMessage(error)}` + ) + } + // Providers differ on whether a non-zero exit throws, so the exit code is + // checked explicitly — a silently-missing mount is exactly what this guard + // exists to prevent. + if (result.exitCode !== 0) { + throw new Error( + `Failed to fetch mounted file into sandbox at ${file.path}: ${result.stderr || `curl exited ${result.exitCode}`}` + ) + } + fetchedByUrl.push(file.path) + } else if (file.encoding === 'base64') { + const buf = Buffer.from(file.content, 'base64') + await sandbox.writeFile( + file.path, + buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) + ) + writtenInline.push(file.path) + } else { + await sandbox.writeFile(file.path, file.content) + writtenInline.push(file.path) + } + } + // Split counts so it's visible whether a mount was fetched in-sandbox (by presigned URL, no bytes + // through the web process) or written inline. + logger.info('Materialized sandbox inputs', { + sandboxId: sandbox.sandboxId, + fetchedByUrlCount: fetchedByUrl.length, + writtenInlineCount: writtenInline.length, + fetchedByUrl, + writtenInline, + }) +} + +/** + * Marker prefix for the serialized code result printed to stdout. Emitters + * (the wrapper builders in the function-execute route) interpolate this + * constant so producer and parser cannot drift. + */ +export const SIM_RESULT_PREFIX = '__SIM_RESULT__=' + +/** + * Extracts the `__SIM_RESULT__=` marker line from stdout and parses its JSON + * payload. Takes the LAST marker line: the wrapper prints its marker after all + * user output, so an earlier user-printed line with the same prefix (debug + * output, a grepped log) never shadows the real result. `parseFailed` means + * the last marker's payload was not valid JSON — `rawPayload` carries it so + * callers whose markers are user-authored (shell) can fall back to the plain + * string, while wrapper-backed callers treat it as transport corruption. + */ +function extractSimResult(stdout: string): { + result: unknown + cleanedStdout: string + parseFailed: boolean + rawPayload?: string +} { + const lines = stdout.split('\n') + let markerIndex = -1 + for (let i = lines.length - 1; i >= 0; i--) { + if (lines[i].startsWith(SIM_RESULT_PREFIX)) { + markerIndex = i + break + } + } + if (markerIndex === -1) { + return { result: null, cleanedStdout: stdout, parseFailed: false } + } + const rawPayload = lines[markerIndex].slice(SIM_RESULT_PREFIX.length) + let result: unknown = null + let parseFailed = false + try { + result = JSON.parse(rawPayload) + } catch { + parseFailed = true + } + const filteredLines = lines.filter((l) => !l.startsWith(SIM_RESULT_PREFIX)) + if (filteredLines.length > 0 && filteredLines[filteredLines.length - 1] === '') { + filteredLines.pop() + } + return { result, cleanedStdout: filteredLines.join('\n'), parseFailed, rawPayload } +} + +const SIM_RESULT_CORRUPTED_ERROR = + 'Sandbox result was corrupted in transport (the __SIM_RESULT__ line failed to parse). ' + + "Do not trust or persist this call's output. For large results, write the content to a " + + 'file inside the sandbox and export it via outputs.files[].sandboxPath instead of returning it.' + +function shouldReadSandboxPathAsBase64(outputSandboxPath: string): boolean { + const ext = outputSandboxPath.slice(outputSandboxPath.lastIndexOf('.')).toLowerCase() + const binaryExts = new Set([ + '.png', + '.jpg', + '.jpeg', + '.gif', + '.webp', + '.pdf', + '.zip', + '.mp3', + '.mp4', + '.docx', + '.pptx', + '.xlsx', + ]) + return binaryExts.has(ext) +} + +async function readSandboxOutputFile( + sandbox: SandboxHandle, + outputSandboxPath: string, + options?: { rootUser?: boolean } +): Promise { + try { + if (shouldReadSandboxPathAsBase64(outputSandboxPath)) { + const b64Result = await sandbox.runCommand(`base64 -w0 "${outputSandboxPath}"`, { + timeoutMs: 120_000, + rootUser: options?.rootUser, + }) + if (b64Result.exitCode !== 0) throw new Error(b64Result.stderr || 'base64 failed') + return b64Result.stdout + } + return await sandbox.readFile(outputSandboxPath) + } catch (error) { + logger.warn('Failed to read requested sandbox output file', { + outputSandboxPath, + error: getErrorMessage(error), + }) + return undefined + } +} + +function requestedOutputSandboxPaths(req: { + outputSandboxPath?: string + outputSandboxPaths?: string[] +}): string[] { + const paths = [...(req.outputSandboxPaths ?? [])] + if (req.outputSandboxPath && !paths.includes(req.outputSandboxPath)) { + paths.push(req.outputSandboxPath) + } + return paths +} + +async function collectExportedFiles( + sandbox: SandboxHandle, + req: { outputSandboxPath?: string; outputSandboxPaths?: string[] }, + options?: { rootUser?: boolean } +): Promise<{ exportedFiles?: Record; exportedFileContent?: string }> { + const exportedFiles: Record = {} + for (const outputSandboxPath of requestedOutputSandboxPaths(req)) { + const content = await readSandboxOutputFile(sandbox, outputSandboxPath, options) + if (content !== undefined) { + exportedFiles[outputSandboxPath] = content + } + } + return { + exportedFileContent: req.outputSandboxPath ? exportedFiles[req.outputSandboxPath] : undefined, + exportedFiles: Object.keys(exportedFiles).length ? exportedFiles : undefined, + } +} + +export async function executeInSandbox( + req: SandboxExecutionRequest +): Promise { + const { code, language, timeoutMs } = req + + const sandbox = await createSandbox(req.sandboxKind ?? 'code', { language }) + const sandboxId = sandbox.sandboxId + + try { + // Inside the try so a failed mount still kills the sandbox via the finally below. + await writeSandboxInputs(sandbox, req.sandboxFiles, {}) + + const execution = await sandbox.runCode(code, { timeoutMs }) + + if (execution.error) { + const errorMessage = `${execution.error.name}: ${execution.error.value}` + logger.error('Sandbox execution error', { sandboxId, error: execution.error, errorMessage }) + return { + result: null, + stdout: execution.error.traceback || errorMessage, + error: errorMessage, + sandboxId, + } + } + + // Distinct sources (final-expression text, stdout, stderr) join with '\n' so + // the marker is found no matter which stream carried it. Each individual + // stream is already concatenated verbatim by the provider, because injecting + // a newline at chunk boundaries corrupted large single-line payloads. + const combinedOutput = [execution.text, execution.stdout, execution.stderr] + .filter(Boolean) + .join('\n') + + const extraction = extractSimResult(combinedOutput) + const cleanedStdout = extraction.cleanedStdout + + // The wrapper always emits valid single-line JSON, so a marker that fails + // to parse means the payload was mangled in transport — never persist it. + if (extraction.parseFailed) { + logger.error('Sandbox result marker failed to parse', { + sandboxId, + stdoutLength: execution.stdout.length, + }) + return { + result: null, + stdout: cleanedStdout, + error: SIM_RESULT_CORRUPTED_ERROR, + sandboxId, + } + } + + const { exportedFiles, exportedFileContent } = await collectExportedFiles(sandbox, req) + + return { + result: extraction.result, + stdout: cleanedStdout, + sandboxId, + exportedFileContent, + exportedFiles, + } + } finally { + try { + await sandbox.kill() + } catch {} + } +} + +export async function executeShellInSandbox( + req: SandboxShellExecutionRequest +): Promise { + const { code, envs, timeoutMs } = req + + const sandbox = await createSandbox(req.sandboxKind ?? 'shell') + const sandboxId = sandbox.sandboxId + + try { + // Inside the try so a failed mount still kills the sandbox via the finally below. + await writeSandboxInputs(sandbox, req.sandboxFiles, { rootUser: true }) + + const result = await sandbox.runCommand(code, { + envs: { + ...envs, + PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin', + }, + timeoutMs, + rootUser: true, + }) + + const stdout = [result.stdout, result.stderr].filter(Boolean).join('\n') + + if (result.exitCode !== 0) { + const errorMessage = result.stderr || `Process exited with code ${result.exitCode}` + logger.error('Sandbox shell execution error', { + sandboxId, + exitCode: result.exitCode, + stderr: result.stderr?.slice(0, 500), + }) + return { result: null, stdout, error: errorMessage, sandboxId } + } + + // Shell scripts have no wrapper: any __SIM_RESULT__ line is user-authored + // (e.g. `echo "__SIM_RESULT__=$STATUS"`), so a non-JSON payload is a plain + // string result, not transport corruption. + const extraction = extractSimResult(stdout) + const parsed = extraction.parseFailed ? extraction.rawPayload : extraction.result + + const { exportedFiles, exportedFileContent } = await collectExportedFiles(sandbox, req, { + rootUser: true, + }) + + return { + result: parsed, + stdout: extraction.cleanedStdout, + sandboxId, + exportedFileContent, + exportedFiles, + } + } finally { + try { + await sandbox.kill() + } catch {} + } +} + +/** Result of one command run inside a Pi sandbox. */ +export interface PiSandboxCommandResult { + stdout: string + stderr: string + exitCode: number +} + +/** Runs commands and moves files inside a live Pi sandbox. */ +export interface PiSandboxRunner { + run( + command: string, + options: { + envs?: Record + timeoutMs: number + onStdout?: (chunk: string) => void + onStderr?: (chunk: string) => void + } + ): Promise + readFile(path: string): Promise + /** + * Writes a file via the sandbox filesystem API. Bytes go through the provider + * SDK, never a shell, so untrusted content (the assembled prompt, a commit + * message) is delivered without any shell parsing — callers reference it by a + * fixed path. + */ + writeFile(path: string, content: string): Promise +} + +/** + * Creates a Pi sandbox, keeps it alive for the duration of `fn` (so the cloned + * repo persists across the clone -> agent -> push commands), streams command + * output, and always kills the sandbox afterward. Per-command envs are isolated, + * so secrets handed to one command never leak into the next. + */ +export async function withPiSandbox(fn: (runner: PiSandboxRunner) => Promise): Promise { + const sandbox = await createSandbox('pi') + logger.info('Started Pi sandbox', { sandboxId: sandbox.sandboxId }) + + const runner: PiSandboxRunner = { + run: (command, options) => + sandbox.runCommand(command, { + envs: options.envs, + timeoutMs: options.timeoutMs, + rootUser: true, + onStdout: options.onStdout, + onStderr: options.onStderr, + }), + readFile: (path) => sandbox.readFile(path), + writeFile: (path, content) => sandbox.writeFile(path, content), + } + + try { + return await fn(runner) + } finally { + try { + await sandbox.kill() + } catch {} + } +} diff --git a/apps/sim/lib/execution/remote-sandbox/types.ts b/apps/sim/lib/execution/remote-sandbox/types.ts new file mode 100644 index 00000000000..5141f49b27d --- /dev/null +++ b/apps/sim/lib/execution/remote-sandbox/types.ts @@ -0,0 +1,129 @@ +import type { CodeLanguage } from '@/lib/execution/languages' + +/** + * Which vetted image a sandbox runs in. Every kind fails closed when its + * template/snapshot id is unset, so LLM-authored code can never land in a + * provider's unvetted default image. + */ +export type SandboxKind = 'code' | 'shell' | 'doc' | 'pi' + +export type SandboxProviderId = 'e2b' | 'daytona' + +/** + * A sandbox input file. `content` entries are written inline; `url` entries are fetched from inside + * the sandbox (so large mounts never pass their bytes through the web process). + */ +export type SandboxFile = + | { type?: 'content'; path: string; content: string; encoding?: 'base64' } + | { type: 'url'; path: string; url: string } + +export interface SandboxExecutionRequest { + code: string + language: CodeLanguage + timeoutMs: number + sandboxFiles?: SandboxFile[] + outputSandboxPath?: string + outputSandboxPaths?: string[] + /** + * Which sandbox image to run in. Defaults to 'code' (mothership-shell). + * Document generation passes 'doc' so it runs in the doc image + * (mothership-docs) that has python-pptx/docx/openpyxl/reportlab installed. + */ + sandboxKind?: 'code' | 'doc' +} + +export interface SandboxShellExecutionRequest { + code: string + envs: Record + timeoutMs: number + sandboxFiles?: SandboxFile[] + outputSandboxPath?: string + outputSandboxPaths?: string[] + /** + * Which sandbox image to run in. Defaults to 'shell' (mothership-shell). + * The Node document engines (pptxgenjs/docx + react-icons/sharp) pass 'doc' so + * they run in the doc image (mothership-docs). + */ + sandboxKind?: 'shell' | 'doc' +} + +export interface SandboxExecutionResult { + result: unknown + stdout: string + sandboxId?: string + error?: string + exportedFileContent?: string + exportedFiles?: Record +} + +/** Result of one command run inside a sandbox. */ +export interface SandboxCommandResult { + stdout: string + stderr: string + exitCode: number +} + +/** + * Normalized error from a code execution. Both providers report this shape: + * E2B's `Execution.error` and Daytona's `ExecutionResult.error` agree on + * `{ name, value, traceback }`, so `formatSandboxError`'s line-offset handling + * works unchanged across providers. + */ +export interface SandboxCodeError { + name: string + value: string + traceback?: string +} + +/** Result of a code (non-shell) execution. */ +export interface SandboxCodeResult { + /** The final-expression value, when the provider surfaces one separately from stdout. */ + text: string + stdout: string + stderr: string + error?: SandboxCodeError +} + +export interface RunCommandOptions { + envs?: Record + timeoutMs: number + /** Run as root. The shell and Pi paths depend on this; the code path does not. */ + rootUser?: boolean + onStdout?: (chunk: string) => void + onStderr?: (chunk: string) => void +} + +/** + * A live sandbox. Deliberately the smallest surface that satisfies every caller, + * so adding a third provider stays cheap. + */ +export interface SandboxHandle { + readonly sandboxId: string + /** + * Runs code in the language fixed at {@link SandboxProvider.create} time. + * Language is bound at creation rather than per call because Daytona applies it + * as a sandbox label (`code-toolbox-language`) and silently ignores a per-call + * override — passing `javascript` to its `codeRun` executes the source through + * Python instead. We create one sandbox per execution, so binding costs nothing. + */ + runCode(code: string, options: { timeoutMs: number }): Promise + runCommand(command: string, options: RunCommandOptions): Promise + readFile(path: string): Promise + /** + * Writes a file via the sandbox filesystem API. Bytes never pass through a + * shell, so untrusted content (an assembled prompt, a commit message) is + * delivered without any shell parsing. + */ + writeFile(path: string, content: string | ArrayBuffer): Promise + kill(): Promise +} + +export interface CreateSandboxOptions { + /** Bound at creation — see {@link SandboxHandle.runCode}. */ + language?: CodeLanguage +} + +export interface SandboxProvider { + readonly id: SandboxProviderId + create(kind: SandboxKind, options?: CreateSandboxOptions): Promise +} diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts index 5cf26fbaeea..76a4ac99c3c 100644 --- a/apps/sim/next.config.ts +++ b/apps/sim/next.config.ts @@ -130,6 +130,7 @@ const nextConfig: NextConfig = { 'isolated-vm', '@e2b/code-interpreter', 'e2b', + '@daytonaio/sdk', '@earendil-works/pi-ai', '@earendil-works/pi-coding-agent', ], diff --git a/apps/sim/package.json b/apps/sim/package.json index 71134235527..68f42a9569f 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -65,6 +65,7 @@ "@browserbasehq/stagehand": "^3.2.1", "@calcom/embed-react": "1.5.3", "@cerebras/cerebras_cloud_sdk": "^1.23.0", + "@daytonaio/sdk": "0.197.0", "@e2b/code-interpreter": "^2.0.0", "@earendil-works/pi-ai": "0.80.10", "@earendil-works/pi-coding-agent": "0.80.10", diff --git a/apps/sim/scripts/build-pi-daytona-snapshot.ts b/apps/sim/scripts/build-pi-daytona-snapshot.ts new file mode 100644 index 00000000000..3c2c3d68c2e --- /dev/null +++ b/apps/sim/scripts/build-pi-daytona-snapshot.ts @@ -0,0 +1,101 @@ +#!/usr/bin/env bun + +/** + * Builds the Daytona snapshot used by Create PR and Review Code — the failover + * counterpart of `build-pi-e2b-template.ts`. + * + * Both renderers consume `pi-sandbox-packages.ts`, so the two providers cannot + * drift apart. + * + * Unlike the E2B template, which layers onto `code-interpreter-v1` (Debian + * trixie + Python 3.13 + Node 20), Daytona has no equivalent base, so this image + * reconstructs that foundation: Python for the review tools' helper script and + * Node 22 for the Pi CLI. + * + * Usage: + * DAYTONA_API_KEY=... bun run apps/sim/scripts/build-pi-daytona-snapshot.ts --name sim-pi: + * bun run apps/sim/scripts/build-pi-daytona-snapshot.ts --print + * + * Daytona rejects the latest/lts/stable tags, so the name MUST carry an explicit + * tag — CI passes the same `-` it uses for ECR images. + * + * After it builds, set the printed value in the Sim app's .env: + * DAYTONA_PI_SNAPSHOT_ID= + */ + +import { Daytona, Image } from '@daytonaio/sdk' +import { + PI_APT, + PI_NODE_MAJOR, + PI_NODE_VERSION_ASSERT, + PI_NPM, +} from '@/scripts/pi-sandbox-packages' + +/** Matches E2B's base: Debian 13 (trixie) with Python 3.13 installed to /usr/local. */ +const BASE_IMAGE = 'python:3.13-slim-trixie' + +/** + * `daytona-large` sizing. 10 GB is a HARD per-sandbox disk cap — the API rejects + * anything larger ("Disk request 20GB exceeds maximum allowed per sandbox + * (10GB)"), regardless of plan tier, and raising it requires contacting Daytona. + * That is the binding constraint on how large a repo Pi can clone here. + */ +const RESOURCES = { cpu: 4, memory: 8, disk: 10 } as const + +const APT_PREFIX = 'DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends' + +export const piImage = Image.base(BASE_IMAGE).runCommands( + `apt-get update && ${APT_PREFIX} curl ca-certificates gnupg && rm -rf /var/lib/apt/lists/*`, + // Node 22 from NodeSource, asserted at build time so an upstream change that + // ships an older Node fails here rather than at the first agent run. + `apt-get update && curl -fsSL https://deb.nodesource.com/setup_${PI_NODE_MAJOR}.x | bash - && ${APT_PREFIX} nodejs && rm -rf /var/lib/apt/lists/* && ${PI_NODE_VERSION_ASSERT}`, + `apt-get update && ${APT_PREFIX} ${PI_APT.join(' ')} && rm -rf /var/lib/apt/lists/*`, + `npm install -g ${PI_NPM.join(' ')}`, + // The clone target. E2B's base ships a world-writable /code; Pi writes to + // /workspace (cloud-review-tools.ts:14), so create it explicitly. + 'mkdir -p /workspace' +) + +async function main() { + const args = process.argv.slice(2) + + if (args.includes('--print')) { + console.log(piImage.dockerfile) + return + } + + const nameIdx = args.indexOf('--name') + const snapshotName = nameIdx !== -1 ? args[nameIdx + 1] : process.env.DAYTONA_SNAPSHOT_NAME + if (!snapshotName) { + console.error('A snapshot name is required (--name or DAYTONA_SNAPSHOT_NAME)') + process.exit(1) + } + // Daytona resolves snapshots by exact tag; `latest` is rejected outright, and an + // untagged name would silently pin to whatever was built last. + if (!snapshotName.includes(':')) { + console.error( + `Snapshot name must include an explicit tag (got "${snapshotName}"). ` + + 'Daytona does not support latest/lts/stable.' + ) + process.exit(1) + } + if (!process.env.DAYTONA_API_KEY) { + console.error('DAYTONA_API_KEY is required') + process.exit(1) + } + + console.log(`Building Pi Daytona snapshot: ${snapshotName}\n`) + + const daytona = new Daytona({ apiKey: process.env.DAYTONA_API_KEY }) + await daytona.snapshot.create( + { name: snapshotName, image: piImage, resources: RESOURCES }, + { onLogs: (log: string) => process.stdout.write(` ${log}`) } + ) + + console.log(`\nDone. Set in .env: DAYTONA_PI_SNAPSHOT_ID=${snapshotName}`) +} + +main().catch((error: unknown) => { + console.error('Build failed:', error instanceof Error ? error.message : error) + process.exit(1) +}) diff --git a/apps/sim/scripts/build-pi-e2b-template.ts b/apps/sim/scripts/build-pi-e2b-template.ts index f4641a2b7b3..147f933dae9 100644 --- a/apps/sim/scripts/build-pi-e2b-template.ts +++ b/apps/sim/scripts/build-pi-e2b-template.ts @@ -16,29 +16,26 @@ */ import { defaultBuildLogger, Template, waitForTimeout } from '@e2b/code-interpreter' +import { + PI_APT, + PI_NODE_MAJOR, + PI_NODE_VERSION_ASSERT, + PI_NPM, +} from '@/scripts/pi-sandbox-packages' const DEFAULT_TEMPLATE_NAME = 'sim-pi' -/** Exact first-party Pi versions mirrored from bun.lock because E2B builds run npm independently. */ -const PI_PACKAGES = [ - '@earendil-works/pi-coding-agent@0.80.10', - '@earendil-works/pi-agent-core@0.80.10', - '@earendil-works/pi-ai@0.80.10', - '@earendil-works/pi-tui@0.80.10', -] as const - /** Pi 0.80 requires Node >=22.19; E2B's code-interpreter base currently ships Node 20. */ -const INSTALL_NODE_COMMAND = - 'curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && apt-get install -y nodejs && node -e "const [major, minor] = process.versions.node.split(\'.\').map(Number); if (major < 22 || (major === 22 && minor < 19)) process.exit(1)"' +const INSTALL_NODE_COMMAND = `curl -fsSL https://deb.nodesource.com/setup_${PI_NODE_MAJOR}.x | bash - && apt-get install -y nodejs && ${PI_NODE_VERSION_ASSERT}` -/** Pi uses E2B's command and filesystem APIs, so the inherited Jupyter service is unnecessary. */ +/** Pi uses the command and filesystem APIs, so the inherited Jupyter service is unnecessary. */ const START_COMMAND = 'sleep infinity' const piTemplate = Template() .fromTemplate('code-interpreter-v1') .runCmd(INSTALL_NODE_COMMAND, { user: 'root' }) - .aptInstall(['git', 'gh', 'openssh-client', 'ca-certificates', 'ripgrep', 'fd-find']) - .npmInstall([...PI_PACKAGES], { g: true }) + .aptInstall([...PI_APT]) + .npmInstall([...PI_NPM], { g: true }) .setStartCmd(START_COMMAND, waitForTimeout(1_000)) async function main() { diff --git a/apps/sim/scripts/pi-sandbox-packages.ts b/apps/sim/scripts/pi-sandbox-packages.ts new file mode 100644 index 00000000000..4a7b29c2fe9 --- /dev/null +++ b/apps/sim/scripts/pi-sandbox-packages.ts @@ -0,0 +1,54 @@ +/** + * The single source of truth for what goes into the Pi sandbox image. + * + * Two renderers consume these lists: + * - `build-pi-e2b-template.ts` — E2B template, via the `Template()` builder DSL + * - `build-pi-daytona-snapshot.ts` — Daytona snapshot, via the `Image` builder + * + * A package added here reaches both providers. Adding one to only a single + * renderer is the drift that makes a failover fail at the worst moment. + * + * The copilot repo holds the equivalent lists for the shell and doc sandboxes + * (`copilot/scripts/sandbox/packages.ts`); the Pi image lives here because Sim + * owns the Pi block. + */ + +/** Exact first-party Pi versions mirrored from bun.lock — image builds run npm independently. */ +export const PI_NPM = [ + '@earendil-works/pi-coding-agent@0.80.10', + '@earendil-works/pi-agent-core@0.80.10', + '@earendil-works/pi-ai@0.80.10', + '@earendil-works/pi-tui@0.80.10', +] as const + +/** + * `git`/`gh`/`openssh-client` back the clone → commit → push flow. `ripgrep` is + * required, not optional: the review tools shell out to the `rg` binary by name + * (`cloud-review-tools-script.ts:146`), so a missing package breaks code search + * at runtime rather than at build time. + */ +export const PI_APT = [ + 'git', + 'gh', + 'openssh-client', + 'ca-certificates', + 'ripgrep', + 'fd-find', +] as const + +/** + * Pi 0.80 requires Node >= 22.19 — higher than the Node 20 both the E2B base and + * the other two sandbox images carry, so this image installs its own. + */ +export const PI_NODE_MAJOR = 22 + +/** Fails the build loudly if the installed Node is older than Pi supports. */ +export const PI_NODE_VERSION_ASSERT = + 'node -e "const [major, minor] = process.versions.node.split(\'.\').map(Number); if (major < 22 || (major === 22 && minor < 19)) process.exit(1)"' + +/** + * The review tools run `python3 /workspace/sim-review-tools.py` + * (`cloud-review-tools.ts:15`). E2B's `code-interpreter-v1` base ships Python, so + * only the Daytona image has to provide it explicitly. + */ +export const PI_REQUIRES_PYTHON3 = true diff --git a/apps/sim/scripts/verify-sandbox-parity.ts b/apps/sim/scripts/verify-sandbox-parity.ts new file mode 100644 index 00000000000..4cb379883f2 --- /dev/null +++ b/apps/sim/scripts/verify-sandbox-parity.ts @@ -0,0 +1,198 @@ +#!/usr/bin/env bun + +/** + * Runs the real sandbox execution paths against whichever provider the + * `sandbox-provider-daytona` flag currently selects, and prints a pass/fail + * matrix. + * + * This is the pre-flip confidence check: run it against E2B, run it against + * Daytona, and compare. Every case exercises the shared layer end-to-end + * (`executeInSandbox` / `executeShellInSandbox`) against a live sandbox — not + * mocks — so it catches the failures unit tests cannot: a missing package, an + * expired snapshot, an image that vanished during an org move, blocked egress. + * + * Usage: + * # E2B (flag off) + * bun run apps/sim/scripts/verify-sandbox-parity.ts + * + * # Daytona (flag on via its env fallback) + * SANDBOX_PROVIDER_DAYTONA=true \ + * DAYTONA_SHELL_SNAPSHOT_ID=mothership-shell: \ + * DAYTONA_DOC_SNAPSHOT_ID=mothership-docs: \ + * bun run apps/sim/scripts/verify-sandbox-parity.ts + * + * Exits non-zero if any case fails, so it can be wired to a schedule later. + */ + +import { CodeLanguage } from '@/lib/execution/languages' +import { executeInSandbox, executeShellInSandbox } from '@/lib/execution/remote-sandbox' + +const SIM_RESULT_PREFIX = '__SIM_RESULT__=' + +interface Case { + name: string + /** Skipped unless the doc image is configured for the active provider. */ + needsDoc?: boolean + run: () => Promise<{ ok: boolean; detail: string }> +} + +const CASES: Case[] = [ + { + name: 'python: result marker round-trip', + run: async () => { + const res = await executeInSandbox({ + code: `import json\nprint("stdout-line")\nprint("${SIM_RESULT_PREFIX}" + json.dumps({"n": 42}))`, + language: CodeLanguage.Python, + timeoutMs: 120_000, + }) + const value = (res.result as { n?: number } | null)?.n + return { + ok: value === 42 && res.stdout.includes('stdout-line') && !res.error, + detail: `result=${JSON.stringify(res.result)} stdout=${JSON.stringify(res.stdout)}`, + } + }, + }, + { + name: 'python: data-science imports (base parity)', + run: async () => { + const res = await executeInSandbox({ + code: `import numpy, pandas, requests, bs4, openpyxl\nimport json\nprint("${SIM_RESULT_PREFIX}" + json.dumps({"numpy": numpy.__version__}))`, + language: CodeLanguage.Python, + timeoutMs: 180_000, + }) + return { + ok: !res.error && Boolean((res.result as { numpy?: string } | null)?.numpy), + detail: res.error ?? `numpy=${(res.result as { numpy?: string } | null)?.numpy}`, + } + }, + }, + { + name: 'python: structured error (name/value/traceback)', + run: async () => { + const res = await executeInSandbox({ + code: 'raise ValueError("boom")', + language: CodeLanguage.Python, + timeoutMs: 120_000, + }) + return { + ok: res.error === 'ValueError: boom' && res.result === null, + detail: `error=${JSON.stringify(res.error)}`, + } + }, + }, + { + name: 'python: outbound network (egress parity)', + run: async () => { + const res = await executeInSandbox({ + code: `import json, urllib.request\ncode = urllib.request.urlopen("https://example.com", timeout=20).status\nprint("${SIM_RESULT_PREFIX}" + json.dumps({"status": code}))`, + language: CodeLanguage.Python, + timeoutMs: 120_000, + }) + const status = (res.result as { status?: number } | null)?.status + return { ok: status === 200, detail: res.error ?? `status=${status}` } + }, + }, + { + name: 'javascript: imports run under node', + run: async () => { + const res = await executeInSandbox({ + code: `const os = require('os')\nconsole.log('${SIM_RESULT_PREFIX}' + JSON.stringify({ platform: os.platform() }))`, + language: CodeLanguage.JavaScript, + timeoutMs: 120_000, + }) + const platform = (res.result as { platform?: string } | null)?.platform + return { ok: platform === 'linux', detail: res.error ?? `platform=${platform}` } + }, + }, + { + name: 'shell: env vars + user-authored marker', + run: async () => { + const res = await executeShellInSandbox({ + code: `echo "${SIM_RESULT_PREFIX}$MY_VAR"`, + envs: { MY_VAR: 'from-env' }, + timeoutMs: 120_000, + }) + return { ok: res.result === 'from-env', detail: `result=${JSON.stringify(res.result)}` } + }, + }, + { + name: 'mount: inline file in, text file out', + run: async () => { + const res = await executeInSandbox({ + code: `data = open("/tmp/in.txt").read().strip()\nopen("/tmp/out.txt", "w").write(data.upper())\nprint("${SIM_RESULT_PREFIX}null")`, + language: CodeLanguage.Python, + timeoutMs: 120_000, + sandboxFiles: [{ path: '/tmp/in.txt', content: 'mounted' }], + outputSandboxPath: '/tmp/out.txt', + }) + return { + ok: res.exportedFileContent?.trim() === 'MOUNTED', + detail: res.error ?? `exported=${JSON.stringify(res.exportedFileContent)}`, + } + }, + }, + { + name: 'doc: xlsx compile + base64 binary export', + needsDoc: true, + run: async () => { + const res = await executeInSandbox({ + code: `import openpyxl\nwb = openpyxl.Workbook(); ws = wb.active\nws["A1"] = 5; ws["A2"] = 7; ws["A3"] = "=A1+A2"\nwb.save("/tmp/out.xlsx")\nprint("${SIM_RESULT_PREFIX}null")`, + language: CodeLanguage.Python, + timeoutMs: 180_000, + sandboxKind: 'doc', + outputSandboxPath: '/tmp/out.xlsx', + }) + const b64 = res.exportedFileContent ?? '' + // xlsx is a ZIP: base64 of a PK.. header always starts UEsD. + return { + ok: b64.startsWith('UEsD') && b64.length > 1000, + detail: res.error ?? `base64Len=${b64.length} head=${b64.slice(0, 8)}`, + } + }, + }, +] + +async function main() { + const provider = process.env.SANDBOX_PROVIDER_DAYTONA ? 'daytona' : 'e2b' + const docConfigured = + provider === 'daytona' + ? Boolean(process.env.DAYTONA_DOC_SNAPSHOT_ID) + : Boolean(process.env.MOTHERSHIP_E2B_DOC_TEMPLATE_ID) + + console.log(`\nsandbox parity — provider: ${provider}\n${'='.repeat(50)}`) + + let failed = 0 + let skipped = 0 + for (const testCase of CASES) { + if (testCase.needsDoc && !docConfigured) { + console.log(`SKIP ${testCase.name} (doc image not configured)`) + skipped++ + continue + } + const started = Date.now() + try { + const { ok, detail } = await testCase.run() + const seconds = ((Date.now() - started) / 1000).toFixed(1) + console.log(`${ok ? 'PASS' : 'FAIL'} ${testCase.name} (${seconds}s)`) + if (!ok) { + console.log(` ${detail}`) + failed++ + } + } catch (error) { + console.log(`FAIL ${testCase.name}`) + console.log(` threw: ${error instanceof Error ? error.message : String(error)}`) + failed++ + } + } + + const passed = CASES.length - failed - skipped + console.log( + `${'='.repeat(50)}\n${provider}: ${passed} passed, ${failed} failed, ${skipped} skipped\n` + ) + if (failed > 0) process.exit(1) +} + +main().catch((error: unknown) => { + console.error('harness error:', error instanceof Error ? error.message : error) + process.exit(1) +}) diff --git a/bun.lock b/bun.lock index 33a2bf8d972..0667e36f5db 100644 --- a/bun.lock +++ b/bun.lock @@ -133,6 +133,7 @@ "@browserbasehq/stagehand": "^3.2.1", "@calcom/embed-react": "1.5.3", "@cerebras/cerebras_cloud_sdk": "^1.23.0", + "@daytonaio/sdk": "0.197.0", "@e2b/code-interpreter": "^2.0.0", "@earendil-works/pi-ai": "0.80.10", "@earendil-works/pi-coding-agent": "0.80.10", @@ -743,6 +744,8 @@ "@aws-sdk/lib-dynamodb": ["@aws-sdk/lib-dynamodb@3.1032.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/util-dynamodb": "^3.996.2", "@smithy/core": "^3.23.15", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "peerDependencies": { "@aws-sdk/client-dynamodb": "^3.1032.0" } }, "sha512-rYGhqP1H0Fy4r1yvWTmEAx0qqy1Zd9OzI8pPkXo6KSEDjZ4EwU+6QN1V+KLX3XTU6FQouF5LTvqLtl/CW4gxyQ=="], + "@aws-sdk/lib-storage": ["@aws-sdk/lib-storage@3.1088.0", "", { "dependencies": { "@smithy/core": "^3.29.4", "@smithy/types": "^4.16.1", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", "tslib": "^2.6.2" }, "peerDependencies": { "@aws-sdk/client-s3": "^3.1088.0" } }, "sha512-OElyotfOuTLAkWeYfWvgFQ/ABVs5xi9+UzlnKveFnbyfzV9um0guDWGanG/xGlw9ofAiplscHlNauChtJUBNWA=="], + "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.25", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.52", "tslib": "^2.6.2" } }, "sha512-zcRjdhS46gQ+omEKod2Q83A+42dQlFgQP9GfsK2XcDCli8kzA3q1QH+hDpIZUDbKaXmkTSn0JG3WP5yds5j38g=="], "@aws-sdk/middleware-endpoint-discovery": ["@aws-sdk/middleware-endpoint-discovery@3.972.19", "", { "dependencies": { "@aws-sdk/endpoint-cache": "^3.972.8", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-FMgyzUq3Jh+ONRYxryBRNdBd+FUX8PwRl07ccQknNdoms6KCeAEusCkl6whqpDrPQ6OH0ddeSifKyqYSs2DLIw=="], @@ -945,6 +948,14 @@ "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="], + "@daytona/analytics-api-client": ["@daytona/analytics-api-client@0.197.0", "", { "dependencies": { "axios": "^1.6.1" } }, "sha512-8S8JBVwIhErhDv22kCtifonfMnpQXtoAqz3migT53u7LCnjnVkqOeUFB/xN4SD7LN+Bhbh6AMVkvNwZA2BkIfw=="], + + "@daytona/api-client": ["@daytona/api-client@0.197.0", "", { "dependencies": { "axios": "^1.6.1" } }, "sha512-O7BF07FOmmbNrp/An3EIx/Vq99wSHvxxWl4nUttw0eHpe7oKdYaUVlM2MmdJ/AbrFnYDLHfrQZGZfeUOEeRjpw=="], + + "@daytona/toolbox-api-client": ["@daytona/toolbox-api-client@0.197.0", "", { "dependencies": { "axios": "^1.6.1" } }, "sha512-uBcbIAPcqeJUOpessqf2Za6Jid/Negn0x3wJlTcby391gsPUYDgsGzOmDZMs/rFKQ+/xtz/DAd7VThJZC5fnIQ=="], + + "@daytonaio/sdk": ["@daytonaio/sdk@0.197.0", "", { "dependencies": { "@aws-sdk/client-s3": "^3.787.0", "@aws-sdk/lib-storage": "^3.798.0", "@daytona/analytics-api-client": "0.197.0", "@daytona/api-client": "0.197.0", "@daytona/toolbox-api-client": "0.197.0", "@iarna/toml": "^2.2.5", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-trace-otlp-http": "^0.219.0", "@opentelemetry/instrumentation-http": "^0.219.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-node": "^0.219.0", "@opentelemetry/sdk-trace-base": "^2.8.0", "@opentelemetry/semantic-conventions": "^1.40.0", "axios": "^1.15.2", "busboy": "^1.0.0", "dotenv": "^17.0.1", "expand-tilde": "^2.0.2", "fast-glob": "^3.3.0", "form-data": "^4.0.4", "isomorphic-ws": "^5.0.0", "pathe": "^2.0.3", "shell-quote": "^1.8.2", "tar": "^7.5.11" } }, "sha512-RFrK/TLZy8S0VyQcXOzOv70VKNUoUyJ45u/HlCJjmXu5vyK3TH0pQJv9yJn8uT49W+4ypMSODcz0YJIRjH16VA=="], + "@derhuerst/http-basic": ["@derhuerst/http-basic@8.2.4", "", { "dependencies": { "caseless": "^0.12.0", "concat-stream": "^2.0.0", "http-response-object": "^3.0.1", "parse-cache-control": "^1.0.1" } }, "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw=="], "@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="], @@ -1061,6 +1072,8 @@ "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="], + "@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="], + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], "@iconify/utils": ["@iconify/utils@3.1.3", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw=="], @@ -1303,9 +1316,11 @@ "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.217.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.217.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-24ucQMjz7Y34Kw3trbxL2ZrssbtgWnR+Clpaa+YdeWuuyH3Cvk23Q03PcQvqiZrDvt8AmQmjgg9v6Y9PHoxG7w=="], - "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + "@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/instrumentation": "0.219.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-nNt1fqpyah/OKjNHdEOu8xLwISppRU2qJuF8aR+fCcftVwdFkPgtworBLA+TI1HU2iF508jcQBF2gerWczJAXg=="], + + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-transformer": "0.219.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA=="], - "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.217.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-exporter-base": "0.217.0", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag=="], + "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.219.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-iIk/s8QQu39zpTrRRmsW/Eg3SE2+Hg8tLWepr2FLRgmwUpNd0IpCTLJEHJ77hpt4hgIS8MAh44UYI4xQPZwWlw=="], "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.217.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.217.0", "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/sdk-logs": "0.217.0", "@opentelemetry/sdk-metrics": "2.7.1", "@opentelemetry/sdk-trace-base": "2.7.1", "protobufjs": "8.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA=="], @@ -2211,7 +2226,7 @@ "bson": ["bson@6.10.4", "", {}, "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng=="], - "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "buffer": ["buffer@5.6.0", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw=="], "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], @@ -2633,6 +2648,8 @@ "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "expand-tilde": ["expand-tilde@2.0.2", "", { "dependencies": { "homedir-polyfill": "^1.0.1" } }, "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], @@ -2703,6 +2720,8 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], + "fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], "framer-motion": ["framer-motion@12.42.2", "", { "dependencies": { "motion-dom": "^12.42.2", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw=="], @@ -2819,6 +2838,8 @@ "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + "homedir-polyfill": ["homedir-polyfill@1.0.3", "", { "dependencies": { "parse-passwd": "^1.0.0" } }, "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA=="], + "hono": ["hono@4.12.25", "", {}, "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ=="], "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], @@ -2943,6 +2964,8 @@ "isomorphic-unfetch": ["isomorphic-unfetch@3.1.0", "", { "dependencies": { "node-fetch": "^2.6.1", "unfetch": "^4.2.0" } }, "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q=="], + "isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="], + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], @@ -3405,6 +3428,8 @@ "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + "parse-passwd": ["parse-passwd@1.0.0", "", {}, "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], @@ -3761,6 +3786,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.10.0", "", {}, "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA=="], + "shiki": ["shiki@4.3.1", "", { "dependencies": { "@shikijs/core": "4.3.1", "@shikijs/engine-javascript": "4.3.1", "@shikijs/engine-oniguruma": "4.3.1", "@shikijs/langs": "4.3.1", "@shikijs/themes": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw=="], "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="], @@ -3837,6 +3864,8 @@ "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + "stream-browserify": ["stream-browserify@3.0.0", "", { "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA=="], + "stream-events": ["stream-events@1.0.5", "", { "dependencies": { "stubs": "^3.0.0" } }, "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg=="], "stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="], @@ -4177,6 +4206,10 @@ "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1069.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.21", "@aws-sdk/nested-clients": "^3.997.21", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ks4X+kngC3PA5howV7Qu1TgG4bfC4jPykKdvw3nmBSXR9yZxRJouBholFSNQ5kY3L+Fgwyw+LCjzQmNi+KR91g=="], + "@aws-sdk/lib-storage/@smithy/core": ["@smithy/core@3.29.4", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-G1GRglAabzEhqghJMBAd54FkRS7SAFGHEwbhcI9r+O+LIMuFsLyXkLZkCoFSgAglRu8s/URVXJB0hglq3ZipIg=="], + + "@aws-sdk/lib-storage/@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="], + "@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="], "@azure/communication-email/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], @@ -4207,6 +4240,12 @@ "@cerebras/cerebras_cloud_sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "@daytonaio/sdk/@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/configuration": "0.219.0", "@opentelemetry/context-async-hooks": "2.8.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.219.0", "@opentelemetry/exporter-logs-otlp-http": "0.219.0", "@opentelemetry/exporter-logs-otlp-proto": "0.219.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.219.0", "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.219.0", "@opentelemetry/exporter-prometheus": "0.219.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.219.0", "@opentelemetry/exporter-trace-otlp-http": "0.219.0", "@opentelemetry/exporter-trace-otlp-proto": "0.219.0", "@opentelemetry/exporter-zipkin": "2.8.0", "@opentelemetry/instrumentation": "0.219.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/propagator-b3": "2.8.0", "@opentelemetry/propagator-jaeger": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0", "@opentelemetry/sdk-trace-node": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NWLpWLEb8gV3+JBHYoIrktbM385wyHpRJoh3J/4Q52d4PR+AlPMNGJT3DzBUrDSUEVbKAXoHR+EDAPxtiNcj8g=="], + + "@daytonaio/sdk/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "@earendil-works/pi-ai/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw=="], "@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="], @@ -4243,18 +4282,34 @@ "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + + "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.217.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-exporter-base": "0.217.0", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag=="], + + "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + + "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="], + "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + + "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.217.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-exporter-base": "0.217.0", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag=="], + "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ=="], + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ=="], + "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ=="], @@ -4263,14 +4318,22 @@ "@opentelemetry/exporter-prometheus/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ=="], + "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + + "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.217.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-exporter-base": "0.217.0", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag=="], + "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="], + "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="], + "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="], @@ -4279,6 +4342,18 @@ "@opentelemetry/exporter-zipkin/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="], + "@opentelemetry/instrumentation-http/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="], + + "@opentelemetry/instrumentation-http/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ=="], + + "@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="], + + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="], + + "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ=="], @@ -4293,6 +4368,8 @@ "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="], + "@opentelemetry/sdk-node/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.217.0", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ=="], + "@opentelemetry/sdk-node/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/sdk-node/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ=="], @@ -4757,6 +4834,8 @@ "mysql2/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "neo4j-driver-bolt-connection/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "neo4j-driver-bolt-connection/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], @@ -4853,6 +4932,8 @@ "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "stream-browserify/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "streamdown/tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="], "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -4919,6 +5000,46 @@ "@cerebras/cerebras_cloud_sdk/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "@daytonaio/sdk/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="], + + "@daytonaio/sdk/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/configuration": ["@opentelemetry/configuration@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "yaml": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-wXZUYv4ngu43nA4WEhuXNacm46LW+17LRM8nKyIhBzroRA24PBYjMnakwzR/w777nFUB5xlgsYTTeuXxumZM1Q=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.8.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.219.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/sdk-logs": "0.219.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7SvzDCIclHWAcCwZ1MTOLcwn4BVNPGI3QxS/DJraPNe1TTL+4TvUBq5zeQV8tsnYvtDN7wKW2qocVmaCP2l7sQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/sdk-logs": "0.219.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-mhl2HL6GmZI8b8PwPfqMws/5ovJfbRTxwc9Y5agVVHiQ+e5SL1btsFr/kJDgt7YCexDtsUn5HAreHQO9szFS0A=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Ayw4Gf71PS9jhBVaYywa4WsajnqfDehMkTdVH3TSAVHqPcsAv/AhH/wTNRYNt99szeYr6Gbd/D6RjZD77wAxHg=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.219.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.8.0", "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-metrics": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-6LaaSrPxK5L55bXevWajvOMxGOpNm0n12tG53TeZaUeNzXwLPg6d2KCC1zAlGsojan+xRG71mA4Qqs9K2VVrKQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-metrics": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-6CaDRbMVHZSDWzNXwrR8y/H4B/Z1eMNnkHiPQlTx3Ojz2OHY4X/aff/UC4P/3pHUQSuTfi3oh2UsPPZppw+Vrg=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-metrics": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DUS7XyIiEnoeccQUvuKy0G2/YqeKhpN8FVIrGbrLNIVMj10yeIFLRzRv0tibCI2kXXvlTTABVexGAk78wHk2ug=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TxOnJ85eWJY5JyOJsNMXiRTYlkDcOv0u3KbXEzWCc+tUS9sjL/BC6BcdxZ0B9r2OFVqsrZFXUzSD2sZUy42Ucw=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.219.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-BkDNv1UD6BscW19MxbAxVmSYSSFuyeqR6buV2/HTYqA7GrR0EbTFzqG6h86T3PtXmpdbsWjMGLDdjG2rikG27Q=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.219.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-transformer": "0.219.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lF/LUBfhOFmxJa+SQsLN7ziV4MHa2pyKgOM6JNehSOfU+npjM4gwm9oIKEJrzrWcexMcqydiyoFy0XCb1Ql3wQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-Mj84UkEa17BK2o903VTXW3wM8CrSZexGs4tRGVZVIMM9ni1T6TuGx5IrRfoWKAbshx42D5/kc7YV+axypLPYyA=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SazlvuSKi5533rPHTW2TwBwdMakhjZST4SYs0YauuvfGDkT13KbG1gJS75hV0uWVeevhtVP9sAIlaZLTHdSbMg=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Xnz9zZvvQzUw+9DrOn0MomR7BxFCkA2pcfXBQuHC28ndJpSbjLs7knzYb05kw5SyCjSsEWombkZMgGcJSk8JVg=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA=="], + "@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="], "@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/node-http-handler": ["@smithy/node-http-handler@4.8.0", "", { "dependencies": { "@smithy/core": "^3.25.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-Mq7TNt/VhlEWiYRLQGpzUWeUxh899UGpjKh7Ru0WVIDIjnE+cTRAn0NYlFQ6bWfsQnKnpCbWJj86HzmcG0qEdg=="], @@ -4975,6 +5096,16 @@ "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + "@opentelemetry/instrumentation-http/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg=="], + + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg=="], + + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA=="], + + "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg=="], + + "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA=="], + "@opentelemetry/otlp-transformer/protobufjs/@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], "@radix-ui/react-accordion/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="], @@ -5273,6 +5404,8 @@ "react-email/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "readable-web-to-node-stream/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "readable-web-to-node-stream/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], @@ -5291,6 +5424,8 @@ "simstudio/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "stream-browserify/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "tar-stream/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "teeny-request/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], @@ -5367,6 +5502,26 @@ "@cerebras/cerebras_cloud_sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "@daytonaio/sdk/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg=="], + + "@daytonaio/sdk/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + + "@daytonaio/sdk/@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], + "@google-cloud/storage/google-auth-library/gcp-metadata/google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], "@opentelemetry/otlp-transformer/protobufjs/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],