From 69c5d776e20bc3d919f80009f574b1d72d14ee06 Mon Sep 17 00:00:00 2001 From: Gregory Gauthier Date: Wed, 4 Mar 2026 11:50:21 +0000 Subject: [PATCH] feat(agent): implement tool calling in agent mode Add support for Grok to call tools (edit, scaffold, testgen, lint, commit) via JSON in ```tool blocks. Introduce HandleToolCall to parse and execute tool requests, integrating with existing commands. Update system prompt and chat loop to handle tool calls and feed results back. --- cmd/chat.go | 23 ++++++++-- internal/agent/tools.go | 95 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 internal/agent/tools.go diff --git a/cmd/chat.go b/cmd/chat.go index 66a5fe5..b433462 100644 --- a/cmd/chat.go +++ b/cmd/chat.go @@ -12,6 +12,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "gmgauthier.com/grokkit/config" + "gmgauthier.com/grokkit/internal/agent" "gmgauthier.com/grokkit/internal/grok" ) @@ -97,10 +98,15 @@ func runChat(cmd *cobra.Command, args []string) { } if agentMode { - systemPrompt["content"] = `You are Grok in Agent Mode. -You have access to tools: edit, scaffold, testgen, lint, commit, etc. -Always use tools when the user wants you to change code, generate tests, lint or commit. -Be concise and action-oriented. After every tool call, wait for the result.` + systemPrompt["content"] = "You are Grok in Agent Mode.\n" + + "You can call tools using this exact JSON format inside ```tool blocks:\n\n" + + "```tool\n" + + "{\"tool\": \"edit\", \"file\": \"main.go\", \"instruction\": \"Add error handling\"}\n" + + "```\n\n" + + "Available tools: edit, scaffold, testgen, lint, commit.\n\n" + + "Always use tools when the user asks you to change code, generate tests, lint, or commit.\n" + + "After every tool call, wait for the result before continuing.\n" + + "Be concise and action-oriented." } history := loadChatHistory() @@ -138,6 +144,15 @@ Be concise and action-oriented. After every tool call, wait for the result.` color.Green("Grok > ") reply := client.Stream(history, model) + // Agent mode: check for tool calls + if agentMode && strings.Contains(reply, "```tool") { + agent.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) diff --git a/internal/agent/tools.go b/internal/agent/tools.go new file mode 100644 index 0000000..d3461f2 --- /dev/null +++ b/internal/agent/tools.go @@ -0,0 +1,95 @@ +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, + }) +}