feat(sdk): add official Node.js / TypeScript SDK#792
Conversation
Code Review: PR #792 — Node.js/TypeScript SDKThis is a substantial, well-structured PR (36 files, ~9500 lines) that adds a first-party Node.js SDK mirroring the Python and Go SDK surface. Overall the code quality is high — good error hierarchy, thorough tests, careful streaming protocol handling, and well-documented public API. Below are the noteworthy findings. 🟡 Code duplication — error response mapping
🟡 Code duplication — idle timeout logic
🟡 Filesystem error hierarchy inconsistency
🟡 Filesystem multipart fallback too aggressive
🟡
|
| this.proxyPort = options.proxyPort ?? parsePort(env.CUBE_PROXY_PORT_HTTP, 80); | ||
| this.proxyScheme = options.proxyScheme ?? env.CUBE_PROXY_SCHEME ?? "http"; | ||
| this.sandboxDomain = options.sandboxDomain ?? env.CUBE_SANDBOX_DOMAIN ?? "cube.app"; | ||
| this.timeout = options.timeout ?? 300; |
There was a problem hiding this comment.
This default timeout is spread everywhere now. cc @fslongjin
327c136 to
e1b0979
Compare
Both addressed in the latest push:
Wire format matches commands.run: streaming Start/Connect over application/connect+json (5-byte envelope framing), unary SendSignal/SendInput/Update over application/json, base64 for bytes fields. Covered by unit tests (test/pty.test.ts) and verified end-to-end against a live deployment (create → sendStdin/echo → resize with stty size readback → connect-reattach → kill; wait() surfaces the SIGKILL end-event). |
Adds a first-party Node.js / TypeScript SDK under sdk/node, published as @cubesandbox/sdk, mirroring the Python and Go SDK surface with idiomatic JS conventions (camelCase, Promise-based async/await). - Sandbox lifecycle: create / connect / pause / resume / kill, list / listV2 / health / getInfo. - Code execution: runCode with streaming callbacks (ndjson parsing). - Commands: commands.run over envd's Connect process API. - Filesystem: read / write / writeFiles / list / stat / exists / makeDir / rename / remove / watchDir. - Snapshots: createSnapshot / listSnapshots / deleteSnapshot / rollback / clone (throttled to min(n, concurrency), matching Python/Go). - Templates: list / get / build / rebuild / build status & logs / delete. - Network policy (L3/L4 + typed L7) and DNS bypass via a custom undici dispatcher with TLS SNI pinning. Control-plane calls enforce config.requestTimeoutMs (mirroring the Go SDK); data-plane calls use idle/connect timeouts. The Connect frame reader and ndjson parser avoid O(n^2) buffer/string growth. Unit tests are hermetic w.r.t. ambient CUBE_* env; the live integration test is gated behind CUBE_RUN_INTEGRATION=1. Closes TencentCloud#760 Signed-off-by: chaojixinren <chaoji_xinren@163.com>
40b9f3c to
c7821fd
Compare
|
Should we merge this PR first so we can iterate on top of it? @tinklone @kinwin-ustc |
|
I think it's ok if it can function properly. |
Thanks! I agree that this can be merged first as a working baseline and then iterated on top of. The current PR has been squashed into one commit, PTY support has been added, and the previous review feedback has been addressed. From my side, it is ready to merge if maintainers are comfortable with the current API shape. I’m happy to continue follow-up refinements in separate PRs. |
|
@chaojixinren Great work,looking forward to more contributions! |
Summary
Adds a first-party Node.js / TypeScript SDK under
sdk/node, to be publishedto npm as
@cubesandbox/sdk. Node.js/TypeScript agent apps (Next.js,LangChain.js, Vercel AI SDK, NestJS, serverless workers) can now use
CubeSandbox directly instead of hand-rolling REST calls or adapting the E2B JS
SDK.
The SDK mirrors the existing Python and Go SDK surface, with idiomatic
JavaScript conventions (camelCase, Promise-based
async/await).What's included
create/connect/pause/resume/kill,plus
list/listV2/health/getInfo.runCodewith streamingonStdout/onStderr/onResult/onErrorcallbacks (ndjson stream parsing).commands.runover envd's Connect process API.read/write/writeFiles/list/stat/exists/makeDir/rename/remove/watchDir.createSnapshot/listSnapshots/deleteSnapshot/rollback/clone.Template.list/get/build/rebuild/build status & logs /
delete.allowOut/denyOut) and typed L7rules,including the E2B per-host transform compat shape.
undicidispatcher that routes data-planeconnections to
CUBE_PROXY_NODE_IPwhile preserving the virtualHostheader (Node equivalent of the Python
IPOverrideTransport/ GoDialContextoverride), with correct TLS SNI pinning forhttps.Design notes
Sandbox.create({ apiUrl, templateId, proxyNodeIp }),runCode(code, { language, timeout, onStdout, onStderr }),commands.run,files.read/write,kill.timeout(ms) is accepted as analias for
timeoutMs.kept compatible with the Python/Go SDKs.
Review feedback addressed
config:CUBE_PROXY_PORT_HTTPnow parses via a helper that falls backto
80onNaN/empty/non-positive/out-of-range (>65535) values.Buffer.concatwith a chunklist so each received byte is copied at most once (removes O(n²) growth on
large frames split across many reads).
CUBE_*env.clone/ snapshot /watchDir/Template.rebuild+getBuildLogspaths.commands.run'stimeoutis also sent to envd asConnect-Timeout-Ms(a hard wall-clock deadline), not a pure idle timeout.Test plan
Automated unit tests
npm run typecheck/npm test/npm run buildall pass locally(144 unit tests; the live integration test also passes — see below).
config— env var resolution, precedence, URL trimming, port fallback (incl. out-of-range)exceptions— error hierarchy & status codesmodels— Execution/Result/Logs/OutputMessage/SnapshotInfo parsing & aliasesstream— ndjson line parsing, callbacks, partial-line bufferingpolicy— rule serialization, E2B per-host transform conversion, allowOut/denyOut validationcommands— Connect envelope encode/decode, exit-code extraction (mocked stream)connect-frames— frame reassembly across split headers/payloads, large multi-chunk frames, oversize/truncated streamsfilesystem— RPC shape, octet-stream + multipart fallback, 404 mapping (mocked)filesystem-watch—watchDir/Watcherevent stream, end-stream error, close idempotency (mocked)sandbox— create/connect/list/pause/kill wire payloads & headers (mocked fetch)snapshots— createSnapshot/listSnapshots/deleteSnapshot/rollback +clonesuccess & failure-cleanup (mocked)template— list/get/build/rebuild/status/logs/delete (mocked)transport— undici IP-override dispatcher + TLS SNI pinningtimeout— idle-timeout reset semantics for runCode & commands.run.d.ts/.d.ctsEnd-to-end against a live CubeAPI
Run against a live deployment with
CUBE_RUN_INTEGRATION=1(CUBE_API_URL,CUBE_TEMPLATE_ID,CUBE_PROXY_NODE_IP,CUBE_PROXY_PORT_HTTP,CUBE_SANDBOX_DOMAIN), using asandbox-codetemplate (code interpreter onport 49999).
1+1)→"2"→ commands.run(echo) → files write/read → killrunCodestreaming stdout (for i in range(3): print(i)→0\n1\n2\n)pause(wait=true) →connectauto-resume → state verifiedrunningclone(n)fan-out (liveclone(1); sibling failure-cleanup covered by unit test)files.watchDirstreams create events (observedEVENT_TYPE_CREATE)Template.buildfrom image via the SDK → poll build status → create from it →runCode(6*7)→"42"→ deletedenyOutenforced (control sandbox reached1.1.1.1:443; deny-all sandbox blocked). L7rulesserialization is unit-tested; live L7 enforcement not separately exercised.CUBE_PROXY_NODE_IPagainst real CubeProxy — all data-plane calls overhttp, plushttpsproxy scheme + TLS SNI over the TLS port (dev cert accepted viaNODE_TLS_REJECT_UNAUTHORIZED=0; SNI routing verified)runCodeidle-abort fires on a hung execution;commands.runtimeout fires via envd'sConnect-Timeout-Mshard deadlineConsumption smoke check
import { Sandbox } from "@cubesandbox/sdk"works from an ESM apprequire("@cubesandbox/sdk")works from a CJS apptscproject (moduleResolution: nodenext, exports-maptypesconditions for both.d.ts/.d.cts)