diff --git a/packages/cli/package.json b/packages/cli/package.json
index 7ed9cfb3..b8a65708 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@abox-dev/cli",
- "version": "0.1.3",
+ "version": "0.1.4",
"description": "CLI for AgentBox sandboxes and templates",
"homepage": "https://docs.agentbox.ru/en/cli/",
"license": "MIT",
diff --git a/packages/code-interpreter-js/package.json b/packages/code-interpreter-js/package.json
index f089f490..1dfa7308 100644
--- a/packages/code-interpreter-js/package.json
+++ b/packages/code-interpreter-js/package.json
@@ -1,6 +1,6 @@
{
"name": "@abox-dev/code-interpreter",
- "version": "0.1.3",
+ "version": "0.1.4",
"packageManager": "pnpm@10.34.5",
"description": "AgentBox Code Interpreter - Stateful code execution",
"homepage": "https://docs.agentbox.ru/en/sdk/code-interpreter/",
diff --git a/packages/code-interpreter-python/package.json b/packages/code-interpreter-python/package.json
index 1518cb9f..86e190e0 100644
--- a/packages/code-interpreter-python/package.json
+++ b/packages/code-interpreter-python/package.json
@@ -1,7 +1,7 @@
{
"name": "@abox-dev/code-interpreter-python",
"private": true,
- "version": "0.1.3",
+ "version": "0.1.4",
"scripts": {
"test": "uv run pytest -n 2 --verbose -x tests/test_sandbox_url.py",
"test:integration": "uv run pytest -n 2 --verbose -x",
diff --git a/packages/code-interpreter-python/pyproject.toml b/packages/code-interpreter-python/pyproject.toml
index 9639824b..52ae57a5 100644
--- a/packages/code-interpreter-python/pyproject.toml
+++ b/packages/code-interpreter-python/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "abox-code-interpreter"
-version = "0.1.3"
+version = "0.1.4"
description = "AgentBox Code Interpreter - Stateful code execution"
authors = [{ name = "RetailDriver LLC" }]
license = "MIT"
diff --git a/packages/code-interpreter-python/uv.lock b/packages/code-interpreter-python/uv.lock
index 3bb7fd06..158f9462 100644
--- a/packages/code-interpreter-python/uv.lock
+++ b/packages/code-interpreter-python/uv.lock
@@ -16,7 +16,7 @@ members = [
[[package]]
name = "abox-code-interpreter"
-version = "0.1.3"
+version = "0.1.4"
source = { editable = "." }
dependencies = [
{ name = "abox-sdk" },
@@ -58,7 +58,7 @@ dev = [
[[package]]
name = "abox-sdk"
-version = "0.1.3"
+version = "0.1.4"
source = { editable = "../python-sdk" }
dependencies = [
{ name = "attrs" },
diff --git a/packages/go-sdk/README.md b/packages/go-sdk/README.md
index de3b72e6..8c777361 100644
--- a/packages/go-sdk/README.md
+++ b/packages/go-sdk/README.md
@@ -44,8 +44,8 @@ func main() {
Code Interpreter is available from
`github.com/abox-dev/sdk/packages/go-sdk/codeinterpreter`.
-API reference for this release: [core SDK on pkg.go.dev](https://pkg.go.dev/github.com/abox-dev/sdk/packages/go-sdk@v0.1.3) and
-[Code Interpreter on pkg.go.dev](https://pkg.go.dev/github.com/abox-dev/sdk/packages/go-sdk/codeinterpreter@v0.1.3).
+API reference for this release: [core SDK on pkg.go.dev](https://pkg.go.dev/github.com/abox-dev/sdk/packages/go-sdk@v0.1.4) and
+[Code Interpreter on pkg.go.dev](https://pkg.go.dev/github.com/abox-dev/sdk/packages/go-sdk/codeinterpreter@v0.1.4).
Documentation: [core SDK](https://docs.agentbox.ru/en/sdk/),
[sandboxes](https://docs.agentbox.ru/en/sdk/sandboxes/),
diff --git a/packages/go-sdk/template.go b/packages/go-sdk/template.go
index 9280c8aa..6161f1d3 100644
--- a/packages/go-sdk/template.go
+++ b/packages/go-sdk/template.go
@@ -341,7 +341,11 @@ func (builder *TemplateBuilder) Run(commands ...string) *TemplateBuilder {
}
// RunAs adds shell commands executed as user.
+// An empty user inherits the current build user, as Run does.
func (builder *TemplateBuilder) RunAs(user string, commands ...string) *TemplateBuilder {
+ if user == "" {
+ return builder.Run(commands...)
+ }
return builder.add("RUN", strings.Join(commands, " && "), user)
}
diff --git a/packages/go-sdk/template_test.go b/packages/go-sdk/template_test.go
index b52ffc82..7d0876d4 100644
--- a/packages/go-sdk/template_test.go
+++ b/packages/go-sdk/template_test.go
@@ -2,17 +2,84 @@ package agentbox
import (
"context"
+ "encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
+ "slices"
"strings"
"testing"
"time"
)
+func TestTemplateRunUserArguments(t *testing.T) {
+ tests := []struct {
+ name string
+ apply func(*TemplateBuilder) *TemplateBuilder
+ args []string
+ }{
+ {"run-as-current", func(b *TemplateBuilder) *TemplateBuilder { return b.RunAs("", "echo one", "echo two") }, []string{"echo one && echo two"}},
+ {"run-as-explicit", func(b *TemplateBuilder) *TemplateBuilder { return b.RunAs("root", "id -un") }, []string{"id -un", "root"}},
+ {"npm-default", func(b *TemplateBuilder) *TemplateBuilder { return b.NPMInstall(PackageInstallOptions{}, "lodash") }, []string{"npm install lodash"}},
+ {"npm-dev", func(b *TemplateBuilder) *TemplateBuilder {
+ return b.NPMInstall(PackageInstallOptions{Dev: true}, "typescript")
+ }, []string{"npm install --save-dev typescript"}},
+ {"npm-global", func(b *TemplateBuilder) *TemplateBuilder {
+ return b.NPMInstall(PackageInstallOptions{Global: true}, "tsx")
+ }, []string{"npm install -g tsx", "root"}},
+ {"bun-default", func(b *TemplateBuilder) *TemplateBuilder { return b.BunInstall(PackageInstallOptions{}, "lodash") }, []string{"bun install lodash"}},
+ {"bun-dev", func(b *TemplateBuilder) *TemplateBuilder {
+ return b.BunInstall(PackageInstallOptions{Dev: true}, "typescript")
+ }, []string{"bun install --dev typescript"}},
+ {"bun-global", func(b *TemplateBuilder) *TemplateBuilder {
+ return b.BunInstall(PackageInstallOptions{Global: true}, "tsx")
+ }, []string{"bun install -g tsx", "root"}},
+ {"git-default", func(b *TemplateBuilder) *TemplateBuilder { return b.GitClone("https://example.test/repo.git", nil) }, []string{"git clone 'https://example.test/repo.git'"}},
+ {"git-options", func(b *TemplateBuilder) *TemplateBuilder {
+ return b.GitClone("https://example.test/repo.git", &GitCloneOptions{Path: "repo", Depth: 1})
+ }, []string{"git clone 'https://example.test/repo.git' --depth 1 'repo'"}},
+ {"git-explicit", func(b *TemplateBuilder) *TemplateBuilder {
+ return b.GitClone("https://example.test/repo.git", &GitCloneOptions{User: "root"})
+ }, []string{"git clone 'https://example.test/repo.git'", "root"}},
+ }
+ for _, currentUser := range []string{"", "app"} {
+ for _, test := range tests {
+ t.Run(currentUser+"/"+test.name, func(t *testing.T) {
+ builder := NewTemplate("").FromBase()
+ if currentUser != "" {
+ builder.User(currentUser)
+ }
+ data, err := test.apply(builder).JSON()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var request struct {
+ Steps []struct {
+ Type string `json:"type"`
+ Args []string `json:"args"`
+ } `json:"steps"`
+ }
+ if err := json.Unmarshal(data, &request); err != nil {
+ t.Fatal(err)
+ }
+ if len(request.Steps) == 0 {
+ t.Fatal("missing build steps")
+ }
+ step := request.Steps[len(request.Steps)-1]
+ if step.Type != "RUN" || !slices.Equal(step.Args, test.args) {
+ t.Fatalf("RUN arguments = %#v, want %#v (type %q)", step.Args, test.args, step.Type)
+ }
+ if currentUser != "" && (request.Steps[0].Type != "USER" || !slices.Equal(request.Steps[0].Args, []string{currentUser})) {
+ t.Fatal("configured build user was not preserved")
+ }
+ })
+ }
+ }
+}
+
func TestTemplateBuilder(t *testing.T) {
contextPath := t.TempDir()
if err := os.WriteFile(filepath.Join(contextPath, "hello.txt"), []byte("hello"), 0o600); err != nil {
diff --git a/packages/go-sdk/version.go b/packages/go-sdk/version.go
index 394e3c58..77627826 100644
--- a/packages/go-sdk/version.go
+++ b/packages/go-sdk/version.go
@@ -1,4 +1,4 @@
package agentbox
// Version is the AgentBox SDK release version.
-const Version = "0.1.3"
+const Version = "0.1.4"
diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json
index d4acbafd..80beef65 100644
--- a/packages/js-sdk/package.json
+++ b/packages/js-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@abox-dev/sdk",
- "version": "0.1.3",
+ "version": "0.1.4",
"description": "AgentBox SDK for secure cloud sandboxes",
"homepage": "https://docs.agentbox.ru/en/sdk/",
"license": "MIT",
diff --git a/packages/python-sdk/package.json b/packages/python-sdk/package.json
index 89cf3bcd..811e3b7a 100644
--- a/packages/python-sdk/package.json
+++ b/packages/python-sdk/package.json
@@ -1,7 +1,7 @@
{
"name": "@abox-dev/python-sdk",
"private": true,
- "version": "0.1.3",
+ "version": "0.1.4",
"scripts": {
"test": "uv run pytest -n 4 --verbose -x tests/test_*.py tests/shared",
"test:integration": "uv run pytest -n 4 --verbose -x tests/sync tests/async",
diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml
index 144886e6..3cb453c9 100644
--- a/packages/python-sdk/pyproject.toml
+++ b/packages/python-sdk/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "abox-sdk"
-version = "0.1.3"
+version = "0.1.4"
description = "AgentBox SDK for secure cloud sandboxes"
authors = [{ name = "RetailDriver LLC" }]
license = "MIT"
diff --git a/packages/python-sdk/uv.lock b/packages/python-sdk/uv.lock
index f182f9ee..6d1646f2 100644
--- a/packages/python-sdk/uv.lock
+++ b/packages/python-sdk/uv.lock
@@ -8,7 +8,7 @@ resolution-markers = [
[[package]]
name = "abox-sdk"
-version = "0.1.3"
+version = "0.1.4"
source = { editable = "." }
dependencies = [
{ name = "attrs" },
diff --git a/reference/manifest.json b/reference/manifest.json
index 824b4307..88308a90 100644
--- a/reference/manifest.json
+++ b/reference/manifest.json
@@ -45,7 +45,7 @@
"sdk/cli/sandbox.md": "16b64c5a4e932eca2452e400e0faf154eba1d3c7373513aa7cd9744e02908313",
"sdk/cli/template.md": "f270b22b9ee10a9b954a14f24e04c5449b4d5823bb28002ffde3ed682594cfc5",
"sdk/go/code-interpreter.md": "9c56333b8181878d656baa27a4b56b768a0cb01c9d764f7ac8d52d32d8faa70c",
- "sdk/go/core.md": "ab075a4a3fbafaefb21e58596311fecb24e614240827c7a322a008c3ac06df1c",
+ "sdk/go/core.md": "41d700b3837e9aa7932779ba44186aaf0c54a6927ebaafa7945b0037b76db49e",
"sdk/javascript/code-interpreter/README.md": "a787206eb86f81fc306b373ce20bc6c91464b0ab3c3ec7650eccbb6be5f676c5",
"sdk/javascript/code-interpreter/classes/Sandbox.md": "19d9f1f1e8847606a54d93c5a6e5d4c89f936a745866e8d6a2aa06758b605e60",
"sdk/javascript/code-interpreter/enumerations/ChartType.md": "cf1ba00cd3258cc88814b7215e101b0d775ceb15dd833636d58ae538d8107ec1",
@@ -184,12 +184,12 @@
},
"monoRevision": "559cf23aa80d22eeb1bd2769d4ab6646b8e40fcb",
"packages": {
- "@abox-dev/cli": "0.1.3",
- "@abox-dev/code-interpreter": "0.1.3",
- "@abox-dev/sdk": "0.1.3",
- "abox-code-interpreter": "0.1.3",
- "abox-sdk": "0.1.3",
- "github.com/abox-dev/sdk/packages/go-sdk": "0.1.3"
+ "@abox-dev/cli": "0.1.4",
+ "@abox-dev/code-interpreter": "0.1.4",
+ "@abox-dev/sdk": "0.1.4",
+ "abox-code-interpreter": "0.1.4",
+ "abox-sdk": "0.1.4",
+ "github.com/abox-dev/sdk/packages/go-sdk": "0.1.4"
},
"schemaVersion": 1
}
diff --git a/reference/sdk/go/core.md b/reference/sdk/go/core.md
index 3fe980c2..f991541b 100644
--- a/reference/sdk/go/core.md
+++ b/reference/sdk/go/core.md
@@ -261,7 +261,7 @@ Create a client, start a sandbox, and run a command:
Version is the AgentBox SDK release version.
- const Version = "0.1.3"
+ const Version = "0.1.4"
## func IAMTokenPlaceholder
@@ -1927,7 +1927,7 @@ Run adds shell commands executed as the current build user.
func (builder *TemplateBuilder) RunAs(user string, commands ...string) *TemplateBuilder
-RunAs adds shell commands executed as user.
+RunAs adds shell commands executed as user. An empty user inherits the current build user, as Run does.
### func \(\*TemplateBuilder\) SkipCache
diff --git a/tests/runtime/go/main.go b/tests/runtime/go/main.go
index cb107085..290b83c4 100644
--- a/tests/runtime/go/main.go
+++ b/tests/runtime/go/main.go
@@ -19,6 +19,7 @@ func main() {
must(err)
sandbox, err := core.Sandboxes.Create(ctx, &agentbox.CreateSandboxOptions{Timeout: 5 * time.Minute})
must(err)
+ defer func() { _, _ = sandbox.Kill(context.Background()) }()
result, err := sandbox.Commands.Run(ctx, "printf", &agentbox.CommandOptions{Args: []string{"go-runtime-smoke"}})
must(err)
if string(result.Stdout) != "go-runtime-smoke" {
@@ -34,6 +35,7 @@ func main() {
must(err)
codeSandbox, err := interpreter.Create(ctx, &agentbox.CreateSandboxOptions{Timeout: 5 * time.Minute})
must(err)
+ defer func() { _, _ = codeSandbox.Kill(context.Background()) }()
execution, err := codeSandbox.RunCode(ctx, "40 + 2", nil)
must(err)
if !strings.Contains(execution.Text(), "42") {
@@ -44,6 +46,43 @@ func main() {
if !killed {
panic("code interpreter sandbox was not killed")
}
+ templateUserSmoke(ctx, core)
+}
+
+func templateUserSmoke(ctx context.Context, client *agentbox.Client) {
+ name := fmt.Sprintf("sdk-go-user-smoke-%d", time.Now().UnixNano())
+ builder := agentbox.NewTemplate("").FromTemplate("base").User("user").Workdir("/home/user").
+ RunAs("", "test \"$(id -un)\" = user", "mkdir -p sdk-user-smoke", "printf '{\"name\":\"sdk-user-smoke\",\"version\":\"1.0.0\"}' >sdk-user-smoke/package.json", "git init --bare sdk-user-smoke/source.git").
+ Workdir("/home/user/sdk-user-smoke").
+ NPMInstall(agentbox.PackageInstallOptions{}).
+ GitClone("/home/user/sdk-user-smoke/source.git", &agentbox.GitCloneOptions{Path: "checkout"}).
+ RunAs("root", "test \"$(id -un)\" = root").
+ Run("test \"$(id -un)\" = user", "test -f package-lock.json", "test -d checkout/.git")
+ reference, err := client.Templates.BuildInBackground(ctx, builder, name, nil)
+ must(err)
+ defer func() {
+ cleanupCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
+ defer cancel()
+ must(client.Templates.Delete(cleanupCtx, reference.TemplateID))
+ }()
+ for {
+ status, err := client.Templates.BuildStatus(ctx, reference.TemplateID, reference.BuildID, 0)
+ must(err)
+ if status.Status == agentbox.BuildReady {
+ fmt.Println("Go template user inheritance smoke passed")
+ return
+ }
+ if status.Status == agentbox.BuildFailed {
+ panic(fmt.Sprintf("template user smoke failed: %+v", status.Reason))
+ }
+ timer := time.NewTimer(time.Second)
+ select {
+ case <-ctx.Done():
+ timer.Stop()
+ panic(ctx.Err())
+ case <-timer.C:
+ }
+ }
}
func must(err error) {