feat(sdk): add TypeScript SDK (@nvidia/openshell-sdk)#2122
feat(sdk): add TypeScript SDK (@nvidia/openshell-sdk)#2122maxdubrinsky wants to merge 11 commits into
Conversation
| @@ -0,0 +1,46 @@ | |||
| { | |||
| "name": "@nvidia/openshell-sdk", | |||
There was a problem hiding this comment.
Naming decision to settle here (resolvable thread).
GH Packages forces the @nvidia scope, so pre-GA ships as @nvidia/openshell-sdk. Proposed GA target: @openshell/sdk on public npm — and reserve the @openshell org now to pre-empt squatting. Public API is identical across the move, so GA is an install-specifier change only.
👍 to confirm, or propose an alternative scope/name.
|
🌿 Preview your docs: https://nvidia-preview-pr-2122.docs.buildwithfern.com/openshell |
benoitf
left a comment
There was a problem hiding this comment.
question: can it generate with namespaces ?
like one sandbox with list() method vs tons or root methods like listSandboxes, createSandbox
const myClient = OpenShellClient.connect(...);
await myClient.sandbox.create(...)
await myClient.sandbox.list(...)
await myClient.gateway.add(...)
await myClient.gateway.list(...)like the CLI where we have verbs gateway, sandbox, policy, provider , etc.
@benoitf good question, no we can't but the client isn't generated so namespacing is something we can just do. Refactored this to be closer to the existing Python SDK with a |
Flip the SDK direction from a shared Rust core exposed over FFI (napi-rs) to native per-language clients generated from proto/. The wire contract is already shared through proto/ and regenerates cheaply, so native beats FFI's per-platform-binary and distribution tax on a thin client. Keep the openshell-sdk Rust crate, rescoped as the shared transport, auth, and error core for the Rust consumers only (CLI and TUI). Add a native language SDK contract (generated stubs, five transport modes, string-coded errors, curated types with a raw escape hatch) and pin single-flight OIDC refresh across languages with a conformance suite instead of a shared binary. Drop the openshell-sdk-node napi crate; TypeScript (PR #2122) is now the reference native client and Go is planned. Move the shared-FFI-core approach into Alternatives with its reasoning preserved, and note that expanding capability via RPCs is a related track under RFC 0007. Signed-off-by: Max Dubrinsky <[email protected]>
First native, per-language SDK for the OpenShell gateway: a thin, idiomatic TypeScript client over proto-generated gRPC stubs (connect-es), no FFI. Covers the v0.1 surface — sandbox lifecycle (create/get/list/delete + waitReady/ waitDeleted), health, and streamed exec. - sdk/typescript/: package, client/transport/errors, protoc + protoc-gen-es codegen (gen/ gitignored, absorbed into dist/ at build), committed lockfile. - tasks/typescript.toml: sdk:ts install/proto/typecheck/build/ci/publish; sdk:ts:typecheck wired into `check`; sdk-typescript job in branch-checks (typecheck, build, and a --dry-run publish that validates the release path). - Enforce SPDX headers on .ts/.tsx/.mts/.cts (skip node_modules and gen/); back-fill docs/_components/jsx.d.ts and fern/components/CustomFooter.tsx. - release.py gains an npm version format; release-tag.yml publishes to GitHub Packages on tag, stamping the version (0.0.0 placeholder in git); prerelease builds publish under the `next` dist-tag, not `latest`. Ships as @nvidia/openshell-sdk on GitHub Packages pre-GA; public npm (@openshell/sdk) follows at GA with an unchanged public API. Signed-off-by: Max Dubrinsky <[email protected]>
- typescript ^5.7.2 -> ^6.0.3 (6.0 is now `latest`; the old caret capped at 5.x) - @types/node ^24.0.0 -> ^24 (same range, tidier) No source changes; codegen, typecheck, and build pass on 6.0.3. Verified the emitted d.ts still type-check for downstream consumers on TypeScript 5.0.4 through 5.9.3, so this does not raise the SDK's consumer TS floor. Signed-off-by: Max Dubrinsky <[email protected]>
Reshape the client from flat methods (createSandbox, listSandboxes, exec) to a scoped SandboxClient reached as `client.sandbox.create/get/list/delete/exec` (+ waitReady/waitDeleted), mirroring the CLI's noun-verb model and the Python SDK's SandboxClient. SandboxClient is also usable standalone via SandboxClient.connect(); OpenShellClient composes it over a single shared transport, so future service/provider clients reuse one connection. health() stays top-level as a gateway call. No behavior change; types are unchanged. Signed-off-by: Max Dubrinsky <[email protected]>
Replace the protoc gen.sh with `buf generate` + buf.gen.yaml. `buf` (@bufbuild/buf) is a package devDependency and self-compiles the protos, so the TS SDK no longer depends on the mise-pinned protoc; it drives the same connect-es plugin. Generation stays limited to the client-surface closure (openshell/sandbox/datamodel) via the input paths. Output is byte-identical to the previous protoc + protoc-gen-es pipeline. Lays the groundwork for a shared buf.yaml (lint/breaking/LSP) as a follow-up. Signed-off-by: Max Dubrinsky <[email protected]>
Declare proto/ as a single buf v2 module in a root buf.yaml so buf generate, lint, breaking, and the editor LSP resolve imports the same way. Lint uses STANDARD with six documented exceptions for deviations the current protos intentionally make: the flat proto/ layout with nested packages (DIRECTORY_SAME_PACKAGE, PACKAGE_DIRECTORY_MATCH) and the established API shape with unsuffixed services and reused request/response messages (RPC_REQUEST_RESPONSE_UNIQUE, RPC_REQUEST_STANDARD_NAME, RPC_RESPONSE_STANDARD_NAME, SERVICE_SUFFIX). Every other STANDARD rule now enforces on future protos. Breaking uses FILE. Code generation stays package-scoped in sdk/typescript/buf.gen.yaml since it binds to that package's connect-es plugin and output dir; its inputs are unchanged and regeneration is byte-identical. Wire the check in via a proto:lint mise task that runs buf from the SDK devDependencies. It is a dependency of both sdk:ts:ci (so the TypeScript SDK CI job enforces it) and the top-level lint aggregate (so local pre-commit covers it). Signed-off-by: Max Dubrinsky <[email protected]>
Rename the package from @nvidia/openshell-sdk to the unscoped openshell-sdk and target public npm (registry.npmjs.org) instead of GitHub Packages. GitHub Packages requires a scope matching the owning org, and the @openshell scope is blocked by an unrelated existing package, so an unscoped name on public npm is the lowest-friction distribution path and needs no org approval. Rework the release-tag publish job to auth against registry.npmjs.org with NPM_TOKEN (the job now only needs packages: read to pull the CI image). Update the README install instructions and usage imports. Signed-off-by: Max Dubrinsky <[email protected]>
Revert the unscoped-name switch. GitHub Packages only accepts scoped names matching the owning org, so shipping there first (which needs no external npm org or NPM_TOKEN, just the repo's GITHUB_TOKEN) requires the @NVIDIA scope. Keeping the @nvidia/openshell-sdk name also lets a later public-npm release use the same install specifier, so adding public npm becomes a second publish step rather than a rename. Restore the GitHub Packages publish auth in the release-tag job and the scoped install instructions in the README (keeping the buf codegen note). Signed-off-by: Max Dubrinsky <[email protected]>
8272903 to
6908672
Compare
|
Question: The v0.1 surface covers non-interactive exec() (server-streaming). Are ExecSandboxInteractive (bidi streaming with TTY, stdin, and window resize) and CreateSshSession + ForwardTcp planned for future TS SDK releases? I saw that the Go SDK prototype already wraps these RPCs. |
@MarsKubeX |
…methods Grow SandboxClient to the surface the first two consumers need. execStream yields stdout/stderr chunks as they arrive and exec now drains it, keeping its buffered ExecResult and signature unchanged. execInteractive is the TTY + stdin transport primitive (start-first framing, output/write/resize/close/done, no terminal glue). forward binds a local TCP listener that tunnels each accepted connection into the sandbox for the process lifetime, minting and revoking a per-socket SSH session token around a forwardTcp bidi. Adds createSshSession / revokeSshSession, attach/detach/listProviders, and getConfig / setPolicy / setSetting (sandbox-scoped, network-policy-only, with an optional wait poll). Signed-off-by: Max Dubrinsky <[email protected]>
The TypeScript SDK had no formatter or linter and no test runner. Add Biome (format + lint, generated src/gen excluded) enforcing 2-space indent, single quotes, semicolons, and a 120-column width, and reformat the existing hand-written sources accordingly. Add Vitest for unit tests. Wire sdk:ts:format, sdk:ts:lint, and sdk:ts:test mise tasks into the fmt/lint aggregates, the root test suite, and sdk:ts:ci so they run in CI. Signed-off-by: Max Dubrinsky <[email protected]>
Exercise SandboxClient against an in-memory OpenShell service built with createRouterTransport: request assembly and id resolution, u64/int64 rendered as strings, enum lowercasing, fromConnect code mapping, the exec/execStream drain plus a backward-compat check on exec, execInteractive start-first ordering and done resolution, and a forward() byte relay against a loopback echo with close() teardown. Signed-off-by: Max Dubrinsky <[email protected]>
…undaries Document execStream, execInteractive, forward, ssh sessions, providers, and config/policy in the SDK README, and record the intentional boundaries: interactive connect / PTY ownership, upload/download (no file-transfer RPC), and detached forwards stay out of scope. Note the Biome/Vitest dev commands. Signed-off-by: Max Dubrinsky <[email protected]>
|
Feedback from my review agent 1. Support the default mTLS gatewayAnchor:
Validation:
Confidence: high. 2. Do not make create-time sandbox policy inexpressibleAnchor:
Validation:
Confidence: high. 3. Make streamed command completion observable in normal TypeScriptAnchor:
Validation:
Confidence: high. 4. Make
|
Cross-SDK API comparison with Go PR #2271Compared against PR #2271 at ConclusionThe approaches are architecturally similar but not yet API-aligned. Both SDKs use one root client, resource-scoped sub-clients, curated domain types, hidden name-to-ID translation, a shared connection/auth layer, and higher-level operations over generated gRPC clients. That is the same overall SDK pattern. They diverge at the level consumers will organize applications around. The Go API treats exec, files, SSH, TCP, configuration, policy, services, providers, health, and sandboxes as peer domains. The TypeScript API puts lifecycle, exec, TCP listening, SSH sessions, provider attachments, sandbox configuration, and policy updates on one PR #2271 also declares the intended full Go surface upfront while implementing only sandbox operations in this PR. Exec, files, health, providers, policy, services, TCP, SSH, and configuration currently return Capability mapping
Shared high-level concepts worth preservingThese concepts are consistent enough to become a cross-language SDK contract:
Decisions needed for alignment
|
Summary
Adds the first native TypeScript SDK for the OpenShell gateway (
@nvidia/openshell-sdk), generated from the protobufs over connect-es with no FFI or napi. It covers the v0.1 surface (sandbox lifecycle, health, streamed exec) and wires up codegen, CI, and tagged publishing to GitHub Packages.Related Issue
Supersedes the shared-FFI-core direction from RFC-0008 (#1764): the frequently-changing surface (RPCs and messages) is already shared through
proto/, so each language builds a native client over the generated stubs instead of binding a common Rust core.Changes
sdk/typescript/— new package.OpenShellClient(create/get/list/delete pluswaitReady/waitDeleted,health, streamedexec), transport/auth (h2c + Node TLS + OIDC bearer / CF-Access), and typed errors. Stubs are generated withprotoc+@bufbuild/protoc-gen-es(pinned protoc 29.6);src/gen/anddist/are gitignored, anddist/ships the compiled stubs so consumers never regenerate.tasks/typescript.toml(install/proto/typecheck/build/ci/publish);sdk:ts:typecheckadded tocheck; newsdk-typescriptjob inbranch-checks.ymlrunning typecheck, build, and a--dry-runpublish that validates the release path..ts/.tsx/.mts/.cts(skipsnode_modulesand generatedgen/); two back-filled headers.release.pygains an npm version format;release-tag.ymlpublishes to GitHub Packages on tag, stamping the version from the tag (repo keeps a0.0.0placeholder). Prerelease builds publish under thenextdist-tag, stable underlatest.Open decision (for review)
Package name/scope is unsettled; see the resolvable thread on
package.json(thenamefield). Pre-GA is@nvidia/openshell-sdk; the proposed GA name is@openshell/sdkon public npm.Testing
protoccodegen,tsctypecheck, and build all green;npm publish --dry-runproduces a valid 30-file tarball (dist/**+ README + package.json) and restores the placeholder.ruffclean on changed Python.mise run pre-commit: validated in CI. (Locally the alias also builds/tests the Rust workspace, which the sandbox blocks; that workspace is untouched here.)Not included: a vitest unit suite and a gateway-gated live e2e test, both follow-ups.
Checklist