on main help sign in
perf: use strings.Builder for string concatenation in loops (#1819)
commit0868681f6665c05552491ad1d9f71939fd47f68e authorCarlos Alexandro Becker <caarlos0@users.noreply.github.com> dateFri 9 Jan 2026, 17:58 (9 months ago) parente4400ad
4 files changed+107−15
internal/agent/agent.go+16−9 internal/agent/agent_test.go+36 internal/message/content.go+10−6 internal/message/content_test.go+45
internal/agent/agent.go+16−9
@@ -561,15 +561,7 @@ func (a *sessionAgent) Summarize(ctx context.Context, sessionID string, opts fan
561561 return err
562562 }
563563
564− summaryPromptText := "Provide a detailed summary of our conversation above."
565− if len(currentSession.Todos) > 0 {
566− summaryPromptText += "\n\n## Current Todo List\n\n"
567− for _, t := range currentSession.Todos {
568− summaryPromptText += fmt.Sprintf("- [%s] %s\n", t.Status, t.Content)
569− }
570− summaryPromptText += "\nInclude these tasks and their statuses in your summary. "
571− summaryPromptText += "Instruct the resuming assistant to use the `todos` tool to continue tracking progress on these tasks."
572− }
564+ summaryPromptText := buildSummaryPrompt(currentSession.Todos)
573565
574566 resp, err := agent.Stream(genCtx, fantasy.AgentStreamCall{
575567 Prompt: summaryPromptText,
@@ -1101,3 +1093,18 @@ func (a *sessionAgent) workaroundProviderMediaLimitations(messages []fantasy.Mes
11011093
11021094 return convertedMessages
11031095}
1096+
1097+// buildSummaryPrompt constructs the prompt text for session summarization.
1098+func buildSummaryPrompt(todos []session.Todo) string {
1099+ var sb strings.Builder
1100+ sb.WriteString("Provide a detailed summary of our conversation above.")
1101+ if len(todos) > 0 {
1102+ sb.WriteString("\n\n## Current Todo List\n\n")
1103+ for _, t := range todos {
1104+ fmt.Fprintf(&sb, "- [%s] %s\n", t.Status, t.Content)
1105+ }
1106+ sb.WriteString("\nInclude these tasks and their statuses in your summary. ")
1107+ sb.WriteString("Instruct the resuming assistant to use the `todos` tool to continue tracking progress on these tasks.")
1108+ }
1109+ return sb.String()
1110+}
internal/agent/agent_test.go+36
@@ -1,6 +1,7 @@
11package agent
22
33import (
4+ "fmt"
45 "os"
56 "path/filepath"
67 "runtime"
@@ -11,6 +12,7 @@ import (
1112 "charm.land/x/vcr"
1213 "github.com/charmbracelet/crush/internal/agent/tools"
1314 "github.com/charmbracelet/crush/internal/message"
15+ "github.com/charmbracelet/crush/internal/session"
1416 "github.com/stretchr/testify/assert"
1517 "github.com/stretchr/testify/require"
1618
@@ -619,3 +621,37 @@ func TestCoderAgent(t *testing.T) {
619621 })
620622 }
621623}
624+
625+func makeTestTodos(n int) []session.Todo {
626+ todos := make([]session.Todo, n)
627+ for i := range n {
628+ todos[i] = session.Todo{
629+ Status: session.TodoStatusPending,
630+ Content: fmt.Sprintf("Task %d: Implement feature with some description that makes it realistic", i),
631+ }
632+ }
633+ return todos
634+}
635+
636+func BenchmarkBuildSummaryPrompt(b *testing.B) {
637+ cases := []struct {
638+ name string
639+ numTodos int
640+ }{
641+ {"0todos", 0},
642+ {"5todos", 5},
643+ {"10todos", 10},
644+ {"50todos", 50},
645+ }
646+
647+ for _, tc := range cases {
648+ todos := makeTestTodos(tc.numTodos)
649+
650+ b.Run(tc.name, func(b *testing.B) {
651+ b.ReportAllocs()
652+ for range b.N {
653+ _ = buildSummaryPrompt(todos)
654+ }
655+ })
656+ }
657+}
internal/message/content.go+10−6
@@ -437,23 +437,27 @@ func (m *Message) AddBinary(mimeType string, data []byte) {
437437}
438438
439439func PromptWithTextAttachments(prompt string, attachments []Attachment) string {
440+ var sb strings.Builder
441+ sb.WriteString(prompt)
440442 addedAttachments := false
441443 for _, content := range attachments {
442444 if !content.IsText() {
443445 continue
444446 }
445447 if !addedAttachments {
446− prompt += "\n<system_info>The files below have been attached by the user, consider them in your response</system_info>\n"
448+ sb.WriteString("\n<system_info>The files below have been attached by the user, consider them in your response</system_info>\n")
447449 addedAttachments = true
448450 }
449− tag := `<file>\n`
450451 if content.FilePath != "" {
451− tag = fmt.Sprintf("<file path='%s'>\n", content.FilePath)
452+ fmt.Fprintf(&sb, "<file path='%s'>\n", content.FilePath)
453+ } else {
454+ sb.WriteString("<file>\n")
452455 }
453− prompt += tag
454− prompt += "\n" + string(content.Content) + "\n</file>\n"
456+ sb.WriteString("\n")
457+ sb.Write(content.Content)
458+ sb.WriteString("\n</file>\n")
455459 }
456− return prompt
460+ return sb.String()
457461}
458462
459463func (m *Message) ToAIMessage() []fantasy.Message {
internal/message/content_test.gonew file+45
@@ -0,0 +1,45 @@
1+package message
2+
3+import (
4+ "fmt"
5+ "strings"
6+ "testing"
7+)
8+
9+func makeTestAttachments(n int, contentSize int) []Attachment {
10+ attachments := make([]Attachment, n)
11+ content := []byte(strings.Repeat("x", contentSize))
12+ for i := range n {
13+ attachments[i] = Attachment{
14+ FilePath: fmt.Sprintf("/path/to/file%d.txt", i),
15+ MimeType: "text/plain",
16+ Content: content,
17+ }
18+ }
19+ return attachments
20+}
21+
22+func BenchmarkPromptWithTextAttachments(b *testing.B) {
23+ cases := []struct {
24+ name string
25+ numFiles int
26+ contentSize int
27+ }{
28+ {"1file_100bytes", 1, 100},
29+ {"5files_1KB", 5, 1024},
30+ {"10files_10KB", 10, 10 * 1024},
31+ {"20files_50KB", 20, 50 * 1024},
32+ }
33+
34+ for _, tc := range cases {
35+ attachments := makeTestAttachments(tc.numFiles, tc.contentSize)
36+ prompt := "Process these files"
37+
38+ b.Run(tc.name, func(b *testing.B) {
39+ b.ReportAllocs()
40+ for range b.N {
41+ _ = PromptWithTextAttachments(prompt, attachments)
42+ }
43+ })
44+ }
45+}