executeTool is the switch that runs whatever the model asked for. The (string, bool) return — content plus is-error flag — is the "errors as tool results" contract from chapter 02: failures travel back to the model so it can recover, instead of crashing the loop.
(name, rawInput string)
| 130 | // tool results" contract from chapter 02: failures travel back to the |
| 131 | // model so it can recover, instead of crashing the loop. |
| 132 | func executeTool(name, rawInput string) (string, bool) { |
| 133 | fmt.Printf("[tool] %s %s\n", name, rawInput) |
| 134 | switch name { |
| 135 | case "bash": |
| 136 | var in struct { |
| 137 | Command string `json:"command"` |
| 138 | } |
| 139 | if err := json.Unmarshal([]byte(rawInput), &in); err != nil { |
| 140 | return err.Error(), true |
| 141 | } |
| 142 | out, err := exec.Command("sh", "-c", in.Command).CombinedOutput() |
| 143 | if err != nil { |
| 144 | return fmt.Sprintf("%s\n[exit error: %v]", out, err), true |
| 145 | } |
| 146 | return string(out), false |
| 147 | |
| 148 | case "read_file": |
| 149 | var in struct { |
| 150 | Path string `json:"path"` |
| 151 | } |
| 152 | if err := json.Unmarshal([]byte(rawInput), &in); err != nil { |
| 153 | return err.Error(), true |
| 154 | } |
| 155 | data, err := os.ReadFile(in.Path) |
| 156 | if err != nil { |
| 157 | return err.Error(), true |
| 158 | } |
| 159 | return string(data), false |
| 160 | |
| 161 | case "write_file": |
| 162 | var in struct { |
| 163 | Path string `json:"path"` |
| 164 | Content string `json:"content"` |
| 165 | } |
| 166 | if err := json.Unmarshal([]byte(rawInput), &in); err != nil { |
| 167 | return err.Error(), true |
| 168 | } |
| 169 | if err := os.WriteFile(in.Path, []byte(in.Content), 0644); err != nil { |
| 170 | return err.Error(), true |
| 171 | } |
| 172 | return "wrote " + in.Path, false |
| 173 | |
| 174 | default: |
| 175 | return fmt.Sprintf("unknown tool: %s", name), true |
| 176 | } |
| 177 | } |