@@ -3,6 +3,8 @@ package toolsdk
3
3
import (
4
4
"bytes"
5
5
"context"
6
+ _ "embed"
7
+ "encoding/base64"
6
8
"errors"
7
9
"fmt"
8
10
"io"
@@ -18,19 +20,24 @@ import (
18
20
"github.com/coder/coder/v2/cli/cliui"
19
21
"github.com/coder/coder/v2/codersdk"
20
22
"github.com/coder/coder/v2/codersdk/workspacesdk"
23
+ "github.com/coder/coder/v2/cryptorand"
21
24
)
22
25
23
26
type WorkspaceBashArgs struct {
24
- Workspace string `json:"workspace"`
25
- Command string `json:"command"`
26
- TimeoutMs int `json:"timeout_ms,omitempty"`
27
+ Workspace string `json:"workspace"`
28
+ Command string `json:"command"`
29
+ TimeoutMs int `json:"timeout_ms,omitempty"`
30
+ Background bool `json:"background,omitempty"`
27
31
}
28
32
29
33
type WorkspaceBashResult struct {
30
34
Output string `json:"output"`
31
35
ExitCode int `json:"exit_code"`
32
36
}
33
37
38
+ //go:embed resources/background.sh
39
+ var backgroundScript string
40
+
34
41
var WorkspaceBash = Tool [WorkspaceBashArgs , WorkspaceBashResult ]{
35
42
Tool : aisdk.Tool {
36
43
Name : ToolNameWorkspaceBash ,
@@ -53,6 +60,7 @@ If the command times out, all output captured up to that point is returned with
53
60
Examples:
54
61
- workspace: "my-workspace", command: "ls -la"
55
62
- workspace: "john/dev-env", command: "git status", timeout_ms: 30000
63
+ - workspace: "my-workspace", command: "npm run dev", background: true
56
64
- workspace: "my-workspace.main", command: "docker ps"` ,
57
65
Schema : aisdk.Schema {
58
66
Properties : map [string ]any {
@@ -70,6 +78,10 @@ Examples:
70
78
"default" : 60000 ,
71
79
"minimum" : 1 ,
72
80
},
81
+ "background" : map [string ]any {
82
+ "type" : "boolean" ,
83
+ "description" : "Whether to run the command in the background. The command will not be affected by the timeout." ,
84
+ },
73
85
},
74
86
Required : []string {"workspace" , "command" },
75
87
},
@@ -137,16 +149,34 @@ Examples:
137
149
138
150
// Set default timeout if not specified (60 seconds)
139
151
timeoutMs := args .TimeoutMs
152
+ defaultTimeoutMs := 60000
140
153
if timeoutMs <= 0 {
141
- timeoutMs = 60000
154
+ timeoutMs = defaultTimeoutMs
155
+ }
156
+ command := args .Command
157
+ if args .Background {
158
+ // Background commands are not affected by the timeout
159
+ timeoutMs = defaultTimeoutMs
160
+ encodedCommand := base64 .StdEncoding .EncodeToString ([]byte (args .Command ))
161
+ encodedScript := base64 .StdEncoding .EncodeToString ([]byte (backgroundScript ))
162
+ commandID , err := cryptorand .StringCharset (cryptorand .Human , 8 )
163
+ if err != nil {
164
+ return WorkspaceBashResult {}, xerrors .Errorf ("failed to generate command ID: %w" , err )
165
+ }
166
+ command = fmt .Sprintf (
167
+ "ARG_COMMAND=\" $(echo -n %s | base64 -d)\" ARG_COMMAND_ID=%s bash -c \" $(echo -n %s | base64 -d)\" " ,
168
+ encodedCommand ,
169
+ commandID ,
170
+ encodedScript ,
171
+ )
142
172
}
143
173
144
174
// Create context with timeout
145
175
ctx , cancel = context .WithTimeout (ctx , time .Duration (timeoutMs )* time .Millisecond )
146
176
defer cancel ()
147
177
148
178
// Execute command with timeout handling
149
- output , err := executeCommandWithTimeout (ctx , session , args . Command )
179
+ output , err := executeCommandWithTimeout (ctx , session , command )
150
180
outputStr := strings .TrimSpace (string (output ))
151
181
152
182
// Handle command execution results
0 commit comments