feat(sdk): expose the sandbox binding to TypeScript - #402
Conversation
Greptile SummaryThe PR exposes sandbox sessions and operations through the Node binding and the public TypeScript SDK.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported early-exit leak is addressed by unconditional stream closure, and the provider lifecycle terminates streaming commands when that stream is dropped.
|
| Filename | Overview |
|---|---|
| crates/alien-bindings-node/src/sandbox.rs | Adds the native sandbox bridge, typed session and frame conversion, pull-based command streaming, capability reporting, and lifecycle/file operations. |
| packages/bindings/src/factories.ts | Adds the lazy typed sandbox wrapper and reliably closes command streams when async iteration ends. |
| packages/bindings/src/loader.ts | Defines the raw native sandbox, session, frame, and stream contracts consumed by the TypeScript wrapper. |
| packages/bindings/src/types.ts | Adds the public TypeScript sandbox API, command-frame union, session type, and command options. |
| packages/sdk/src/index.ts | Re-exports the sandbox factory and associated types through the public SDK facade. |
Sequence Diagram
sequenceDiagram
participant App as TypeScript application
participant SDK as TypeScript sandbox wrapper
participant NAPI as N-API SandboxHandle
participant Provider as Rust Sandbox provider
App->>SDK: runCommand(sessionId, command, options)
SDK->>NAPI: runCommand(...)
NAPI->>Provider: run_command(...)
Provider-->>NAPI: command frame stream
loop Pull frames
App->>SDK: iterator.next()
SDK->>NAPI: next()
NAPI-->>SDK: stdout / stderr / exit frame
SDK-->>App: typed CommandFrame
end
App->>SDK: break, return, throw, or finish
SDK->>NAPI: close()
NAPI->>Provider: drop stream
Reviews (5): Last reviewed commit: "feat(sdk): expose the sandbox binding to..." | Re-trigger Greptile
| runCommand: (sessionId, command, options) => ({ | ||
| async *[Symbol.asyncIterator]() { | ||
| const stream = await guard(handle, raw => | ||
| raw.runCommand(sessionId, command, options.deadlineMs, options.workingDirectory ?? null), | ||
| ) | ||
|
|
||
| while (true) { | ||
| let next: RawCommandFrame | null | ||
| try { | ||
| next = await stream.next() | ||
| } catch (err) { | ||
| throw unwrapNapiError(err) | ||
| } | ||
| if (next === null) return | ||
| yield frame(next) | ||
| } | ||
| }, | ||
| }), |
There was a problem hiding this comment.
Early exit leaves command running
When a caller breaks, returns, or throws from the for await loop before the terminal frame, the generator exits without explicitly closing the native stream. Cleanup then depends on garbage collection, causing the sandbox command to continue consuming compute until the wrapper is collected or its deadline expires.
Knowledge Base Used: SDK and bindings: how apps reach storage, KV, queue, and vault
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/bindings/src/factories.ts
Line: 187-204
Comment:
**Early exit leaves command running**
When a caller breaks, returns, or throws from the `for await` loop before the terminal frame, the generator exits without explicitly closing the native stream. Cleanup then depends on garbage collection, causing the sandbox command to continue consuming compute until the wrapper is collected or its deadline expires.
**Knowledge Base Used:** [SDK and bindings: how apps reach storage, KV, queue, and vault](https://app.greptile.com/alien/-/custom-context/knowledge-base/alienplatform/alien/-/docs/sdk-bindings.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.1b99225 to
b89ff3c
Compare
f81e5a8 to
64d4aeb
Compare
b89ff3c to
52bc095
Compare
11c5234 to
469d8cf
Compare
52bc095 to
28faa2f
Compare
469d8cf to
a7c48f5
Compare
28faa2f to
57c6f58
Compare
Summary
Exposes the sandbox binding to TypeScript, so an application written in TypeScript can create a session, run a command, move files and stop it through the same contract the Rust bindings use.
When a TypeScript application asks for a sandbox binding:
create,runCommand,readFile,writeFiles,terminate.What I did
The published capability set is the interesting part. A portable application cannot assume every backend does everything — preview exists only on AWS, hostname allowlists exist nowhere, and ceilings are enforced on some platforms and refused on others. Rather than let a call fail somewhere deep in a cloud SDK, the capability set is exposed to TypeScript as data, so an application can branch before it calls.
The types are generated from the Rust definitions rather than hand-written, so the two languages cannot drift: the binding JSON is the cross-language contract.
Files touched
crates/alien-bindings-node/— the N-API surface.packages/bindings/src/types.ts— the generated binding types.packages/sdk/src/index.ts— the public export.How I tested
packages/bindings/tests/sandbox.test.ts— the binding shape and the capability set as TypeScript sees them.pnpm generate— confirms the committed types match the Rust definitions; CI fails the build if they drift.