Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.

Commit 0b3e5f5

Browse files
committed
handle errors correctly in the other tools
1 parent 921f5ee commit 0b3e5f5

File tree

8 files changed

+72
-26
lines changed

8 files changed

+72
-26
lines changed

internal/llm/tools/edit.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ type EditPermissionsParams struct {
2727
}
2828

2929
type EditResponseMetadata struct {
30-
Additions int `json:"additions"`
31-
Removals int `json:"removals"`
30+
Diff string `json:"diff"`
31+
Additions int `json:"additions"`
32+
Removals int `json:"removals"`
3233
}
3334

3435
type editTool struct {
@@ -216,6 +217,7 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
216217
return WithResponseMetadata(
217218
NewTextResponse("File created: "+filePath),
218219
EditResponseMetadata{
220+
Diff: diff,
219221
Additions: stats.Additions,
220222
Removals: stats.Removals,
221223
},
@@ -308,6 +310,7 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
308310
return WithResponseMetadata(
309311
NewTextResponse("Content deleted from file: "+filePath),
310312
EditResponseMetadata{
313+
Diff: diff,
311314
Additions: stats.Additions,
312315
Removals: stats.Removals,
313316
},
@@ -401,6 +404,7 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
401404
return WithResponseMetadata(
402405
NewTextResponse("Content replaced in file: "+filePath),
403406
EditResponseMetadata{
407+
Diff: diff,
404408
Additions: stats.Additions,
405409
Removals: stats.Removals,
406410
}), nil

internal/llm/tools/fetch.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func (t *fetchTool) Info() ToolInfo {
8686
"format": map[string]any{
8787
"type": "string",
8888
"description": "The format to return the content in (text, markdown, or html)",
89+
"enum": []string{"text", "markdown", "html"},
8990
},
9091
"timeout": map[string]any{
9192
"type": "number",
@@ -126,7 +127,7 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
126127
)
127128

128129
if !p {
129-
return NewTextErrorResponse("Permission denied to fetch from URL: " + params.URL), nil
130+
return ToolResponse{}, permission.ErrorPermissionDenied
130131
}
131132

132133
client := t.client
@@ -142,14 +143,14 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
142143

143144
req, err := http.NewRequestWithContext(ctx, "GET", params.URL, nil)
144145
if err != nil {
145-
return NewTextErrorResponse("Failed to create request: " + err.Error()), nil
146+
return ToolResponse{}, fmt.Errorf("failed to create request: %w", err)
146147
}
147148

148149
req.Header.Set("User-Agent", "termai/1.0")
149150

150151
resp, err := client.Do(req)
151152
if err != nil {
152-
return NewTextErrorResponse("Failed to execute request: " + err.Error()), nil
153+
return ToolResponse{}, fmt.Errorf("failed to fetch URL: %w", err)
153154
}
154155
defer resp.Body.Close()
155156

internal/llm/tools/glob.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ type GlobParams struct {
6363
Path string `json:"path"`
6464
}
6565

66+
type GlobMetadata struct {
67+
NumberOfFiles int `json:"number_of_files"`
68+
Truncated bool `json:"truncated"`
69+
}
70+
6671
type globTool struct{}
6772

6873
func NewGlobTool() BaseTool {
@@ -104,7 +109,7 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
104109

105110
files, truncated, err := globFiles(params.Pattern, searchPath, 100)
106111
if err != nil {
107-
return NewTextErrorResponse(fmt.Sprintf("error performing glob search: %s", err)), nil
112+
return ToolResponse{}, fmt.Errorf("error finding files: %w", err)
108113
}
109114

110115
var output string
@@ -117,7 +122,13 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
117122
}
118123
}
119124

120-
return NewTextResponse(output), nil
125+
return WithResponseMetadata(
126+
NewTextResponse(output),
127+
GlobMetadata{
128+
NumberOfFiles: len(files),
129+
Truncated: truncated,
130+
},
131+
), nil
121132
}
122133

123134
func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {

internal/llm/tools/grep.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ type grepMatch struct {
2727
modTime time.Time
2828
}
2929

30+
type GrepMetadata struct {
31+
NumberOfMatches int `json:"number_of_matches"`
32+
Truncated bool `json:"truncated"`
33+
}
34+
3035
type grepTool struct{}
3136

3237
const (
@@ -110,7 +115,7 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
110115

111116
matches, truncated, err := searchFiles(params.Pattern, searchPath, params.Include, 100)
112117
if err != nil {
113-
return NewTextErrorResponse(fmt.Sprintf("error searching files: %s", err)), nil
118+
return ToolResponse{}, fmt.Errorf("error searching files: %w", err)
114119
}
115120

116121
var output string
@@ -127,7 +132,13 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
127132
}
128133
}
129134

130-
return NewTextResponse(output), nil
135+
return WithResponseMetadata(
136+
NewTextResponse(output),
137+
GrepMetadata{
138+
NumberOfMatches: len(matches),
139+
Truncated: truncated,
140+
},
141+
), nil
131142
}
132143

133144
func pluralize(count int) string {

internal/llm/tools/ls.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ type TreeNode struct {
2323
Children []*TreeNode `json:"children,omitempty"`
2424
}
2525

26+
type LSMetadata struct {
27+
NumberOfFiles int `json:"number_of_files"`
28+
Truncated bool `json:"truncated"`
29+
}
30+
2631
type lsTool struct{}
2732

2833
const (
@@ -104,7 +109,7 @@ func (l *lsTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
104109

105110
files, truncated, err := listDirectory(searchPath, params.Ignore, MaxLSFiles)
106111
if err != nil {
107-
return NewTextErrorResponse(fmt.Sprintf("error listing directory: %s", err)), nil
112+
return ToolResponse{}, fmt.Errorf("error listing directory: %w", err)
108113
}
109114

110115
tree := createFileTree(files)
@@ -114,7 +119,13 @@ func (l *lsTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
114119
output = fmt.Sprintf("There are more than %d files in the directory. Use a more specific path or use the Glob tool to find specific files. The first %d files and directories are included below:\n\n%s", MaxLSFiles, MaxLSFiles, output)
115120
}
116121

117-
return NewTextResponse(output), nil
122+
return WithResponseMetadata(
123+
NewTextResponse(output),
124+
LSMetadata{
125+
NumberOfFiles: len(files),
126+
Truncated: truncated,
127+
},
128+
), nil
118129
}
119130

120131
func listDirectory(initialPath string, ignorePatterns []string, limit int) ([]string, bool, error) {

internal/llm/tools/sourcegraph.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ type SourcegraphParams struct {
1818
Timeout int `json:"timeout,omitempty"`
1919
}
2020

21+
type SourcegraphMetadata struct {
22+
NumberOfMatches int `json:"number_of_matches"`
23+
Truncated bool `json:"truncated"`
24+
}
25+
2126
type sourcegraphTool struct {
2227
client *http.Client
2328
}
@@ -198,7 +203,7 @@ func (t *sourcegraphTool) Run(ctx context.Context, call ToolCall) (ToolResponse,
198203

199204
graphqlQueryBytes, err := json.Marshal(request)
200205
if err != nil {
201-
return NewTextErrorResponse("Failed to create GraphQL request: " + err.Error()), nil
206+
return ToolResponse{}, fmt.Errorf("failed to marshal GraphQL request: %w", err)
202207
}
203208
graphqlQuery := string(graphqlQueryBytes)
204209

@@ -209,15 +214,15 @@ func (t *sourcegraphTool) Run(ctx context.Context, call ToolCall) (ToolResponse,
209214
bytes.NewBuffer([]byte(graphqlQuery)),
210215
)
211216
if err != nil {
212-
return NewTextErrorResponse("Failed to create request: " + err.Error()), nil
217+
return ToolResponse{}, fmt.Errorf("failed to create request: %w", err)
213218
}
214219

215220
req.Header.Set("Content-Type", "application/json")
216221
req.Header.Set("User-Agent", "termai/1.0")
217222

218223
resp, err := client.Do(req)
219224
if err != nil {
220-
return NewTextErrorResponse("Failed to execute request: " + err.Error()), nil
225+
return ToolResponse{}, fmt.Errorf("failed to fetch URL: %w", err)
221226
}
222227
defer resp.Body.Close()
223228

@@ -231,12 +236,12 @@ func (t *sourcegraphTool) Run(ctx context.Context, call ToolCall) (ToolResponse,
231236
}
232237
body, err := io.ReadAll(resp.Body)
233238
if err != nil {
234-
return NewTextErrorResponse("Failed to read response body: " + err.Error()), nil
239+
return ToolResponse{}, fmt.Errorf("failed to read response body: %w", err)
235240
}
236241

237242
var result map[string]any
238243
if err = json.Unmarshal(body, &result); err != nil {
239-
return NewTextErrorResponse("Failed to parse response: " + err.Error()), nil
244+
return ToolResponse{}, fmt.Errorf("failed to unmarshal response: %w", err)
240245
}
241246

242247
formattedResults, err := formatSourcegraphResults(result, params.ContextWindow)

internal/llm/tools/view.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func (v *viewTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
135135

136136
return NewTextErrorResponse(fmt.Sprintf("File not found: %s", filePath)), nil
137137
}
138-
return NewTextErrorResponse(fmt.Sprintf("Failed to access file: %s", err)), nil
138+
return ToolResponse{}, fmt.Errorf("error accessing file: %w", err)
139139
}
140140

141141
// Check if it's a directory
@@ -156,14 +156,15 @@ func (v *viewTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
156156

157157
// Check if it's an image file
158158
isImage, imageType := isImageFile(filePath)
159+
// TODO: handle images
159160
if isImage {
160161
return NewTextErrorResponse(fmt.Sprintf("This is an image file of type: %s\nUse a different tool to process images", imageType)), nil
161162
}
162163

163164
// Read the file content
164165
content, lineCount, err := readTextFile(filePath, params.Offset, params.Limit)
165166
if err != nil {
166-
return NewTextErrorResponse(fmt.Sprintf("Failed to read file: %s", err)), nil
167+
return ToolResponse{}, fmt.Errorf("error reading file: %w", err)
167168
}
168169

169170
notifyLspOpenFile(ctx, filePath, v.lspClients)

internal/llm/tools/write.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ type writeTool struct {
3030
}
3131

3232
type WriteResponseMetadata struct {
33-
Additions int `json:"additions"`
34-
Removals int `json:"removals"`
33+
Diff string `json:"diff"`
34+
Additions int `json:"additions"`
35+
Removals int `json:"removals"`
3536
}
3637

3738
const (
@@ -128,12 +129,12 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
128129
return NewTextErrorResponse(fmt.Sprintf("File %s already contains the exact content. No changes made.", filePath)), nil
129130
}
130131
} else if !os.IsNotExist(err) {
131-
return NewTextErrorResponse(fmt.Sprintf("Failed to access file: %s", err)), nil
132+
return ToolResponse{}, fmt.Errorf("error checking file: %w", err)
132133
}
133134

134135
dir := filepath.Dir(filePath)
135136
if err = os.MkdirAll(dir, 0o755); err != nil {
136-
return NewTextErrorResponse(fmt.Sprintf("Failed to create parent directories: %s", err)), nil
137+
return ToolResponse{}, fmt.Errorf("error creating directory: %w", err)
137138
}
138139

139140
oldContent := ""
@@ -146,15 +147,15 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
146147

147148
sessionID, messageID := GetContextValues(ctx)
148149
if sessionID == "" || messageID == "" {
149-
return NewTextErrorResponse("session ID or message ID is missing"), nil
150+
return ToolResponse{}, fmt.Errorf("session_id and message_id are required")
150151
}
151152
diff, stats, err := git.GenerateGitDiffWithStats(
152153
removeWorkingDirectoryPrefix(filePath),
153154
oldContent,
154155
params.Content,
155156
)
156157
if err != nil {
157-
return NewTextErrorResponse(fmt.Sprintf("Failed to get file diff: %s", err)), nil
158+
return ToolResponse{}, fmt.Errorf("error generating diff: %w", err)
158159
}
159160
p := w.permissions.Request(
160161
permission.CreatePermissionRequest{
@@ -169,12 +170,12 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
169170
},
170171
)
171172
if !p {
172-
return NewTextErrorResponse(fmt.Sprintf("Permission denied to create file: %s", filePath)), nil
173+
return ToolResponse{}, permission.ErrorPermissionDenied
173174
}
174175

175176
err = os.WriteFile(filePath, []byte(params.Content), 0o644)
176177
if err != nil {
177-
return NewTextErrorResponse(fmt.Sprintf("Failed to write file: %s", err)), nil
178+
return ToolResponse{}, fmt.Errorf("error writing file: %w", err)
178179
}
179180

180181
recordFileWrite(filePath)
@@ -186,6 +187,7 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
186187
result += getDiagnostics(filePath, w.lspClients)
187188
return WithResponseMetadata(NewTextResponse(result),
188189
WriteResponseMetadata{
190+
Diff: diff,
189191
Additions: stats.Additions,
190192
Removals: stats.Removals,
191193
},

0 commit comments

Comments
 (0)