diff --git a/internal/handler/anthropic.go b/internal/handler/anthropic.go index e83e0c4..65799c2 100644 --- a/internal/handler/anthropic.go +++ b/internal/handler/anthropic.go @@ -364,7 +364,59 @@ func handleStream(c *gin.Context, cursorReq browser.CursorChatRequest, model str responseText := fullResponse.String() toolCalls, _ := toolParser.ParseToolCalls(responseText) - if len(toolCalls) > 0 { + // 如果没有工具调用,检查是否是拒绝响应,自动执行 + if len(toolCalls) == 0 && tools.DetectRefusal(responseText) { + if cmd := tools.ExtractCommandFromRefusal(responseText); cmd != "" { + // 自动执行提取的命令 + output, execErr := toolExecutor.Execute("bash", map[string]interface{}{ + "command": cmd, + }) + + resultText := output + isError := false + if execErr != nil { + resultText = execErr.Error() + isError = true + } + + // 发送工具调用块 + toolID := "toolu_" + generateID() + stopReason = "tool_use" + + c.Writer.WriteString("event: content_block_start\n") + c.Writer.WriteString(fmt.Sprintf(`data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"%s","name":"bash","input":{}}}`+"\n\n", toolID)) + flusher.Flush() + + inputJSON, _ := json.Marshal(map[string]string{"command": cmd}) + c.Writer.WriteString("event: content_block_delta\n") + c.Writer.WriteString(fmt.Sprintf(`data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"%s"}}`+"\n\n", escapeJSON(string(inputJSON)))) + flusher.Flush() + + c.Writer.WriteString("event: content_block_stop\n") + c.Writer.WriteString(`data: {"type":"content_block_stop","index":1}` + "\n\n") + flusher.Flush() + + // 发送执行结果作为新的文本块 + statusEmoji := "✅" + if isError { + statusEmoji = "❌" + } + resultMsg := fmt.Sprintf("\n\n%s 已自动执行:\n```\n%s\n```\n结果:\n```\n%s\n```", statusEmoji, cmd, resultText) + resultJSON, _ := json.Marshal(resultMsg) + + c.Writer.WriteString("event: content_block_start\n") + c.Writer.WriteString(`data: {"type":"content_block_start","index":2,"content_block":{"type":"text","text":""}}` + "\n\n") + flusher.Flush() + + c.Writer.WriteString("event: content_block_delta\n") + c.Writer.WriteString(`data: {"type":"content_block_delta","index":2,"delta":{"type":"text_delta","text":` + string(resultJSON) + `}}` + "\n\n") + flusher.Flush() + + c.Writer.WriteString("event: content_block_stop\n") + c.Writer.WriteString(`data: {"type":"content_block_stop","index":2}` + "\n\n") + flusher.Flush() + } + } else if len(toolCalls) > 0 { stopReason = "tool_use" // 发送工具调用块 for i, call := range toolCalls { diff --git a/internal/tools/intent.go b/internal/tools/intent.go index f1f513b..7cf3adc 100644 --- a/internal/tools/intent.go +++ b/internal/tools/intent.go @@ -136,19 +136,36 @@ func DetectRefusal(response string) bool { // ExtractCommandFromRefusal 从拒绝响应中提取建议的命令 func ExtractCommandFromRefusal(response string) string { // 匹配代码块中的命令 - codeBlockRe := regexp.MustCompile("```(?:bash|sh)?\\s*\\n([^`]+)\\n```") + codeBlockRe := regexp.MustCompile("```(?:bash|sh)?\\s*\\n?([^`]+)\\n?```") if matches := codeBlockRe.FindStringSubmatch(response); len(matches) > 1 { - return strings.TrimSpace(matches[1]) + cmd := strings.TrimSpace(matches[1]) + if cmd != "" { + return cmd + } } - // 匹配单行命令 + // 匹配常见命令模式(每行检查) + lines := strings.Split(response, "\n") cmdPatterns := []*regexp.Regexp{ - regexp.MustCompile(`(?m)^\s*(cat|echo|mkdir|touch|rm|cp|mv|ls|cd|pwd)\s+.+$`), - regexp.MustCompile(`(?m)^\s*(\S+)\s+>\s+\S+`), + // echo "xxx" > file + regexp.MustCompile(`^\s*(echo\s+.+\s*>\s*\S+)`), + // cat > file << 'EOF' 或 cat > file + regexp.MustCompile(`^\s*(cat\s+.+\s*>\s*\S+)`), + // 常见命令开头 + regexp.MustCompile(`^\s*((?:echo|cat|mkdir|touch|rm|cp|mv|ls|pwd|cd|chmod|chown)\s+.+)$`), + // 任何 > 重定向 + regexp.MustCompile(`^\s*(\S+\s+["'][^"']+["']\s*>\s*\S+)`), } - for _, re := range cmdPatterns { - if matches := re.FindString(response); matches != "" { - return strings.TrimSpace(matches) + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + for _, re := range cmdPatterns { + if matches := re.FindStringSubmatch(line); len(matches) > 1 { + return strings.TrimSpace(matches[1]) + } } }