Skip to content

Commit e413836

Browse files
committed
searchfile custom description
Signed-off-by: Jean-Laurent de Morlhon <[email protected]>
1 parent 1c9a9c7 commit e413836

File tree

3 files changed

+183
-0
lines changed

3 files changed

+183
-0
lines changed

pkg/tui/components/tool/factory.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/docker/cagent/pkg/tui/components/tool/listdirectory"
99
"github.com/docker/cagent/pkg/tui/components/tool/readfile"
1010
"github.com/docker/cagent/pkg/tui/components/tool/readmultiplefiles"
11+
"github.com/docker/cagent/pkg/tui/components/tool/searchfiles"
1112
"github.com/docker/cagent/pkg/tui/components/tool/shell"
1213
"github.com/docker/cagent/pkg/tui/components/tool/todotool"
1314
"github.com/docker/cagent/pkg/tui/components/tool/transfertask"
@@ -64,6 +65,7 @@ func newDefaultRegistry() *Registry {
6465
registry.Register(builtin.ToolNameReadFile, readfile.New)
6566
registry.Register(builtin.ToolNameReadMultipleFiles, readmultiplefiles.New)
6667
registry.Register(builtin.ToolNameListDirectory, listdirectory.New)
68+
registry.Register(builtin.ToolNameSearchFiles, searchfiles.New)
6769
registry.Register(builtin.ToolNameCreateTodo, todotool.New)
6870
registry.Register(builtin.ToolNameCreateTodos, todotool.New)
6971
registry.Register(builtin.ToolNameUpdateTodo, todotool.New)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package searchfiles
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
tea "charm.land/bubbletea/v2"
9+
10+
"github.com/docker/cagent/pkg/tools/builtin"
11+
"github.com/docker/cagent/pkg/tui/components/spinner"
12+
"github.com/docker/cagent/pkg/tui/components/toolcommon"
13+
"github.com/docker/cagent/pkg/tui/core/layout"
14+
"github.com/docker/cagent/pkg/tui/service"
15+
"github.com/docker/cagent/pkg/tui/types"
16+
)
17+
18+
// Component is a specialized component for rendering search_files tool calls.
19+
type Component struct {
20+
message *types.Message
21+
spinner spinner.Spinner
22+
width int
23+
height int
24+
}
25+
26+
// New creates a new search files component.
27+
func New(
28+
msg *types.Message,
29+
_ *service.SessionState,
30+
) layout.Model {
31+
return &Component{
32+
message: msg,
33+
spinner: spinner.New(spinner.ModeSpinnerOnly),
34+
width: 80,
35+
height: 1,
36+
}
37+
}
38+
39+
func (c *Component) SetSize(width, height int) tea.Cmd {
40+
c.width = width
41+
c.height = height
42+
return nil
43+
}
44+
45+
func (c *Component) Init() tea.Cmd {
46+
if c.message.ToolStatus == types.ToolStatusPending || c.message.ToolStatus == types.ToolStatusRunning {
47+
return c.spinner.Init()
48+
}
49+
return nil
50+
}
51+
52+
func (c *Component) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
53+
if c.message.ToolStatus == types.ToolStatusPending || c.message.ToolStatus == types.ToolStatusRunning {
54+
var cmd tea.Cmd
55+
var model layout.Model
56+
model, cmd = c.spinner.Update(msg)
57+
c.spinner = model.(spinner.Spinner)
58+
return c, cmd
59+
}
60+
61+
return c, nil
62+
}
63+
64+
func (c *Component) View() string {
65+
msg := c.message
66+
67+
// Parse arguments
68+
var args builtin.SearchFilesArgs
69+
if err := json.Unmarshal([]byte(msg.ToolCall.Function.Arguments), &args); err != nil {
70+
return toolcommon.RenderTool(toolcommon.Icon(msg.ToolStatus), msg.ToolDefinition.DisplayName(), c.spinner.View(), "", c.width)
71+
}
72+
73+
// Format display name with pattern
74+
displayName := fmt.Sprintf("%s(%q)", msg.ToolDefinition.DisplayName(), args.Pattern)
75+
76+
// For pending/running state, show spinner
77+
if msg.ToolStatus == types.ToolStatusPending || msg.ToolStatus == types.ToolStatusRunning {
78+
return toolcommon.RenderTool(toolcommon.Icon(msg.ToolStatus), displayName, c.spinner.View(), "", c.width)
79+
}
80+
81+
// For completed/error state, show concise summary
82+
summary := formatSummary(msg.Content)
83+
params := fmt.Sprintf(": %s", summary)
84+
85+
return toolcommon.RenderTool(toolcommon.Icon(msg.ToolStatus), displayName, params, "", c.width)
86+
}
87+
88+
// formatSummary creates a concise summary of the search results
89+
func formatSummary(content string) string {
90+
if content == "" {
91+
return "no result"
92+
}
93+
94+
// Handle "No files found" case
95+
if strings.HasPrefix(content, "No files found") {
96+
return "no file found"
97+
}
98+
99+
// Handle error cases
100+
if strings.HasPrefix(content, "Error") {
101+
return content
102+
}
103+
104+
// Parse "X files found:\n..." format
105+
lines := strings.Split(content, "\n")
106+
if len(lines) > 0 {
107+
firstLine := lines[0]
108+
// Extract count from "X files found:" or "X file found:"
109+
if strings.Contains(firstLine, "file found:") || strings.Contains(firstLine, "files found:") {
110+
// Check if it's a single file
111+
if strings.HasPrefix(firstLine, "1 file found:") && len(lines) > 1 {
112+
// Return "one file found: filename"
113+
fileName := strings.TrimSpace(lines[1])
114+
return fmt.Sprintf("one file found: %s", fileName)
115+
}
116+
// Multiple files
117+
return strings.TrimSuffix(firstLine, ":") + "."
118+
}
119+
}
120+
121+
// Fallback
122+
return content
123+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package searchfiles
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestFormatSummary(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
content string
11+
want string
12+
}{
13+
{
14+
name: "single file found",
15+
content: "1 file found:\ntest.txt",
16+
want: "one file found: test.txt",
17+
},
18+
{
19+
name: "single file found with path",
20+
content: "1 file found:\n/path/to/file.go",
21+
want: "one file found: /path/to/file.go",
22+
},
23+
{
24+
name: "two files found",
25+
content: "2 files found:\nfile1.txt\nfile2.txt",
26+
want: "2 files found.",
27+
},
28+
{
29+
name: "multiple files found",
30+
content: "7 files found:\ngordon.yaml\ngordon_dev.yaml\ngordon_workspace/gordon_dev_modular.yaml\nold/gordon-handoff.yaml\nold/gordon_dev_bu.yaml\nold/gordon_dev_test.yaml\nold/v2/gordon_dev_modular.yaml",
31+
want: "7 files found.",
32+
},
33+
{
34+
name: "no files found",
35+
content: "No files found",
36+
want: "no file found",
37+
},
38+
{
39+
name: "empty content",
40+
content: "",
41+
want: "no result",
42+
},
43+
{
44+
name: "error case",
45+
content: "Error: permission denied",
46+
want: "Error: permission denied",
47+
},
48+
}
49+
50+
for _, tt := range tests {
51+
t.Run(tt.name, func(t *testing.T) {
52+
got := formatSummary(tt.content)
53+
if got != tt.want {
54+
t.Errorf("formatSummary() = %q, want %q", got, tt.want)
55+
}
56+
})
57+
}
58+
}

0 commit comments

Comments
 (0)