on main help sign in
Merge pull request #75 from charmbracelet/chore/modernize
431a6c4Kujtim Hoxha
internal/format/format.go+5−5
@@ -86,11 +86,11 @@ func formatAsJSON(content string) string {
86 jsonBytes, err := json.MarshalIndent(response, "", " ")
86 jsonBytes, err := json.MarshalIndent(response, "", " ")
87 if err != nil {
87 if err != nil {
88 // In case of an error, return a manually formatted JSON
88 // In case of an error, return a manually formatted JSON
89− jsonEscaped := strings.Replace(content, "\\", "\\\\", -1)
89+ jsonEscaped := strings.ReplaceAll(content, "\\", "\\\\")
90− jsonEscaped = strings.Replace(jsonEscaped, "\"", "\\\"", -1)
90+ jsonEscaped = strings.ReplaceAll(jsonEscaped, "\"", "\\\"")
91− jsonEscaped = strings.Replace(jsonEscaped, "\n", "\\n", -1)
91+ jsonEscaped = strings.ReplaceAll(jsonEscaped, "\n", "\\n")
92− jsonEscaped = strings.Replace(jsonEscaped, "\r", "\\r", -1)
92+ jsonEscaped = strings.ReplaceAll(jsonEscaped, "\r", "\\r")
93− jsonEscaped = strings.Replace(jsonEscaped, "\t", "\\t", -1)
93+ jsonEscaped = strings.ReplaceAll(jsonEscaped, "\t", "\\t")
94
94
95 return fmt.Sprintf("{\n \"response\": \"%s\"\n}", jsonEscaped)
95 return fmt.Sprintf("{\n \"response\": \"%s\"\n}", jsonEscaped)
96 }
96 }
internal/llm/agent/agent.go+1−1
@@ -135,7 +135,7 @@ func (a *agent) Cancel(sessionID string) {
135
135
136func (a *agent) IsBusy() bool {
136func (a *agent) IsBusy() bool {
137 busy := false
137 busy := false
138− a.activeRequests.Range(func(key, value interface{}) bool {
138+ a.activeRequests.Range(func(key, value any) bool {
139 if cancelFunc, ok := value.(context.CancelFunc); ok {
139 if cancelFunc, ok := value.(context.CancelFunc); ok {
140 if cancelFunc != nil {
140 if cancelFunc != nil {
141 busy = true
141 busy = true
internal/llm/provider/anthropic.go+3−2
@@ -273,9 +273,10 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
273
273
274 switch event := event.AsAny().(type) {
274 switch event := event.AsAny().(type) {
275 case anthropic.ContentBlockStartEvent:
275 case anthropic.ContentBlockStartEvent:
276− if event.ContentBlock.Type == "text" {
276+ switch event.ContentBlock.Type {
277+ case "text":
277 eventChan <- ProviderEvent{Type: EventContentStart}
278 eventChan <- ProviderEvent{Type: EventContentStart}
278− } else if event.ContentBlock.Type == "tool_use" {
279+ case "tool_use":
279 currentToolCallID = event.ContentBlock.ID
280 currentToolCallID = event.ContentBlock.ID
280 eventChan <- ProviderEvent{
281 eventChan <- ProviderEvent{
281 Type: EventToolUseStart,
282 Type: EventToolUseStart,
internal/llm/provider/gemini.go+13−16
@@ -96,7 +96,7 @@ func (g *geminiClient) convertMessages(messages []message.Message) []*genai.Cont
96
96
97 case message.Tool:
97 case message.Tool:
98 for _, result := range msg.ToolResults() {
98 for _, result := range msg.ToolResults() {
99− response := map[string]interface{}{"result": result.Content}
99+ response := map[string]any{"result": result.Content}
100 parsed, err := parseJsonToMap(result.Content)
100 parsed, err := parseJsonToMap(result.Content)
101 if err == nil {
101 if err == nil {
102 response = parsed
102 response = parsed
@@ -155,10 +155,10 @@ func (g *geminiClient) convertTools(tools []tools.BaseTool) []*genai.Tool {
155}
155}
156
156
157func (g *geminiClient) finishReason(reason genai.FinishReason) message.FinishReason {
157func (g *geminiClient) finishReason(reason genai.FinishReason) message.FinishReason {
158− switch {
158+ switch reason {
159− case reason == genai.FinishReasonStop:
159+ case genai.FinishReasonStop:
160 return message.FinishReasonEndTurn
160 return message.FinishReasonEndTurn
161− case reason == genai.FinishReasonMaxTokens:
161+ case genai.FinishReasonMaxTokens:
162 return message.FinishReasonMaxTokens
162 return message.FinishReasonMaxTokens
163 default:
163 default:
164 return message.FinishReasonUnknown
164 return message.FinishReasonUnknown
@@ -402,12 +402,9 @@ func (g *geminiClient) shouldRetry(attempts int, err error) (bool, int64, error)
402 }
402 }
403
403
404 errMsg := err.Error()
404 errMsg := err.Error()
405− isRateLimit := false
405+ isRateLimit := contains(errMsg, "rate limit", "quota exceeded", "too many requests")
406
406
407 // Check for common rate limit error messages
407 // Check for common rate limit error messages
408− if contains(errMsg, "rate limit", "quota exceeded", "too many requests") {
409− isRateLimit = true
410− }
411
408
412 if !isRateLimit {
409 if !isRateLimit {
413 return false, 0, err
410 return false, 0, err
@@ -462,13 +459,13 @@ func WithGeminiDisableCache() GeminiOption {
462}
459}
463
460
464// Helper functions
461// Helper functions
465−func parseJsonToMap(jsonStr string) (map[string]interface{}, error) {
462+func parseJsonToMap(jsonStr string) (map[string]any, error) {
466− var result map[string]interface{}
463+ var result map[string]any
467 err := json.Unmarshal([]byte(jsonStr), &result)
464 err := json.Unmarshal([]byte(jsonStr), &result)
468 return result, err
465 return result, err
469}
466}
470
467
471−func convertSchemaProperties(parameters map[string]interface{}) map[string]*genai.Schema {
468+func convertSchemaProperties(parameters map[string]any) map[string]*genai.Schema {
472 properties := make(map[string]*genai.Schema)
469 properties := make(map[string]*genai.Schema)
473
470
474 for name, param := range parameters {
471 for name, param := range parameters {
@@ -478,10 +475,10 @@ func convertSchemaProperties(parameters map[string]interface{}) map[string]*gena
478 return properties
475 return properties
479}
476}
480
477
481−func convertToSchema(param interface{}) *genai.Schema {
478+func convertToSchema(param any) *genai.Schema {
482 schema := &genai.Schema{Type: genai.TypeString}
479 schema := &genai.Schema{Type: genai.TypeString}
483
480
484− paramMap, ok := param.(map[string]interface{})
481+ paramMap, ok := param.(map[string]any)
485 if !ok {
482 if !ok {
486 return schema
483 return schema
487 }
484 }
@@ -506,7 +503,7 @@ func convertToSchema(param interface{}) *genai.Schema {
506 case "array":
503 case "array":
507 schema.Items = processArrayItems(paramMap)
504 schema.Items = processArrayItems(paramMap)
508 case "object":
505 case "object":
509− if props, ok := paramMap["properties"].(map[string]interface{}); ok {
506+ if props, ok := paramMap["properties"].(map[string]any); ok {
510 schema.Properties = convertSchemaProperties(props)
507 schema.Properties = convertSchemaProperties(props)
511 }
508 }
512 }
509 }
@@ -514,8 +511,8 @@ func convertToSchema(param interface{}) *genai.Schema {
514 return schema
511 return schema
515}
512}
516
513
517−func processArrayItems(paramMap map[string]interface{}) *genai.Schema {
514+func processArrayItems(paramMap map[string]any) *genai.Schema {
518− items, ok := paramMap["items"].(map[string]interface{})
515+ items, ok := paramMap["items"].(map[string]any)
519 if !ok {
516 if !ok {
520 return nil
517 return nil
521 }
518 }
internal/llm/provider/openai.go+1−1
@@ -284,7 +284,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
284 if err == nil || errors.Is(err, io.EOF) {
284 if err == nil || errors.Is(err, io.EOF) {
285 // Stream completed successfully
285 // Stream completed successfully
286 finishReason := o.finishReason(string(acc.ChatCompletion.Choices[0].FinishReason))
286 finishReason := o.finishReason(string(acc.ChatCompletion.Choices[0].FinishReason))
287− if len(acc.ChatCompletion.Choices[0].Message.ToolCalls) > 0 {
287+ if len(acc.Choices[0].Message.ToolCalls) > 0 {
288 toolCalls = append(toolCalls, o.toolCalls(acc.ChatCompletion)...)
288 toolCalls = append(toolCalls, o.toolCalls(acc.ChatCompletion)...)
289 }
289 }
290 if len(toolCalls) > 0 {
290 if len(toolCalls) > 0 {
internal/llm/tools/bash_test.go+6−35
@@ -2,6 +2,7 @@ package tools
2
2
3import (
3import (
4 "runtime"
4 "runtime"
5+ "slices"
5 "testing"
6 "testing"
6)
7)
7
8
@@ -16,13 +17,7 @@ func TestGetSafeReadOnlyCommands(t *testing.T) {
16 // Check for cross-platform commands that should always be present
17 // Check for cross-platform commands that should always be present
17 crossPlatformCommands := []string{"echo", "hostname", "whoami", "git status", "go version"}
18 crossPlatformCommands := []string{"echo", "hostname", "whoami", "git status", "go version"}
18 for _, cmd := range crossPlatformCommands {
19 for _, cmd := range crossPlatformCommands {
19− found := false
20+ found := slices.Contains(commands, cmd)
20− for _, safeCmd := range commands {
21− if safeCmd == cmd {
22− found = true
23− break
24− }
25− }
26 if !found {
21 if !found {
27 t.Errorf("Expected cross-platform command %q to be in safe commands", cmd)
22 t.Errorf("Expected cross-platform command %q to be in safe commands", cmd)
28 }
23 }
@@ -32,13 +27,7 @@ func TestGetSafeReadOnlyCommands(t *testing.T) {
32 // Check for Windows-specific commands
27 // Check for Windows-specific commands
33 windowsCommands := []string{"dir", "type", "Get-Process"}
28 windowsCommands := []string{"dir", "type", "Get-Process"}
34 for _, cmd := range windowsCommands {
29 for _, cmd := range windowsCommands {
35− found := false
30+ found := slices.Contains(commands, cmd)
36− for _, safeCmd := range commands {
37− if safeCmd == cmd {
38− found = true
39− break
40− }
41− }
42 if !found {
31 if !found {
43 t.Errorf("Expected Windows command %q to be in safe commands on Windows", cmd)
32 t.Errorf("Expected Windows command %q to be in safe commands on Windows", cmd)
44 }
33 }
@@ -47,13 +36,7 @@ func TestGetSafeReadOnlyCommands(t *testing.T) {
47 // Check that Unix commands are NOT present on Windows
36 // Check that Unix commands are NOT present on Windows
48 unixCommands := []string{"ls", "pwd", "ps"}
37 unixCommands := []string{"ls", "pwd", "ps"}
49 for _, cmd := range unixCommands {
38 for _, cmd := range unixCommands {
50− found := false
39+ found := slices.Contains(commands, cmd)
51− for _, safeCmd := range commands {
52− if safeCmd == cmd {
53− found = true
54− break
55− }
56− }
57 if found {
40 if found {
58 t.Errorf("Unix command %q should not be in safe commands on Windows", cmd)
41 t.Errorf("Unix command %q should not be in safe commands on Windows", cmd)
59 }
42 }
@@ -62,13 +45,7 @@ func TestGetSafeReadOnlyCommands(t *testing.T) {
62 // Check for Unix-specific commands
45 // Check for Unix-specific commands
63 unixCommands := []string{"ls", "pwd", "ps"}
46 unixCommands := []string{"ls", "pwd", "ps"}
64 for _, cmd := range unixCommands {
47 for _, cmd := range unixCommands {
65− found := false
48+ found := slices.Contains(commands, cmd)
66− for _, safeCmd := range commands {
67− if safeCmd == cmd {
68− found = true
69− break
70− }
71− }
72 if !found {
49 if !found {
73 t.Errorf("Expected Unix command %q to be in safe commands on Unix", cmd)
50 t.Errorf("Expected Unix command %q to be in safe commands on Unix", cmd)
74 }
51 }
@@ -77,13 +54,7 @@ func TestGetSafeReadOnlyCommands(t *testing.T) {
77 // Check that Windows-specific commands are NOT present on Unix
54 // Check that Windows-specific commands are NOT present on Unix
78 windowsOnlyCommands := []string{"dir", "Get-Process", "systeminfo"}
55 windowsOnlyCommands := []string{"dir", "Get-Process", "systeminfo"}
79 for _, cmd := range windowsOnlyCommands {
56 for _, cmd := range windowsOnlyCommands {
80− found := false
57+ found := slices.Contains(commands, cmd)
81− for _, safeCmd := range commands {
82− if safeCmd == cmd {
83− found = true
84− break
85− }
86− }
87 if found {
58 if found {
88 t.Errorf("Windows-only command %q should not be in safe commands on Unix", cmd)
59 t.Errorf("Windows-only command %q should not be in safe commands on Unix", cmd)
89 }
60 }
internal/permission/permission.go+1−4
@@ -116,10 +116,7 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
116 s.Publish(pubsub.CreatedEvent, permission)
116 s.Publish(pubsub.CreatedEvent, permission)
117
117
118 // Wait for the response indefinitely
118 // Wait for the response indefinitely
119− select {
120− case resp := <-respCh:
119+ return <-respCh
121− return resp
122− }
123}
120}
124
121
125func (s *permissionService) AutoApproveSession(sessionID string) {
122func (s *permissionService) AutoApproveSession(sessionID string) {
internal/tui/components/chat/messages/renderer.go+1−1
@@ -112,7 +112,7 @@ func (br baseRenderer) unmarshalParams(input string, target any) error {
112}
112}
113
113
114// makeHeader builds the tool call header with status icon and parameters for a nested tool call.
114// makeHeader builds the tool call header with status icon and parameters for a nested tool call.
115−func (br baseRenderer) makeNestedHeader(v *toolCallCmp, tool string, width int, params ...string) string {
115+func (br baseRenderer) makeNestedHeader(_ *toolCallCmp, tool string, width int, params ...string) string {
116 t := styles.CurrentTheme()
116 t := styles.CurrentTheme()
117 tool = t.S().Base.Foreground(t.FgHalfMuted).Render(tool) + " "
117 tool = t.S().Base.Foreground(t.FgHalfMuted).Render(tool) + " "
118 return tool + renderParamList(true, width-lipgloss.Width(tool), params...)
118 return tool + renderParamList(true, width-lipgloss.Width(tool), params...)