Commit f5184a0
Changed files (2)
internal
del
internal/del/assistant.go
@@ -5,90 +5,159 @@ import (
"context"
"encoding/json"
"fmt"
+ "io/ioutil"
"os"
+ "os/exec"
+ "regexp"
"strings"
)
+// Tool represents a tool Del can run
+type Tool struct {
+ Name string
+ Description string
+ Handler func(args map[string]interface{}) (interface{}, error)
+}
+
+// Del represents the assistant instance
type Del struct {
aiProvider AIProvider
tools map[string]*Tool
+ mcpServers map[string]string
}
-func NewDel(ai AIProvider) *Del {
- d := &Del{
- aiProvider: ai,
- tools: map[string]*Tool{},
+// NewDel creates a new assistant
+func NewDel(provider AIProvider) *Del {
+ tools := map[string]*Tool{
+ "run_command": {
+ Name: "run_command",
+ Description: "Execute a shell command and return the output",
+ Handler: func(args map[string]interface{}) (interface{}, error) {
+ cmd, ok := args["cmd"].(string)
+ if !ok {
+ return nil, fmt.Errorf("missing 'cmd' string argument")
+ }
+ out, err := exec.Command("bash", "-c", cmd).CombinedOutput()
+ if err != nil {
+ return nil, fmt.Errorf("command failed: %v\n%s", err, string(out))
+ }
+ return string(out), nil
+ },
+ },
+ "read_file": {
+ Name: "read_file",
+ Description: "Reads the contents of a file",
+ Handler: func(args map[string]interface{}) (interface{}, error) {
+ path, ok := args["path"].(string)
+ if !ok {
+ return nil, fmt.Errorf("missing 'path' string argument")
+ }
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ return string(data), nil
+ },
+ },
+ "write_file": {
+ Name: "write_file",
+ Description: "Writes content to a file",
+ Handler: func(args map[string]interface{}) (interface{}, error) {
+ path, ok1 := args["path"].(string)
+ content, ok2 := args["content"].(string)
+ if !ok1 || !ok2 {
+ return nil, fmt.Errorf("missing 'path' or 'content' argument")
+ }
+ if err := ioutil.WriteFile(path, []byte(content), 0644); err != nil {
+ return nil, err
+ }
+ return fmt.Sprintf("wrote %d bytes to %s", len(content), path), nil
+ },
+ },
+ "list_dir": {
+ Name: "list_dir",
+ Description: "Lists files in a directory",
+ Handler: func(args map[string]interface{}) (interface{}, error) {
+ dir, ok := args["path"].(string)
+ if !ok {
+ dir = "."
+ }
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return nil, err
+ }
+ var names []string
+ for _, entry := range entries {
+ names = append(names, entry.Name())
+ }
+ return names, nil
+ },
+ },
+ }
+
+ return &Del{
+ aiProvider: provider,
+ tools: tools,
+ mcpServers: make(map[string]string),
}
- d.registerTools()
- return d
}
+// StartREPL launches an interactive session
func (d *Del) StartREPL(ctx context.Context) {
- fmt.Println("๐ค Del is ready with", d.aiProvider.Name())
+ fmt.Printf("๐ค Del is ready with %s\n", d.aiProvider.Name())
scanner := bufio.NewScanner(os.Stdin)
-
for {
fmt.Print("๐ค You: ")
if !scanner.Scan() {
break
}
- line := scanner.Text()
- if strings.HasPrefix(line, "/") {
- d.handleSlash(line)
- continue
+ input := scanner.Text()
+ if strings.HasPrefix(input, "/quit") {
+ fmt.Println("๐ Goodbye!")
+ return
}
- prompt := d.buildPrompt(line)
- response, err := d.aiProvider.Generate(ctx, prompt)
+ resp, err := d.aiProvider.Generate(ctx, input)
if err != nil {
- fmt.Println("โ Error:", err)
+ fmt.Println("[error]", err)
continue
}
-
- fmt.Println("๐ค Del:", response)
- d.handleToolResponse(response)
+ output := d.handleToolCalls(ctx, resp)
+ fmt.Println(output)
}
}
-func (d *Del) handleSlash(line string) {
- fields := strings.Fields(line)
- switch fields[0] {
- case "/quit":
- fmt.Println("๐ Bye!")
- os.Exit(0)
- case "/model":
- fmt.Println("Current model:", d.aiProvider.Name())
- default:
- fmt.Println("โ Unknown command:", line)
- }
-}
+// handleToolCalls parses output for TOOL_USE and runs the associated tool.
+func (d *Del) handleToolCalls(ctx context.Context, response string) string {
+ toolUseRE := regexp.MustCompile(`(?m)^TOOL_USE:\s*(\w+)\s*(\{.*\})`)
+ matches := toolUseRE.FindAllStringSubmatch(response, -1)
+
+ var results []string
+ for _, match := range matches {
+ name, jsonArgs := match[1], match[2]
+ tool, ok := d.tools[name]
+ if !ok {
+ results = append(results, fmt.Sprintf("[error] tool '%s' not found", name))
+ continue
+ }
+
+ var args map[string]interface{}
+ if err := json.Unmarshal([]byte(jsonArgs), &args); err != nil {
+ results = append(results, fmt.Sprintf("[error] invalid JSON args for tool '%s': %v", name, err))
+ continue
+ }
-func (d *Del) handleToolResponse(response string) {
- for _, line := range strings.Split(response, " ") {
- if strings.HasPrefix(line, "TOOL_USE:") {
- parts := strings.SplitN(line[len("TOOL_USE:"):], " ", 2)
- if len(parts) != 2 {
- fmt.Println("โ Malformed tool call:", line)
- continue
- }
- name := strings.TrimSpace(parts[0])
- argsJSON := strings.TrimSpace(parts[1])
- tool, ok := d.tools[name]
- if !ok {
- fmt.Println("โ Unknown tool:", name)
- continue
- }
- var args map[string]interface{}
- if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
- fmt.Println("โ JSON error:", err)
- continue
- }
- result, err := tool.Handler(args)
- if err != nil {
- fmt.Println("โ Tool error:", err)
- } else {
- fmt.Printf("โ
%s result: %+v ", name, result)
- }
+ result, err := tool.Handler(args)
+ if err != nil {
+ results = append(results, fmt.Sprintf("[error] tool '%s' failed: %v", name, err))
+ continue
}
+ results = append(results, fmt.Sprintf("[tool:%s] %v", name, result))
}
+
+ if len(results) == 0 {
+ return response
+ }
+
+ return strings.TrimSpace(response) + "\n\n" + strings.Join(results, "\n")
}
internal/del/tools.go
@@ -1,66 +1,60 @@
package del
import (
- "fmt"
- "os"
- "os/exec"
+ "fmt"
+ "os"
+ "os/exec"
)
-type Tool struct {
- Name string
- Description string
- Handler func(args map[string]interface{}) (interface{}, error)
-}
-
func (d *Del) registerTools() {
- d.tools["read_file"] = &Tool{"read_file", "Read a file from disk", d.readFile}
- d.tools["write_file"] = &Tool{"write_file", "Write content to a file", d.writeFile}
- d.tools["run_command"] = &Tool{"run_command", "Execute a shell command", d.runCommand}
- d.tools["analyze_code"] = &Tool{"analyze_code", "Analyze code quality", d.analyzeCode}
- d.tools["mcp_git_list"] = &Tool{"mcp_git_list", "MCP: List repo files", d.stubMCP}
- d.tools["mcp_git_read"] = &Tool{"mcp_git_read", "MCP: Read repo file", d.stubMCP}
+ d.tools["read_file"] = &Tool{"read_file", "Read a file from disk", d.readFile}
+ d.tools["write_file"] = &Tool{"write_file", "Write content to a file", d.writeFile}
+ d.tools["run_command"] = &Tool{"run_command", "Execute a shell command", d.runCommand}
+ d.tools["analyze_code"] = &Tool{"analyze_code", "Analyze code quality", d.analyzeCode}
+ d.tools["mcp_git_list"] = &Tool{"mcp_git_list", "MCP: List repo files", d.stubMCP}
+ d.tools["mcp_git_read"] = &Tool{"mcp_git_read", "MCP: Read repo file", d.stubMCP}
}
func (d *Del) readFile(args map[string]interface{}) (interface{}, error) {
- path, ok := args["path"].(string)
- if !ok {
- return nil, fmt.Errorf("missing 'path'")
- }
- data, err := os.ReadFile(path)
- if err != nil {
- return nil, err
- }
- return string(data), nil
+ path, ok := args["path"].(string)
+ if !ok {
+ return nil, fmt.Errorf("missing 'path'")
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ return string(data), nil
}
func (d *Del) writeFile(args map[string]interface{}) (interface{}, error) {
- path, ok := args["path"].(string)
- content, ok2 := args["content"].(string)
- if !ok || !ok2 {
- return nil, fmt.Errorf("missing 'path' or 'content'")
- }
- return "written", os.WriteFile(path, []byte(content), 0644)
+ path, ok := args["path"].(string)
+ content, ok2 := args["content"].(string)
+ if !ok || !ok2 {
+ return nil, fmt.Errorf("missing 'path' or 'content'")
+ }
+ return "written", os.WriteFile(path, []byte(content), 0644)
}
func (d *Del) runCommand(args map[string]interface{}) (interface{}, error) {
- command, ok := args["command"].(string)
- if !ok {
- return nil, fmt.Errorf("missing 'command'")
- }
- output, err := exec.Command("sh", "-c", command).CombinedOutput()
- return string(output), err
+ command, ok := args["command"].(string)
+ if !ok {
+ return nil, fmt.Errorf("missing 'command'")
+ }
+ output, err := exec.Command("sh", "-c", command).CombinedOutput()
+ return string(output), err
}
func (d *Del) analyzeCode(args map[string]interface{}) (interface{}, error) {
- return map[string]interface{}{
- "quality": "good",
- "suggestions": []string{
- "Consider breaking large functions into smaller ones.",
- "Add more comments for readability.",
- },
- }, nil
+ return map[string]interface{}{
+ "quality": "good",
+ "suggestions": []string{
+ "Consider breaking large functions into smaller ones.",
+ "Add more comments for readability.",
+ },
+ }, nil
}
func (d *Del) stubMCP(args map[string]interface{}) (interface{}, error) {
- return map[string]string{"status": "MCP stub - not connected"}, nil
+ return map[string]string{"status": "MCP stub - not connected"}, nil
}