refactor(chat): move tool handling to cmd/chat.go to resolve import cycle
Relocate ToolCall struct and HandleToolCall function from internal/agent/tools.go to cmd/chat.go, renaming to handleToolCall for package-internal use. This eliminates the import cycle between cmd and agent packages while preserving agent mode functionality.
This commit is contained in:
parent
69c5d776e2
commit
a5fda5bbfd
71
cmd/chat.go
71
cmd/chat.go
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"gmgauthier.com/grokkit/config"
|
"gmgauthier.com/grokkit/config"
|
||||||
"gmgauthier.com/grokkit/internal/agent"
|
"gmgauthier.com/grokkit/internal/git"
|
||||||
"gmgauthier.com/grokkit/internal/grok"
|
"gmgauthier.com/grokkit/internal/grok"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,6 +20,15 @@ type ChatHistory struct {
|
|||||||
Messages []map[string]string `json:"messages"`
|
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 {
|
func loadChatHistory() []map[string]string {
|
||||||
histFile := getChatHistoryFile()
|
histFile := getChatHistoryFile()
|
||||||
data, err := os.ReadFile(histFile)
|
data, err := os.ReadFile(histFile)
|
||||||
@ -31,7 +40,6 @@ func loadChatHistory() []map[string]string {
|
|||||||
if err := json.Unmarshal(data, &hist); err != nil {
|
if err := json.Unmarshal(data, &hist); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return hist.Messages
|
return hist.Messages
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +58,6 @@ func getChatHistoryFile() string {
|
|||||||
if configFile != "" {
|
if configFile != "" {
|
||||||
return configFile
|
return configFile
|
||||||
}
|
}
|
||||||
|
|
||||||
home, _ := os.UserHomeDir()
|
home, _ := os.UserHomeDir()
|
||||||
if home == "" {
|
if home == "" {
|
||||||
home = "."
|
home = "."
|
||||||
@ -80,7 +87,6 @@ func runChat(cmd *cobra.Command, args []string) {
|
|||||||
agentMode, _ := cmd.Flags().GetBool("agent")
|
agentMode, _ := cmd.Flags().GetBool("agent")
|
||||||
modelFlag, _ := cmd.Flags().GetString("model")
|
modelFlag, _ := cmd.Flags().GetString("model")
|
||||||
|
|
||||||
// Model switching logic
|
|
||||||
var model string
|
var model string
|
||||||
if modelFlag != "" {
|
if modelFlag != "" {
|
||||||
model = modelFlag
|
model = modelFlag
|
||||||
@ -115,7 +121,7 @@ func runChat(cmd *cobra.Command, args []string) {
|
|||||||
} else if history[0]["role"] != "system" {
|
} else if history[0]["role"] != "system" {
|
||||||
history = append([]map[string]string{systemPrompt}, history...)
|
history = append([]map[string]string{systemPrompt}, history...)
|
||||||
} else {
|
} else {
|
||||||
history[0] = systemPrompt // refresh system prompt
|
history[0] = systemPrompt
|
||||||
}
|
}
|
||||||
|
|
||||||
color.Cyan("┌──────────────────────────────────────────────────────────────┐")
|
color.Cyan("┌──────────────────────────────────────────────────────────────┐")
|
||||||
@ -144,18 +150,63 @@ func runChat(cmd *cobra.Command, args []string) {
|
|||||||
color.Green("Grok > ")
|
color.Green("Grok > ")
|
||||||
reply := client.Stream(history, model)
|
reply := client.Stream(history, model)
|
||||||
|
|
||||||
// Agent mode: check for tool calls
|
// === AGENT TOOL CALL HANDLING ===
|
||||||
if agentMode && strings.Contains(reply, "```tool") {
|
if agentMode && strings.Contains(reply, "```tool") {
|
||||||
agent.HandleToolCall(reply, &history, model)
|
handleToolCall(reply, &history, model)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
history = append(history, map[string]string{"role": "assistant", "content": reply})
|
history = append(history, map[string]string{"role": "assistant", "content": reply})
|
||||||
_ = saveChatHistory(history)
|
_ = saveChatHistory(history)
|
||||||
|
|
||||||
history = append(history, map[string]string{"role": "assistant", "content": reply})
|
|
||||||
_ = saveChatHistory(history)
|
|
||||||
|
|
||||||
fmt.Println()
|
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})
|
||||||
|
}
|
||||||
|
|||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user