diff --git a/cmd/chat.go b/cmd/chat.go index b433462..bbfa2f8 100644 --- a/cmd/chat.go +++ b/cmd/chat.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "gmgauthier.com/grokkit/config" - "gmgauthier.com/grokkit/internal/agent" + "gmgauthier.com/grokkit/internal/git" "gmgauthier.com/grokkit/internal/grok" ) @@ -20,6 +20,15 @@ type ChatHistory struct { Messages []map[string]string `json:"messages"` } +type ToolCall struct { + Tool string `json:"tool"` + File string `json:"file,omitempty"` + Path string `json:"path,omitempty"` + Instruction string `json:"instruction,omitempty"` + Description string `json:"description,omitempty"` + Message string `json:"message,omitempty"` +} + func loadChatHistory() []map[string]string { histFile := getChatHistoryFile() data, err := os.ReadFile(histFile) @@ -31,7 +40,6 @@ func loadChatHistory() []map[string]string { if err := json.Unmarshal(data, &hist); err != nil { return nil } - return hist.Messages } @@ -50,7 +58,6 @@ func getChatHistoryFile() string { if configFile != "" { return configFile } - home, _ := os.UserHomeDir() if home == "" { home = "." @@ -80,7 +87,6 @@ func runChat(cmd *cobra.Command, args []string) { agentMode, _ := cmd.Flags().GetBool("agent") modelFlag, _ := cmd.Flags().GetString("model") - // Model switching logic var model string if modelFlag != "" { model = modelFlag @@ -115,7 +121,7 @@ func runChat(cmd *cobra.Command, args []string) { } else if history[0]["role"] != "system" { history = append([]map[string]string{systemPrompt}, history...) } else { - history[0] = systemPrompt // refresh system prompt + history[0] = systemPrompt } color.Cyan("┌──────────────────────────────────────────────────────────────┐") @@ -144,18 +150,63 @@ func runChat(cmd *cobra.Command, args []string) { color.Green("Grok > ") reply := client.Stream(history, model) - // Agent mode: check for tool calls + // === AGENT TOOL CALL HANDLING === if agentMode && strings.Contains(reply, "```tool") { - agent.HandleToolCall(reply, &history, model) + handleToolCall(reply, &history, model) continue } history = append(history, map[string]string{"role": "assistant", "content": reply}) _ = saveChatHistory(history) - history = append(history, map[string]string{"role": "assistant", "content": reply}) - _ = saveChatHistory(history) - fmt.Println() } } + +// handleToolCall is now inside cmd package — no import cycle +func handleToolCall(reply string, history *[]map[string]string, model string) { + start := strings.Index(reply, "```tool") + if start == -1 { + return + } + end := strings.Index(reply[start+7:], "```") + if end == -1 { + return + } + block := strings.TrimSpace(reply[start+7 : start+7+end]) + + var tc ToolCall + if err := json.Unmarshal([]byte(block), &tc); err != nil { + color.Red("Failed to parse tool call: %v", err) + return + } + + color.Yellow("\n[Agent] Grok wants to call tool: %s", tc.Tool) + + switch tc.Tool { + case "edit": + if tc.File != "" && tc.Instruction != "" { + RunEditWithInstruction(tc.File, tc.Instruction) + } + case "scaffold": + if tc.Path != "" && tc.Description != "" { + RunScaffoldWithDescription(tc.Path, tc.Description) + } + case "testgen": + if tc.File != "" { + RunTestgenWithFile(tc.File) + } + case "lint": + if tc.File != "" { + RunLintWithFile(tc.File) + } + case "commit": + if tc.Message != "" { + git.Run([]string{"commit", "-m", tc.Message}) + } + default: + color.Red("Unknown tool: %s", tc.Tool) + } + + *history = append(*history, map[string]string{"role": "assistant", "content": reply}) +} diff --git a/internal/agent/tools.go b/internal/agent/tools.go deleted file mode 100644 index d3461f2..0000000 --- a/internal/agent/tools.go +++ /dev/null @@ -1,95 +0,0 @@ -package agent - -import ( - "encoding/json" - _ "fmt" - "strings" - - "github.com/fatih/color" - "gmgauthier.com/grokkit/cmd" - "gmgauthier.com/grokkit/internal/git" -) - -// ToolCall represents a tool request from Grok -type ToolCall struct { - Tool string `json:"tool"` - File string `json:"file,omitempty"` - Path string `json:"path,omitempty"` - Instruction string `json:"instruction,omitempty"` - Description string `json:"description,omitempty"` - Message string `json:"message,omitempty"` -} - -// handleToolCall parses and executes tool calls from Grok's response -func HandleToolCall(reply string, history *[]map[string]string, model string) { - // Look for ```tool ... ``` blocks - start := strings.Index(reply, "```tool") - if start == -1 { - return - } - end := strings.Index(reply[start:], "```") - if end == -1 { - return - } - block := strings.TrimSpace(reply[start+7 : start+end]) - - var tc ToolCall - if err := json.Unmarshal([]byte(block), &tc); err != nil { - color.Red("Failed to parse tool call: %v", err) - return - } - - color.Yellow("\n[Agent] Grok wants to call tool: %s", tc.Tool) - - switch tc.Tool { - case "edit": - if tc.File == "" || tc.Instruction == "" { - color.Red("Invalid edit call — missing file or instruction") - break - } - color.Cyan("Editing %s with instruction: %s", tc.File, tc.Instruction) - // Reuse the existing edit flow (it already does preview + confirm) - cmd.RunEditWithInstruction(tc.File, tc.Instruction) - - case "scaffold": - if tc.Path == "" || tc.Description == "" { - color.Red("Invalid scaffold call") - break - } - color.Cyan("Scaffolding %s: %s", tc.Path, tc.Description) - cmd.RunScaffoldWithDescription(tc.Path, tc.Description) - - case "testgen": - if tc.File == "" { - color.Red("Invalid testgen call") - break - } - color.Cyan("Generating tests for %s", tc.File) - cmd.RunTestgenWithFile(tc.File) - - case "lint": - if tc.File == "" { - color.Red("Invalid lint call") - break - } - color.Cyan("Linting %s", tc.File) - cmd.RunLintWithFile(tc.File) - - case "commit": - if tc.Message == "" { - color.Red("Invalid commit call") - break - } - color.Cyan("Committing with message: %s", tc.Message) - git.Run([]string{"commit", "-m", tc.Message}) - - default: - color.Red("Unknown tool: %s", tc.Tool) - } - - // Feed result back to Grok - *history = append(*history, map[string]string{ - "role": "assistant", - "content": reply, - }) -}