@arnilo/prism-tools 0.0.0-stage → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +418 -0
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/browser/README.md +98 -0
- package/browser/skills/browser-automation/SKILL.md +14 -0
- package/browser/src/actions/cdp.ts +131 -0
- package/browser/src/actions/dialog.ts +25 -0
- package/browser/src/actions/download_release.ts +22 -0
- package/browser/src/actions/index.ts +37 -0
- package/browser/src/actions/locator.ts +70 -0
- package/browser/src/actions/navigate.ts +23 -0
- package/browser/src/actions/screenshot.ts +27 -0
- package/browser/src/actions/scroll.ts +24 -0
- package/browser/src/actions/select_page.ts +21 -0
- package/browser/src/actions/types.ts +114 -0
- package/browser/src/actions/upload.ts +44 -0
- package/browser/src/actions/wait.ts +45 -0
- package/browser/src/browser.ts +197 -0
- package/browser/src/cdp.ts +178 -0
- package/browser/src/checkpoint.ts +189 -0
- package/browser/src/config.ts +72 -0
- package/browser/src/downloads.ts +253 -0
- package/browser/src/errors.ts +31 -0
- package/browser/src/evaluate.ts +101 -0
- package/browser/src/index.ts +129 -0
- package/browser/src/limits.ts +271 -0
- package/browser/src/manager.ts +730 -0
- package/browser/src/network.ts +263 -0
- package/browser/src/observe.ts +178 -0
- package/browser/src/policy.ts +133 -0
- package/browser/src/screenshot.ts +107 -0
- package/browser/src/shared-sandbox.ts +53 -0
- package/browser/src/snapshot.ts +99 -0
- package/browser/src/targets.ts +165 -0
- package/browser/src/tools.ts +480 -0
- package/browser/src/types.ts +316 -0
- package/browser/src/uploads.ts +174 -0
- package/coding/README.md +337 -0
- package/coding/src/agent/acp-operations.ts +146 -0
- package/coding/src/agent/artifacts.ts +38 -0
- package/coding/src/agent/ask-user-decision.ts +623 -0
- package/coding/src/agent/atomic-write.ts +23 -0
- package/coding/src/agent/bounded-file.ts +21 -0
- package/coding/src/agent/checks.ts +301 -0
- package/coding/src/agent/coding-checkpoint.ts +828 -0
- package/coding/src/agent/delete.ts +237 -0
- package/coding/src/agent/diagnostics-tool.ts +163 -0
- package/coding/src/agent/diagnostics.ts +262 -0
- package/coding/src/agent/edit-diff.ts +382 -0
- package/coding/src/agent/edit.ts +322 -0
- package/coding/src/agent/effects.ts +102 -0
- package/coding/src/agent/env.ts +28 -0
- package/coding/src/agent/execution-policy.ts +89 -0
- package/coding/src/agent/file-mutation-queue.ts +72 -0
- package/coding/src/agent/forge/github.ts +603 -0
- package/coding/src/agent/forge/index.ts +16 -0
- package/coding/src/agent/forge/types.ts +185 -0
- package/coding/src/agent/git-aware-repository.ts +305 -0
- package/coding/src/agent/git-exec.ts +326 -0
- package/coding/src/agent/git-status.ts +177 -0
- package/coding/src/agent/git-tools.ts +360 -0
- package/coding/src/agent/git.ts +1074 -0
- package/coding/src/agent/glob-match.ts +146 -0
- package/coding/src/agent/glob.ts +182 -0
- package/coding/src/agent/goal-verify.ts +385 -0
- package/coding/src/agent/grep-worker.ts +74 -0
- package/coding/src/agent/grep.ts +459 -0
- package/coding/src/agent/identity.ts +48 -0
- package/coding/src/agent/index.ts +676 -0
- package/coding/src/agent/language/client.ts +429 -0
- package/coding/src/agent/language/framing.ts +190 -0
- package/coding/src/agent/language/index.ts +23 -0
- package/coding/src/agent/language/intelligence.ts +682 -0
- package/coding/src/agent/language/types.ts +187 -0
- package/coding/src/agent/lifecycle-events.ts +121 -0
- package/coding/src/agent/lifecycle.ts +166 -0
- package/coding/src/agent/limits.ts +214 -0
- package/coding/src/agent/move.ts +178 -0
- package/coding/src/agent/mutation-path.ts +52 -0
- package/coding/src/agent/output-accumulator.ts +304 -0
- package/coding/src/agent/path-utils.ts +120 -0
- package/coding/src/agent/process/index.ts +58 -0
- package/coding/src/agent/process/recovery.ts +508 -0
- package/coding/src/agent/process/sessions-host.ts +222 -0
- package/coding/src/agent/process/sessions-monitor.ts +141 -0
- package/coding/src/agent/process/sessions-recovery.ts +241 -0
- package/coding/src/agent/process/sessions-spawn.ts +613 -0
- package/coding/src/agent/process/sessions-teardown.ts +281 -0
- package/coding/src/agent/process/sessions.ts +30 -0
- package/coding/src/agent/process/types.ts +358 -0
- package/coding/src/agent/read-path-set.ts +126 -0
- package/coding/src/agent/read.ts +713 -0
- package/coding/src/agent/repository/glob.ts +147 -0
- package/coding/src/agent/repository/indexed-search.ts +480 -0
- package/coding/src/agent/repository/list.ts +131 -0
- package/coding/src/agent/repository/operations.ts +22 -0
- package/coding/src/agent/repository/path.ts +98 -0
- package/coding/src/agent/repository/search.ts +277 -0
- package/coding/src/agent/repository/types.ts +243 -0
- package/coding/src/agent/repository/walk.ts +126 -0
- package/coding/src/agent/repository.ts +12 -0
- package/coding/src/agent/shell-background.ts +264 -0
- package/coding/src/agent/shell-output.ts +75 -0
- package/coding/src/agent/shell.ts +526 -0
- package/coding/src/agent/todo.ts +399 -0
- package/coding/src/agent/truncate.ts +251 -0
- package/coding/src/agent/write.ts +170 -0
- package/coding/src/ask-user-host.ts +48 -0
- package/coding/src/config.ts +278 -0
- package/coding/src/hard-deny.ts +97 -0
- package/coding/src/index.ts +318 -0
- package/coding/src/principal-guard.ts +84 -0
- package/coding/src/sandbox.ts +92 -0
- package/coding/src/security/approval.ts +144 -0
- package/coding/src/security/command-rules.ts +57 -0
- package/coding/src/security/docker-cli.ts +222 -0
- package/coding/src/security/docker-sandbox.ts +1205 -0
- package/coding/src/security/e2b-sandbox.ts +853 -0
- package/coding/src/security/egress/dns-pin.ts +88 -0
- package/coding/src/security/egress/index.ts +32 -0
- package/coding/src/security/egress/limits.ts +57 -0
- package/coding/src/security/egress/policy.ts +137 -0
- package/coding/src/security/egress/proxy.ts +599 -0
- package/coding/src/security/egress/types.ts +30 -0
- package/coding/src/security/index.ts +199 -0
- package/coding/src/security/native-sandbox.ts +639 -0
- package/coding/src/security/path-containment.ts +55 -0
- package/coding/src/security/sandbox-coding-operations.ts +392 -0
- package/coding/src/security/sandbox-fs-operations.ts +644 -0
- package/coding/src/security/sandbox-limits.ts +147 -0
- package/coding/src/security/sandbox-tar.ts +308 -0
- package/coding/src/security/sandbox.ts +179 -0
- package/coding/src/security/semaphore.ts +55 -0
- package/coding/src/targets.ts +110 -0
- package/coding/src/workspace/diff.ts +78 -0
- package/coding/src/workspace/errors.ts +29 -0
- package/coding/src/workspace/index.ts +5 -0
- package/coding/src/workspace/merge.ts +151 -0
- package/coding/src/workspace/routing.ts +76 -0
- package/coding/src/workspace/service.ts +584 -0
- package/computer-use/README.md +105 -0
- package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
- package/computer-use/src/classify.ts +53 -0
- package/computer-use/src/command.ts +29 -0
- package/computer-use/src/config.ts +69 -0
- package/computer-use/src/create.ts +170 -0
- package/computer-use/src/devices.ts +247 -0
- package/computer-use/src/env.ts +28 -0
- package/computer-use/src/index.ts +157 -0
- package/computer-use/src/skill.ts +21 -0
- package/openapi/README.md +97 -0
- package/openapi/src/compile.ts +386 -0
- package/openapi/src/config.ts +73 -0
- package/openapi/src/errors.ts +16 -0
- package/openapi/src/execute.ts +241 -0
- package/openapi/src/index.ts +124 -0
- package/openapi/src/limits.ts +63 -0
- package/openapi/src/specs.ts +86 -0
- package/openapi/src/tools.ts +60 -0
- package/openapi/src/types.ts +61 -0
- package/package.json +171 -4
- package/research/README.md +72 -0
- package/research/skills/research-report/SKILL.md +12 -0
- package/research/skills/research-scoping/SKILL.md +10 -0
- package/research/skills/research-synthesis/SKILL.md +10 -0
- package/research/skills/source-triage/SKILL.md +10 -0
- package/research/src/check.ts +71 -0
- package/research/src/config.ts +41 -0
- package/research/src/index.ts +147 -0
- package/research/src/ledger.ts +157 -0
- package/research/src/urls.ts +21 -0
- package/web/README.md +287 -0
- package/web/skills/web-research/SKILL.md +17 -0
- package/web/src/brave.ts +66 -0
- package/web/src/cache.ts +151 -0
- package/web/src/config.ts +210 -0
- package/web/src/controls.ts +128 -0
- package/web/src/domains.ts +114 -0
- package/web/src/exa.ts +60 -0
- package/web/src/firecrawl.ts +160 -0
- package/web/src/index.ts +387 -0
- package/web/src/kagi.ts +73 -0
- package/web/src/limits.ts +55 -0
- package/web/src/native/convert.ts +84 -0
- package/web/src/native/fetch.ts +226 -0
- package/web/src/normalize.ts +131 -0
- package/web/src/obscura/cdp.ts +201 -0
- package/web/src/obscura/classify.ts +35 -0
- package/web/src/obscura/cli.ts +254 -0
- package/web/src/obscura/errors.ts +8 -0
- package/web/src/obscura/index.ts +59 -0
- package/web/src/obscura/limits.ts +92 -0
- package/web/src/obscura/mcp.ts +104 -0
- package/web/src/obscura/playwright.ts +133 -0
- package/web/src/obscura/process.ts +235 -0
- package/web/src/obscura/search-profile.ts +73 -0
- package/web/src/obscura/types.ts +48 -0
- package/web/src/obscura/web.ts +419 -0
- package/web/src/prompt-fetch.ts +88 -0
- package/web/src/render.ts +75 -0
- package/web/src/searxng.ts +78 -0
- package/web/src/slice.ts +139 -0
- package/web/src/tavily.ts +77 -0
- package/web/src/tools.ts +182 -0
- package/web/src/transport.ts +310 -0
- package/web/src/types.ts +199 -0
- package/work/README.md +77 -0
- package/work/THIRD_PARTY_NOTICES +24 -0
- package/work/docling/ocr.py +150 -0
- package/work/sandbox/Dockerfile +36 -0
- package/work/sandbox/soffice.sh +12 -0
- package/work/src/command.ts +29 -0
- package/work/src/config.ts +138 -0
- package/work/src/connectors/cli.ts +343 -0
- package/work/src/connectors/drafts.ts +647 -0
- package/work/src/connectors/errors.ts +8 -0
- package/work/src/connectors/file-bytes.ts +149 -0
- package/work/src/connectors/google-workspace-http.ts +399 -0
- package/work/src/connectors/google-workspace.ts +465 -0
- package/work/src/connectors/gws-tools.ts +378 -0
- package/work/src/connectors/http.ts +123 -0
- package/work/src/connectors/idempotency.ts +235 -0
- package/work/src/connectors/identity.ts +34 -0
- package/work/src/connectors/index.ts +93 -0
- package/work/src/connectors/limits.ts +50 -0
- package/work/src/connectors/m365-tools.ts +308 -0
- package/work/src/connectors/microsoft365-http.ts +428 -0
- package/work/src/connectors/microsoft365.ts +402 -0
- package/work/src/connectors/normalize.ts +120 -0
- package/work/src/connectors/tool-helpers.ts +398 -0
- package/work/src/connectors/tools.ts +42 -0
- package/work/src/connectors/types.ts +399 -0
- package/work/src/diagrams/canonicalize.ts +166 -0
- package/work/src/diagrams/caps.ts +52 -0
- package/work/src/diagrams/embed.ts +340 -0
- package/work/src/diagrams/errors.ts +76 -0
- package/work/src/diagrams/index.ts +81 -0
- package/work/src/diagrams/messages.ts +150 -0
- package/work/src/diagrams/origin.ts +52 -0
- package/work/src/diagrams/telemetry.ts +34 -0
- package/work/src/diagrams/xml.ts +267 -0
- package/work/src/document-extraction/index.ts +563 -0
- package/work/src/document-reader/ambient.d.ts +2 -0
- package/work/src/document-reader/errors.ts +7 -0
- package/work/src/document-reader/index.ts +181 -0
- package/work/src/document-reader/mistral-ocr.ts +318 -0
- package/work/src/document-reader/parsers.ts +171 -0
- package/work/src/documents/caps.ts +135 -0
- package/work/src/documents/diff.ts +269 -0
- package/work/src/documents/errors.ts +44 -0
- package/work/src/documents/fidelity.ts +98 -0
- package/work/src/documents/generate.ts +96 -0
- package/work/src/documents/hash.ts +8 -0
- package/work/src/documents/index.ts +14 -0
- package/work/src/documents/model-schema.ts +639 -0
- package/work/src/documents/parse.ts +252 -0
- package/work/src/documents/patch-history.ts +81 -0
- package/work/src/documents/patch.ts +476 -0
- package/work/src/documents/preview-html.ts +245 -0
- package/work/src/documents/preview.ts +227 -0
- package/work/src/documents/telemetry.ts +36 -0
- package/work/src/documents/translate/docx.ts +292 -0
- package/work/src/documents/translate/pptx.ts +304 -0
- package/work/src/documents/translate/xlsx.ts +137 -0
- package/work/src/documents/types.ts +188 -0
- package/work/src/index.ts +311 -0
- package/work/src/runtime/worker-pool-entry.ts +102 -0
- package/work/src/runtime/worker-pool.ts +267 -0
- package/work/src/sandbox/composition.ts +212 -0
- package/work/src/sandbox/image.ts +6 -0
- package/work/src/sandbox/index.ts +14 -0
- package/work/src/sheets/caps.ts +68 -0
- package/work/src/sheets/csv.ts +351 -0
- package/work/src/sheets/decimal.ts +388 -0
- package/work/src/sheets/errors.ts +52 -0
- package/work/src/sheets/index.ts +62 -0
- package/work/src/sheets/inference.ts +378 -0
- package/work/src/sheets/telemetry.ts +34 -0
- package/work/src/sheets/types.ts +162 -0
- package/work/src/sheets/xlsx.ts +388 -0
- package/work/src/skills/index.ts +8 -0
- package/work/src/skills/load.ts +43 -0
- package/work/src/tools/filesystem.ts +21 -0
- package/work/src/tools/index.ts +1 -0
- package/work/src/tools/local-filesystem.ts +33 -0
- package/work/src/tools/office.ts +311 -0
- package/work/vendor/hermes-agent/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/work/vendor-lock.json +11 -0
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import type { ProcessRecoveryBackend } from "../agent/process/recovery.js";
|
|
4
|
+
import type { ProcessSandboxHandle } from "../agent/process/types.js";
|
|
5
|
+
import { createSecretRedactor } from "./docker-cli.js";
|
|
6
|
+
import { computeCommandFingerprint } from "./docker-sandbox.js";
|
|
7
|
+
import type {
|
|
8
|
+
DisposableSandbox,
|
|
9
|
+
SandboxCapabilities,
|
|
10
|
+
SandboxCloseOptions,
|
|
11
|
+
SandboxExecFileRequest,
|
|
12
|
+
SandboxExecRequest,
|
|
13
|
+
SandboxExportMetadata,
|
|
14
|
+
SandboxPauseResult,
|
|
15
|
+
SandboxProcessHandle,
|
|
16
|
+
SandboxSnapshotKind,
|
|
17
|
+
SandboxStatus,
|
|
18
|
+
SandboxStatusState,
|
|
19
|
+
} from "./sandbox.js";
|
|
20
|
+
import { type DockerSandboxLimitOptions, type ResolvedDockerSandboxLimits, resolveDockerSandboxLimits } from "./sandbox-limits.js";
|
|
21
|
+
import { Semaphore } from "./semaphore.js";
|
|
22
|
+
|
|
23
|
+
/** Verified `e2b` peer (MIT, Node >=20.18.1 <21 || >=22). */
|
|
24
|
+
export const E2B_PEER_VERSION = "2.49.1";
|
|
25
|
+
|
|
26
|
+
const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
27
|
+
const DEFAULT_WORKDIR = "/workspace";
|
|
28
|
+
const DEFAULT_TEMPLATE = "base";
|
|
29
|
+
|
|
30
|
+
export class E2BSandboxError extends Error {
|
|
31
|
+
readonly code = "ERR_PRISM_E2B_SANDBOX";
|
|
32
|
+
readonly statusCode?: number;
|
|
33
|
+
constructor(message: string, statusCode?: number) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "E2BSandboxError";
|
|
36
|
+
this.statusCode = statusCode;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface E2BCommandResult {
|
|
41
|
+
readonly exitCode: number | null;
|
|
42
|
+
readonly stdout?: string;
|
|
43
|
+
readonly stderr?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface E2BCommandHandle {
|
|
47
|
+
readonly pid?: number;
|
|
48
|
+
wait(opts?: { signal?: AbortSignal }): Promise<E2BCommandResult>;
|
|
49
|
+
kill?(): Promise<boolean | undefined>;
|
|
50
|
+
sendStdin?(data: string | Uint8Array): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface E2BCommandStartOpts {
|
|
54
|
+
readonly background?: boolean;
|
|
55
|
+
readonly cwd?: string;
|
|
56
|
+
readonly envs?: Readonly<Record<string, string>>;
|
|
57
|
+
readonly timeoutMs?: number;
|
|
58
|
+
readonly stdin?: boolean;
|
|
59
|
+
readonly signal?: AbortSignal;
|
|
60
|
+
readonly onStdout?: (data: string) => void | Promise<void>;
|
|
61
|
+
readonly onStderr?: (data: string) => void | Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface E2BSandboxInstance {
|
|
65
|
+
readonly sandboxId: string;
|
|
66
|
+
readonly commands: {
|
|
67
|
+
run(cmd: string, opts?: E2BCommandStartOpts): Promise<E2BCommandResult | E2BCommandHandle>;
|
|
68
|
+
connect?(pid: number, opts?: { signal?: AbortSignal }): Promise<E2BCommandHandle>;
|
|
69
|
+
kill?(pid: number, opts?: { signal?: AbortSignal }): Promise<boolean>;
|
|
70
|
+
sendStdin?(pid: number, data: string | Uint8Array, opts?: { signal?: AbortSignal }): Promise<void>;
|
|
71
|
+
list?(opts?: {
|
|
72
|
+
signal?: AbortSignal;
|
|
73
|
+
}): Promise<readonly { readonly pid: number; readonly cmd?: string; readonly args?: readonly string[] }[]>;
|
|
74
|
+
};
|
|
75
|
+
pause?(opts?: { keepMemory?: boolean; signal?: AbortSignal }): Promise<boolean | undefined>;
|
|
76
|
+
kill?(opts?: { signal?: AbortSignal }): Promise<boolean | undefined>;
|
|
77
|
+
getInfo?(opts?: { signal?: AbortSignal }): Promise<E2BSandboxInfo>;
|
|
78
|
+
isRunning?(opts?: { signal?: AbortSignal }): Promise<boolean>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface E2BSandboxInfo {
|
|
82
|
+
readonly sandboxId?: string;
|
|
83
|
+
readonly templateId?: string;
|
|
84
|
+
readonly state?: string;
|
|
85
|
+
readonly metadata?: Readonly<Record<string, string>>;
|
|
86
|
+
readonly startedAt?: Date | string;
|
|
87
|
+
readonly endAt?: Date | string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface E2BSandboxStatic {
|
|
91
|
+
create(templateOrOpts?: string | E2BCreateOpts, opts?: E2BCreateOpts): Promise<E2BSandboxInstance>;
|
|
92
|
+
connect(sandboxId: string, opts?: E2BConnectOpts): Promise<E2BSandboxInstance>;
|
|
93
|
+
getInfo?(sandboxId: string, opts?: E2BConnectOpts): Promise<E2BSandboxInfo>;
|
|
94
|
+
pause?(sandboxId: string, opts?: { keepMemory?: boolean; signal?: AbortSignal } & E2BConnectOpts): Promise<boolean | undefined>;
|
|
95
|
+
kill?(sandboxId: string, opts?: E2BConnectOpts): Promise<boolean | undefined>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface E2BCreateOpts {
|
|
99
|
+
readonly apiKey?: string;
|
|
100
|
+
readonly timeoutMs?: number;
|
|
101
|
+
readonly envs?: Readonly<Record<string, string>>;
|
|
102
|
+
readonly metadata?: Readonly<Record<string, string>>;
|
|
103
|
+
readonly lifecycle?: {
|
|
104
|
+
readonly onTimeout?: "kill" | "pause" | { readonly action: "pause"; readonly keepMemory?: boolean };
|
|
105
|
+
readonly autoResume?: boolean;
|
|
106
|
+
};
|
|
107
|
+
readonly signal?: AbortSignal;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface E2BConnectOpts {
|
|
111
|
+
readonly apiKey?: string;
|
|
112
|
+
readonly signal?: AbortSignal;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface E2BClient {
|
|
116
|
+
readonly Sandbox: E2BSandboxStatic;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface CreateE2BSandboxOptions {
|
|
120
|
+
readonly apiKey?: string;
|
|
121
|
+
readonly client?: E2BClient;
|
|
122
|
+
readonly template?: string;
|
|
123
|
+
readonly workdir?: string;
|
|
124
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
125
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
126
|
+
readonly secrets?: readonly string[];
|
|
127
|
+
readonly limits?: DockerSandboxLimitOptions;
|
|
128
|
+
readonly timeoutMs?: number;
|
|
129
|
+
readonly onTimeout?: "kill" | "pause";
|
|
130
|
+
readonly capabilities?: Readonly<Partial<SandboxCapabilities>>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ConnectE2BSandboxOptions {
|
|
134
|
+
readonly sandboxId: string;
|
|
135
|
+
readonly apiKey?: string;
|
|
136
|
+
readonly client?: E2BClient;
|
|
137
|
+
readonly workdir?: string;
|
|
138
|
+
readonly expectedLabels?: Readonly<Record<string, string>>;
|
|
139
|
+
readonly secrets?: readonly string[];
|
|
140
|
+
readonly limits?: DockerSandboxLimitOptions;
|
|
141
|
+
readonly timeoutMs?: number;
|
|
142
|
+
/** When true, `Sandbox.connect` may resume a paused sandbox. Default false. */
|
|
143
|
+
readonly resume?: boolean;
|
|
144
|
+
readonly capabilities?: Readonly<Partial<SandboxCapabilities>>;
|
|
145
|
+
readonly template?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface E2BProcessRefData {
|
|
149
|
+
readonly version: 1;
|
|
150
|
+
readonly sandboxId: string;
|
|
151
|
+
readonly pid: number;
|
|
152
|
+
readonly commandFingerprint: string;
|
|
153
|
+
readonly workspace: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface E2BProcessRecoveryBackendOptions {
|
|
157
|
+
readonly expectedSandboxId?: string;
|
|
158
|
+
readonly expectedWorkspace?: string;
|
|
159
|
+
readonly expectedLabels?: Readonly<Record<string, string>>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const E2B_CAPABILITIES: SandboxCapabilities = Object.freeze({
|
|
163
|
+
workspaceCoherent: true,
|
|
164
|
+
filesystemIsolated: true,
|
|
165
|
+
networkIsolated: false,
|
|
166
|
+
processIsolated: true,
|
|
167
|
+
privilegeIsolated: false,
|
|
168
|
+
egressRestricted: false,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export function resolveE2BCapabilities(override?: Readonly<Partial<SandboxCapabilities>>): SandboxCapabilities {
|
|
172
|
+
if (!override) return E2B_CAPABILITIES;
|
|
173
|
+
return Object.freeze({
|
|
174
|
+
workspaceCoherent: override.workspaceCoherent === true,
|
|
175
|
+
filesystemIsolated: override.filesystemIsolated === true,
|
|
176
|
+
networkIsolated: override.networkIsolated === true,
|
|
177
|
+
processIsolated: override.processIsolated === true,
|
|
178
|
+
privilegeIsolated: override.privilegeIsolated === true,
|
|
179
|
+
egressRestricted: override.egressRestricted === true,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function posixQuote(value: string): string {
|
|
184
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function argvCommand(file: string, args: readonly string[] = []): string {
|
|
188
|
+
return [file, ...args].map(posixQuote).join(" ");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function encodeE2BProcessRef(data: E2BProcessRefData): string {
|
|
192
|
+
const json = JSON.stringify({
|
|
193
|
+
v: 1,
|
|
194
|
+
sid: data.sandboxId,
|
|
195
|
+
pid: data.pid,
|
|
196
|
+
fp: data.commandFingerprint,
|
|
197
|
+
ws: data.workspace,
|
|
198
|
+
});
|
|
199
|
+
return `prism-e2b-proc:${Buffer.from(json, "utf8").toString("base64url")}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function decodeE2BProcessRef(ref: string): E2BProcessRefData | null {
|
|
203
|
+
if (!ref.startsWith("prism-e2b-proc:")) return null;
|
|
204
|
+
try {
|
|
205
|
+
const parsed = JSON.parse(Buffer.from(ref.slice("prism-e2b-proc:".length), "base64url").toString("utf8")) as Record<string, unknown>;
|
|
206
|
+
if (
|
|
207
|
+
parsed.v !== 1 ||
|
|
208
|
+
typeof parsed.sid !== "string" ||
|
|
209
|
+
typeof parsed.pid !== "number" ||
|
|
210
|
+
!Number.isInteger(parsed.pid) ||
|
|
211
|
+
parsed.pid < 1 ||
|
|
212
|
+
typeof parsed.fp !== "string" ||
|
|
213
|
+
typeof parsed.ws !== "string"
|
|
214
|
+
) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
version: 1,
|
|
219
|
+
sandboxId: parsed.sid,
|
|
220
|
+
pid: parsed.pid,
|
|
221
|
+
commandFingerprint: parsed.fp,
|
|
222
|
+
workspace: parsed.ws,
|
|
223
|
+
};
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function httpStatus(error: unknown): number | undefined {
|
|
230
|
+
if (error && typeof error === "object" && "statusCode" in error && typeof (error as { statusCode: unknown }).statusCode === "number") {
|
|
231
|
+
return (error as { statusCode: number }).statusCode;
|
|
232
|
+
}
|
|
233
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
234
|
+
const match = /^(?:Error:\s*)?(\d{3}):/.exec(message);
|
|
235
|
+
return match ? Number(match[1]) : undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isBusyPause(error: unknown): boolean {
|
|
239
|
+
if (httpStatus(error) === 503) return true;
|
|
240
|
+
const name = error instanceof Error ? error.name : "";
|
|
241
|
+
return name === "ServiceBusyError" || name === "ServiceBusyException";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isHandle(value: unknown): value is E2BCommandHandle {
|
|
245
|
+
return !!value && typeof value === "object" && typeof (value as E2BCommandHandle).wait === "function";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function validateEnv(env: Readonly<Record<string, string>> | undefined, limits: ResolvedDockerSandboxLimits): Record<string, string> {
|
|
249
|
+
const entries = Object.entries(env ?? {});
|
|
250
|
+
if (entries.length > limits.maxEnvNames) throw new E2BSandboxError(`env exceeds maxEnvNames (${limits.maxEnvNames})`);
|
|
251
|
+
let bytes = 0;
|
|
252
|
+
const out: Record<string, string> = {};
|
|
253
|
+
for (const [name, value] of entries) {
|
|
254
|
+
if (!ENV_NAME_RE.test(name)) throw new E2BSandboxError(`invalid env name: ${name}`);
|
|
255
|
+
if (typeof value !== "string") throw new E2BSandboxError(`env value for ${name} must be a string`);
|
|
256
|
+
bytes += Buffer.byteLength(name, "utf8") + Buffer.byteLength(value, "utf8");
|
|
257
|
+
if (bytes > limits.maxEnvBytes) throw new E2BSandboxError(`env exceeds maxEnvBytes (${limits.maxEnvBytes})`);
|
|
258
|
+
out[name] = value;
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function validateLabels(labels: Readonly<Record<string, string>> | undefined): Record<string, string> {
|
|
264
|
+
const out: Record<string, string> = {};
|
|
265
|
+
for (const [key, value] of Object.entries(labels ?? {})) {
|
|
266
|
+
if (!key || /[\s=]/.test(key) || key.includes("\0") || value.includes("\n") || value.includes("\0")) {
|
|
267
|
+
throw new E2BSandboxError("invalid sandbox label");
|
|
268
|
+
}
|
|
269
|
+
out[key] = value;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function validateWorkdir(workdir: string | undefined): string {
|
|
275
|
+
const path = workdir ?? DEFAULT_WORKDIR;
|
|
276
|
+
if (!path.startsWith("/") || path.includes("\0")) throw new E2BSandboxError("workdir must be an absolute sandbox path");
|
|
277
|
+
return path;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function assertNotAborted(signal?: AbortSignal): void {
|
|
281
|
+
if (signal?.aborted) throw new E2BSandboxError("sandbox operation aborted");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function loadSandboxApi(client: E2BClient | undefined): Promise<E2BSandboxStatic> {
|
|
285
|
+
if (client?.Sandbox) return client.Sandbox;
|
|
286
|
+
try {
|
|
287
|
+
const require = createRequire(import.meta.url);
|
|
288
|
+
const resolved = require.resolve("e2b");
|
|
289
|
+
const mod = (await import(pathToFileURL(resolved).href)) as {
|
|
290
|
+
Sandbox?: E2BSandboxStatic;
|
|
291
|
+
default?: { Sandbox?: E2BSandboxStatic };
|
|
292
|
+
};
|
|
293
|
+
const Sandbox = mod.Sandbox ?? mod.default?.Sandbox;
|
|
294
|
+
if (typeof Sandbox?.create !== "function" || typeof Sandbox.connect !== "function") {
|
|
295
|
+
throw new E2BSandboxError("installed e2b does not export Sandbox.create/connect");
|
|
296
|
+
}
|
|
297
|
+
return Sandbox;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (error instanceof E2BSandboxError) throw error;
|
|
300
|
+
throw new E2BSandboxError(`optional peer e2b@${E2B_PEER_VERSION} is not installed; install it or pass createE2BSandbox({ client })`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function edgeOpts(apiKey: string | undefined): { apiKey?: string } {
|
|
305
|
+
return apiKey ? { apiKey } : {};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
class E2BProcessHandle implements SandboxProcessHandle {
|
|
309
|
+
private _exited = false;
|
|
310
|
+
private _exitCode: number | null = null;
|
|
311
|
+
private _released = false;
|
|
312
|
+
private readonly waiters: Array<() => void> = [];
|
|
313
|
+
readonly ref: string;
|
|
314
|
+
readonly pid: number;
|
|
315
|
+
|
|
316
|
+
constructor(
|
|
317
|
+
readonly sandboxId: string,
|
|
318
|
+
pid: number,
|
|
319
|
+
readonly commandFingerprint: string,
|
|
320
|
+
readonly workspace: string,
|
|
321
|
+
private readonly commands: E2BSandboxInstance["commands"],
|
|
322
|
+
private readonly redact: (text: string) => string,
|
|
323
|
+
waitPromise?: Promise<E2BCommandResult>,
|
|
324
|
+
) {
|
|
325
|
+
this.pid = pid;
|
|
326
|
+
this.ref = encodeE2BProcessRef({ version: 1, sandboxId, pid, commandFingerprint, workspace });
|
|
327
|
+
waitPromise?.then(
|
|
328
|
+
(result) => this.markExited(result.exitCode),
|
|
329
|
+
() => this.markExited(null),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
markExited(exitCode: number | null): void {
|
|
334
|
+
if (this._exited) return;
|
|
335
|
+
this._exited = true;
|
|
336
|
+
this._exitCode = exitCode;
|
|
337
|
+
for (const waiter of this.waiters) waiter();
|
|
338
|
+
this.waiters.length = 0;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
get isExited(): boolean {
|
|
342
|
+
return this._exited;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async write(data: Uint8Array | string): Promise<void> {
|
|
346
|
+
if (this._exited || this._released) throw new E2BSandboxError("process is not running");
|
|
347
|
+
if (typeof this.commands.sendStdin !== "function") throw new E2BSandboxError("sandbox commands.sendStdin is unsupported");
|
|
348
|
+
try {
|
|
349
|
+
await this.commands.sendStdin(this.pid, data);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
throw new E2BSandboxError(this.redact(error instanceof Error ? error.message : String(error)));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async signal(name: string): Promise<void> {
|
|
356
|
+
if (this._exited) throw new E2BSandboxError("process is not running");
|
|
357
|
+
if (name === "SIGKILL" || name === "KILL" || name === "9") {
|
|
358
|
+
await this.kill();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
throw new E2BSandboxError(`E2B process signal ${name} is unsupported; only SIGKILL`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async kill(): Promise<void> {
|
|
365
|
+
if (this._exited) return;
|
|
366
|
+
try {
|
|
367
|
+
await this.commands.kill?.(this.pid);
|
|
368
|
+
} catch {
|
|
369
|
+
// best effort
|
|
370
|
+
}
|
|
371
|
+
this.markExited(null);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async release(): Promise<void> {
|
|
375
|
+
if (this._exited) return;
|
|
376
|
+
this._released = true;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async wait(waitOptions?: { timeoutMs?: number; signal?: AbortSignal }): Promise<{ exitCode: number | null }> {
|
|
380
|
+
if (this._exited) return { exitCode: this._exitCode };
|
|
381
|
+
return await new Promise<{ exitCode: number | null }>((resolve, reject) => {
|
|
382
|
+
const onExit = () => {
|
|
383
|
+
cleanup();
|
|
384
|
+
resolve({ exitCode: this._exitCode });
|
|
385
|
+
};
|
|
386
|
+
const onAbort = () => {
|
|
387
|
+
cleanup();
|
|
388
|
+
reject(new E2BSandboxError("wait aborted"));
|
|
389
|
+
};
|
|
390
|
+
let timer: NodeJS.Timeout | undefined;
|
|
391
|
+
const cleanup = () => {
|
|
392
|
+
if (timer) clearTimeout(timer);
|
|
393
|
+
waitOptions?.signal?.removeEventListener("abort", onAbort);
|
|
394
|
+
const idx = this.waiters.indexOf(onExit);
|
|
395
|
+
if (idx >= 0) this.waiters.splice(idx, 1);
|
|
396
|
+
};
|
|
397
|
+
this.waiters.push(onExit);
|
|
398
|
+
if (waitOptions?.timeoutMs !== undefined) {
|
|
399
|
+
timer = setTimeout(() => {
|
|
400
|
+
cleanup();
|
|
401
|
+
reject(new E2BSandboxError(`wait timed out after ${waitOptions.timeoutMs}ms`));
|
|
402
|
+
}, waitOptions.timeoutMs);
|
|
403
|
+
}
|
|
404
|
+
if (waitOptions?.signal) {
|
|
405
|
+
if (waitOptions.signal.aborted) onAbort();
|
|
406
|
+
else waitOptions.signal.addEventListener("abort", onAbort, { once: true });
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
class E2BSandboxSession implements DisposableSandbox {
|
|
413
|
+
readonly id: string;
|
|
414
|
+
readonly capabilities: SandboxCapabilities;
|
|
415
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
416
|
+
readonly timeoutMs: number;
|
|
417
|
+
private state: SandboxStatusState = "running";
|
|
418
|
+
private snapshotKind: SandboxSnapshotKind | undefined;
|
|
419
|
+
private commandCount = 0;
|
|
420
|
+
private readonly startedAt = Date.now();
|
|
421
|
+
private lastActivityAt = Date.now();
|
|
422
|
+
private readonly execLock: Semaphore;
|
|
423
|
+
private readonly activeProcesses = new Map<number, E2BProcessHandle>();
|
|
424
|
+
private readonly redact: (text: string) => string;
|
|
425
|
+
|
|
426
|
+
constructor(
|
|
427
|
+
private instance: E2BSandboxInstance | undefined,
|
|
428
|
+
private readonly api: E2BSandboxStatic,
|
|
429
|
+
private readonly opts: {
|
|
430
|
+
readonly sandboxId: string;
|
|
431
|
+
readonly template: string;
|
|
432
|
+
readonly workdir: string;
|
|
433
|
+
readonly apiKey?: string;
|
|
434
|
+
readonly limits: ResolvedDockerSandboxLimits;
|
|
435
|
+
readonly timeoutMs: number;
|
|
436
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
437
|
+
readonly capabilities: SandboxCapabilities;
|
|
438
|
+
readonly secrets: readonly string[];
|
|
439
|
+
},
|
|
440
|
+
) {
|
|
441
|
+
this.id = opts.sandboxId;
|
|
442
|
+
this.capabilities = opts.capabilities;
|
|
443
|
+
this.labels = opts.labels;
|
|
444
|
+
this.timeoutMs = opts.timeoutMs;
|
|
445
|
+
this.execLock = new Semaphore(opts.limits.maxConcurrentExecs, E2BSandboxError);
|
|
446
|
+
this.redact = createSecretRedactor([opts.apiKey ?? "", ...opts.secrets]);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
markPaused(kind: SandboxSnapshotKind): void {
|
|
450
|
+
this.state = "stopped";
|
|
451
|
+
this.snapshotKind = kind;
|
|
452
|
+
this.instance = undefined;
|
|
453
|
+
if (kind === "filesystem") {
|
|
454
|
+
for (const handle of this.activeProcesses.values()) handle.markExited(null);
|
|
455
|
+
this.activeProcesses.clear();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private touch(): void {
|
|
460
|
+
this.lastActivityAt = Date.now();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private remainingWallMs(): number {
|
|
464
|
+
return Math.max(1, this.opts.timeoutMs - (Date.now() - this.startedAt));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private assertRunning(): E2BSandboxInstance {
|
|
468
|
+
if (this.state === "removed" || this.state === "failed") throw new E2BSandboxError("sandbox is not available");
|
|
469
|
+
if (this.state === "stopped" || !this.instance) {
|
|
470
|
+
throw new E2BSandboxError("sandbox is paused; call resume() (exec does not auto-resume)");
|
|
471
|
+
}
|
|
472
|
+
return this.instance;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async status(): Promise<SandboxStatus> {
|
|
476
|
+
return {
|
|
477
|
+
id: this.id,
|
|
478
|
+
state: this.state,
|
|
479
|
+
image: this.opts.template,
|
|
480
|
+
startedAt: this.startedAt,
|
|
481
|
+
lastActivityAt: this.lastActivityAt,
|
|
482
|
+
commandCount: this.commandCount,
|
|
483
|
+
...(this.snapshotKind && this.state === "stopped" ? { snapshot: { kind: this.snapshotKind } } : {}),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async exec(request: SandboxExecRequest): Promise<{ exitCode: number | null }> {
|
|
488
|
+
return this.execFile({
|
|
489
|
+
file: "/bin/sh",
|
|
490
|
+
args: ["-c", request.command],
|
|
491
|
+
cwd: request.cwd,
|
|
492
|
+
env: request.env as Record<string, string> | undefined,
|
|
493
|
+
onData: request.onData,
|
|
494
|
+
signal: request.signal,
|
|
495
|
+
timeout: request.timeout,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async execFile(request: SandboxExecFileRequest): Promise<{ exitCode: number | null }> {
|
|
500
|
+
const instance = this.assertRunning();
|
|
501
|
+
if (!request.file || request.file.includes("\0")) throw new E2BSandboxError("execFile requires a non-empty file path");
|
|
502
|
+
if (!Array.isArray(request.args) || request.args.some((a) => typeof a !== "string" || a.includes("\0"))) {
|
|
503
|
+
throw new E2BSandboxError("execFile args must be a string array without NUL");
|
|
504
|
+
}
|
|
505
|
+
if (this.commandCount >= this.opts.limits.maxCommands) {
|
|
506
|
+
throw new E2BSandboxError(`sandbox exceeded maxCommands (${this.opts.limits.maxCommands})`);
|
|
507
|
+
}
|
|
508
|
+
const cwd = request.cwd ?? this.opts.workdir;
|
|
509
|
+
if (!cwd.startsWith("/") || cwd.includes("\0")) throw new E2BSandboxError("cwd must be an absolute sandbox path");
|
|
510
|
+
const extraEnv = validateEnv(request.env, this.opts.limits);
|
|
511
|
+
const release = await this.execLock.acquire(request.signal);
|
|
512
|
+
this.commandCount += 1;
|
|
513
|
+
this.touch();
|
|
514
|
+
try {
|
|
515
|
+
const timeoutMs = request.timeout !== undefined ? Math.min(request.timeout, this.remainingWallMs()) : this.remainingWallMs();
|
|
516
|
+
let outputBytes = 0;
|
|
517
|
+
const onChunk = (data: string) => {
|
|
518
|
+
const buf = Buffer.from(data);
|
|
519
|
+
outputBytes += buf.byteLength;
|
|
520
|
+
if (outputBytes > this.opts.limits.maxOutputBytes) {
|
|
521
|
+
throw new E2BSandboxError(`command output exceeded maxOutputBytes (${this.opts.limits.maxOutputBytes})`);
|
|
522
|
+
}
|
|
523
|
+
request.onData?.(buf);
|
|
524
|
+
};
|
|
525
|
+
const result = await instance.commands.run(argvCommand(request.file, request.args), {
|
|
526
|
+
cwd,
|
|
527
|
+
envs: extraEnv,
|
|
528
|
+
timeoutMs,
|
|
529
|
+
signal: request.signal,
|
|
530
|
+
onStdout: onChunk,
|
|
531
|
+
onStderr: onChunk,
|
|
532
|
+
});
|
|
533
|
+
this.touch();
|
|
534
|
+
if (isHandle(result)) {
|
|
535
|
+
const waited = await result.wait({ signal: request.signal });
|
|
536
|
+
return { exitCode: waited.exitCode };
|
|
537
|
+
}
|
|
538
|
+
return { exitCode: result.exitCode };
|
|
539
|
+
} catch (error) {
|
|
540
|
+
throw new E2BSandboxError(this.redact(error instanceof Error ? error.message : String(error)), httpStatus(error));
|
|
541
|
+
} finally {
|
|
542
|
+
release();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async startProcess(request: SandboxExecFileRequest): Promise<SandboxProcessHandle> {
|
|
547
|
+
const instance = this.assertRunning();
|
|
548
|
+
if (!request.file || request.file.includes("\0")) throw new E2BSandboxError("startProcess requires a non-empty file path");
|
|
549
|
+
const args = request.args ?? [];
|
|
550
|
+
if (!Array.isArray(args) || args.some((a) => typeof a !== "string" || a.includes("\0"))) {
|
|
551
|
+
throw new E2BSandboxError("startProcess args must be a string array without NUL");
|
|
552
|
+
}
|
|
553
|
+
if (this.commandCount >= this.opts.limits.maxCommands) {
|
|
554
|
+
throw new E2BSandboxError(`sandbox exceeded maxCommands (${this.opts.limits.maxCommands})`);
|
|
555
|
+
}
|
|
556
|
+
const cwd = request.cwd ?? this.opts.workdir;
|
|
557
|
+
if (!cwd.startsWith("/") || cwd.includes("\0")) throw new E2BSandboxError("cwd must be an absolute sandbox path");
|
|
558
|
+
const extraEnv = validateEnv(request.env, this.opts.limits);
|
|
559
|
+
const release = await this.execLock.acquire(request.signal);
|
|
560
|
+
this.commandCount += 1;
|
|
561
|
+
this.touch();
|
|
562
|
+
try {
|
|
563
|
+
const timeoutMs = request.timeout !== undefined ? Math.min(request.timeout, this.remainingWallMs()) : this.remainingWallMs();
|
|
564
|
+
let outputBytes = 0;
|
|
565
|
+
const onChunk = (data: string) => {
|
|
566
|
+
const buf = Buffer.from(data);
|
|
567
|
+
outputBytes += buf.byteLength;
|
|
568
|
+
if (outputBytes > this.opts.limits.maxOutputBytes) {
|
|
569
|
+
throw new E2BSandboxError(`command output exceeded maxOutputBytes (${this.opts.limits.maxOutputBytes})`);
|
|
570
|
+
}
|
|
571
|
+
request.onData?.(buf);
|
|
572
|
+
};
|
|
573
|
+
const started = await instance.commands.run(argvCommand(request.file, args), {
|
|
574
|
+
background: true,
|
|
575
|
+
stdin: true,
|
|
576
|
+
cwd,
|
|
577
|
+
envs: extraEnv,
|
|
578
|
+
timeoutMs,
|
|
579
|
+
signal: request.signal,
|
|
580
|
+
onStdout: onChunk,
|
|
581
|
+
onStderr: onChunk,
|
|
582
|
+
});
|
|
583
|
+
if (!isHandle(started) || typeof started.pid !== "number" || !Number.isInteger(started.pid) || started.pid < 1) {
|
|
584
|
+
throw new E2BSandboxError("sandbox startProcess did not return a pid");
|
|
585
|
+
}
|
|
586
|
+
const fingerprint = computeCommandFingerprint(request.file, args);
|
|
587
|
+
const waitPromise = started.wait();
|
|
588
|
+
const handle = new E2BProcessHandle(
|
|
589
|
+
this.id,
|
|
590
|
+
started.pid,
|
|
591
|
+
fingerprint,
|
|
592
|
+
this.opts.workdir,
|
|
593
|
+
instance.commands,
|
|
594
|
+
this.redact,
|
|
595
|
+
waitPromise,
|
|
596
|
+
);
|
|
597
|
+
this.activeProcesses.set(started.pid, handle);
|
|
598
|
+
waitPromise.then(
|
|
599
|
+
() => {
|
|
600
|
+
this.activeProcesses.delete(started.pid!);
|
|
601
|
+
this.touch();
|
|
602
|
+
},
|
|
603
|
+
() => {
|
|
604
|
+
this.activeProcesses.delete(started.pid!);
|
|
605
|
+
this.touch();
|
|
606
|
+
},
|
|
607
|
+
);
|
|
608
|
+
return handle;
|
|
609
|
+
} catch (error) {
|
|
610
|
+
throw new E2BSandboxError(this.redact(error instanceof Error ? error.message : String(error)), httpStatus(error));
|
|
611
|
+
} finally {
|
|
612
|
+
release();
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async attachProcess(ref: string): Promise<SandboxProcessHandle | null> {
|
|
617
|
+
if (this.state !== "running" || !this.instance) return null;
|
|
618
|
+
if (this.snapshotKind === "filesystem") return null;
|
|
619
|
+
const decoded = decodeE2BProcessRef(ref);
|
|
620
|
+
if (!decoded) return null;
|
|
621
|
+
if (decoded.sandboxId !== this.id) return null;
|
|
622
|
+
if (decoded.workspace !== this.opts.workdir) return null;
|
|
623
|
+
const live = this.activeProcesses.get(decoded.pid);
|
|
624
|
+
if (live && !live.isExited) {
|
|
625
|
+
if (live.commandFingerprint !== decoded.commandFingerprint) return null;
|
|
626
|
+
return live;
|
|
627
|
+
}
|
|
628
|
+
if (typeof this.instance.commands.connect !== "function") return null;
|
|
629
|
+
try {
|
|
630
|
+
const listed = this.instance.commands.list ? await this.instance.commands.list() : undefined;
|
|
631
|
+
if (listed) {
|
|
632
|
+
const info = listed.find((entry) => entry.pid === decoded.pid);
|
|
633
|
+
if (!info) return null;
|
|
634
|
+
if (info.cmd && info.args) {
|
|
635
|
+
const fp = computeCommandFingerprint(info.cmd, info.args);
|
|
636
|
+
if (fp !== decoded.commandFingerprint) return null;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const connected = await this.instance.commands.connect(decoded.pid);
|
|
640
|
+
if (!isHandle(connected)) return null;
|
|
641
|
+
const handle = new E2BProcessHandle(
|
|
642
|
+
this.id,
|
|
643
|
+
decoded.pid,
|
|
644
|
+
decoded.commandFingerprint,
|
|
645
|
+
this.opts.workdir,
|
|
646
|
+
this.instance.commands,
|
|
647
|
+
this.redact,
|
|
648
|
+
connected.wait(),
|
|
649
|
+
);
|
|
650
|
+
this.activeProcesses.set(decoded.pid, handle);
|
|
651
|
+
return handle;
|
|
652
|
+
} catch {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async pause(options?: { keepMemory?: boolean; signal?: AbortSignal }): Promise<SandboxPauseResult> {
|
|
658
|
+
assertNotAborted(options?.signal);
|
|
659
|
+
if (this.state === "removed" || this.state === "failed") throw new E2BSandboxError("sandbox is not available");
|
|
660
|
+
const keepMemory = options?.keepMemory !== false;
|
|
661
|
+
const kind: SandboxSnapshotKind = keepMemory ? "memory" : "filesystem";
|
|
662
|
+
if (this.state === "stopped") return { kind: this.snapshotKind ?? kind, state: "paused" };
|
|
663
|
+
const instance = this.instance;
|
|
664
|
+
try {
|
|
665
|
+
if (instance?.pause) await instance.pause({ keepMemory, signal: options?.signal });
|
|
666
|
+
else if (this.api.pause) await this.api.pause(this.id, { keepMemory, signal: options?.signal, ...edgeOpts(this.opts.apiKey) });
|
|
667
|
+
else throw new E2BSandboxError("sandbox pause is unsupported");
|
|
668
|
+
} catch (error) {
|
|
669
|
+
if (isBusyPause(error)) {
|
|
670
|
+
throw new E2BSandboxError("pause refused (503); sandbox still running", 503);
|
|
671
|
+
}
|
|
672
|
+
throw new E2BSandboxError(this.redact(error instanceof Error ? error.message : String(error)), httpStatus(error));
|
|
673
|
+
}
|
|
674
|
+
this.markPaused(kind);
|
|
675
|
+
this.touch();
|
|
676
|
+
return { kind, state: "paused" };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async resume(options?: { signal?: AbortSignal }): Promise<void> {
|
|
680
|
+
assertNotAborted(options?.signal);
|
|
681
|
+
if (this.state === "running" && this.instance) return;
|
|
682
|
+
if (this.state === "removed" || this.state === "failed") throw new E2BSandboxError("sandbox is not available");
|
|
683
|
+
try {
|
|
684
|
+
this.instance = await this.api.connect(this.id, { ...edgeOpts(this.opts.apiKey), signal: options?.signal });
|
|
685
|
+
} catch (error) {
|
|
686
|
+
const status = httpStatus(error);
|
|
687
|
+
if (status === 404) {
|
|
688
|
+
this.state = "removed";
|
|
689
|
+
throw new E2BSandboxError("sandbox not found", 404);
|
|
690
|
+
}
|
|
691
|
+
throw new E2BSandboxError(this.redact(error instanceof Error ? error.message : String(error)), status);
|
|
692
|
+
}
|
|
693
|
+
this.state = "running";
|
|
694
|
+
if (this.snapshotKind === "filesystem") this.activeProcesses.clear();
|
|
695
|
+
this.snapshotKind = undefined;
|
|
696
|
+
this.touch();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
async stop(options?: { graceMs?: number; signal?: AbortSignal }): Promise<void> {
|
|
700
|
+
void options?.graceMs;
|
|
701
|
+
await this.pause({ keepMemory: true, signal: options?.signal });
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async kill(options?: { signal?: AbortSignal }): Promise<void> {
|
|
705
|
+
assertNotAborted(options?.signal);
|
|
706
|
+
try {
|
|
707
|
+
if (this.instance?.kill) await this.instance.kill({ signal: options?.signal });
|
|
708
|
+
else if (this.api.kill) await this.api.kill(this.id, { ...edgeOpts(this.opts.apiKey), signal: options?.signal });
|
|
709
|
+
} catch {
|
|
710
|
+
// best effort
|
|
711
|
+
}
|
|
712
|
+
for (const handle of this.activeProcesses.values()) handle.markExited(null);
|
|
713
|
+
this.activeProcesses.clear();
|
|
714
|
+
this.instance = undefined;
|
|
715
|
+
this.state = "removed";
|
|
716
|
+
this.snapshotKind = undefined;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async close(options?: SandboxCloseOptions): Promise<SandboxExportMetadata | undefined> {
|
|
720
|
+
if (options?.export) throw new E2BSandboxError("E2B close({ export }) is unsupported; pause() is the snapshot");
|
|
721
|
+
await this.kill({ signal: options?.signal });
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
async function mkdirWorkspace(instance: E2BSandboxInstance, workdir: string, signal?: AbortSignal): Promise<void> {
|
|
727
|
+
await instance.commands.run(`mkdir -p ${posixQuote(workdir)}`, { timeoutMs: 15_000, signal });
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export async function createE2BSandbox(options: CreateE2BSandboxOptions): Promise<DisposableSandbox> {
|
|
731
|
+
if (!options.client && !options.apiKey) {
|
|
732
|
+
throw new E2BSandboxError("createE2BSandbox requires apiKey or client");
|
|
733
|
+
}
|
|
734
|
+
const limits = resolveDockerSandboxLimits(options.limits);
|
|
735
|
+
const workdir = validateWorkdir(options.workdir);
|
|
736
|
+
const env = validateEnv(options.env, limits);
|
|
737
|
+
const labels = validateLabels(options.labels);
|
|
738
|
+
const template = options.template ?? DEFAULT_TEMPLATE;
|
|
739
|
+
if (!template || template.includes("\0") || /\s/.test(template)) throw new E2BSandboxError("invalid template");
|
|
740
|
+
const timeoutMs = options.timeoutMs ?? limits.wallTimeMs;
|
|
741
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) throw new E2BSandboxError("timeoutMs must be a positive integer");
|
|
742
|
+
const onTimeout = options.onTimeout ?? "kill";
|
|
743
|
+
const api = await loadSandboxApi(options.client);
|
|
744
|
+
const metadata = { "prism.sandbox": "1", ...labels };
|
|
745
|
+
const createOpts: E2BCreateOpts = {
|
|
746
|
+
...edgeOpts(options.apiKey),
|
|
747
|
+
timeoutMs,
|
|
748
|
+
...(Object.keys(env).length ? { envs: env } : {}),
|
|
749
|
+
metadata,
|
|
750
|
+
lifecycle: { onTimeout, autoResume: false },
|
|
751
|
+
};
|
|
752
|
+
let instance: E2BSandboxInstance;
|
|
753
|
+
try {
|
|
754
|
+
instance = template === DEFAULT_TEMPLATE ? await api.create(createOpts) : await api.create(template, createOpts);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
throw new E2BSandboxError(
|
|
757
|
+
createSecretRedactor([options.apiKey ?? "", ...(options.secrets ?? [])])(error instanceof Error ? error.message : String(error)),
|
|
758
|
+
httpStatus(error),
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
if (!instance?.sandboxId) throw new E2BSandboxError("e2b create returned no sandboxId");
|
|
762
|
+
try {
|
|
763
|
+
await mkdirWorkspace(instance, workdir);
|
|
764
|
+
} catch (error) {
|
|
765
|
+
try {
|
|
766
|
+
await instance.kill?.();
|
|
767
|
+
} catch {
|
|
768
|
+
// ignore
|
|
769
|
+
}
|
|
770
|
+
throw new E2BSandboxError(
|
|
771
|
+
createSecretRedactor([options.apiKey ?? "", ...(options.secrets ?? [])])(error instanceof Error ? error.message : String(error)),
|
|
772
|
+
httpStatus(error),
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
return new E2BSandboxSession(instance, api, {
|
|
776
|
+
sandboxId: instance.sandboxId,
|
|
777
|
+
template,
|
|
778
|
+
workdir,
|
|
779
|
+
apiKey: options.apiKey,
|
|
780
|
+
limits,
|
|
781
|
+
timeoutMs,
|
|
782
|
+
labels,
|
|
783
|
+
capabilities: resolveE2BCapabilities(options.capabilities),
|
|
784
|
+
secrets: options.secrets ?? [],
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
export async function connectE2BSandbox(options: ConnectE2BSandboxOptions): Promise<DisposableSandbox> {
|
|
789
|
+
if (!options.sandboxId || options.sandboxId.includes("\0")) throw new E2BSandboxError("sandboxId required");
|
|
790
|
+
if (!options.client && !options.apiKey) throw new E2BSandboxError("connectE2BSandbox requires apiKey or client");
|
|
791
|
+
const limits = resolveDockerSandboxLimits(options.limits);
|
|
792
|
+
const workdir = validateWorkdir(options.workdir);
|
|
793
|
+
const expected = validateLabels(options.expectedLabels);
|
|
794
|
+
const api = await loadSandboxApi(options.client);
|
|
795
|
+
if (typeof api.getInfo !== "function") throw new E2BSandboxError("e2b Sandbox.getInfo is required to reconnect without auto-resume");
|
|
796
|
+
let info: E2BSandboxInfo;
|
|
797
|
+
try {
|
|
798
|
+
info = await api.getInfo(options.sandboxId, edgeOpts(options.apiKey));
|
|
799
|
+
} catch (error) {
|
|
800
|
+
const status = httpStatus(error);
|
|
801
|
+
throw new E2BSandboxError(status === 404 ? "sandbox not found" : error instanceof Error ? error.message : String(error), status);
|
|
802
|
+
}
|
|
803
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
804
|
+
if (info.metadata?.[key] !== value) throw new E2BSandboxError("sandbox label mismatch");
|
|
805
|
+
}
|
|
806
|
+
const state = (info.state ?? "").toLowerCase();
|
|
807
|
+
if (state === "killed") throw new E2BSandboxError("sandbox not found", 404);
|
|
808
|
+
if (!state) throw new E2BSandboxError("sandbox state unknown; pass resume: true only for an explicit resume");
|
|
809
|
+
const session = new E2BSandboxSession(undefined, api, {
|
|
810
|
+
sandboxId: options.sandboxId,
|
|
811
|
+
template: options.template ?? info.templateId ?? DEFAULT_TEMPLATE,
|
|
812
|
+
workdir,
|
|
813
|
+
apiKey: options.apiKey,
|
|
814
|
+
limits,
|
|
815
|
+
timeoutMs: options.timeoutMs ?? limits.wallTimeMs,
|
|
816
|
+
labels: info.metadata ? { ...info.metadata } : expected,
|
|
817
|
+
capabilities: resolveE2BCapabilities(options.capabilities),
|
|
818
|
+
secrets: options.secrets ?? [],
|
|
819
|
+
});
|
|
820
|
+
if (state === "paused") {
|
|
821
|
+
if (options.resume) await session.resume();
|
|
822
|
+
else session.markPaused("memory");
|
|
823
|
+
return session;
|
|
824
|
+
}
|
|
825
|
+
if (options.resume === false && state !== "running") {
|
|
826
|
+
session.markPaused("memory");
|
|
827
|
+
return session;
|
|
828
|
+
}
|
|
829
|
+
await session.resume();
|
|
830
|
+
return session;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
export function createE2BProcessRecoveryBackend(
|
|
834
|
+
sandbox: DisposableSandbox,
|
|
835
|
+
options?: E2BProcessRecoveryBackendOptions,
|
|
836
|
+
): ProcessRecoveryBackend {
|
|
837
|
+
return {
|
|
838
|
+
async attach(ref: string): Promise<ProcessSandboxHandle | null> {
|
|
839
|
+
if (typeof sandbox.attachProcess !== "function") return null;
|
|
840
|
+
const decoded = decodeE2BProcessRef(ref);
|
|
841
|
+
if (!decoded) return null;
|
|
842
|
+
if (options?.expectedSandboxId && decoded.sandboxId !== options.expectedSandboxId) return null;
|
|
843
|
+
if (options?.expectedWorkspace && decoded.workspace !== options.expectedWorkspace) return null;
|
|
844
|
+
if (options?.expectedLabels && sandbox.labels) {
|
|
845
|
+
for (const [key, value] of Object.entries(options.expectedLabels)) {
|
|
846
|
+
if (sandbox.labels[key] !== value) return null;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const handle = await sandbox.attachProcess(ref);
|
|
850
|
+
return (handle as unknown as ProcessSandboxHandle) ?? null;
|
|
851
|
+
},
|
|
852
|
+
};
|
|
853
|
+
}
|