Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions e2e/testdata/cassettes/TestExec_Anthropic_ToolCall.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions e2e/testdata/cassettes/TestExec_Gemini_ToolCall.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions e2e/testdata/cassettes/TestExec_Mistral_ToolCall.yaml

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions e2e/testdata/cassettes/TestExec_OpenAI_HideToolCalls.yaml

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions e2e/testdata/cassettes/TestExec_OpenAI_ToolCall.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interactions:
proto_minor: 1
content_length: 0
host: api.openai.com
body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"}],"model":"gpt-5-mini","tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}'
body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations\n\n### Reading Large Files\n- read_file and read_multiple_files support offset and line_count parameters for pagination\n- When a file is large, read it in chunks: start with offset=1 and a reasonable line_count\n- The response includes total_lines so you know how many more lines remain\n- Continue reading with an incremented offset until you have read all required content","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"}],"model":"gpt-5-mini","tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}'
url: https://api.openai.com/v1/responses
method: POST
response:
Expand Down Expand Up @@ -91,7 +91,7 @@ interactions:
proto_minor: 1
content_length: 0
host: api.openai.com
body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"},{"arguments":"{\"content\":\"Hello, World!\",\"path\":\"hello.txt\"}","call_id":"call_5W18F6XkDh9NllAH9r0P9GuF","name":"write_file","type":"function_call"},{"call_id":"call_5W18F6XkDh9NllAH9r0P9GuF","output":"The user rejected the tool call.","type":"function_call_output"}],"model":"gpt-5-mini","tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}'
body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations\n\n### Reading Large Files\n- read_file and read_multiple_files support offset and line_count parameters for pagination\n- When a file is large, read it in chunks: start with offset=1 and a reasonable line_count\n- The response includes total_lines so you know how many more lines remain\n- Continue reading with an incremented offset until you have read all required content","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"},{"arguments":"{\"content\":\"Hello, World!\",\"path\":\"hello.txt\"}","call_id":"call_5W18F6XkDh9NllAH9r0P9GuF","name":"write_file","type":"function_call"},{"call_id":"call_5W18F6XkDh9NllAH9r0P9GuF","output":"The user rejected the tool call.","type":"function_call_output"}],"model":"gpt-5-mini","tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}'
url: https://api.openai.com/v1/responses
method: POST
response:
Expand Down
72 changes: 58 additions & 14 deletions pkg/tools/builtin/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ This toolset provides comprehensive filesystem operations.
### Performance Tips
- Use read_multiple_files instead of multiple read_file calls
- Use directory_tree with max_depth to limit large traversals
- Use appropriate exclude patterns in search operations`
- Use appropriate exclude patterns in search operations

### Reading Large Files
- read_file and read_multiple_files support offset and line_count parameters for pagination
- When a file is large, read it in chunks: start with offset=1 and a reasonable line_count
- The response includes total_lines so you know how many more lines remain
- Continue reading with an incremented offset until you have read all required content`
}

type DirectoryTreeArgs struct {
Expand All @@ -104,8 +110,10 @@ type WriteFileArgs struct {
}

type ReadMultipleFilesArgs struct {
Paths []string `json:"paths" jsonschema:"Array of file paths to read"`
JSON bool `json:"json,omitempty" jsonschema:"Whether to return the result as JSON"`
Paths []string `json:"paths" jsonschema:"Array of file paths to read"`
JSON bool `json:"json,omitempty" jsonschema:"Whether to return the result as JSON"`
Offset int `json:"offset,omitempty" jsonschema:"1-based line number to start reading from, applied to all files (default: 1)"`
LineCount int `json:"line_count,omitempty" jsonschema:"Maximum number of lines to return per file (default: all remaining lines)"`
}

type ReadMultipleFilesMeta struct {
Expand Down Expand Up @@ -141,14 +149,16 @@ type DirectoryTreeMeta struct {
}

type ReadFileArgs struct {
Path string `json:"path" jsonschema:"The file path to read"`
Path string `json:"path" jsonschema:"The file path to read"`
Offset int `json:"offset,omitempty" jsonschema:"1-based line number to start reading from (default: 1)"`
LineCount int `json:"line_count,omitempty" jsonschema:"Maximum number of lines to return (default: all remaining lines)"`
}

type ReadFileMeta struct {
Path string `json:"path"`
Content string `json:"content"`
LineCount int `json:"lineCount"`
Error string `json:"error,omitempty"`
Path string `json:"path"`
Content string `json:"content"`
TotalLines int `json:"totalLines"`
Error string `json:"error,omitempty"`
}

type Edit struct {
Expand Down Expand Up @@ -226,7 +236,7 @@ func (t *FilesystemTool) Tools(context.Context) ([]tools.Tool, error) {
{
Name: ToolNameReadFile,
Category: "filesystem",
Description: "Read the complete contents of a file from the file system.",
Description: "Read the contents of a file from the file system.",
Parameters: tools.MustSchemaFor[ReadFileArgs](),
OutputSchema: tools.MustSchemaFor[string](),
Handler: tools.NewHandler(t.handleReadFile),
Expand Down Expand Up @@ -483,6 +493,38 @@ func (t *FilesystemTool) handleListDirectory(_ context.Context, args ListDirecto
}, nil
}

// applyLineWindow slices lines from a file's content according to offset (1-based) and
// lineCount (0 means all remaining lines). It returns the windowed content and the total
// line count of the original. A header is prepended when only a subset is returned.
func applyLineWindow(content, path string, offset, lineCount int) (windowed string, totalLines int) {
lines := strings.Split(content, "\n")
totalLines = len(lines)

// Normalise offset: default to 1, clamp to valid range
if offset <= 0 {
offset = 1
}
if offset > totalLines {
offset = totalLines
}

// Convert to 0-based index
from := offset - 1
to := totalLines
if lineCount > 0 && from+lineCount < totalLines {
to = from + lineCount
}

windowed = strings.Join(lines[from:to], "\n")

// Prepend a header only when we are returning a subset of the file
if from > 0 || to < totalLines {
windowed = fmt.Sprintf("[Showing lines %d-%d of %d from %s]\n%s", offset, to, totalLines, path, windowed)
}

return windowed, totalLines
}

func (t *FilesystemTool) handleReadFile(_ context.Context, args ReadFileArgs) (*tools.ToolCallResult, error) {
resolvedPath := t.resolvePath(args.Path)

Expand All @@ -504,10 +546,11 @@ func (t *FilesystemTool) handleReadFile(_ context.Context, args ReadFileArgs) (*
}, nil
}

windowed, totalLines := applyLineWindow(string(content), args.Path, args.Offset, args.LineCount)
return &tools.ToolCallResult{
Output: string(content),
Output: windowed,
Meta: ReadFileMeta{
LineCount: strings.Count(string(content), "\n") + 1,
TotalLines: totalLines,
},
}, nil
}
Expand Down Expand Up @@ -545,12 +588,13 @@ func (t *FilesystemTool) handleReadMultipleFiles(ctx context.Context, args ReadM
continue
}

windowed, totalLines := applyLineWindow(string(content), path, args.Offset, args.LineCount)
contents = append(contents, PathContent{
Path: path,
Content: string(content),
Content: windowed,
})
entry.Content = string(content)
entry.LineCount = strings.Count(string(content), "\n") + 1
entry.Content = windowed
entry.TotalLines = totalLines
meta.Files = append(meta.Files, entry)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/tui/components/tool/readfile/readfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ func extractResult(msg *types.Message) string {
if meta.Error != "" {
return meta.Error
}
return fmt.Sprintf("%d lines", meta.LineCount)
return fmt.Sprintf("%d lines", meta.TotalLines)
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func formatSummaryLines(meta *builtin.ReadMultipleFilesMeta) []fileSummary {
if file.Error != "" {
output = " " + file.Error
} else {
output = fmt.Sprintf(" %d lines", file.LineCount)
output = fmt.Sprintf(" %d lines", file.TotalLines)
}

summaries = append(summaries, fileSummary{
Expand Down