feat(sandbox-blaxel): add Blaxel sandbox provider#1275
Conversation
Ships @voltagent/sandbox-blaxel implementing WorkspaceSandbox over @blaxel/core. Uses the SDK-native pattern (process.exec → process.wait → process.logs/get) with native env/streaming/timeout support — no manual polling, no shell-prefix env hacks. Public API: - BlaxelSandbox class (execute, getSandbox, destroy, getInfo) - BlaxelSandboxOptions with apiKey/workspace + a config bag extending SandboxCreateConfiguration with cwd, env, defaultTimeoutMs, maxOutputBytes, pollIntervalMs - BlaxelSandboxConfig, BlaxelSandboxInstance type re-exports - SandboxCreateConfiguration re-export from @blaxel/core for full SDK type access without a separate import Tests cover 53 cases at 100% coverage on lines/branches/funcs/stmts. Docs updated in website/docs/workspaces/sandbox.md with import, getSandbox, and tenant-router examples.
…nfig Cleanup pass on top of the initial provider commit: - Extract option parsing into shell.ts. parseOptions returns the attempt-tuple shape and absorbs the stdin/empty-command validation. Shell-escape and env helpers are now private to shell.ts. - Pull bracket helpers into utils.ts (toError, truncateOutput, withEventListener). withAbort lives at the bottom of sandbox.ts as a thin wrapper over withEventListener. - Fold per-call sandbox/process plumbing into private methods on the class (killProcess, runProcess, fetchProcessOutput). Each helper resolves the sandbox via this.resolveSandbox() rather than threading it through every signature. - Class layout: fields -> constructor -> public methods -> private methods. - Replace single-file index.spec.ts with sandbox.spec.ts / shell.spec.ts / utils.spec.ts. 67 tests, 100% line/branch/func/stmt coverage. New test:coverage script in package.json. - Modernize tsconfig.json for Node 22+: target/lib ES2023, module Preserve, moduleResolution Bundler, verbatimModuleSyntax. tsup target bumped to es2023 to match. - Drop output.ts (truncateOutput moved into utils.ts). - Re-export NormalizedCommand from @voltagent/core so the wrapper has a type for the parser result.
🦋 Changeset detectedLatest commit: 320cf75 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughAdds ChangesBlaxel Sandbox Provider Implementation
Core Workspace Type Exports
Documentation & Changeset
Sequence Diagram(s)sequenceDiagram
participant Client
participant WorkspaceSandbox as BlaxelSandbox
participant BlaxelSDK as `@blaxel/core`
Client->>WorkspaceSandbox: execute(options)
WorkspaceSandbox->>WorkspaceSandbox: parseOptions()
WorkspaceSandbox->>BlaxelSDK: createIfNotExists(config) (lazy)
BlaxelSDK-->>WorkspaceSandbox: sandboxInstance
WorkspaceSandbox->>BlaxelSDK: exec(processName, command)
WorkspaceSandbox->>BlaxelSDK: wait(processName, maxWait)
BlaxelSDK-->>WorkspaceSandbox: finished / timeout
WorkspaceSandbox->>BlaxelSDK: get/logs(processName)
WorkspaceSandbox-->>Client: WorkspaceSandboxResult
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ions - Add a plain-English "what is a sandbox" intro and explain how agents use the execute_command tool. - Reorganize the remote providers section per-provider (Blaxel, Daytona, E2B) with a uniform layout: blurb, install, configure, getSandbox example, multi-tenant routing. - Add an "Available providers" table at the top with links to upstream docs. - Add a small italic note under each install snippet calling out that the upstream SDK ships as a regular dependency. - Convert the "Experimental" blockquote at the top of every workspace doc to the site's :::warning admonition style. - Rewrite the sandbox-blaxel changeset to be consumer-focused with a code example.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
packages/sandbox-blaxel/src/sandbox.ts (1)
128-147: ⚖️ Poor tradeoff
destroy()clears the cache before awaiting deletion — concurrentexecute()can re-provision mid-tear-down.
this.sandbox = undefinedhappens synchronously beforecurrent.delete()resolves. A concurrentexecute()(orgetSandbox()) arriving in that window triggersresolveSandbox()→createSandbox()→SandboxInstance.createIfNotExists(...), which (depending on the SDK's naming/idempotency) may either resurrect the same sandbox (racing with delete) or spin up a new one that's never reaped. Consider either (a) keeping the cache populated untildelete()settles, or (b) tracking adestroyingstate so concurrent calls reject with a clear error.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/sandbox-blaxel/src/sandbox.ts` around lines 128 - 147, The destroy() method clears this.sandbox before awaiting deletion, allowing concurrent execute() / getSandbox() → resolveSandbox() → createSandbox() → SandboxInstance.createIfNotExists to re-provision mid-teardown; fix by not nulling this.sandbox synchronously — either (A) keep this.sandbox until current.delete() finishes (set this.sandbox = pending only after delete resolves) or (B) add a destroying flag (e.g., this.destroying) that destroy() sets immediately and check it in getSandbox()/resolveSandbox()/createSandbox()/execute() to reject or wait, and ensure you still call current.delete() and clear state after deletion; update references to destroy(), execute(), getSandbox(), resolveSandbox(), createSandbox(), SandboxInstance.createIfNotExists, this.sandbox, and current.delete() accordingly.packages/sandbox-blaxel/src/sandbox.spec.ts (1)
65-65: ⚡ Quick winReduce
as unknown aschains to preserve test-time type guarantees.Several double-casts bypass static checks. For deliberate invalid-input tests, prefer
//@ts-expect-error`` at the callsite; for mocks, use narrowed helper types (Partial<...>/ explicit interfaces) instead of `unknown` bridges.💡 Example pattern
- await sandbox.execute({ - command: "ls", - env: { KEEP: "yes", DROP: undefined, NULLISH: null } as unknown as Record<string, string>, - }); + await sandbox.execute({ + command: "ls", + // `@ts-expect-error` intentional invalid values to test runtime env normalization + env: { KEEP: "yes", DROP: undefined, NULLISH: null }, + }); - await expect( - sandbox.execute({} as unknown as Parameters<BlaxelSandbox["execute"]>[0]), - ).rejects.toThrow("Sandbox command is required"); + await expect( + // `@ts-expect-error` intentional missing command to test runtime validation + sandbox.execute({}), + ).rejects.toThrow("Sandbox command is required");As per coding guidelines,
**/*.ts: Maintain type safety in TypeScript-first codebase.Also applies to: 123-123, 131-131, 167-167, 223-223, 369-369, 472-472, 502-502, 579-579, 650-650, 675-675
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/sandbox-blaxel/src/sandbox.spec.ts` at line 65, The test creates instance: null as unknown as BlaxelSandboxInstance which double-casts away type safety; replace that pattern by declaring the variable with a narrowed mock-friendly type (e.g., let instance: Partial<BlaxelSandboxInstance> | null = null) and update usages to either cast at the callsite or use // `@ts-expect-error` for deliberate invalid-input tests; locate the variable named instance in sandbox.spec.ts and change its declaration to a Partial or explicit test interface for BlaxelSandboxInstance, then adjust any code that assumes full instance shape to access mocked fields or perform targeted casts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/sandbox-blaxel/src/sandbox.spec.ts`:
- Around line 134-142: Save the original environment values before tests and
restore them after each test to avoid cross-test leakage: capture
process.env.BL_API_KEY, process.env.BL_WORKSPACE, and process.env.BL_REGION into
variables (e.g., originalBlApiKey, originalBlWorkspace, originalBlRegion) before
the test that runs the current beforeEach, keep the existing vi.restoreAllMocks
call, and then in an afterEach restore each env var by setting
process.env.BL_API_KEY = originalBlApiKey (or deleting process.env.BL_API_KEY if
the original was undefined), doing the same for BL_WORKSPACE and BL_REGION; also
apply the same backup/restore pattern for the similar block referenced at lines
144-146.
In `@packages/sandbox-blaxel/src/sandbox.ts`:
- Around line 59-69: Constructor currently calls applyEnvBindings which mutates
process.env causing cross-instance clobbering; update the BlaxelSandbox
constructor to stop writing BL_API_KEY/BL_WORKSPACE into process.env by default
(e.g., remove or gate the applyEnvBindings call and store apiKey/workspace on
the instance config instead), or add an explicit opt-in flag on
BlaxelSandboxOptions to enable global env binding and document that it is
process-global; also add a clear docstring to BlaxelSandboxOptions noting that
enabling env binding mutates process.env and will affect other instances.
- Around line 216-227: The code currently treats any rejection from
sandbox.process.wait as a timeout; change the error handling in the attemptAsync
block around sandbox.process.wait (used in this function) to distinguish a real
timeout from other failures by checking the waitError message/class (match the
SDK timeout text, e.g. "did not finish in time" or the SDK-specific error class)
and only set timedOut = true and call this.killProcess({ processName }) when it
is a timeout; for non-timeout errors re-throw the waitError (or return it in the
result) so callers can see the actual failure instead of an unconditional
timedOut: true.
In `@packages/sandbox-blaxel/src/utils.spec.ts`:
- Around line 57-63: The test for truncateOutput currently only asserts
byteLength ≤ 4 which allows invalid/truncated UTF-8 sequences; update the spec
to assert that the returned content is valid UTF-8 and preserves whole
codepoints (e.g. for input "éééé" expect content to equal "éé" and truncated ===
true), or alternatively validate by round-tripping: Buffer.from(content,
"utf8").toString("utf8") === content and content is a prefix of the original
string by codepoints; target the truncateOutput function and the test case to
ensure no partial codepoints are returned.
In `@website/docs/workspaces/sandbox.md`:
- Around line 178-214: The TenantBlaxelSandboxRouter example currently never
disposes per-tenant sandboxes; add a destroy method on TenantBlaxelSandboxRouter
that iterates this.sandboxes, calls destroy() on each BlaxelSandbox instance
(from getSandboxForTenant), awaits/handles promises as needed, and then clears
the map, ensuring the class implements the WorkspaceSandbox destroy signature;
apply the same pattern to the Daytona/E2B router examples so copy-pasting
doesn't leak sandboxes.
---
Nitpick comments:
In `@packages/sandbox-blaxel/src/sandbox.spec.ts`:
- Line 65: The test creates instance: null as unknown as BlaxelSandboxInstance
which double-casts away type safety; replace that pattern by declaring the
variable with a narrowed mock-friendly type (e.g., let instance:
Partial<BlaxelSandboxInstance> | null = null) and update usages to either cast
at the callsite or use // `@ts-expect-error` for deliberate invalid-input tests;
locate the variable named instance in sandbox.spec.ts and change its declaration
to a Partial or explicit test interface for BlaxelSandboxInstance, then adjust
any code that assumes full instance shape to access mocked fields or perform
targeted casts.
In `@packages/sandbox-blaxel/src/sandbox.ts`:
- Around line 128-147: The destroy() method clears this.sandbox before awaiting
deletion, allowing concurrent execute() / getSandbox() → resolveSandbox() →
createSandbox() → SandboxInstance.createIfNotExists to re-provision
mid-teardown; fix by not nulling this.sandbox synchronously — either (A) keep
this.sandbox until current.delete() finishes (set this.sandbox = pending only
after delete resolves) or (B) add a destroying flag (e.g., this.destroying) that
destroy() sets immediately and check it in
getSandbox()/resolveSandbox()/createSandbox()/execute() to reject or wait, and
ensure you still call current.delete() and clear state after deletion; update
references to destroy(), execute(), getSandbox(), resolveSandbox(),
createSandbox(), SandboxInstance.createIfNotExists, this.sandbox, and
current.delete() accordingly.
🪄 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: fcb4ee91-3c57-4c86-a662-f5e118036b84
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (23)
.changeset/sandbox-blaxel.md.gitignorepackages/core/src/workspace/index.tspackages/core/src/workspace/sandbox/index.tspackages/sandbox-blaxel/package.jsonpackages/sandbox-blaxel/src/constants.tspackages/sandbox-blaxel/src/index.tspackages/sandbox-blaxel/src/sandbox.spec.tspackages/sandbox-blaxel/src/sandbox.tspackages/sandbox-blaxel/src/shell.spec.tspackages/sandbox-blaxel/src/shell.tspackages/sandbox-blaxel/src/types.tspackages/sandbox-blaxel/src/utils.spec.tspackages/sandbox-blaxel/src/utils.tspackages/sandbox-blaxel/tsconfig.jsonpackages/sandbox-blaxel/tsup.config.tspackages/sandbox-blaxel/vitest.config.tswebsite/docs/workspaces/filesystem.mdwebsite/docs/workspaces/overview.mdwebsite/docs/workspaces/sandbox.mdwebsite/docs/workspaces/search.mdwebsite/docs/workspaces/security.mdwebsite/docs/workspaces/skills.md
There was a problem hiding this comment.
3 issues found across 24 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/sandbox-blaxel/src/utils.ts">
<violation number="1" location="packages/sandbox-blaxel/src/utils.ts:58">
P2: `truncateOutput` can return UTF-8 text whose byte length exceeds `maxBytes` when the slice ends in the middle of a multibyte character.</violation>
</file>
<file name="packages/sandbox-blaxel/src/shell.ts">
<violation number="1" location="packages/sandbox-blaxel/src/shell.ts:74">
P1: Constructor-time mutation of global env vars can leak or overwrite Blaxel credentials across sandbox instances in the same process.</violation>
</file>
<file name="packages/sandbox-blaxel/src/sandbox.ts">
<violation number="1" location="packages/sandbox-blaxel/src/sandbox.ts:104">
P1: Abort can be missed during sandbox provisioning, so the command may still start after cancellation.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
- Test env hygiene: snapshot BL_API_KEY/BL_WORKSPACE/BL_REGION at file load and restore them in afterEach so tests don't leak inherited env state to other workers. - Document the env-mutation auth model on BlaxelSandboxOptions, the constructor, and as an :::info admonition under the Blaxel docs section, with a link to the upstream Blaxel auth docs. - runProcess: only treat wait() rejections that match "did not finish in time" as timeouts; re-throw other rejections so real failures (network, teardown) aren't masked. Adds an isWaitTimeoutError helper. - truncateOutput: walk the cut point back to a UTF-8 codepoint boundary so the result is always valid UTF-8 with byte length <= maxBytes. Strengthen the spec to assert exact value, byte length, and round-trip validity; add a 4-byte-doesn't-fit edge case. - execute(): re-check options.signal?.aborted after resolveSandbox() returns, so a signal that fires during provisioning bails before starting a process. Extracts an abortedResult helper. - Tenant router doc examples (Blaxel/Daytona/E2B): add destroy() so copy-pasted code disposes per-tenant sandboxes. - Test plumbing: replace fragile microtask-counting in the abort mid-flight test with a setImmediate yield.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/sandbox-blaxel/src/sandbox.ts`:
- Around line 208-218: Pass the abort signal into runProcess and add immediate
checks before and after launching the remote process so a late abort cannot
leave it running: update the call site that currently does const sandbox = await
this.resolveSandbox(); ... await sandbox.process.exec({...}) to include the
controller.signal (or parsed.signal) when calling runProcess/runProcess
invocation, and inside runProcess (the function that calls sandbox.process.exec)
check signal.aborted and short-circuit (and call killProcess if needed)
immediately before invoking sandbox.process.exec and again immediately after
exec returns/throws; ensure you reference the sandbox.process.exec call,
processName, parsed (for env/cwd/timeoutMs/pollIntervalMs), and killProcess so
the guard uses the same identifiers and correctly prevents starting or cleans up
any remote process if the signal fired just before/during exec.
- Around line 132-150: The destroy() implementation clears this.sandbox before
the pending promise resolves, allowing concurrent execute() calls (via
resolveSandbox(), killProcess(), fetchProcessOutput()) to create a new sandbox
and operate on the wrong instance; fix by not removing the shared promise until
the original instance is resolved and deleted: capture the pending promise in a
local (already done as pending), await resolution to a concrete
BlaxelSandboxInstance (using attemptAsync to get [err, current]), perform
current.delete(), only after successful deletion set this.sandbox = undefined
(or otherwise atomically replace it), and ensure execute()/resolveSandbox()
patterns capture the resolved sandbox per execution rather than re-resolving
this.sandbox mid-flight so the same concrete instance (BlaxelSandboxInstance) is
used throughout the lifecycle.
- Around line 230-235: The timeout branch currently discards the result of
killProcess so callers get { timedOut: true } even if remote cleanup failed;
update the isWaitTimeoutError handling to capture the return value from
killProcess({ processName }) (call it e.g. killResult), check its
success/failure indicator (the shape returned by killProcess), and if the kill
failed either throw an error (or return a different result conveying failure)
instead of unconditionally returning { timedOut: true }; modify the logic in the
function containing isNotNil/isWaitTimeoutError to propagate the killProcess
failure via throw or an explicit failure return so callers aren’t misled.
🪄 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: a6557698-e7fd-461d-97e7-31c571376edb
📒 Files selected for processing (6)
packages/sandbox-blaxel/src/sandbox.spec.tspackages/sandbox-blaxel/src/sandbox.tspackages/sandbox-blaxel/src/types.tspackages/sandbox-blaxel/src/utils.spec.tspackages/sandbox-blaxel/src/utils.tswebsite/docs/workspaces/sandbox.md
✅ Files skipped from review due to trivial changes (2)
- packages/sandbox-blaxel/src/utils.spec.ts
- website/docs/workspaces/sandbox.md
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/sandbox-blaxel/src/types.ts
- packages/sandbox-blaxel/src/utils.ts
- packages/sandbox-blaxel/src/sandbox.spec.ts
Thread the resolved sandbox through one execute() call so a concurrent destroy() can't cause re-entrant resolveSandbox() calls in runProcess / fetchProcessOutput / killProcess to provision a fresh sandbox and operate on the wrong instance. Add a post-exec abort check inside runProcess to handle the case where the abort listener fires while exec() is in flight — its kill lands before the remote process exists, then exec() leaves the just-launched process running unchecked. Matches the post-run check in the e2b provider. Kill failures during timeout cleanup remain swallowed to stay consistent with the e2b provider (`requestKill().catch(() => undefined)`); callers see `timedOut: true` and can `destroy()` if they suspect the remote process is still running.
Tighten comments added in the previous commit so each is at most one line, in keeping with the codebase's "comments only when the why is non-obvious" posture.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/sandbox-blaxel/src/sandbox.ts (1)
362-364: 💤 Low valueAdd an explicit return type to
getSdkConfig().
omit(...)infers aPartial<...>-like shape via es-toolkit, but pinning the return type (e.g.Omit<BlaxelSandboxConfig, "cwd" | "defaultTimeoutMs" | "maxOutputBytes" | "pollIntervalMs">) makes the SDK contract explicit and prevents accidental drift ifBlaxelSandboxConfiggrows new voltagent-only keys that aren't added to the omit list.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/sandbox-blaxel/src/sandbox.ts` around lines 362 - 364, Add an explicit return type to the getSdkConfig method: change its signature to return Omit<BlaxelSandboxConfig, "cwd" | "defaultTimeoutMs" | "maxOutputBytes" | "pollIntervalMs"> so the SDK contract is explicit; keep the body using omit(this.config ?? {}, [...]) but annotate the method return type as Omit<BlaxelSandboxConfig, "cwd" | "defaultTimeoutMs" | "maxOutputBytes" | "pollIntervalMs"> to prevent accidental shape drift when BlaxelSandboxConfig gains new keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/sandbox-blaxel/src/sandbox.ts`:
- Around line 362-364: Add an explicit return type to the getSdkConfig method:
change its signature to return Omit<BlaxelSandboxConfig, "cwd" |
"defaultTimeoutMs" | "maxOutputBytes" | "pollIntervalMs"> so the SDK contract is
explicit; keep the body using omit(this.config ?? {}, [...]) but annotate the
method return type as Omit<BlaxelSandboxConfig, "cwd" | "defaultTimeoutMs" |
"maxOutputBytes" | "pollIntervalMs"> to prevent accidental shape drift when
BlaxelSandboxConfig gains new keys.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3fdb95d5-e3f5-413a-8ed8-f862d80362f4
📒 Files selected for processing (1)
packages/sandbox-blaxel/src/sandbox.ts
…yping Drop the `SandboxCreateConfiguration` re-export from index.ts. It was unnecessary (BlaxelSandboxConfig already extends it, so consumers get the full SDK shape transitively) and inconsistent with sandbox-daytona and sandbox-e2b, which don't re-export their SDK config types. Consumers who genuinely need the raw SDK type can import it directly from `@blaxel/core`. Annotate `getSdkConfig` with an explicit return type (`Omit<BlaxelSandboxConfig, voltagent-extras>`) so the SDK contract is declared at the function boundary and typos in the omit list become a type error instead of a silent runtime no-op.
442e1b9 to
320cf75
Compare
PR Checklist
Bugs / Features
What is the current behavior?
VoltAgent has no first-party workspace sandbox provider for Blaxel. Users wanting Blaxel-managed sandboxes have to wire it up themselves.
What is the new behavior?
Adds
@voltagent/sandbox-blaxel, aWorkspaceSandboximplementation backed by@blaxel/core. Supports streaming stdout/stderr, per-call timeouts andAbortSignal, output truncation, lazy provisioning, andgetSandbox()for direct SDK access.Notes for reviewers
es-toolkit(used forattempt/attemptAsyncresult tuples and small nullish utilities).target: ES2023,module: Preserve,verbatimModuleSyntax) targeting Node 22+; tsup handles emission.process.env.BL_API_KEY/BL_WORKSPACE— the only auth path the Blaxel SDK supports (no per-call credential injection).pnpm -C packages/sandbox-blaxel typecheck && pnpm -C packages/sandbox-blaxel test:coverage(67 tests, 100% coverage).website/docs/workspaces/*.mdconverted to:::warningadmonitions.Summary by CodeRabbit
New Features
Documentation
Tests
Chores