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.
This commit is contained in:
Gregory Gauthier 2026-03-04 11:50:21 +00:00
parent 875e34669c
commit 69c5d776e2
2 changed files with 114 additions and 4 deletions

View File

@ -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)

95
internal/agent/tools.go Normal file
View File

@ -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,
})
}