Skip to content

feat: initial implementation - #1

Merged
steven-passynkov merged 12 commits into
leap0-dev:mainfrom
ipassynk:feat/initial
Apr 11, 2026
Merged

steven-passynkov merged 12 commits into
leap0-dev:mainfrom
ipassynk:feat/initial

Conversation

@ipassynk

@ipassynk ipassynk commented Apr 10, 2026 •

Copy link
Copy Markdown
Collaborator

initial implementation

Summary by CodeRabbit

  • New Features

    • Released a Leap0 sandbox backend package for Deep Agents with sandbox provisioning, command execution, file transfer, and lifecycle management.
  • Documentation

    • Added comprehensive README with install steps, env vars, usage patterns, examples, and dev/test guidance.
  • Examples

    • Included runnable example scripts demonstrating basic sandbox usage and Deep Agent integration.
  • Tests

    • Added unit and integration test suites validating lifecycle, execution, and filesystem behaviors.
  • Chores

    • Added project manifest, TypeScript/build/test configs, and a comprehensive .gitignore.

@coderabbitai

coderabbitai Bot commented Apr 10, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8eddf03-403a-4a11-a84d-a09bab705fa0

📥 Commits

Reviewing files that changed from the base of the PR and between b668bf9 and 1bf783c.

📒 Files selected for processing (2)
  • src/sandbox.test.ts
  • src/sandbox.ts
✅ Files skipped from review due to trivial changes (2)
  • src/sandbox.test.ts
  • src/sandbox.ts

📝 Walkthrough

Walkthrough

Adds the @leap0/langchain-leap0 package: a Leap0-backed LangChain Deep Agents sandbox backend with implementation, types, tests, examples, README, and build/test tooling to run commands and transfer files inside Leap0 sandboxes.

Changes

Cohort / File(s) Summary
Project config & tooling
​.gitignore, package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts
Add repository ignore patterns; new package manifest and export map; TypeScript config; dual-target build config; and Vitest configuration including an integration-test mode.
Documentation
README.md
Add README describing install, usage (connected and one-step create flows), examples, env vars, dev/test commands, and license.
Core API & types
src/index.ts, src/types.ts
Introduce public exports and types including Leap0SandboxOptions, Leap0SandboxErrorCode, and Leap0SandboxError.
Sandbox implementation
src/sandbox.ts
New Leap0Sandbox implementing lifecycle (fromConnected, create, fromId, initialize, close), command execution (execute), file ops (uploadFiles, downloadFiles), error mapping, and factory helpers.
Tests
src/sandbox.test.ts, src/sandbox.int.test.ts
Add unit tests with mocked leap0 client and full integration tests (skippable without LEAP0_API_KEY).
Examples
examples/basic_sandbox.ts, examples/deep_agent_sandbox.ts
Add runnable examples: basic sandbox create/execute/cleanup and Deep Agents integration using OpenAI chat models.
Misc
.gitignore
Add a comprehensive Node/JS and framework-specific ignore list (144 lines).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev as Developer
  participant Client as Leap0Client
  participant Sandbox as Leap0 Sandbox
  participant Backend as Leap0Sandbox
  participant Agent as Deep Agent
  participant FS as Filesystem

  Dev->>Client: construct (LEAP0_API_KEY)
  Dev->>Client: create sandbox
  Client-->>Sandbox: sandbox handle
  Dev->>Backend: Leap0Sandbox.fromConnected(client, sandbox)
  Dev->>Backend: initialize(initialFiles?)
  Backend->>FS: create dirs + writeBytes (uploadFiles)
  Backend-->>Dev: initialized (isRunning, id)
  Dev->>Agent: create/use agent with Backend
  Agent->>Backend: execute(command)
  Backend->>Sandbox: box.process.execute(command, timeout)
  Sandbox-->>Backend: stdout/stderr, exitCode
  Backend-->>Agent: ExecuteResponse(output, exitCode)
  Dev->>Backend: downloadFiles(path)
  Backend->>FS: readBytes(path)
  Backend-->>Dev: content or file_not_found
  Dev->>Backend: close()
  Backend->>Sandbox: delete()
  Backend->>Client: client.close() (if owned)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped a sandbox, soft and neat,

Commands I fed and files to eat,
Agents chatted, outputs bright,
I penned a log, then cleaned at night,
A tiny hop — deployment complete.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and generic, using the non-descriptive term 'initial implementation' without conveying what is actually being implemented. Replace with a more descriptive title that identifies the specific component or feature, such as 'feat: add Leap0 sandbox backend for LangChain' or 'feat: add @leap0/langchain-leap0 package'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
src/sandbox.test.ts (1)

34-37: Reset mock implementations between tests.

vi.clearAllMocks() only clears history. Stubs like mockSandbox.filesystem.mkdir.mockResolvedValue(...) survive into later cases, so this suite can become order-dependent as it grows. Recreate mockSandbox in beforeEach or use vi.resetAllMocks() plus explicit re-stubbing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sandbox.test.ts` around lines 34 - 37, The tests currently call
vi.clearAllMocks() which only clears call history and lets stub implementations
(e.g., mockSandbox.filesystem.mkdir) persist; change the setup in beforeEach to
either recreate mockSandbox (re-initialize mockSandbox and reattach its nested
mocks) before calling mockCreate.mockResolvedValue(mockSandbox) or switch to
vi.resetAllMocks() and then explicitly re-stub the needed methods (e.g.,
mockSandbox.filesystem.mkdir.mockResolvedValue(...)) so each test gets a fresh
set of mock implementations; update the beforeEach that references mockCreate
and mockSandbox to perform one of these two approaches.
src/sandbox.int.test.ts (1)

27-31: Use a much shorter sandbox TTL for integration runs.

timeoutMin: 60 is far longer than the 3-minute test budget. Combined with retries and the best-effort cleanup below, a flaky CI run can leave chargeable sandboxes alive for up to an hour. Prefer a low test default or make it env-configurable.

Suggested change
 function createParams() {
   return {
     templateName: templateName(),
-    timeoutMin: 60,
+    timeoutMin: Number(process.env.LEAP0_TEST_TIMEOUT_MIN ?? 10),
   } as const;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sandbox.int.test.ts` around lines 27 - 31, The createParams function
currently sets timeoutMin: 60 which is too long for CI; change createParams to
use a much smaller default TTL (e.g., 3 minutes) or read the TTL from an
environment variable (e.g., SANDBOX_TTL_MIN) so tests can override it; update
the code that constructs the params in createParams (and any callers expecting a
const shape, referencing templateName()) to parse and validate the env value
(fall back to the short default like 3) to avoid long-lived chargeable sandboxes
during flaky CI runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/basic_sandbox.ts`:
- Around line 45-47: The current promise handling for main() silently ignores
rejections; update the invocation of main() (the void main() call) to explicitly
handle rejected promises by attaching a .catch handler (or use an async IIFE
with try/catch) that logs the error (including stack/message) and sets
process.exitCode to a non-zero value before exiting; ensure you reference the
main function and process.exitCode in the fix so all rejections produce
deterministic logging and exit behavior.

In `@examples/deep_agent_sandbox.ts`:
- Around line 85-87: The top-level promise from main() is not handling
rejections; update the invocation of main() so it attaches a .catch handler that
logs the error (e.g., console.error or processLogger.error) and sets
process.exitCode to a non-zero value (e.g., 1) for deterministic failure
handling—i.e., replace or augment the current void main().then(...) call with a
terminal .catch that references the same main() invocation and ensures errors
are logged and process.exitCode is set.

In `@README.md`:
- Around line 40-43: The final cleanup block should not skip client.close() if
sandbox.delete() throws; replace the single finally with nested try/finally so
sandbox.delete() is attempted first and client.close() is always executed
regardless of delete errors — update the snippet that currently calls
sandbox.delete() and client.close() in the same finally to use a try { await
sandbox.delete(); } finally { await client.close(); } (referencing
sandbox.delete() and client.close() to locate the code).

In `@src/sandbox.ts`:
- Around line 243-245: The timeout error currently includes the raw command
string; update the throw of Leap0SandboxError so it does not emit the full
command variable — instead provide a non-sensitive identifier (e.g., "command
timed out", an obscured/truncated summary, or a stable hash/fingerprint of
command) while keeping the error code "COMMAND_TIMEOUT"; locate the throw that
constructs Leap0SandboxError with the message `Command timed out: ${command}`
and replace the message portion with a sanitized/hashed identifier (or a static
placeholder plus metadata) to avoid leaking secrets.
- Around line 179-205: When initialization fails after sandboxes.create (e.g.,
`#uploadInitialFiles` throws), ensure you roll back the created sandbox before
rethrowing: in the catch block (where you currently check error instanceof
Leap0SandboxError and perform client-close/cleanup), first check if
this.#sandbox is set and attempt to delete the remote sandbox via the client API
(e.g., call this.#client.sandboxes.delete(this.#sandbox.id or
this.#sandbox.delete()) inside a try/catch), then clear this.#sandbox, and only
afterwards close/clear this.#client and this.#ownsClient and rethrow the
original error (or wrap as Leap0SandboxError) so created resources are not left
running.
- Around line 143-153: The catch in fromId() is converting any error from
sandboxes.get() into a SANDBOX_NOT_FOUND Leap0SandboxError, which hides
auth/network/5xx errors; change the error handling to detect a true "not found"
(e.g., check error.status === 404 or the SDK's NotFound error type) and only
throw Leap0SandboxError with "SANDBOX_NOT_FOUND" for that case, otherwise
preserve and rethrow the original error (or wrap it without changing its
type/code); also move/ensure client.close() runs in a finally block so the
client is always closed regardless of error type.

In `@tsdown.config.ts`:
- Around line 10-20: The second build target object (the config with entry:
["./src/index.ts"], format: ["cjs"], dts: true) currently has clean: true;
change that property to clean: false so the shared outDir cleanup only runs once
up front and the secondary target does not remove artifacts from the primary
target, avoiding race conditions when multiple configs write to the same outDir.

In `@vitest.config.ts`:
- Line 5: The config currently calls dotenv.config({ path:
path.resolve(__dirname, ".env") }) but __dirname is undefined in ESM; replace
use of __dirname by deriving the directory from import.meta.url (e.g., use
fileURLToPath(import.meta.url) and path.dirname on that) and then pass
path.resolve(<derivedDir>, ".env") to dotenv.config; update any imports to
ensure fileURLToPath is imported from "url" and keep the dotenv.config call
using the new resolved path (refer to dotenv.config, path.resolve, __dirname,
import.meta.url, and fileURLToPath to locate the change).

---

Nitpick comments:
In `@src/sandbox.int.test.ts`:
- Around line 27-31: The createParams function currently sets timeoutMin: 60
which is too long for CI; change createParams to use a much smaller default TTL
(e.g., 3 minutes) or read the TTL from an environment variable (e.g.,
SANDBOX_TTL_MIN) so tests can override it; update the code that constructs the
params in createParams (and any callers expecting a const shape, referencing
templateName()) to parse and validate the env value (fall back to the short
default like 3) to avoid long-lived chargeable sandboxes during flaky CI runs.

In `@src/sandbox.test.ts`:
- Around line 34-37: The tests currently call vi.clearAllMocks() which only
clears call history and lets stub implementations (e.g.,
mockSandbox.filesystem.mkdir) persist; change the setup in beforeEach to either
recreate mockSandbox (re-initialize mockSandbox and reattach its nested mocks)
before calling mockCreate.mockResolvedValue(mockSandbox) or switch to
vi.resetAllMocks() and then explicitly re-stub the needed methods (e.g.,
mockSandbox.filesystem.mkdir.mockResolvedValue(...)) so each test gets a fresh
set of mock implementations; update the beforeEach that references mockCreate
and mockSandbox to perform one of these two approaches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e600040a-b720-4f7e-b377-629b8b63d8ae

📥 Commits

Reviewing files that changed from the base of the PR and between b79e1ad and f2852cd.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • .gitignore
  • README.md
  • examples/basic_sandbox.ts
  • examples/deep_agent_sandbox.ts
  • package.json
  • src/index.ts
  • src/sandbox.int.test.ts
  • src/sandbox.test.ts
  • src/sandbox.ts
  • src/types.ts
  • tsconfig.json
  • tsdown.config.ts
  • vitest.config.ts

Comment thread examples/basic_sandbox.ts Resolved
Comment thread examples/deep_agent_sandbox.ts Resolved
Comment thread README.md Resolved
Comment thread src/sandbox.ts Resolved
Comment thread src/sandbox.ts Resolved
Comment thread src/sandbox.ts Resolved
Comment thread tsdown.config.ts Resolved
Comment thread vitest.config.ts Resolved
ipassynk and others added 4 commits April 10, 2026 19:07
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/sandbox.ts (1)

335-340: Consider distinguishing truly unknown errors.

The fallback to "permission_denied" for non-Leap0Error exceptions could mask unexpected failure modes. If the FileOperationError type supports it, an "unknown_error" code would provide better diagnostics.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sandbox.ts` around lines 335 - 340, mapFilesystemError currently returns
"permission_denied" for any non-Leap0Error which can mask unexpected failures;
update static mapFilesystemError(error: unknown) to return a distinct fallback
like "unknown_error" (or another appropriate FileOperationError variant) instead
of "permission_denied", keeping the existing special-case call to
Leap0Sandbox.mapFilesystemApiError(error) for Leap0Error instances to preserve
existing mapping logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sandbox.ts`:
- Around line 241-258: The catch block contains a duplicated nested `if (error
instanceof Leap0TimeoutError)` and stray tokens (`error,` and `);`) causing a
syntax error; fix it by removing the inner duplicate check and the orphaned
tokens and replace with a single timeout branch that throws `new
Leap0SandboxError("Command timed out", "COMMAND_TIMEOUT", error)` when `error
instanceof Leap0TimeoutError`, otherwise throw `new Leap0SandboxError(\`Command
execution failed: ${...}\`, "COMMAND_FAILED", error instanceof Error ? error :
undefined)`; look for these symbols (`Leap0TimeoutError`, `Leap0SandboxError`,
the catch block around the command execution) to apply the change.

---

Nitpick comments:
In `@src/sandbox.ts`:
- Around line 335-340: mapFilesystemError currently returns "permission_denied"
for any non-Leap0Error which can mask unexpected failures; update static
mapFilesystemError(error: unknown) to return a distinct fallback like
"unknown_error" (or another appropriate FileOperationError variant) instead of
"permission_denied", keeping the existing special-case call to
Leap0Sandbox.mapFilesystemApiError(error) for Leap0Error instances to preserve
existing mapping logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb17b37b-b978-4e7b-acca-c54820ddbf32

📥 Commits

Reviewing files that changed from the base of the PR and between f2852cd and 923f423.

📒 Files selected for processing (4)
  • README.md
  • examples/basic_sandbox.ts
  • src/sandbox.ts
  • vitest.config.ts
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/basic_sandbox.ts
  • vitest.config.ts

Comment thread src/sandbox.ts Resolved
ipassynk and others added 5 commits April 10, 2026 19:14
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sandbox.ts`:
- Around line 100-110: fromConnected() and fromId() attach to an existing
sandbox but the close() path always deletes the sandbox (line ~359), risking
removal of sandboxes this instance didn't create; add an ownership flag (e.g.,
this.ownsSandbox) on the Leap0Sandbox class set to true only when the instance
actually creates the sandbox (constructor path that creates new sandboxes) and
set to false in factory attachers (fromConnected, fromId), then update close()
to call the deletion logic only if this.ownsSandbox is true; ensure constructor,
fromConnected, fromId, and close() are the referenced symbols updated so
attached-but-not-created sandboxes are not deleted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eca0734a-47dc-4310-8d12-af5d58e8b46b

📥 Commits

Reviewing files that changed from the base of the PR and between 923f423 and b668bf9.

📒 Files selected for processing (3)
  • src/sandbox.ts
  • tsdown.config.ts
  • vitest.config.ts
✅ Files skipped from review due to trivial changes (1)
  • tsdown.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • vitest.config.ts

Comment thread src/sandbox.ts Resolved
@steven-passynkov
steven-passynkov merged commit 105d513 into leap0-dev:main Apr 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants