Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
254cd8f
feat(sdk): Go SDK with oapi-codegen for Lifecycle, Execd, and Egress …
AlexandrePh Mar 30, 2026
61a8b7f
feat(sdk): add high-level Sandbox API, SandboxManager, and Go e2e tests
AlexandrePh Mar 30, 2026
ebdb986
refactor(sdk): simplify code from review — DRY config, strings.Builde…
AlexandrePh Mar 30, 2026
f2dfd1a
feat(sdk): expand e2e coverage — filesystem CRUD, sessions, connect, …
AlexandrePh Mar 30, 2026
e37f1f8
feat(sdk): add CodeInterpreter wrapper + 6 e2e tests
AlexandrePh Mar 30, 2026
7de8373
feat(sdk): close e2e gaps — env injection, sessions, volumes, network…
AlexandrePh Mar 30, 2026
6fe1ad8
feat(sdk): add real scenario e2e tests — LLM agent in sandbox
AlexandrePh Mar 30, 2026
7fa5d42
docs(sdk): add Go SDK examples — agent loop + code interpreter agent
AlexandrePh Mar 30, 2026
dece184
style(sdk): run gofmt on Go SDK files
AlexandrePh Mar 30, 2026
8440231
fix(sdk): address code review — error handling, race condition, doc a…
AlexandrePh Mar 30, 2026
7959da0
fix(sdk): replace sync.Once with mutex for endpoint resolution
AlexandrePh Mar 30, 2026
5a83b20
test(sdk): add concurrent sandbox creation test + PVC volume test now…
AlexandrePh Mar 30, 2026
9d6a77c
fix(sdk): address Copilot review — nested events, unused config, test…
AlexandrePh Mar 30, 2026
d83a932
fix(sdk): parse nested error/results objects per execd OpenAPI spec
AlexandrePh Mar 30, 2026
d50d837
docs(sdk): fix README streaming example and stale API signatures
AlexandrePh Mar 30, 2026
22f8493
fix(sdk): preserve endpoint headers and apply custom headers to strea…
AlexandrePh Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions sdks/sandbox/go/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.PHONY: all build vet test lint generate

all: build vet test

generate:
cd opensandbox/api/lifecycle && oapi-codegen --config cfg.yaml ../specs/sandbox-lifecycle.yml
cd opensandbox/api/execd && oapi-codegen --config cfg.yaml ../specs/execd-api.yaml
cd opensandbox/api/egress && oapi-codegen --config cfg.yaml ../specs/egress-api.yaml
@echo "Generated all API clients from OpenAPI specs"

build:
go build ./...

vet:
go vet ./...

test:
go test ./opensandbox/ -v

test-integration:
go test -tags=integration ./opensandbox/ -v -timeout 3m

test-staging:
go test -tags=staging ./opensandbox/ -v -timeout 3m

lint:
@which staticcheck >/dev/null 2>&1 && staticcheck ./... || echo "staticcheck not installed — skipping lint"
237 changes: 237 additions & 0 deletions sdks/sandbox/go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# OpenSandbox Go SDK

Go client library for the [OpenSandbox](https://github.com/alibaba/OpenSandbox) API.

Covers all three OpenAPI specs:
- **Lifecycle** — Create, manage, and destroy sandbox instances
- **Execd** — Execute commands, manage files, monitor metrics inside sandboxes
- **Egress** — Inspect and mutate sandbox network policy at runtime

## Installation

```bash
go get github.com/alibaba/OpenSandbox/sdks/sandbox/go
```

## Quick Start

### Create and manage a sandbox

```go
package main

import (
"context"
"fmt"
"log"

"github.com/alibaba/OpenSandbox/sdks/sandbox/go/opensandbox"
)

func main() {
ctx := context.Background()

// Create a lifecycle client
lc := opensandbox.NewLifecycleClient("http://localhost:8080/v1", "your-api-key")

// Create a sandbox
sbx, err := lc.CreateSandbox(ctx, opensandbox.CreateSandboxRequest{
Image: opensandbox.ImageSpec{URI: "python:3.12"},
Entrypoint: []string{"/bin/sh"},
ResourceLimits: opensandbox.ResourceLimits{
"cpu": "500m",
"memory": "512Mi",
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created sandbox: %s (state: %s)\n", sbx.ID, sbx.Status.State)

// Get sandbox details
sbx, err = lc.GetSandbox(ctx, sbx.ID)
if err != nil {
log.Fatal(err)
}

// List all running sandboxes
list, err := lc.ListSandboxes(ctx, opensandbox.ListOptions{
States: []opensandbox.SandboxState{opensandbox.StateRunning},
PageSize: 10,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Running sandboxes: %d\n", list.Pagination.TotalItems)

// Pause and resume
_ = lc.PauseSandbox(ctx, sbx.ID)
_ = lc.ResumeSandbox(ctx, sbx.ID)

// Clean up
_ = lc.DeleteSandbox(ctx, sbx.ID)
}
```

### Run a command with streaming output

```go
exec := opensandbox.NewExecdClient("http://localhost:9090", "your-execd-token")

err := exec.RunCommand(ctx, opensandbox.RunCommandRequest{
Command: "echo 'Hello from sandbox!'",
Timeout: 30000,
}, func(event opensandbox.StreamEvent) error {
switch event.Event {
case "stdout":
fmt.Print(event.Data)
case "stderr":
fmt.Fprintf(os.Stderr, "%s", event.Data)
case "result":
fmt.Printf("\n[done] %s\n", event.Data)
}
return nil
})
```

### Check egress policy

```go
egress := opensandbox.NewEgressClient("http://localhost:18080", "your-egress-token")

// Get current policy
policy, err := egress.GetPolicy(ctx)
fmt.Printf("Mode: %s, Default: %s\n", policy.Mode, policy.Policy.DefaultAction)

// Add a rule
updated, err := egress.PatchPolicy(ctx, []opensandbox.NetworkRule{
{Action: "allow", Target: "api.example.com"},
})
```

## API Reference

### LifecycleClient

Created with `NewLifecycleClient(baseURL, apiKey string, opts ...Option)`.

| Method | Description |
|--------|-------------|
| `CreateSandbox(ctx, req)` | Create a new sandbox from a container image |
| `GetSandbox(ctx, id)` | Get sandbox details by ID |
| `ListSandboxes(ctx, opts)` | List sandboxes with filtering and pagination |
| `DeleteSandbox(ctx, id)` | Delete a sandbox |
| `PauseSandbox(ctx, id)` | Pause a running sandbox |
| `ResumeSandbox(ctx, id)` | Resume a paused sandbox |
| `RenewExpiration(ctx, id, duration)` | Extend sandbox expiration time |
| `GetEndpoint(ctx, sandboxID, port)` | Get public endpoint for a sandbox port |

### ExecdClient

Created with `NewExecdClient(baseURL, accessToken string, opts ...Option)`.

**Health:**
| Method | Description |
|--------|-------------|
| `Ping(ctx)` | Check server health |

**Code Execution:**
| Method | Description |
|--------|-------------|
| `ListContexts(ctx, language)` | List active code execution contexts |
| `CreateContext(ctx, req)` | Create a code execution context |
| `GetContext(ctx, contextID)` | Get context details |
| `DeleteContext(ctx, contextID)` | Delete a context |
| `DeleteContextsByLanguage(ctx, language)` | Delete all contexts for a language |
| `ExecuteCode(ctx, req, handler)` | Execute code with SSE streaming |
| `InterruptCode(ctx, sessionID)` | Interrupt running code |

**Command Execution:**
| Method | Description |
|--------|-------------|
| `CreateSession(ctx)` | Create a bash session |
| `RunInSession(ctx, sessionID, req, handler)` | Run command in session with SSE |
| `DeleteSession(ctx, sessionID)` | Delete a bash session |
| `RunCommand(ctx, req, handler)` | Run a command with SSE streaming |
| `InterruptCommand(ctx, sessionID)` | Interrupt running command |
| `GetCommandStatus(ctx, commandID)` | Get command execution status |
| `GetCommandLogs(ctx, commandID, cursor)` | Get command stdout/stderr |

**File Operations:**
| Method | Description |
|--------|-------------|
| `GetFileInfo(ctx, path)` | Get file metadata |
| `DeleteFiles(ctx, paths)` | Delete files |
| `SetPermissions(ctx, req)` | Change file permissions |
| `MoveFiles(ctx, req)` | Move/rename files |
| `SearchFiles(ctx, dir, pattern)` | Search files by glob pattern |
| `ReplaceInFiles(ctx, req)` | Text replacement in files |
| `UploadFile(ctx, localPath, remotePath)` | Upload a file to the sandbox |
| `DownloadFile(ctx, remotePath, rangeHeader)` | Download a file from the sandbox |

**Directory Operations:**
| Method | Description |
|--------|-------------|
| `CreateDirectory(ctx, path, mode)` | Create a directory (mkdir -p) |
| `DeleteDirectory(ctx, path, recursive)` | Delete a directory |

**Metrics:**
| Method | Description |
|--------|-------------|
| `GetMetrics(ctx)` | Get system resource metrics |
| `WatchMetrics(ctx, handler)` | Stream metrics via SSE |

### EgressClient

Created with `NewEgressClient(baseURL, authToken string, opts ...Option)`.

| Method | Description |
|--------|-------------|
| `GetPolicy(ctx)` | Get current egress policy |
| `PatchPolicy(ctx, rules)` | Merge rules into current policy |

## SSE Streaming

Methods that stream output (`RunCommand`, `ExecuteCode`, `RunInSession`, `WatchMetrics`) accept an `EventHandler` callback:

```go
type EventHandler func(event StreamEvent) error
```

Each `StreamEvent` contains:
- `Event` — the event type (e.g. `"stdout"`, `"stderr"`, `"result"`)
- `Data` — the event payload (multi-line data fields are joined with `\n`)
- `ID` — optional event identifier

Return a non-nil error from the handler to stop processing the stream early.

## Client Options

All client constructors accept optional `Option` functions:

```go
// Use a custom http.Client
client := opensandbox.NewLifecycleClient(url, key,
opensandbox.WithHTTPClient(myHTTPClient),
)

// Set a custom timeout
client := opensandbox.NewExecdClient(url, token,
opensandbox.WithTimeout(60 * time.Second),
)
```

## Error Handling

Non-2xx responses are returned as `*opensandbox.APIError`:

```go
_, err := lc.GetSandbox(ctx, "nonexistent")
if apiErr, ok := err.(*opensandbox.APIError); ok {
fmt.Printf("HTTP %d: %s — %s\n", apiErr.StatusCode, apiErr.Response.Code, apiErr.Response.Message)
}
```

## License

Apache 2.0
Loading