@@ -11,13 +11,11 @@ import (
1111 "os"
1212 "os/exec"
1313 "path"
14- "path/filepath"
1514 "runtime"
1615 "sort"
1716 "strings"
1817 "sync"
1918
20- "github.com/google/shlex"
2119 "github.com/gptscript-ai/gptscript/pkg/counter"
2220 "github.com/gptscript-ai/gptscript/pkg/env"
2321 "github.com/gptscript-ai/gptscript/pkg/types"
@@ -254,28 +252,14 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
254252 interpreter , rest , _ := strings .Cut (tool .Instructions , "\n " )
255253 interpreter = strings .TrimSpace (interpreter )[2 :]
256254
257- args , err := shlex .Split (interpreter )
258- if err != nil {
259- return nil , nil , err
260- }
255+ args := strings .Fields (interpreter )
261256
262- envvars , err = e .getRuntimeEnv (ctx , tool , args , envvars )
257+ envvars , err : = e .getRuntimeEnv (ctx , tool , args , envvars )
263258 if err != nil {
264259 return nil , nil , err
265260 }
266261
267262 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- }
279263
280264 if runtime .GOOS == "windows" && (args [0 ] == "/bin/bash" || args [0 ] == "/bin/sh" ) {
281265 args [0 ] = path .Base (args [0 ])
@@ -286,8 +270,7 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
286270 }
287271
288272 var (
289- cmdArgs = args [1 :]
290- stop = func () {}
273+ stop = func () {}
291274 )
292275
293276 if strings .TrimSpace (rest ) != "" {
@@ -305,105 +288,30 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
305288 stop ()
306289 return nil , nil , err
307290 }
308- cmdArgs = append (cmdArgs , f .Name ())
291+ args = append (args , f .Name ())
309292 }
310293
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 ... )
320- }
321-
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 {
332- 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- }
353- return envMap [s ]
354- })
355- }
356-
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 )
294+ // translate UNIX style to Windows
295+ for i , arg := range args {
296+ args [i ] = os .Expand (arg , func (s string ) string {
297+ if strings .HasPrefix (s , "!" ) {
298+ return envMap [s [1 :]]
386299 }
387- result = append (result , buf .String ())
388- buf .Reset ()
389- if ! inQuote {
390- buf .WriteRune (c )
300+ if runtime .GOOS == "windows" {
301+ return "%" + s + "%"
391302 }
392- inQuote = ! inQuote
393- case '\\' :
394- inEscape = true
395- buf .WriteRune (c )
396- default :
397- buf .WriteRune (c )
398- }
303+ return "${" + s + "}"
304+ })
399305 }
400306
401- if buf . Len () > 0 {
402- if inQuote {
403- result = append (result , "" )
404- }
405- result = append (result , buf . String ( ))
307+ if runtime . GOOS == "windows" {
308+ args [ 0 ] = strings . ReplaceAll ( args [ 0 ], "/" , " \\ " )
309+ args = append ([] string { "cmd.exe" , "/c" }, strings . Join ( args , " " ) )
310+ } else {
311+ args = append ([] string { "/bin/sh" , "-c" }, strings . Join ( args , " " ))
406312 }
407313
408- return
314+ cmd := exec .CommandContext (ctx , args [0 ], args [1 :]... )
315+ cmd .Env = compressEnv (envvars )
316+ return cmd , stop , nil
409317}
0 commit comments