diff --git a/internal/format/format.go b/internal/format/format.go index 3d91ba057e9bb18e47a582ce4e96d22c429426d7..f21da240868c541bc1fb0a29626cdc2305d103d1 100644 --- a/internal/format/format.go +++ b/internal/format/format.go @@ -86,11 +86,11 @@ func formatAsJSON(content string) string { jsonBytes, err := json.MarshalIndent(response, "", " ") if err != nil { // In case of an error, return a manually formatted JSON - jsonEscaped := strings.Replace(content, "\\", "\\\\", -1) - jsonEscaped = strings.Replace(jsonEscaped, "\"", "\\\"", -1) - jsonEscaped = strings.Replace(jsonEscaped, "\n", "\\n", -1) - jsonEscaped = strings.Replace(jsonEscaped, "\r", "\\r", -1) - jsonEscaped = strings.Replace(jsonEscaped, "\t", "\\t", -1) + jsonEscaped := strings.ReplaceAll(content, "\\", "\\\\") + jsonEscaped = strings.ReplaceAll(jsonEscaped, "\"", "\\\"") + jsonEscaped = strings.ReplaceAll(jsonEscaped, "\n", "\\n") + jsonEscaped = strings.ReplaceAll(jsonEscaped, "\r", "\\r") + jsonEscaped = strings.ReplaceAll(jsonEscaped, "\t", "\\t") return fmt.Sprintf("{\n \"response\": \"%s\"\n}", jsonEscaped) } diff --git a/internal/llm/agent/agent.go b/internal/llm/agent/agent.go index 4c8bae171118b4550c9d2a28cb5df0456099530c..09602c52321cd5716c76c9ab3fe569a8331e962b 100644 --- a/internal/llm/agent/agent.go +++ b/internal/llm/agent/agent.go @@ -135,7 +135,7 @@ func (a *agent) Cancel(sessionID string) { func (a *agent) IsBusy() bool { busy := false - a.activeRequests.Range(func(key, value interface{}) bool { + a.activeRequests.Range(func(key, value any) bool { if cancelFunc, ok := value.(context.CancelFunc); ok { if cancelFunc != nil { busy = true diff --git a/internal/llm/provider/anthropic.go b/internal/llm/provider/anthropic.go index f5f627c228f5708307980efdcaf9e35a8a9f48c8..634040ebc3dad8d6dc9e7642ebbe95ac3b051c63 100644 --- a/internal/llm/provider/anthropic.go +++ b/internal/llm/provider/anthropic.go @@ -273,9 +273,10 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message switch event := event.AsAny().(type) { case anthropic.ContentBlockStartEvent: - if event.ContentBlock.Type == "text" { + switch event.ContentBlock.Type { + case "text": eventChan <- ProviderEvent{Type: EventContentStart} - } else if event.ContentBlock.Type == "tool_use" { + case "tool_use": currentToolCallID = event.ContentBlock.ID eventChan <- ProviderEvent{ Type: EventToolUseStart, diff --git a/internal/llm/provider/gemini.go b/internal/llm/provider/gemini.go index 57a81d9af0dbc97db992d43f246c4cde8e9927a4..9481d8d545aab12a3739fe99b4af61f4ed99a514 100644 --- a/internal/llm/provider/gemini.go +++ b/internal/llm/provider/gemini.go @@ -96,7 +96,7 @@ func (g *geminiClient) convertMessages(messages []message.Message) []*genai.Cont case message.Tool: for _, result := range msg.ToolResults() { - response := map[string]interface{}{"result": result.Content} + response := map[string]any{"result": result.Content} parsed, err := parseJsonToMap(result.Content) if err == nil { response = parsed @@ -155,10 +155,10 @@ func (g *geminiClient) convertTools(tools []tools.BaseTool) []*genai.Tool { } func (g *geminiClient) finishReason(reason genai.FinishReason) message.FinishReason { - switch { - case reason == genai.FinishReasonStop: + switch reason { + case genai.FinishReasonStop: return message.FinishReasonEndTurn - case reason == genai.FinishReasonMaxTokens: + case genai.FinishReasonMaxTokens: return message.FinishReasonMaxTokens default: return message.FinishReasonUnknown @@ -402,12 +402,9 @@ func (g *geminiClient) shouldRetry(attempts int, err error) (bool, int64, error) } errMsg := err.Error() - isRateLimit := false + isRateLimit := contains(errMsg, "rate limit", "quota exceeded", "too many requests") // Check for common rate limit error messages - if contains(errMsg, "rate limit", "quota exceeded", "too many requests") { - isRateLimit = true - } if !isRateLimit { return false, 0, err @@ -462,13 +459,13 @@ func WithGeminiDisableCache() GeminiOption { } // Helper functions -func parseJsonToMap(jsonStr string) (map[string]interface{}, error) { - var result map[string]interface{} +func parseJsonToMap(jsonStr string) (map[string]any, error) { + var result map[string]any err := json.Unmarshal([]byte(jsonStr), &result) return result, err } -func convertSchemaProperties(parameters map[string]interface{}) map[string]*genai.Schema { +func convertSchemaProperties(parameters map[string]any) map[string]*genai.Schema { properties := make(map[string]*genai.Schema) for name, param := range parameters { @@ -478,10 +475,10 @@ func convertSchemaProperties(parameters map[string]interface{}) map[string]*gena return properties } -func convertToSchema(param interface{}) *genai.Schema { +func convertToSchema(param any) *genai.Schema { schema := &genai.Schema{Type: genai.TypeString} - paramMap, ok := param.(map[string]interface{}) + paramMap, ok := param.(map[string]any) if !ok { return schema } @@ -506,7 +503,7 @@ func convertToSchema(param interface{}) *genai.Schema { case "array": schema.Items = processArrayItems(paramMap) case "object": - if props, ok := paramMap["properties"].(map[string]interface{}); ok { + if props, ok := paramMap["properties"].(map[string]any); ok { schema.Properties = convertSchemaProperties(props) } } @@ -514,8 +511,8 @@ func convertToSchema(param interface{}) *genai.Schema { return schema } -func processArrayItems(paramMap map[string]interface{}) *genai.Schema { - items, ok := paramMap["items"].(map[string]interface{}) +func processArrayItems(paramMap map[string]any) *genai.Schema { + items, ok := paramMap["items"].(map[string]any) if !ok { return nil } diff --git a/internal/llm/provider/openai.go b/internal/llm/provider/openai.go index aa917ca40620c386d1b5b27ea6f6197260697b2e..05658dd6db760a1d05a88ae4931de5c70d9cc453 100644 --- a/internal/llm/provider/openai.go +++ b/internal/llm/provider/openai.go @@ -284,7 +284,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t if err == nil || errors.Is(err, io.EOF) { // Stream completed successfully finishReason := o.finishReason(string(acc.ChatCompletion.Choices[0].FinishReason)) - if len(acc.ChatCompletion.Choices[0].Message.ToolCalls) > 0 { + if len(acc.Choices[0].Message.ToolCalls) > 0 { toolCalls = append(toolCalls, o.toolCalls(acc.ChatCompletion)...) } if len(toolCalls) > 0 { diff --git a/internal/llm/tools/bash_test.go b/internal/llm/tools/bash_test.go index b26c96097d25414567f0c456b88dbf54e6503e12..28ed7ab39925ca06aae1d49b51d370fd01613434 100644 --- a/internal/llm/tools/bash_test.go +++ b/internal/llm/tools/bash_test.go @@ -2,58 +2,41 @@ package tools import ( "runtime" + "slices" "testing" ) func TestGetSafeReadOnlyCommands(t *testing.T) { commands := getSafeReadOnlyCommands() - + // Check that we have some commands if len(commands) == 0 { t.Fatal("Expected some safe commands, got none") } - + // Check for cross-platform commands that should always be present crossPlatformCommands := []string{"echo", "hostname", "whoami", "git status", "go version"} for _, cmd := range crossPlatformCommands { - found := false - for _, safeCmd := range commands { - if safeCmd == cmd { - found = true - break - } - } + found := slices.Contains(commands, cmd) if !found { t.Errorf("Expected cross-platform command %q to be in safe commands", cmd) } } - + if runtime.GOOS == "windows" { // Check for Windows-specific commands windowsCommands := []string{"dir", "type", "Get-Process"} for _, cmd := range windowsCommands { - found := false - for _, safeCmd := range commands { - if safeCmd == cmd { - found = true - break - } - } + found := slices.Contains(commands, cmd) if !found { t.Errorf("Expected Windows command %q to be in safe commands on Windows", cmd) } } - + // Check that Unix commands are NOT present on Windows unixCommands := []string{"ls", "pwd", "ps"} for _, cmd := range unixCommands { - found := false - for _, safeCmd := range commands { - if safeCmd == cmd { - found = true - break - } - } + found := slices.Contains(commands, cmd) if found { t.Errorf("Unix command %q should not be in safe commands on Windows", cmd) } @@ -62,28 +45,16 @@ func TestGetSafeReadOnlyCommands(t *testing.T) { // Check for Unix-specific commands unixCommands := []string{"ls", "pwd", "ps"} for _, cmd := range unixCommands { - found := false - for _, safeCmd := range commands { - if safeCmd == cmd { - found = true - break - } - } + found := slices.Contains(commands, cmd) if !found { t.Errorf("Expected Unix command %q to be in safe commands on Unix", cmd) } } - + // Check that Windows-specific commands are NOT present on Unix windowsOnlyCommands := []string{"dir", "Get-Process", "systeminfo"} for _, cmd := range windowsOnlyCommands { - found := false - for _, safeCmd := range commands { - if safeCmd == cmd { - found = true - break - } - } + found := slices.Contains(commands, cmd) if found { t.Errorf("Windows-only command %q should not be in safe commands on Unix", cmd) } @@ -94,10 +65,10 @@ func TestGetSafeReadOnlyCommands(t *testing.T) { func TestPlatformSpecificSafeCommands(t *testing.T) { // Test that the function returns different results on different platforms commands := getSafeReadOnlyCommands() - + hasWindowsCommands := false hasUnixCommands := false - + for _, cmd := range commands { if cmd == "dir" || cmd == "Get-Process" || cmd == "systeminfo" { hasWindowsCommands = true @@ -106,7 +77,7 @@ func TestPlatformSpecificSafeCommands(t *testing.T) { hasUnixCommands = true } } - + if runtime.GOOS == "windows" { if !hasWindowsCommands { t.Error("Expected Windows commands on Windows platform") @@ -122,4 +93,5 @@ func TestPlatformSpecificSafeCommands(t *testing.T) { t.Error("Expected Unix commands on Unix platform") } } -} \ No newline at end of file +} + diff --git a/internal/permission/permission.go b/internal/permission/permission.go index 3492885c05fdb8a3167757d9e4c20388de0be6df..f2d92249f9ce926d6421b06714b1302de0b1530d 100644 --- a/internal/permission/permission.go +++ b/internal/permission/permission.go @@ -116,10 +116,7 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool { s.Publish(pubsub.CreatedEvent, permission) // Wait for the response indefinitely - select { - case resp := <-respCh: - return resp - } + return <-respCh } func (s *permissionService) AutoApproveSession(sessionID string) { diff --git a/internal/tui/components/chat/messages/renderer.go b/internal/tui/components/chat/messages/renderer.go index a222a4618134047ca600925655d2ded043265c01..54bdd4c84ef4a7914e16d994e94ed84158d64f4e 100644 --- a/internal/tui/components/chat/messages/renderer.go +++ b/internal/tui/components/chat/messages/renderer.go @@ -112,7 +112,7 @@ func (br baseRenderer) unmarshalParams(input string, target any) error { } // makeHeader builds the tool call header with status icon and parameters for a nested tool call. -func (br baseRenderer) makeNestedHeader(v *toolCallCmp, tool string, width int, params ...string) string { +func (br baseRenderer) makeNestedHeader(_ *toolCallCmp, tool string, width int, params ...string) string { t := styles.CurrentTheme() tool = t.S().Base.Foreground(t.FgHalfMuted).Render(tool) + " " return tool + renderParamList(true, width-lipgloss.Width(tool), params...)