on main help sign in
chore: tidy agent package (#1857)
commit632ba621bfc07721baf9207e49ebb9324b5122b4 authorChristian Rocha <christian@rocha.is> dateWed 14 Jan 2026, 15:28 (9 months ago) parentf7de0d5
8 files changed+55−46
internal/agent/agent.go+15−8 internal/agent/coordinator.go+1−1 internal/agent/tools/diagnostics.go+1−1 internal/agent/tools/edit.go+12−7 internal/agent/tools/fetch.go+13−9 internal/agent/tools/ls.go+12−5 internal/agent/tools/view.go−7 internal/agent/tools/write.go+1−8
internal/agent/agent.go+15−8
@@ -40,7 +40,14 @@ import (
4040 "github.com/charmbracelet/crush/internal/stringext"
4141)
4242
43−const defaultSessionName = "Untitled Session"
43+const (
44+ defaultSessionName = "Untitled Session"
45+
46+ // Constants for auto-summarization thresholds
47+ largeContextWindowThreshold = 200_000
48+ largeContextWindowBuffer = 20_000
49+ smallContextWindowRatio = 0.2
50+)
4451
4552//go:embed templates/title.md
4653var titlePrompt []byte
@@ -383,10 +390,10 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
383390 tokens := currentSession.CompletionTokens + currentSession.PromptTokens
384391 remaining := cw - tokens
385392 var threshold int64
386− if cw > 200_000 {
387− threshold = 20_000
393+ if cw > largeContextWindowThreshold {
394+ threshold = largeContextWindowBuffer
388395 } else {
389− threshold = int64(float64(cw) * 0.2)
396+ threshold = int64(float64(cw) * smallContextWindowRatio)
390397 }
391398 if (remaining <= threshold) && !a.disableAutoSummarize {
392399 shouldSummarize = true
@@ -720,15 +727,15 @@ func (a *sessionAgent) getSessionMessages(ctx context.Context, session session.S
720727 }
721728
722729 if session.SummaryMessageID != "" {
723− summaryMsgInex := -1
730+ summaryMsgIndex := -1
724731 for i, msg := range msgs {
725732 if msg.ID == session.SummaryMessageID {
726− summaryMsgInex = i
733+ summaryMsgIndex = i
727734 break
728735 }
729736 }
730− if summaryMsgInex != -1 {
731− msgs = msgs[summaryMsgInex:]
737+ if summaryMsgIndex != -1 {
738+ msgs = msgs[summaryMsgIndex:]
732739 msgs[0].Role = message.User
733740 }
734741 }
internal/agent/coordinator.go+1−1
@@ -489,7 +489,7 @@ func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Mo
489489 }
490490
491491 if smallCatwalkModel == nil {
492− return Model{}, Model{}, errors.New("snall model not found in provider config")
492+ return Model{}, Model{}, errors.New("small model not found in provider config")
493493 }
494494
495495 largeModelID := largeModelCfg.Model
internal/agent/tools/diagnostics.go+1−1
@@ -16,7 +16,7 @@ import (
1616)
1717
1818type DiagnosticsParams struct {
19− FilePath string `json:"file_path,omitempty" description:"The path to the file to get diagnostics for (leave w empty for project diagnostics)"`
19+ FilePath string `json:"file_path,omitempty" description:"The path to the file to get diagnostics for (leave empty for project diagnostics)"`
2020}
2121
2222const DiagnosticsToolName = "lsp_diagnostics"
internal/agent/tools/edit.go+12−7
@@ -44,6 +44,11 @@ type EditResponseMetadata struct {
4444
4545const EditToolName = "edit"
4646
47+var (
48+ oldStringNotFoundErr = fantasy.NewTextErrorResponse("old_string not found in file. Make sure it matches exactly, including whitespace and line breaks.")
49+ oldStringMultipleMatchesErr = fantasy.NewTextErrorResponse("old_string appears multiple times in the file. Please provide more context to ensure a unique match, or set replace_all to true")
50+)
51+
4752//go:embed edit.md
4853var editDescription []byte
4954
@@ -217,12 +222,12 @@ func deleteContent(edit editContext, filePath, oldString string, replaceAll bool
217222 newContent = strings.ReplaceAll(oldContent, oldString, "")
218223 deletionCount = strings.Count(oldContent, oldString)
219224 if deletionCount == 0 {
220− return fantasy.NewTextErrorResponse("old_string not found in file. Make sure it matches exactly, including whitespace and line breaks"), nil
225+ return oldStringNotFoundErr, nil
221226 }
222227 } else {
223228 index := strings.Index(oldContent, oldString)
224229 if index == -1 {
225− return fantasy.NewTextErrorResponse("old_string not found in file. Make sure it matches exactly, including whitespace and line breaks"), nil
230+ return oldStringNotFoundErr, nil
226231 }
227232
228233 lastIndex := strings.LastIndex(oldContent, oldString)
@@ -287,7 +292,7 @@ func deleteContent(edit editContext, filePath, oldString string, replaceAll bool
287292 }
288293 }
289294 if file.Content != oldContent {
290− // User Manually changed the content store an intermediate version
295+ // User manually changed the content; store an intermediate version
291296 _, err = edit.files.CreateVersion(edit.ctx, sessionID, filePath, oldContent)
292297 if err != nil {
293298 slog.Error("Error creating file history version", "error", err)
@@ -353,17 +358,17 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
353358 newContent = strings.ReplaceAll(oldContent, oldString, newString)
354359 replacementCount = strings.Count(oldContent, oldString)
355360 if replacementCount == 0 {
356− return fantasy.NewTextErrorResponse("old_string not found in file. Make sure it matches exactly, including whitespace and line breaks"), nil
361+ return oldStringNotFoundErr, nil
357362 }
358363 } else {
359364 index := strings.Index(oldContent, oldString)
360365 if index == -1 {
361− return fantasy.NewTextErrorResponse("old_string not found in file. Make sure it matches exactly, including whitespace and line breaks"), nil
366+ return oldStringNotFoundErr, nil
362367 }
363368
364369 lastIndex := strings.LastIndex(oldContent, oldString)
365370 if index != lastIndex {
366− return fantasy.NewTextErrorResponse("old_string appears multiple times in the file. Please provide more context to ensure a unique match, or set replace_all to true"), nil
371+ return oldStringMultipleMatchesErr, nil
367372 }
368373
369374 newContent = oldContent[:index] + newString + oldContent[index+len(oldString):]
@@ -425,7 +430,7 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
425430 }
426431 }
427432 if file.Content != oldContent {
428− // User Manually changed the content store an intermediate version
433+ // User manually changed the content; store an intermediate version
429434 _, err = edit.files.CreateVersion(edit.ctx, sessionID, filePath, oldContent)
430435 if err != nil {
431436 slog.Debug("Error creating file history version", "error", err)
internal/agent/tools/fetch.go+13−9
@@ -73,12 +73,14 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
7373 return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
7474 }
7575
76+ // maxFetchTimeoutSeconds is the maximum allowed timeout for fetch requests (2 minutes)
77+ const maxFetchTimeoutSeconds = 120
78+
7679 // Handle timeout with context
7780 requestCtx := ctx
7881 if params.Timeout > 0 {
79− maxTimeout := 120 // 2 minutes
80− if params.Timeout > maxTimeout {
81− params.Timeout = maxTimeout
82+ if params.Timeout > maxFetchTimeoutSeconds {
83+ params.Timeout = maxFetchTimeoutSeconds
8284 }
8385 var cancel context.CancelFunc
8486 requestCtx, cancel = context.WithTimeout(ctx, time.Duration(params.Timeout)*time.Second)
@@ -102,7 +104,10 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
102104 return fantasy.NewTextErrorResponse(fmt.Sprintf("Request failed with status code: %d", resp.StatusCode)), nil
103105 }
104106
105− maxSize := int64(5 * 1024 * 1024) // 5MB
107+ // maxFetchResponseSizeBytes is the maximum size of response body to read (5MB)
108+ const maxFetchResponseSizeBytes = int64(5 * 1024 * 1024)
109+
110+ maxSize := maxFetchResponseSizeBytes
106111 body, err := io.ReadAll(io.LimitReader(resp.Body, maxSize))
107112 if err != nil {
108113 return fantasy.NewTextErrorResponse("Failed to read response body: " + err.Error()), nil
@@ -110,8 +115,8 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
110115
111116 content := string(body)
112117
113− isValidUt8 := utf8.ValidString(content)
114− if !isValidUt8 {
118+ validUTF8 := utf8.ValidString(content)
119+ if !validUTF8 {
115120 return fantasy.NewTextErrorResponse("Response content is not valid UTF-8"), nil
116121 }
117122 contentType := resp.Header.Get("Content-Type")
@@ -154,9 +159,8 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
154159 content = "<html>\n<body>\n" + body + "\n</body>\n</html>"
155160 }
156161 }
157− // calculate byte size of content
158− contentSize := int64(len(content))
159− if contentSize > MaxReadSize {
162+ // truncate content if it exceeds max read size
163+ if int64(len(content)) > MaxReadSize {
160164 content = content[:MaxReadSize]
161165 content += fmt.Sprintf("\n\n[Content truncated to %d bytes]", MaxReadSize)
162166 }
internal/agent/tools/ls.go+12−5
@@ -28,10 +28,17 @@ type LSPermissionsParams struct {
2828 Depth int `json:"depth"`
2929}
3030
31+type NodeType string
32+
33+const (
34+ NodeTypeFile NodeType = "file"
35+ NodeTypeDirectory NodeType = "directory"
36+)
37+
3138type TreeNode struct {
3239 Name string `json:"name"`
3340 Path string `json:"path"`
34− Type string `json:"type"` // "file" or "directory"
41+ Type NodeType `json:"type"`
3542 Children []*TreeNode `json:"children,omitempty"`
3643}
3744
@@ -179,9 +186,9 @@ func createFileTree(sortedPaths []string, rootPath string) []*TreeNode {
179186
180187 isLastPart := i == len(parts)-1
181188 isDir := !isLastPart || strings.HasSuffix(relativePath, string(filepath.Separator))
182− nodeType := "file"
189+ nodeType := NodeTypeFile
183190 if isDir {
184− nodeType = "directory"
191+ nodeType = NodeTypeDirectory
185192 }
186193 newNode := &TreeNode{
187194 Name: part,
@@ -228,13 +235,13 @@ func printNode(builder *strings.Builder, node *TreeNode, level int) {
228235 indent := strings.Repeat(" ", level)
229236
230237 nodeName := node.Name
231− if node.Type == "directory" {
238+ if node.Type == NodeTypeDirectory {
232239 nodeName = nodeName + "/"
233240 }
234241
235242 fmt.Fprintf(builder, "%s- %s\n", indent, nodeName)
236243
237− if node.Type == "directory" && len(node.Children) > 0 {
244+ if node.Type == NodeTypeDirectory && len(node.Children) > 0 {
238245 for _, child := range node.Children {
239246 printNode(builder, child, level+1)
240247 }
internal/agent/tools/view.go−7
@@ -35,13 +35,6 @@ type ViewPermissionsParams struct {
3535 Limit int `json:"limit"`
3636}
3737
38−type viewTool struct {
39− lspClients *csync.Map[string, *lsp.Client]
40− workingDir string
41− permissions permission.Service
42− skillsPaths []string
43−}
44−
4538type ViewResponseMetadata struct {
4639 FilePath string `json:"file_path"`
4740 Content string `json:"content"`
internal/agent/tools/write.go+1−8
@@ -36,13 +36,6 @@ type WritePermissionsParams struct {
3636 NewContent string `json:"new_content,omitempty"`
3737}
3838
39−type writeTool struct {
40− lspClients *csync.Map[string, *lsp.Client]
41− permissions permission.Service
42− files history.Service
43− workingDir string
44−}
45−
4639type WriteResponseMetadata struct {
4740 Diff string `json:"diff"`
4841 Additions int `json:"additions"`
@@ -148,7 +141,7 @@ func NewWriteTool(lspClients *csync.Map[string, *lsp.Client], permissions permis
148141 }
149142 }
150143 if file.Content != oldContent {
151− // User Manually changed the content store an intermediate version
144+ // User manually changed the content; store an intermediate version
152145 _, err = files.CreateVersion(ctx, sessionID, filePath, oldContent)
153146 if err != nil {
154147 slog.Error("Error creating file history version", "error", err)