Skip to content

Commit 27f1663

Browse files
Merge pull request #119 from langchain-ai/ramonn/sandbox-create-without-snapshot
feat: allow sandbox create without snapshot id
2 parents 8d9a35c + 3e237a0 commit 27f1663

4 files changed

Lines changed: 73 additions & 32 deletions

File tree

internal/cmd/issues.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ var severityLabels = map[int]string{
3939

4040
func newProjectIssuesCmd() *cobra.Command {
4141
cmd := &cobra.Command{
42-
Use: "issues",
42+
Use: "issues",
4343
Short: "Manage issues for a tracing project",
4444
Long: `Manage Issues Board issues for a tracing project.
4545
@@ -453,7 +453,7 @@ func truncate(s string, n int) string {
453453

454454
func newProjectIssuesRunsCmd() *cobra.Command {
455455
cmd := &cobra.Command{
456-
Use: "runs",
456+
Use: "runs",
457457
Short: "Manage linked runs for an issue",
458458
Long: `Link and unlink runs to/from an issue.
459459

internal/cmd/message.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func newTraceMessagesCmd() *cobra.Command {
3737
)
3838

3939
cmd := &cobra.Command{
40-
Use: "messages",
40+
Use: "messages",
4141
Short: "Get conversation messages for multiple traces (batch)",
4242
Long: `Get conversation messages for multiple traces in a single request.
4343

internal/cmd/sandbox_box.go

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,41 @@ var sandboxBoxDetailRender = structured.PropertyList{
6161
},
6262
}
6363

64+
func sandboxCreateParams(name string, in *sandboxCreateInput) (langsmith.SandboxBoxNewParams, error) {
65+
memBytes, err := parseByteSize(in.Memory)
66+
if err != nil {
67+
return langsmith.SandboxBoxNewParams{}, fmt.Errorf("invalid --memory: %w", err)
68+
}
69+
70+
params := langsmith.SandboxBoxNewParams{
71+
Name: langsmith.F(name),
72+
Vcpus: langsmith.F(int64(in.VCPUs)),
73+
MemBytes: langsmith.F(memBytes),
74+
}
75+
if in.SnapshotID != "" {
76+
params.SnapshotID = langsmith.F(in.SnapshotID)
77+
}
78+
if in.RootFS != "" {
79+
rootfsBytes, err := parseByteSize(in.RootFS)
80+
if err != nil {
81+
return langsmith.SandboxBoxNewParams{}, fmt.Errorf("invalid --rootfs-capacity: %w", err)
82+
}
83+
params.FsCapacityBytes = langsmith.F(rootfsBytes)
84+
}
85+
if in.ProxyConfig != "" {
86+
pc, err := loadJSONArg(in.ProxyConfig)
87+
if err != nil {
88+
return langsmith.SandboxBoxNewParams{}, fmt.Errorf("invalid --proxy-config: %w", err)
89+
}
90+
params.ProxyConfig = langsmith.Raw[langsmith.SandboxBoxNewParamsProxyConfig](pc)
91+
}
92+
return params, nil
93+
}
94+
6495
var sandboxCreateCommand = structured.Command[*sandboxCreateInput]{
6596
Use: "create <name>",
66-
Short: "Create a sandbox VM from a snapshot",
67-
Long: `Create a sandbox VM from a snapshot.
97+
Short: "Create a sandbox VM",
98+
Long: `Create a sandbox VM.
6899
69100
The --proxy-config flag accepts inline JSON or a file path prefixed with @.
70101
The proxy config controls which outbound HTTP requests the sandbox proxy
@@ -92,6 +123,7 @@ Header types: "plaintext" (literal value), "opaque" (encrypted, hidden in API
92123
responses), "workspace_secret" (resolved from workspace secrets via {KEY}).
93124
94125
Examples:
126+
langsmith sandbox create my-vm
95127
langsmith sandbox create my-vm --snapshot-id <id>
96128
langsmith sandbox create my-vm --snapshot-id <id> --vcpus 4 --memory 1gb
97129
langsmith sandbox create my-vm --snapshot-id <id> --rootfs-capacity 8gb
@@ -102,7 +134,7 @@ Examples:
102134
VCPUs: 2,
103135
Memory: "512mb",
104136
}
105-
cmd.Flags().StringVar(&in.SnapshotID, "snapshot-id", in.SnapshotID, "Snapshot ID to boot from (required)")
137+
cmd.Flags().StringVar(&in.SnapshotID, "snapshot-id", in.SnapshotID, "Snapshot ID to boot from")
106138
cmd.Flags().IntVar(&in.VCPUs, "vcpus", in.VCPUs, "Number of vCPUs")
107139
cmd.Flags().StringVar(&in.Memory, "memory", in.Memory, "Memory with unit (e.g. 512mb, 1gb)")
108140
cmd.Flags().StringVar(&in.RootFS, "rootfs-capacity", in.RootFS, "Root filesystem capacity with unit (e.g. 4gb, 8gb)")
@@ -111,39 +143,15 @@ Examples:
111143
},
112144
Action: func(ctx context.Context, cmd *cobra.Command, in *sandboxCreateInput, args []string) (any, error) {
113145
name := args[0]
114-
if in.SnapshotID == "" {
115-
return nil, fmt.Errorf("--snapshot-id is required")
116-
}
117146

118147
c, err := cmdutil.GetClient(cmd)
119148
if err != nil {
120149
return nil, err
121150
}
122151

123-
memBytes, err := parseByteSize(in.Memory)
152+
params, err := sandboxCreateParams(name, in)
124153
if err != nil {
125-
return nil, fmt.Errorf("invalid --memory: %w", err)
126-
}
127-
128-
params := langsmith.SandboxBoxNewParams{
129-
Name: langsmith.F(name),
130-
SnapshotID: langsmith.F(in.SnapshotID),
131-
Vcpus: langsmith.F(int64(in.VCPUs)),
132-
MemBytes: langsmith.F(memBytes),
133-
}
134-
if in.RootFS != "" {
135-
rootfsBytes, err := parseByteSize(in.RootFS)
136-
if err != nil {
137-
return nil, fmt.Errorf("invalid --rootfs-capacity: %w", err)
138-
}
139-
params.FsCapacityBytes = langsmith.F(rootfsBytes)
140-
}
141-
if in.ProxyConfig != "" {
142-
pc, err := loadJSONArg(in.ProxyConfig)
143-
if err != nil {
144-
return nil, fmt.Errorf("invalid --proxy-config: %w", err)
145-
}
146-
params.ProxyConfig = langsmith.Raw[langsmith.SandboxBoxNewParamsProxyConfig](pc)
154+
return nil, err
147155
}
148156
resp, err := c.SDK.Sandboxes.Boxes.New(ctx, params)
149157
if err != nil {

internal/cmd/sandbox_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
langsmith "github.com/langchain-ai/langsmith-go"
11+
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
1213
)
1314

@@ -262,6 +263,38 @@ func TestSandboxBoxDetailRenderSupportsSDKResponseTypes(t *testing.T) {
262263
}
263264
}
264265

266+
func TestSandboxCreateParams_OmitsEmptySnapshotID(t *testing.T) {
267+
params, err := sandboxCreateParams("my-vm", &sandboxCreateInput{
268+
VCPUs: 2,
269+
Memory: "512mb",
270+
})
271+
require.NoError(t, err)
272+
273+
raw, err := json.Marshal(params)
274+
require.NoError(t, err)
275+
var body map[string]any
276+
require.NoError(t, json.Unmarshal(raw, &body))
277+
278+
assert.NotContains(t, body, "snapshot_id")
279+
assert.Equal(t, "my-vm", body["name"])
280+
}
281+
282+
func TestSandboxCreateParams_IncludesSnapshotIDWhenSet(t *testing.T) {
283+
params, err := sandboxCreateParams("my-vm", &sandboxCreateInput{
284+
SnapshotID: "snap-123",
285+
VCPUs: 2,
286+
Memory: "512mb",
287+
})
288+
require.NoError(t, err)
289+
290+
raw, err := json.Marshal(params)
291+
require.NoError(t, err)
292+
var body map[string]any
293+
require.NoError(t, json.Unmarshal(raw, &body))
294+
295+
assert.Equal(t, "snap-123", body["snapshot_id"])
296+
}
297+
265298
func TestSandboxUpdateCmd_SizeFlags(t *testing.T) {
266299
cmd := sandboxUpdateCommand.Cobra()
267300
for _, name := range []string{"memory", "rootfs-capacity"} {

0 commit comments

Comments
 (0)