@@ -11,7 +11,6 @@ import (
11
11
"os"
12
12
"os/exec"
13
13
"path"
14
- "path/filepath"
15
14
"runtime"
16
15
"sort"
17
16
"strings"
@@ -121,7 +120,7 @@ func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCate
121
120
var extraEnv = []string {
122
121
strings .TrimSpace ("GPTSCRIPT_CONTEXT=" + strings .Join (instructions , "\n " )),
123
122
}
124
- cmd , stop , err := e .newCommand (ctx .Ctx , extraEnv , tool , input )
123
+ cmd , stop , err := e .newCommand (ctx .Ctx , extraEnv , tool , input , true )
125
124
if err != nil {
126
125
if toolCategory == NoCategory {
127
126
return fmt .Sprintf ("ERROR: got (%v) while parsing command" , err ), nil
@@ -244,7 +243,11 @@ func appendInputAsEnv(env []string, input string) []string {
244
243
return env
245
244
}
246
245
247
- func (e * Engine ) newCommand (ctx context.Context , extraEnv []string , tool types.Tool , input string ) (* exec.Cmd , func (), error ) {
246
+ func (e * Engine ) newCommand (ctx context.Context , extraEnv []string , tool types.Tool , input string , useShell bool ) (* exec.Cmd , func (), error ) {
247
+ if runtime .GOOS == "windows" {
248
+ useShell = false
249
+ }
250
+
248
251
envvars := append (e .Env [:], extraEnv ... )
249
252
envvars = appendInputAsEnv (envvars , input )
250
253
if log .IsDebug () {
@@ -254,9 +257,17 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
254
257
interpreter , rest , _ := strings .Cut (tool .Instructions , "\n " )
255
258
interpreter = strings .TrimSpace (interpreter )[2 :]
256
259
257
- args , err := shlex .Split (interpreter )
258
- if err != nil {
259
- return nil , nil , err
260
+ var (
261
+ args []string
262
+ err error
263
+ )
264
+ if useShell {
265
+ args = strings .Fields (interpreter )
266
+ } else {
267
+ args , err = shlex .Split (interpreter )
268
+ if err != nil {
269
+ return nil , nil , err
270
+ }
260
271
}
261
272
262
273
envvars , err = e .getRuntimeEnv (ctx , tool , args , envvars )
@@ -265,17 +276,6 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
265
276
}
266
277
267
278
envvars , envMap := envAsMapAndDeDup (envvars )
268
- for i , arg := range args {
269
- args [i ] = os .Expand (arg , func (s string ) string {
270
- return envMap [s ]
271
- })
272
- }
273
-
274
- // After we determined the interpreter we again interpret the args by env vars
275
- args , err = replaceVariablesForInterpreter (interpreter , envMap )
276
- if err != nil {
277
- return nil , nil , err
278
- }
279
279
280
280
if runtime .GOOS == "windows" && (args [0 ] == "/bin/bash" || args [0 ] == "/bin/sh" ) {
281
281
args [0 ] = path .Base (args [0 ])
@@ -286,8 +286,7 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
286
286
}
287
287
288
288
var (
289
- cmdArgs = args [1 :]
290
- stop = func () {}
289
+ stop = func () {}
291
290
)
292
291
293
292
if strings .TrimSpace (rest ) != "" {
@@ -305,105 +304,33 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
305
304
stop ()
306
305
return nil , nil , err
307
306
}
308
- cmdArgs = append (cmdArgs , f .Name ())
309
- }
310
-
311
- // This is a workaround for Windows, where the command interpreter is constructed with unix style paths
312
- // It converts unix style paths to windows style paths
313
- if runtime .GOOS == "windows" {
314
- parts := strings .Split (args [0 ], "/" )
315
- if parts [len (parts )- 1 ] == "gptscript-go-tool" {
316
- parts [len (parts )- 1 ] = "gptscript-go-tool.exe"
317
- }
318
-
319
- args [0 ] = filepath .Join (parts ... )
307
+ args = append (args , f .Name ())
320
308
}
321
309
322
- cmd := exec .CommandContext (ctx , env .Lookup (envvars , args [0 ]), cmdArgs ... )
323
- cmd .Env = compressEnv (envvars )
324
- return cmd , stop , nil
325
- }
326
-
327
- func replaceVariablesForInterpreter (interpreter string , envMap map [string ]string ) ([]string , error ) {
328
- var parts []string
329
- for i , part := range splitByQuotes (interpreter ) {
330
- if i % 2 == 0 {
331
- part = os .Expand (part , func (s string ) string {
310
+ // Expand and/or normalize env references
311
+ for i , arg := range args {
312
+ args [i ] = os .Expand (arg , func (s string ) string {
313
+ if strings .HasPrefix (s , "!" ) {
314
+ return envMap [s [1 :]]
315
+ }
316
+ if ! useShell {
332
317
return envMap [s ]
333
- })
334
- // We protect newly resolved env vars from getting replaced when we do the second Expand
335
- // after shlex. Yeah, crazy. I'm guessing this isn't secure, but just trying to avoid a foot gun.
336
- part = os .Expand (part , func (s string ) string {
337
- return "${__" + s + "}"
338
- })
339
- }
340
- parts = append (parts , part )
341
- }
342
-
343
- parts , err := shlex .Split (strings .Join (parts , "" ))
344
- if err != nil {
345
- return nil , err
346
- }
347
-
348
- for i , part := range parts {
349
- parts [i ] = os .Expand (part , func (s string ) string {
350
- if strings .HasPrefix (s , "__" ) {
351
- return "${" + s [2 :] + "}"
352
318
}
353
- return envMap [ s ]
319
+ return "${" + s + "}"
354
320
})
355
321
}
356
322
357
- return parts , nil
358
- }
359
-
360
- // splitByQuotes will split a string by parsing matching double quotes (with \ as the escape character).
361
- // The return value conforms to the following properties
362
- // 1. s == strings.Join(result, "")
363
- // 2. Even indexes are strings that were not in quotes.
364
- // 3. Odd indexes are strings that were quoted.
365
- //
366
- // Example: s = `In a "quoted string" quotes can be escaped with \"`
367
- //
368
- // result = [`In a `, `"quoted string"`, ` quotes can be escaped with \"`]
369
- func splitByQuotes (s string ) (result []string ) {
370
- var (
371
- buf strings.Builder
372
- inEscape , inQuote bool
373
- )
374
-
375
- for _ , c := range s {
376
- if inEscape {
377
- buf .WriteRune (c )
378
- inEscape = false
379
- continue
380
- }
381
-
382
- switch c {
383
- case '"' :
384
- if inQuote {
385
- buf .WriteRune (c )
386
- }
387
- result = append (result , buf .String ())
388
- buf .Reset ()
389
- if ! inQuote {
390
- buf .WriteRune (c )
391
- }
392
- inQuote = ! inQuote
393
- case '\\' :
394
- inEscape = true
395
- buf .WriteRune (c )
396
- default :
397
- buf .WriteRune (c )
398
- }
323
+ if runtime .GOOS == "windows" {
324
+ args [0 ] = strings .ReplaceAll (args [0 ], "/" , "\\ " )
399
325
}
400
326
401
- if buf .Len () > 0 {
402
- if inQuote {
403
- result = append (result , "" )
404
- }
405
- result = append (result , buf .String ())
327
+ if useShell {
328
+ args = append ([]string {"/bin/sh" , "-c" }, strings .Join (args , " " ))
329
+ } else {
330
+ args [0 ] = env .Lookup (envvars , args [0 ])
406
331
}
407
332
408
- return
333
+ cmd := exec .CommandContext (ctx , args [0 ], args [1 :]... )
334
+ cmd .Env = compressEnv (envvars )
335
+ return cmd , stop , nil
409
336
}
0 commit comments