|
8 | 8 | "io" |
9 | 9 | "os" |
10 | 10 | "os/exec" |
| 11 | + "path/filepath" |
| 12 | + "sort" |
11 | 13 | "strings" |
12 | 14 | "sync/atomic" |
13 | 15 |
|
@@ -40,12 +42,7 @@ func (e *Engine) runCommand(ctx context.Context, tool types.Tool, input string) |
40 | 42 | return tool.BuiltinFunc(ctx, e.Env, input) |
41 | 43 | } |
42 | 44 |
|
43 | | - var extraEnv []string |
44 | | - if tool.WorkingDir != "" { |
45 | | - extraEnv = append(extraEnv, "GPTSCRIPT_TOOL_DIR="+tool.WorkingDir) |
46 | | - } |
47 | | - |
48 | | - cmd, stop, err := e.newCommand(ctx, extraEnv, tool.Instructions, input) |
| 45 | + cmd, stop, err := e.newCommand(ctx, nil, tool, input) |
49 | 46 | if err != nil { |
50 | 47 | return "", err |
51 | 48 | } |
@@ -74,62 +71,106 @@ func (e *Engine) runCommand(ctx context.Context, tool types.Tool, input string) |
74 | 71 | return output.String(), nil |
75 | 72 | } |
76 | 73 |
|
77 | | -func (e *Engine) newCommand(ctx context.Context, extraEnv []string, instructions, input string) (*exec.Cmd, func(), error) { |
78 | | - env := append(e.Env[:], extraEnv...) |
79 | | - data := map[string]any{} |
80 | | - |
81 | | - dec := json.NewDecoder(bytes.NewReader([]byte(input))) |
82 | | - dec.UseNumber() |
| 74 | +func (e *Engine) getRuntimeEnv(ctx context.Context, tool types.Tool, cmd, env []string) ([]string, error) { |
| 75 | + var ( |
| 76 | + workdir = tool.WorkingDir |
| 77 | + err error |
| 78 | + ) |
| 79 | + if e.RuntimeManager != nil { |
| 80 | + workdir, env, err = e.RuntimeManager.GetContext(ctx, tool, cmd, env) |
| 81 | + if err != nil { |
| 82 | + return nil, err |
| 83 | + } |
| 84 | + workdir = filepath.Join(workdir, tool.Source.Repo.Path) |
| 85 | + } |
| 86 | + return append(env, "GPTSCRIPT_TOOL_DIR="+workdir), nil |
| 87 | +} |
83 | 88 |
|
| 89 | +func envAsMapAndDeDup(env []string) (sortedEnv []string, _ map[string]string) { |
84 | 90 | envMap := map[string]string{} |
| 91 | + var keys []string |
85 | 92 | for _, env := range env { |
86 | 93 | key, value, _ := strings.Cut(env, "=") |
| 94 | + if _, existing := envMap[key]; !existing { |
| 95 | + keys = append(keys, key) |
| 96 | + } |
87 | 97 | envMap[key] = value |
88 | 98 | } |
| 99 | + sort.Strings(keys) |
| 100 | + for _, key := range keys { |
| 101 | + sortedEnv = append(sortedEnv, key+"="+envMap[key]) |
| 102 | + } |
| 103 | + |
| 104 | + return sortedEnv, envMap |
| 105 | +} |
| 106 | + |
| 107 | +var ignoreENV = map[string]struct{}{ |
| 108 | + "PATH": {}, |
| 109 | + "GPTSCRIPT_TOOL_DIR": {}, |
| 110 | +} |
| 111 | + |
| 112 | +func appendEnv(env []string, k, v string) []string { |
| 113 | + for _, k := range []string{k, strings.ToUpper(strings.ReplaceAll(k, "-", "_"))} { |
| 114 | + if _, ignore := ignoreENV[k]; !ignore { |
| 115 | + env = append(env, k+"="+v) |
| 116 | + } |
| 117 | + } |
| 118 | + return env |
| 119 | +} |
| 120 | + |
| 121 | +func appendInputAsEnv(env []string, input string) []string { |
| 122 | + data := map[string]any{} |
| 123 | + dec := json.NewDecoder(bytes.NewReader([]byte(input))) |
| 124 | + dec.UseNumber() |
| 125 | + |
| 126 | + if err := json.Unmarshal([]byte(input), &data); err != nil { |
| 127 | + // ignore invalid JSON |
| 128 | + return env |
| 129 | + } |
89 | 130 |
|
90 | | - if err := json.Unmarshal([]byte(input), &data); err == nil { |
91 | | - for k, v := range data { |
92 | | - envName := strings.ToUpper(strings.ReplaceAll(k, "-", "_")) |
93 | | - switch val := v.(type) { |
94 | | - case string: |
95 | | - envMap[envName] = val |
96 | | - env = append(env, envName+"="+val) |
97 | | - envMap[k] = val |
98 | | - env = append(env, k+"="+val) |
99 | | - case json.Number: |
100 | | - envMap[envName] = string(val) |
101 | | - env = append(env, envName+"="+string(val)) |
102 | | - envMap[k] = string(val) |
103 | | - env = append(env, k+"="+string(val)) |
104 | | - case bool: |
105 | | - envMap[envName] = fmt.Sprint(val) |
106 | | - env = append(env, envName+"="+fmt.Sprint(val)) |
107 | | - envMap[k] = fmt.Sprint(val) |
108 | | - env = append(env, k+"="+fmt.Sprint(val)) |
109 | | - default: |
110 | | - data, err := json.Marshal(val) |
111 | | - if err == nil { |
112 | | - envMap[envName] = string(data) |
113 | | - env = append(env, envName+"="+string(data)) |
114 | | - envMap[k] = string(data) |
115 | | - env = append(env, k+"="+string(data)) |
116 | | - } |
| 131 | + for k, v := range data { |
| 132 | + switch val := v.(type) { |
| 133 | + case string: |
| 134 | + env = appendEnv(env, k, val) |
| 135 | + case json.Number: |
| 136 | + env = appendEnv(env, k, string(val)) |
| 137 | + case bool: |
| 138 | + env = appendEnv(env, k, fmt.Sprint(val)) |
| 139 | + default: |
| 140 | + data, err := json.Marshal(val) |
| 141 | + if err == nil { |
| 142 | + env = appendEnv(env, k, string(data)) |
117 | 143 | } |
118 | 144 | } |
119 | 145 | } |
120 | 146 |
|
121 | | - interpreter, rest, _ := strings.Cut(instructions, "\n") |
122 | | - interpreter = strings.TrimSpace(interpreter)[2:] |
| 147 | + return env |
| 148 | +} |
123 | 149 |
|
124 | | - interpreter = os.Expand(interpreter, func(s string) string { |
125 | | - return envMap[s] |
126 | | - }) |
| 150 | +func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.Tool, input string) (*exec.Cmd, func(), error) { |
| 151 | + env := append(e.Env[:], extraEnv...) |
| 152 | + env = appendInputAsEnv(env, input) |
| 153 | + |
| 154 | + interpreter, rest, _ := strings.Cut(tool.Instructions, "\n") |
| 155 | + interpreter = strings.TrimSpace(interpreter)[2:] |
127 | 156 |
|
128 | 157 | args, err := shlex.Split(interpreter) |
129 | 158 | if err != nil { |
130 | 159 | return nil, nil, err |
131 | 160 | } |
132 | 161 |
|
| 162 | + env, err = e.getRuntimeEnv(ctx, tool, args, env) |
| 163 | + if err != nil { |
| 164 | + return nil, nil, err |
| 165 | + } |
| 166 | + |
| 167 | + env, envMap := envAsMapAndDeDup(env) |
| 168 | + for i, arg := range args { |
| 169 | + args[i] = os.Expand(arg, func(s string) string { |
| 170 | + return envMap[s] |
| 171 | + }) |
| 172 | + } |
| 173 | + |
133 | 174 | var ( |
134 | 175 | cmdArgs = args[1:] |
135 | 176 | stop = func() {} |
|
0 commit comments