Solari

Sandboxes

github.com/solari-sdk/solari-sandbox-go, package solari. Types Client and Sandbox. A sandbox is a headless microVM for commands, code, files, and git. See the Go SDK hub for install and configuration.

go get github.com/solari-sdk/solari-sandbox-go
This is the core surface only
The Go module ports the @solarisdk/core surface: session lifecycle (including Pause/Resume) plus Commands, Files, Code, and Git. PTYs, full volume CRUD, snapshots, metrics, signed transfer URLs, and preview URLs are not ported. See what is not ported.

Contents

Client

The sandbox entry point. Talks the gateway REST API.

Constructor

NewClient()

func NewClient(opts ClientOptions) (*Client, error)

Constructs a client. Opens no connection.

Parameters:

Returns: (*Client, error)

Errors: *SolariError if APIKey or BaseURL is empty.

Example:

import solari "github.com/solari-sdk/solari-sandbox-go"

client, err := solari.NewClient(solari.ClientOptions{
    APIKey:  os.Getenv("SOLARI_API_KEY"),
    BaseURL: "https://api.getsolari.com",
})

Methods

Create()

func (c *Client) Create(ctx context.Context, opts CreateOptions) (*Sandbox, error)

Provisions a sandbox (POST /sandboxes) and returns a handle. Retry-safe, since it sends an idempotency key. The control channel is not opened yet.

Parameters:

  • ctx context.Context.
  • opts CreateOptions: see CreateOptions. Kind defaults to KindSandbox.

Returns: (*Sandbox, error)

Errors: *AuthError, *PlanError, *ConcurrencyLimitError, *NoCapacityError, or *GatewayError.

Example:

sbx, err := client.Create(ctx, solari.CreateOptions{
    Template: "base",
    CPU:      2,
    MemMb:    4096,
    Envs:     map[string]string{"NODE_ENV": "test"},
})
if err != nil {
    log.Fatal(err)
}
defer sbx.Kill(ctx)

Get()

func (c *Client) Get(ctx context.Context, sandboxID string) (*SandboxView, error)

Fetches a sandbox’s current record (GET /sandboxes/:id). Returns the raw view, not a handle.

Parameters:

  • ctx context.Context.
  • sandboxID string: the sandbox id.

Returns: (*SandboxView, error)

Errors: *GatewayError if the id is unknown.

Example:

view, _ := client.Get(ctx, "sbx_abc123")
fmt.Println(view.State, view.CPU, view.MemMb)  // "running" 2 4096

Connect()

func (c *Client) Connect(ctx context.Context, sandboxID string) (*Sandbox, error)

Re-attaches to a running sandbox by id. When the view carries no ControlURL, one is derived by swapping the base URL scheme to ws/wss and appending /control/<id>.

Parameters:

  • ctx context.Context.
  • sandboxID string: the sandbox id.

Returns: (*Sandbox, error)

Errors: *GatewayError if the id is unknown.

Example:

sbx, err := client.Connect(ctx, "sbx_abc123")
r, _ := sbx.Commands.Run(ctx, "uptime", solari.CommandOptions{})

Kill()

func (c *Client) Kill(ctx context.Context, sandboxID string) error

Destroys a sandbox by id (DELETE /sandboxes/:id). Idempotent. Use Sandbox.Kill when you hold a handle.

Parameters:

  • ctx context.Context.
  • sandboxID string: the sandbox id.

Returns: error

Example:

err := client.Kill(ctx, "sbx_abc123")

Pause()

func (c *Client) Pause(ctx context.Context, sandboxID string) error

Snapshots a session’s RAM+disk and frees its host slot (POST /sandboxes/:id/pause). The session keeps its id and can be brought back with Resume; its control channel is dead until then. Prefer Sandbox.Pause when you hold a handle — it also closes the local channel.

Parameters:

  • ctx context.Context.
  • sandboxID string: the sandbox id.

Returns: error

Example:

err := client.Pause(ctx, "sbx_abc123")

Resume()

func (c *Client) Resume(ctx context.Context, sandboxID string) (string, error)

Re-hydrates a paused session (POST /sandboxes/:id/resume) and returns the control URL to re-attach to. The session comes back on a fresh slot, so any control URL from before the pause is stale.

Parameters:

  • ctx context.Context.
  • sandboxID string: the sandbox id.

Returns: (string, error), the fresh control URL.

Example:

controlURL, err := client.Resume(ctx, "sbx_abc123")

Sandbox

A live session handle. Construct via Client.Create or Client.Connect.

Fields

  • ID string: the session id.
  • ControlURL string: the wss:// JSON-RPC control channel.
  • ExpiresAt string: ISO 8601 expiry.
  • Kind SandboxKind: KindSandbox or KindDesktop.
  • Commands *Commands, Files *Files, Code *Code, Git *Git: the namespaces.

Channel

Connect()

func (s *Sandbox) Connect(ctx context.Context) error

Opens the control WebSocket. Idempotent. Only Commands.Run can avoid it. Every other namespace requires the channel, so connect first.

Parameters:

  • ctx context.Context: bounds the dial.

Returns: error

Errors: *ConnectionError if the dial fails; *TimeoutError with method "connect" after 15s.

Example:

if err := sbx.Connect(ctx); err != nil {
    log.Fatal(err)
}

Reconnect()

func (s *Sandbox) Reconnect(ctx context.Context) error

Re-opens the control channel after a drop.

Parameters:

  • ctx context.Context.

Returns: error

Example:

if !sbx.Connected() {
    err := sbx.Reconnect(ctx)
}

Connected()

func (s *Sandbox) Connected() bool

Reports whether the control channel is currently open.

Returns: bool

Example:

fmt.Println(sbx.Connected())

Close()

func (s *Sandbox) Close()

Closes the control channel locally. Does not release the remote session. Use Kill for that.

Returns: nothing.

Example:

sbx.Close()  // session keeps running until its timeout

Pause()

func (s *Sandbox) Pause(ctx context.Context) error

Snapshots this session’s RAM+disk, frees its host slot, and closes the control channel locally. The session keeps its id; bring it back with Resume.

Parameters:

  • ctx context.Context.

Returns: error

Example:

if err := sbx.Pause(ctx); err != nil {
    log.Fatal(err)
}

Resume()

func (s *Sandbox) Resume(ctx context.Context) error

Re-hydrates this paused session and re-points the control channel at the fresh slot it came back on: updates ControlURL and reconnects.

Parameters:

  • ctx context.Context.

Returns: error

Example:

if err := sbx.Resume(ctx); err != nil {
    log.Fatal(err)
}
r, _ := sbx.Commands.Run(ctx, "echo", solari.CommandOptions{Args: []string{"back"}})

Kill()

func (s *Sandbox) Kill(ctx context.Context) error

Destroys the remote session and closes the channel. Idempotent. The channel is closed even when the delete fails.

Parameters:

  • ctx context.Context.

Returns: error

Example:

sbx, err := client.Create(ctx, solari.CreateOptions{Template: "base"})
if err != nil {
    log.Fatal(err)
}
defer sbx.Kill(ctx)

Commands

The process-execution namespace. Reach it at sandbox.Commands.

Run()

func (c *Commands) Run(ctx context.Context, cmd string, opts CommandOptions) (*CommandResult, error)

Executes a command to completion and returns its exit code and captured output. OnStdout/OnStderr, when set, receive output as it streams.

Parameters:

  • ctx context.Context.
  • cmd string: the program to run.
  • opts CommandOptions: see CommandOptions.

Returns: (*CommandResult, error), carrying ExitCode, Stdout, Stderr. A non-zero exit returns a result, not an error.

Errors: *ActionError, *TimeoutError, *ConnectionError, or a gateway error from the fast path.

Example:

r, err := sbx.Commands.Run(ctx, "ls", solari.CommandOptions{
    Args: []string{"-la", "/tmp"},
})
fmt.Println(r.ExitCode, r.Stdout)

// stream output as it arrives (needs the control channel)
_, err = sbx.Commands.Run(ctx, "npm", solari.CommandOptions{
    Args:     []string{"install"},
    Cwd:      "/app",
    OnStdout: func(s string) { fmt.Print(s) },
})
No shell by default
cmd runs directly with Args, not through a shell. Pipes, globs, and && will not expand. For shell syntax use Run(ctx, "sh", CommandOptions{Args: []string{"-c", "…"}}).
The first Run takes a warm HTTP fast path
On an unconnected sandbox a plain run-to-completion goes over POST /sandboxes/:id/exec, skipping the cold WS handshake. The fast path is used only when Background is false and OnStdout, OnStderr, Env, and User are all unset. It falls back to the control channel only when the route is unserved (404/405/501). A real command failure propagates, so nothing double-executes.

Start()

func (c *Commands) Start(ctx context.Context, cmd string, opts CommandOptions) (*CommandHandle, error)

Launches a command and returns a handle immediately, without waiting for exit. Requires the control channel.

Parameters:

  • ctx context.Context.
  • cmd string: the program to run.
  • opts CommandOptions: TimeoutMs and Background are ignored here.

Returns: (*CommandHandle, error). See CommandHandle.

Errors: *ConnectionError if the channel is not open; *ActionError if the guest rejects the start.

Example:

h, err := sbx.Commands.Start(ctx, "python3", solari.CommandOptions{
    Args: []string{"-u", "worker.py"},
})
h.OnData(func(stream, data string) { fmt.Print(data) })
h.Stdin(ctx, []byte("input\n"))
code, err := h.Wait(ctx)

CommandHandle

A started command from Start.

  • CmdID string: the command id.
func (h *CommandHandle) OnData(cb func(stream, data string))
func (h *CommandHandle) Wait(ctx context.Context) (int, error)
func (h *CommandHandle) Stdin(ctx context.Context, data []byte) error
func (h *CommandHandle) Kill(ctx context.Context, signal int) error
  • OnData: subscribes to stdout/stderr chunks. Output buffered before the first subscriber is replayed, so early output is never dropped.
  • Wait: blocks until exit and returns the exit code. Also returns when the channel drops or ctx is cancelled.
  • Stdin: writes bytes to the command’s stdin.
  • Kill: signals the process. signal ≤ 0 means SIGTERM.

Files

The filesystem namespace (fs.* RPCs), moving bytes over the control channel. Reach it at sandbox.Files.

Read()

func (f *Files) Read(ctx context.Context, path string) ([]byte, error)

Returns the raw bytes of an in-guest file.

Parameters:

  • ctx context.Context.
  • path string: absolute in-guest path.

Returns: ([]byte, error)

Errors: *ActionError if the path does not exist; *ConnectionError if the channel is not open.

Example:

b, err := sbx.Files.Read(ctx, "/tmp/out.bin")

ReadText()

func (f *Files) ReadText(ctx context.Context, path string) (string, error)

Read decoded as UTF-8 text.

Parameters:

  • ctx context.Context.
  • path string: absolute in-guest path.

Returns: (string, error)

Example:

host, err := sbx.Files.ReadText(ctx, "/etc/hostname")

Write()

func (f *Files) Write(ctx context.Context, path string, data []byte, mode int) error

Writes bytes to an in-guest path, creating or truncating it.

Parameters:

  • ctx context.Context.
  • path string: absolute in-guest path.
  • data []byte: content.
  • mode int: unix permission bits. 0 means unset and is omitted from the wire.

Returns: error

Example:

err := sbx.Files.Write(ctx, "/app/run.sh", []byte("#!/bin/sh\necho hi\n"), 0o755)

List()

func (f *Files) List(ctx context.Context, path string) ([]FsEntry, error)

Returns a directory’s entries (non-recursive).

Parameters:

  • ctx context.Context.
  • path string: directory path.

Returns: ([]FsEntry, error), each with Name, Dir, Size.

Example:

entries, _ := sbx.Files.List(ctx, "/app")
for _, e := range entries {
    fmt.Println(e.Name, e.Dir, e.Size)
}

Stat()

func (f *Files) Stat(ctx context.Context, path string) (*FsStat, error)

Returns metadata for a single path.

Parameters:

  • ctx context.Context.
  • path string: file or directory path.

Returns: (*FsStat, error), carrying Name, Dir, Size, Mode, ModTimeMs.

Example:

st, _ := sbx.Files.Stat(ctx, "/app/run.sh")
fmt.Println(st.Size, strconv.FormatInt(int64(st.Mode), 8), st.ModTimeMs)

Mkdir()

func (f *Files) Mkdir(ctx context.Context, path string) error

Creates a directory, and any missing parents.

Parameters:

  • ctx context.Context.
  • path string: directory to create.

Returns: error

Example:

err := sbx.Files.Mkdir(ctx, "/app/data")

Remove()

func (f *Files) Remove(ctx context.Context, path string, recursive bool) error

Deletes a file or directory.

Parameters:

  • ctx context.Context.
  • path string: path to delete.
  • recursive bool: required for a non-empty directory.

Returns: error

Example:

err := sbx.Files.Remove(ctx, "/tmp/build", true)

Rename()

func (f *Files) Rename(ctx context.Context, from, to string) error

Renames or moves a path.

Parameters:

  • ctx context.Context.
  • from, to string: source and destination.

Returns: error

Example:

err := sbx.Files.Rename(ctx, "/tmp/a.txt", "/tmp/b.txt")

Code

The stateful-kernel namespace. Reach it at sandbox.Code.

Run()

func (c *Code) Run(ctx context.Context, code string, opts RunCodeOptions) (*RunCodeResult, error)

Executes code in a stateful kernel. Rich outputs (PNG, HTML, JSON, structured charts) come back as Results items.

Parameters:

  • ctx context.Context.
  • code string: source to execute.
  • opts RunCodeOptions: Language, ContextID, OnStdout, OnStderr.

Returns: (*RunCodeResult, error), carrying Results, Charts, Error. A runtime error in the executed code lands in Error; it is not returned as a Go error.

Errors: *ActionError, *TimeoutError, *ConnectionError.

Example:

r, err := sbx.Code.Run(ctx, `
import matplotlib.pyplot as plt
plt.plot([1, 2, 3], [4, 5, 6])
plt.title("demo")
plt.show()
`, solari.RunCodeOptions{})

fmt.Println(r.Charts[0].Type)   // "line"
fmt.Println(r.Charts[0].Title)  // "demo"

CreateContext()

func (c *Code) CreateContext(ctx context.Context, language string) (string, error)

Creates a fresh kernel context so state persists across Run calls, like a REPL.

Parameters:

  • ctx context.Context.
  • language string: empty defaults to "python".

Returns: (string, error), the context id.

Example:

cid, _ := sbx.Code.CreateContext(ctx, "python")
sbx.Code.Run(ctx, "x = 41", solari.RunCodeOptions{ContextID: cid})
r, _ := sbx.Code.Run(ctx, "print(x + 1)", solari.RunCodeOptions{ContextID: cid})  // 42

Git

Every method is a safe, non-shell git invocation over the command RPC, with client-side parsing and no injection surface. Reach it at sandbox.Git.

Clone()

func (g *Git) Clone(ctx context.Context, rawURL string, opts GitCloneOptions) error

Clones a repository into the guest.

Parameters:

  • ctx context.Context.
  • rawURL string: remote URL.
  • opts GitCloneOptions: Path, Branch, Depth, Username, Password, Cwd.

Returns: error

Errors: a plain error carrying the subcommand, exit code, and git’s stderr on a non-zero exit.

Example:

err := sbx.Git.Clone(ctx, "https://github.com/org/repo.git", solari.GitCloneOptions{
    Path:   "/app/repo",
    Branch: "main",
    Depth:  1,
})
Credentials are spliced per-invocation
Username/Password are URL-encoded into the remote for this one command and never written to the repo config. A non-http(s) remote (ssh, scp-style) is passed through unchanged.

Status()

func (g *Git) Status(ctx context.Context, cwd string) (*GitStatus, error)

Parsed working-tree status.

Parameters:

  • ctx context.Context.
  • cwd string: repository directory; empty uses the session default.

Returns: (*GitStatus, error). See GitStatus.

Errors: a plain error on a non-zero exit.

Example:

st, _ := sbx.Git.Status(ctx, "/app/repo")
fmt.Println(st.Branch, st.Clean, st.Ahead, st.Modified)

Add()

func (g *Git) Add(ctx context.Context, paths []string, cwd string) error

Stages paths. An empty slice is a no-op; a -- separator guards paths that look like flags.

Parameters:

  • ctx context.Context.
  • paths []string: use []string{"."} for everything.
  • cwd string: repository directory.

Returns: error

Example:

err := sbx.Git.Add(ctx, []string{"."}, "/app/repo")

Commit()

func (g *Git) Commit(ctx context.Context, message string, opts GitCommitOptions) (string, error)

Commits staged changes and returns the new hash. Set Author/Email, because an ephemeral session has no git identity.

Parameters:

  • ctx context.Context.
  • message string: commit message.
  • opts GitCommitOptions: Cwd, Author, Email (scoped to this commit only), All.

Returns: (string, error), the commit hash, read back with rev-parse HEAD.

Errors: a plain error on a non-zero exit.

Example:

hash, err := sbx.Git.Commit(ctx, "add feature", solari.GitCommitOptions{
    Cwd:    "/app/repo",
    Author: "CI Bot",
    Email:  "ci@example.com",
    All:    true,
})

Push()

func (g *Git) Push(ctx context.Context, opts GitRemoteOptions) error

Pushes to a remote. Defaults to origin and the current branch.

Parameters:

  • ctx context.Context.
  • opts GitRemoteOptions: Cwd, Remote, Branch, Username, Password.

Returns: error

Errors: a plain error on a non-zero exit.

Example:

err := sbx.Git.Push(ctx, solari.GitRemoteOptions{
    Cwd:      "/app/repo",
    Username: "x-access-token",
    Password: os.Getenv("GITHUB_TOKEN"),
})
Push/Pull auth never touches the repo config
With credentials set, the SDK reads the remote’s URL and rewrites it for this one invocation via -c url.<authed>.insteadOf=<plain>. Nothing is persisted.

Pull()

func (g *Git) Pull(ctx context.Context, opts GitRemoteOptions) error

Pulls from a remote. Same options and credential handling as Push.

Parameters:

  • ctx context.Context.
  • opts GitRemoteOptions: Cwd, Remote, Branch, Username, Password.

Returns: error

Example:

err := sbx.Git.Pull(ctx, solari.GitRemoteOptions{Cwd: "/app/repo"})

Checkout()

func (g *Git) Checkout(ctx context.Context, ref string, cwd string, create bool) error

Checks out an existing ref, or creates a branch.

Parameters:

  • ctx context.Context.
  • ref string: branch, tag, or commit.
  • cwd string: repository directory.
  • create bool: create the branch (-b).

Returns: error

Example:

err := sbx.Git.Checkout(ctx, "feature/x", "/app/repo", true)

Branches()

func (g *Git) Branches(ctx context.Context, cwd string) ([]GitBranch, error)

Lists local branches.

Parameters:

  • ctx context.Context.
  • cwd string: repository directory.

Returns: ([]GitBranch, error), each with Name, Commit (short), Current.

Example:

branches, _ := sbx.Git.Branches(ctx, "/app/repo")
for _, b := range branches {
    fmt.Println(b.Current, b.Name, b.Commit)
}

Log()

func (g *Git) Log(ctx context.Context, opts GitLogOptions) ([]GitCommit, error)

Recent commits, newest first.

Parameters:

  • ctx context.Context.
  • opts GitLogOptions: Cwd, MaxCount.

Returns: ([]GitCommit, error), each with Hash, Author, Email, Date, Message.

Example:

commits, _ := sbx.Git.Log(ctx, solari.GitLogOptions{Cwd: "/app/repo", MaxCount: 10})

Not ported to Go

These exist in the TypeScript SDK but have no Go equivalent. Reach them over the HTTP API.

MissingTypeScript equivalent
PTYspty.create()
Listing and filtering sessionslist(), listAll()
Snapshotssnapshot(), revert(), listSnapshots(), promoteSnapshot()
Volume create/list/deletevolumes.* (attach-at-create is ported — see CreateOptions.Volumes)
Timeout extensionsetTimeout()
Signed transfer + preview URLsdownloadUrl(), uploadUrl(), previewUrl()
Metrics, session env, file search/watchmetrics(), env(), files.search(), files.watch()

Types

ClientOptions

  • APIKey string: required. Authenticates REST requests and the control-WS upgrade.
  • BaseURL string: required. The gateway origin.
  • HTTPClient *http.Client: override for tests.
  • CallTimeoutMs int: per-call control-WS RPC timeout. Default 300000.
  • MaxRetries int: default 5.
  • RetryDelayMs *int: when non-nil, a fixed delay instead of exponential backoff. 0 disables the wait.

CreateOptions

Zero and nil fields are omitted from the wire body.

  • Template string: e.g. "base", "code", or a promoted template id.
  • Kind SandboxKind: KindSandbox (default) or KindDesktop.
  • CPU int, MemMb int, DiskGb int: resources; the host clamps to slot capacity.
  • Envs map[string]string: per-session environment.
  • Metadata map[string]string: opaque labels.
  • TimeoutMs int: idle window before auto-release.
  • FromSnapshot string: boot from a snapshot instead of the template.
  • Lifecycle *SandboxLifecycle: OnTimeout ("pause" / "kill") and AutoResume *bool.
  • Resolution string: initial display resolution, e.g. "1280x720". Desktops only (KindDesktop) — a headless sandbox has no display.
  • Record *bool: ask the gateway to record the session server-side; the create response carries a presigned playback URL. Desktops only — rejected (400 RecordingRequiresDesktop) on a headless sandbox. A pointer so an explicit false is distinguishable from unset.
  • Volumes []VolumeAttachment: persistent volumes to mount before the session starts, each with VolumeID (a vol_… id) and Path. Attaching at create time is ported; creating, listing, and deleting volumes is not (see what is not ported).

SandboxView

  • SandboxID string, Kind SandboxKind, State string.
  • Metadata map[string]string, ExpiresAt string.
  • ControlURL string: may be empty; Connect derives one when it is.
  • CPU int, MemMb int.

CommandOptions

  • Args []string: the argv tail. No shell expansion.
  • Cwd string, User string.
  • Env map[string]string: per-command environment.
  • TimeoutMs int: bounds the one-shot exec fast path, server-side.
  • Background bool: Run returns immediately with an empty result; output still streams to the callbacks.
  • OnStdout, OnStderr func(string): streamed output.

CommandResult

  • ExitCode int, Stdout string, Stderr string.

FsEntry / FsStat

  • FsEntry: Name string, Dir bool, Size int64.
  • FsStat: Name, Dir, Size, plus Mode int (permission bits) and ModTimeMs int64 (unix millis).

RunCodeOptions / RunCodeResult

  • RunCodeOptions: Language string ("python", "javascript", "typescript", "bash", "r"), ContextID string, OnStdout, OnStderr func(string).
  • RunCodeResult.Results []CodeResultItem: each has Type (stdout/stderr/result) plus any of Text, PNG, JPEG, SVG, HTML, LaTeX, JSON, Markdown, Chart.
  • RunCodeResult.Charts []Chart: every present chart across Results, flattened client-side.
  • RunCodeResult.Error interface{}: a *CodeError (Name, Message, Traceback) or a string; kept raw so both wire shapes round-trip.

Chart

  • Type ChartType: ChartLine, ChartScatter, ChartBar, ChartPie, ChartBoxAndWhisker, ChartComposite, ChartUnknown.
  • Title, XLabel, YLabel string.
  • X, Y *ChartAxis: Label, Ticks, Scale.
  • Elements []interface{}: per-type data (points, bars, slices), kept loose so new chart types need no SDK bump.

GitStatus

  • Branch string: empty on a detached HEAD.
  • Detached bool, Clean bool.
  • Ahead, Behind int: relative to the upstream, when set.
  • Staged, Modified, Untracked []string.

GitBranch / GitCommit

  • GitBranch: Name string, Commit string (short), Current bool.
  • GitCommit: Hash, Author, Email, Date (ISO 8601), Message string.

Errors

Every error embeds *SolariError directly or transitively. Match broadly on *SolariError or narrowly on a concrete type with errors.As. Gateway errors also carry Status, Code, and Body.

TypeStatusMeaning
*AuthError401, 403API key missing, malformed, or rejected.
*PlanError402The plan does not allow this.
*ConcurrencyLimitError429Too many live sessions. Not retryable.
*NoCapacityError503No host available right now. Retryable.
*GatewayErrorotherAny other non-2xx response.
*ActionErrorNoneAn RPC returned ok: false. Carries Method, Code.
*TimeoutErrorNoneNo reply within the deadline. Carries Method (or "connect") and TimeoutMs.
*ConnectionErrorNoneThe control channel is not open, or the dial failed.
_, err := client.Create(ctx, solari.CreateOptions{Template: "base"})

var capErr *solari.ConcurrencyLimitError
var noCap *solari.NoCapacityError
switch {
case errors.As(err, &noCap):
    retryLater()
case errors.As(err, &capErr):
    waitForSlot()
case err != nil:
    log.Fatal(err)
}