Commit 56ba470
Changed files (1)
cmd
del
cmd/del/main.go
@@ -3,10 +3,14 @@ package main
import (
"bufio"
"context"
+ "encoding/json"
"flag"
"fmt"
+ "io"
+ "net/http"
"os"
"os/exec"
+ "path/filepath"
"regexp"
"strings"
"sync"
@@ -63,32 +67,44 @@ func NewDel(model string) *Del {
chatHistory: []api.Message{
{
Role: "system",
- Content: `You are Del, an AI coding assistant. When users need file operations or code analysis, use your available tools.
+ Content: `You are Del, an AI coding assistant with comprehensive Claude Code capabilities. When users need file operations, code analysis, or project management, use your available tools.
Available tools:
-- read_file: Read file contents
-- list_dir: List directory contents
-- run_command: Execute shell commands
-- git_status: Check git repository status
-- write_file: Write content to files
-- analyze_code: Analyze code structure (auto-detects files if no path provided)
-- search_code: Search for patterns in code
+• FILE OPERATIONS: read_file, write_file, edit_file, multi_edit, list_dir
+• SEARCH & DISCOVERY: glob, grep, search_code
+• COMMAND EXECUTION: bash (enhanced with timeout), git_status
+• CODE ANALYSIS: analyze_code (auto-detects project files)
+• PROJECT MANAGEMENT: todo_read, todo_write, exit_plan_mode
+• NOTEBOOKS: notebook_read, notebook_edit (Jupyter support)
+• WEB OPERATIONS: web_fetch, web_search
+
+KEY CAPABILITIES:
+- Edit files with exact string replacement (edit_file) or multiple edits (multi_edit)
+- Find files with glob patterns (**/*.js, src/**/*.go)
+- Search file contents with regex (grep) or code patterns (search_code)
+- Execute bash commands with safety timeouts
+- Manage todos and project planning workflows
+- Read and analyze Jupyter notebooks
+- Fetch and process web content
EXAMPLES:
-- When user says "analyze the code" or "analyze this project": use analyze_code (auto-detects files)
-- When user says "list files": use list_dir
-- When user says "read main.go": use read_file with path "main.go"
-- When user says "check git status": use git_status
+- "list files" → use list_dir
+- "read main.go" → use read_file
+- "find all .js files" → use glob with pattern "**/*.js"
+- "search for function main" → use grep with pattern "function main"
+- "run tests" → use bash with command "npm test"
+- "edit config.yaml" → use edit_file to make precise changes
+- "show todos" → use todo_read
-FORMATTING: Always format your responses using markdown. Use:
-- ## Headers for main sections
-- **bold** for important terms
-- backticks for inline code, file names, and commands
-- - bullet points for lists
+FORMATTING: Always format responses using markdown:
+- ## Headers for sections
+- **bold** for important terms
+- backticks for code/files/commands
+- • bullet points for lists
- 1. numbered lists for steps
- code blocks for multi-line code
-IMPORTANT: Use tools first, then provide a natural markdown response based on the results.`,
+IMPORTANT: Use tools first, then provide natural markdown responses based on results.`,
},
},
}
@@ -207,13 +223,40 @@ func renderMarkdown(text string) string {
}
func (d *Del) registerTools() {
+ // Core file operations
d.tools["read_file"] = d.readFile
+ d.tools["write_file"] = d.writeFile
+ d.tools["edit_file"] = d.editFile
+ d.tools["multi_edit"] = d.multiEdit
+
+ // Directory and search operations
d.tools["list_dir"] = d.listDir
- d.tools["run_command"] = d.runCommand
+ d.tools["glob"] = d.globFiles
+ d.tools["grep"] = d.grepSearch
+
+ // Command execution (enhanced)
+ d.tools["bash"] = d.bashCommand
+ d.tools["run_command"] = d.runCommand // Keep legacy alias
+
+ // Git operations
d.tools["git_status"] = d.gitStatus
- d.tools["write_file"] = d.writeFile
+
+ // Code analysis
d.tools["analyze_code"] = d.analyzeCode
d.tools["search_code"] = d.searchCode
+
+ // Planning and organization
+ d.tools["todo_read"] = d.todoRead
+ d.tools["todo_write"] = d.todoWrite
+ d.tools["exit_plan_mode"] = d.exitPlanMode
+
+ // Notebook operations
+ d.tools["notebook_read"] = d.notebookRead
+ d.tools["notebook_edit"] = d.notebookEdit
+
+ // Web operations
+ d.tools["web_fetch"] = d.webFetch
+ d.tools["web_search"] = d.webSearch
}
func (d *Del) readFile(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
@@ -663,205 +706,846 @@ func (d *Del) searchCode(ctx context.Context, args map[string]interface{}, progr
return result, nil
}
-
-func (d *Del) executeTool(ctx context.Context, call ToolCall) string {
- d.emit(StreamMessage{
- Type: MessageTypeTool,
- ToolName: call.Name,
- ToolArgs: call.Args,
- Status: "starting",
- })
+// Edit tool - performs exact string replacements in files
+func (d *Del) editFile(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ filePath, ok1 := args["file_path"].(string)
+ oldString, ok2 := args["old_string"].(string)
+ newString, ok3 := args["new_string"].(string)
+ replaceAll, _ := args["replace_all"].(bool)
+
+ if !ok1 || !ok2 || !ok3 {
+ return "", fmt.Errorf("missing required arguments: file_path, old_string, new_string")
+ }
- tool, exists := d.tools[call.Name]
- if !exists {
- d.emit(StreamMessage{
- Type: MessageTypeTool,
- ToolName: call.Name,
- Status: "error",
- Error: fmt.Sprintf("Unknown tool: %s", call.Name),
- })
- return fmt.Sprintf("Unknown tool: %s", call.Name)
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "edit_file",
+ Status: "reading",
+ Content: fmt.Sprintf("Reading %s for editing...", filePath),
}
- progressChan := make(chan StreamMessage, 10)
- done := make(chan bool)
+ data, err := os.ReadFile(filePath)
+ if err != nil {
+ return "", err
+ }
- // Forward progress messages
- go func() {
- for msg := range progressChan {
- d.emit(msg)
- }
- done <- true
- }()
+ content := string(data)
+ var result string
- result, err := tool(ctx, call.Args, progressChan)
- close(progressChan)
- <-done
+ if replaceAll {
+ count := strings.Count(content, oldString)
+ if count == 0 {
+ return "", fmt.Errorf("old_string not found in file")
+ }
+ content = strings.ReplaceAll(content, oldString, newString)
+ result = fmt.Sprintf("Replaced %d occurrences in %s", count, filePath)
+ } else {
+ if !strings.Contains(content, oldString) {
+ return "", fmt.Errorf("old_string not found in file")
+ }
+ if strings.Count(content, oldString) > 1 {
+ return "", fmt.Errorf("old_string appears multiple times; use replace_all=true or provide more context")
+ }
+ content = strings.Replace(content, oldString, newString, 1)
+ result = fmt.Sprintf("Replaced 1 occurrence in %s", filePath)
+ }
+ err = os.WriteFile(filePath, []byte(content), 0644)
if err != nil {
- d.emit(StreamMessage{
- Type: MessageTypeTool,
- ToolName: call.Name,
- Status: "error",
- Error: err.Error(),
- })
- return err.Error()
+ return "", err
}
- d.emit(StreamMessage{
- Type: MessageTypeTool,
- ToolName: call.Name,
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "edit_file",
Status: "completed",
- Result: result,
- })
+ Content: result,
+ }
- return result
+ return result, nil
}
-func (d *Del) formatArgs(args map[string]interface{}) string {
- if len(args) == 0 {
- return ""
+// MultiEdit tool - multiple edits to a single file in one operation
+func (d *Del) multiEdit(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ filePath, ok := args["file_path"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing file_path argument")
}
- var parts []string
- for key, value := range args {
- if str, ok := value.(string); ok && len(str) > 30 {
- parts = append(parts, fmt.Sprintf("%s: \"%.30s...\"", key, str))
+ editsArg, ok := args["edits"]
+ if !ok {
+ return "", fmt.Errorf("missing edits argument")
+ }
+
+ // Parse edits array
+ editsArray, ok := editsArg.([]interface{})
+ if !ok {
+ return "", fmt.Errorf("edits must be an array")
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "multi_edit",
+ Status: "reading",
+ Content: fmt.Sprintf("Reading %s for multi-edit...", filePath),
+ }
+
+ data, err := os.ReadFile(filePath)
+ if err != nil {
+ return "", err
+ }
+
+ content := string(data)
+ editCount := 0
+
+ // Apply edits sequentially
+ for i, editArg := range editsArray {
+ editMap, ok := editArg.(map[string]interface{})
+ if !ok {
+ return "", fmt.Errorf("edit %d must be an object", i)
+ }
+
+ oldString, ok1 := editMap["old_string"].(string)
+ newString, ok2 := editMap["new_string"].(string)
+ replaceAll, _ := editMap["replace_all"].(bool)
+
+ if !ok1 || !ok2 {
+ return "", fmt.Errorf("edit %d missing old_string or new_string", i)
+ }
+
+ if replaceAll {
+ count := strings.Count(content, oldString)
+ if count == 0 {
+ return "", fmt.Errorf("edit %d: old_string not found", i)
+ }
+ content = strings.ReplaceAll(content, oldString, newString)
+ editCount += count
} else {
- parts = append(parts, fmt.Sprintf("%s: %v", key, value))
+ if !strings.Contains(content, oldString) {
+ return "", fmt.Errorf("edit %d: old_string not found", i)
+ }
+ if strings.Count(content, oldString) > 1 {
+ return "", fmt.Errorf("edit %d: old_string appears multiple times; use replace_all=true", i)
+ }
+ content = strings.Replace(content, oldString, newString, 1)
+ editCount++
}
}
- return strings.Join(parts, ", ")
-}
-
-func (d *Del) streamResponseChunks(ctx context.Context, text string) {
- // Instead of word-by-word streaming, send the full response for better markdown rendering
- d.emit(StreamMessage{
- Type: MessageTypeAssistant,
- Content: text,
- })
+
+ err = os.WriteFile(filePath, []byte(content), 0644)
+ if err != nil {
+ return "", err
+ }
+
+ result := fmt.Sprintf("Applied %d edits to %s (total %d replacements)", len(editsArray), filePath, editCount)
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "multi_edit",
+ Status: "completed",
+ Content: result,
+ }
+
+ return result, nil
}
-func (d *Del) buildOllamaTools() []api.Tool {
- var tools []api.Tool
+// Glob tool - fast file pattern matching
+func (d *Del) globFiles(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ pattern, ok := args["pattern"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing pattern argument")
+ }
- // Helper function to create property
- makeProperty := func(propType string, description string) struct {
- Type api.PropertyType `json:"type"`
- Items any `json:"items,omitempty"`
- Description string `json:"description"`
- Enum []any `json:"enum,omitempty"`
- } {
- return struct {
- Type api.PropertyType `json:"type"`
- Items any `json:"items,omitempty"`
- Description string `json:"description"`
- Enum []any `json:"enum,omitempty"`
- }{
- Type: api.PropertyType{propType},
- Description: description,
- }
+ searchPath, ok := args["path"].(string)
+ if !ok {
+ searchPath = "."
}
- // read_file tool
- readFileFunc := api.ToolFunction{
- Name: "read_file",
- Description: "Read file contents",
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "glob",
+ Status: "searching",
+ Content: fmt.Sprintf("Finding files matching '%s'...", pattern),
}
- readFileFunc.Parameters.Type = "object"
- readFileFunc.Parameters.Required = []string{"path"}
- readFileFunc.Parameters.Properties = make(map[string]struct {
- Type api.PropertyType `json:"type"`
- Items any `json:"items,omitempty"`
- Description string `json:"description"`
- Enum []any `json:"enum,omitempty"`
- })
- readFileFunc.Parameters.Properties["path"] = makeProperty("string", "Path to the file to read")
- tools = append(tools, api.Tool{
- Type: "function",
- Function: readFileFunc,
- })
+ // Use filepath.Glob for simple patterns or walk for complex patterns
+ var matches []string
+ var err error
- // list_dir tool
- listDirFunc := api.ToolFunction{
- Name: "list_dir",
- Description: "List directory contents",
+ if strings.Contains(pattern, "**") {
+ // Handle recursive patterns manually
+ err = filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return nil // Skip errors
+ }
+
+ // Convert ** pattern to simple matching
+ simplePattern := strings.ReplaceAll(pattern, "**", "*")
+ matched, _ := filepath.Match(simplePattern, filepath.Base(path))
+ if matched {
+ matches = append(matches, path)
+ }
+
+ return nil
+ })
+ } else {
+ // Use standard glob
+ fullPattern := filepath.Join(searchPath, pattern)
+ matches, err = filepath.Glob(fullPattern)
}
- listDirFunc.Parameters.Type = "object"
- listDirFunc.Parameters.Required = []string{}
- listDirFunc.Parameters.Properties = make(map[string]struct {
- Type api.PropertyType `json:"type"`
- Items any `json:"items,omitempty"`
- Description string `json:"description"`
- Enum []any `json:"enum,omitempty"`
- })
- listDirFunc.Parameters.Properties["path"] = makeProperty("string", "Path to the directory to list (defaults to current directory)")
-
- tools = append(tools, api.Tool{
- Type: "function",
- Function: listDirFunc,
- })
- // run_command tool
- runCommandFunc := api.ToolFunction{
- Name: "run_command",
- Description: "Execute shell commands",
+ if err != nil {
+ return "", err
}
- runCommandFunc.Parameters.Type = "object"
- runCommandFunc.Parameters.Required = []string{"command"}
- runCommandFunc.Parameters.Properties = make(map[string]struct {
- Type api.PropertyType `json:"type"`
- Items any `json:"items,omitempty"`
- Description string `json:"description"`
- Enum []any `json:"enum,omitempty"`
- })
- runCommandFunc.Parameters.Properties["command"] = makeProperty("string", "Shell command to execute")
- tools = append(tools, api.Tool{
- Type: "function",
- Function: runCommandFunc,
- })
+ var result strings.Builder
+ result.WriteString(fmt.Sprintf("Glob pattern '%s' matches:\n", pattern))
- // git_status tool
- gitStatusFunc := api.ToolFunction{
- Name: "git_status",
- Description: "Check git repository status",
+ if len(matches) == 0 {
+ result.WriteString("No files found")
+ } else {
+ for _, match := range matches {
+ result.WriteString(fmt.Sprintf(" %s\n", match))
+ }
}
- gitStatusFunc.Parameters.Type = "object"
- gitStatusFunc.Parameters.Required = []string{}
- gitStatusFunc.Parameters.Properties = make(map[string]struct {
- Type api.PropertyType `json:"type"`
- Items any `json:"items,omitempty"`
- Description string `json:"description"`
- Enum []any `json:"enum,omitempty"`
- })
- tools = append(tools, api.Tool{
- Type: "function",
- Function: gitStatusFunc,
- })
-
- // write_file tool
- writeFileFunc := api.ToolFunction{
- Name: "write_file",
- Description: "Write content to files",
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "glob",
+ Status: "completed",
+ Content: fmt.Sprintf("Found %d files", len(matches)),
}
- writeFileFunc.Parameters.Type = "object"
- writeFileFunc.Parameters.Required = []string{"path", "content"}
- writeFileFunc.Parameters.Properties = make(map[string]struct {
- Type api.PropertyType `json:"type"`
- Items any `json:"items,omitempty"`
- Description string `json:"description"`
- Enum []any `json:"enum,omitempty"`
- })
- writeFileFunc.Parameters.Properties["path"] = makeProperty("string", "Path to the file to write")
- writeFileFunc.Parameters.Properties["content"] = makeProperty("string", "Content to write to the file")
- tools = append(tools, api.Tool{
- Type: "function",
- Function: writeFileFunc,
- })
+ return result.String(), nil
+}
+
+// Grep tool - fast content search using regular expressions
+func (d *Del) grepSearch(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ pattern, ok := args["pattern"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing pattern argument")
+ }
+
+ searchPath, ok := args["path"].(string)
+ if !ok {
+ searchPath = "."
+ }
+
+ include, _ := args["include"].(string)
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "grep",
+ Status: "searching",
+ Content: fmt.Sprintf("Searching for pattern '%s'...", pattern),
+ }
+
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return "", fmt.Errorf("invalid regex pattern: %v", err)
+ }
+
+ var matches []string
+
+ err = filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return nil // Skip errors
+ }
+
+ if info.IsDir() {
+ return nil
+ }
+
+ // Apply include filter if specified
+ if include != "" {
+ matched, _ := filepath.Match(include, filepath.Base(path))
+ if !matched {
+ return nil
+ }
+ }
+
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil // Skip unreadable files
+ }
+
+ content := string(data)
+ lines := strings.Split(content, "\n")
+
+ for lineNum, line := range lines {
+ if re.MatchString(line) {
+ matches = append(matches, fmt.Sprintf("%s:%d:%s", path, lineNum+1, line))
+ }
+ }
+
+ return nil
+ })
+
+ if err != nil {
+ return "", err
+ }
+
+ var result strings.Builder
+ result.WriteString(fmt.Sprintf("Grep search for '%s':\n", pattern))
+
+ if len(matches) == 0 {
+ result.WriteString("No matches found")
+ } else {
+ for _, match := range matches {
+ result.WriteString(fmt.Sprintf("%s\n", match))
+ }
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "grep",
+ Status: "completed",
+ Content: fmt.Sprintf("Found %d matches", len(matches)),
+ }
+
+ return result.String(), nil
+}
+
+// Enhanced Bash tool with timeout and security
+func (d *Del) bashCommand(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ command, ok := args["command"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing command argument")
+ }
+
+ timeoutMs, _ := args["timeout"].(float64)
+ description, _ := args["description"].(string)
+
+ if timeoutMs == 0 {
+ timeoutMs = 120000 // Default 2 minutes
+ }
+
+ if timeoutMs > 600000 {
+ timeoutMs = 600000 // Max 10 minutes
+ }
+
+ if description == "" {
+ description = fmt.Sprintf("Executing: %s", command)
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "bash",
+ Status: "running",
+ Content: description,
+ }
+
+ // Create context with timeout
+ timeout := time.Duration(timeoutMs) * time.Millisecond
+ execCtx, cancel := context.WithTimeout(ctx, timeout)
+ defer cancel()
+
+ cmd := exec.CommandContext(execCtx, "bash", "-c", command)
+ output, err := cmd.CombinedOutput()
+
+ outputStr := string(output)
+ lines := strings.Split(outputStr, "\n")
+
+ var result string
+ if err != nil {
+ result = fmt.Sprintf("Command: %s\nError: %v\nOutput: %s", command, err, outputStr)
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "bash",
+ Status: "error",
+ Content: fmt.Sprintf("Command failed: %v", err),
+ }
+ } else {
+ result = fmt.Sprintf("Command: %s\nOutput:\n%s", command, outputStr)
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "bash",
+ Status: "completed",
+ Content: fmt.Sprintf("Output: %d lines", len(lines)),
+ }
+ }
+
+ return result, nil
+}
+
+// Todo management tools
+type TodoItem struct {
+ ID string `json:"id"`
+ Content string `json:"content"`
+ Status string `json:"status"` // pending, in_progress, completed
+ Priority string `json:"priority"` // high, medium, low
+}
+
+var sessionTodos []TodoItem
+
+func (d *Del) todoRead(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "todo_read",
+ Status: "reading",
+ Content: "Reading todo list...",
+ }
+
+ if len(sessionTodos) == 0 {
+ return "No todos in current session", nil
+ }
+
+ var result strings.Builder
+ result.WriteString("Current Todo List:\n\n")
+
+ for _, todo := range sessionTodos {
+ status := "⏳"
+ switch todo.Status {
+ case "completed":
+ status = "✅"
+ case "in_progress":
+ status = "🔄"
+ }
+
+ priority := ""
+ switch todo.Priority {
+ case "high":
+ priority = " [HIGH]"
+ case "medium":
+ priority = " [MED]"
+ case "low":
+ priority = " [LOW]"
+ }
+
+ result.WriteString(fmt.Sprintf("%s %s%s\n", status, todo.Content, priority))
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "todo_read",
+ Status: "completed",
+ Content: fmt.Sprintf("Found %d todos", len(sessionTodos)),
+ }
+
+ return result.String(), nil
+}
+
+func (d *Del) todoWrite(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ todosArg, ok := args["todos"]
+ if !ok {
+ return "", fmt.Errorf("missing todos argument")
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "todo_write",
+ Status: "updating",
+ Content: "Updating todo list...",
+ }
+
+ // Parse todos array
+ todosArray, ok := todosArg.([]interface{})
+ if !ok {
+ return "", fmt.Errorf("todos must be an array")
+ }
+
+ var newTodos []TodoItem
+ for i, todoArg := range todosArray {
+ todoMap, ok := todoArg.(map[string]interface{})
+ if !ok {
+ return "", fmt.Errorf("todo %d must be an object", i)
+ }
+
+ todo := TodoItem{
+ ID: todoMap["id"].(string),
+ Content: todoMap["content"].(string),
+ Status: todoMap["status"].(string),
+ Priority: todoMap["priority"].(string),
+ }
+
+ newTodos = append(newTodos, todo)
+ }
+
+ sessionTodos = newTodos
+
+ result := fmt.Sprintf("Updated todo list with %d items", len(newTodos))
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "todo_write",
+ Status: "completed",
+ Content: result,
+ }
+
+ return result, nil
+}
+
+func (d *Del) exitPlanMode(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ plan, ok := args["plan"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing plan argument")
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "exit_plan_mode",
+ Status: "presenting",
+ Content: "Presenting plan to user...",
+ }
+
+ result := fmt.Sprintf("Plan presented:\n\n%s\n\nReady to proceed with implementation?", plan)
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "exit_plan_mode",
+ Status: "completed",
+ Content: "Plan presented to user",
+ }
+
+ return result, nil
+}
+
+// Notebook tools (basic implementation)
+func (d *Del) notebookRead(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ notebookPath, ok := args["notebook_path"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing notebook_path argument")
+ }
+
+ cellID, _ := args["cell_id"].(string)
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "notebook_read",
+ Status: "reading",
+ Content: fmt.Sprintf("Reading notebook %s...", notebookPath),
+ }
+
+ data, err := os.ReadFile(notebookPath)
+ if err != nil {
+ return "", err
+ }
+
+ var notebook map[string]interface{}
+ if err := json.Unmarshal(data, ¬ebook); err != nil {
+ return "", fmt.Errorf("invalid notebook format: %v", err)
+ }
+
+ cells, ok := notebook["cells"].([]interface{})
+ if !ok {
+ return "", fmt.Errorf("notebook has no cells")
+ }
+
+ var result strings.Builder
+ result.WriteString(fmt.Sprintf("Notebook: %s\n\n", notebookPath))
+
+ for i, cellArg := range cells {
+ cell, ok := cellArg.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ id, _ := cell["id"].(string)
+ cellType, _ := cell["cell_type"].(string)
+
+ // If specific cell requested, only show that one
+ if cellID != "" && id != cellID {
+ continue
+ }
+
+ result.WriteString(fmt.Sprintf("Cell %d (%s):\n", i, cellType))
+
+ source, ok := cell["source"].([]interface{})
+ if ok {
+ for _, line := range source {
+ if lineStr, ok := line.(string); ok {
+ result.WriteString(lineStr)
+ }
+ }
+ }
+ result.WriteString("\n---\n")
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "notebook_read",
+ Status: "completed",
+ Content: fmt.Sprintf("Read %d cells", len(cells)),
+ }
+
+ return result.String(), nil
+}
+
+func (d *Del) notebookEdit(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ return "", fmt.Errorf("notebook editing not yet implemented - requires complex JSON manipulation")
+}
+
+// Web tools (basic implementation)
+func (d *Del) webFetch(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ url, ok := args["url"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing url argument")
+ }
+
+ promptArg, ok := args["prompt"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing prompt argument")
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "web_fetch",
+ Status: "fetching",
+ Content: fmt.Sprintf("Fetching %s...", url),
+ }
+
+ resp, err := http.Get(url)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+
+ content := string(body)
+
+ // Basic HTML to text conversion (simplified)
+ content = regexp.MustCompile(`<[^>]*>`).ReplaceAllString(content, "")
+ content = strings.TrimSpace(content)
+
+ result := fmt.Sprintf("Fetched content from %s:\n\nPrompt: %s\n\nContent:\n%s", url, promptArg, content)
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "web_fetch",
+ Status: "completed",
+ Content: fmt.Sprintf("Fetched %d characters", len(content)),
+ }
+
+ return result, nil
+}
+
+func (d *Del) webSearch(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ query, ok := args["query"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing query argument")
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "web_search",
+ Status: "searching",
+ Content: fmt.Sprintf("Searching for '%s'...", query),
+ }
+
+ // Note: This is a placeholder - real web search requires API integration
+ result := fmt.Sprintf("Web search for '%s':\n\nNote: Web search functionality requires API integration (Google, Bing, etc.)\nThis is a placeholder implementation.", query)
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "web_search",
+ Status: "completed",
+ Content: "Search completed (placeholder)",
+ }
+
+ return result, nil
+}
+
+
+func (d *Del) executeTool(ctx context.Context, call ToolCall) string {
+ d.emit(StreamMessage{
+ Type: MessageTypeTool,
+ ToolName: call.Name,
+ ToolArgs: call.Args,
+ Status: "starting",
+ })
+
+ tool, exists := d.tools[call.Name]
+ if !exists {
+ d.emit(StreamMessage{
+ Type: MessageTypeTool,
+ ToolName: call.Name,
+ Status: "error",
+ Error: fmt.Sprintf("Unknown tool: %s", call.Name),
+ })
+ return fmt.Sprintf("Unknown tool: %s", call.Name)
+ }
+
+ progressChan := make(chan StreamMessage, 10)
+ done := make(chan bool)
+
+ // Forward progress messages
+ go func() {
+ for msg := range progressChan {
+ d.emit(msg)
+ }
+ done <- true
+ }()
+
+ result, err := tool(ctx, call.Args, progressChan)
+ close(progressChan)
+ <-done
+
+ if err != nil {
+ d.emit(StreamMessage{
+ Type: MessageTypeTool,
+ ToolName: call.Name,
+ Status: "error",
+ Error: err.Error(),
+ })
+ return err.Error()
+ }
+
+ d.emit(StreamMessage{
+ Type: MessageTypeTool,
+ ToolName: call.Name,
+ Status: "completed",
+ Result: result,
+ })
+
+ return result
+}
+
+func (d *Del) formatArgs(args map[string]interface{}) string {
+ if len(args) == 0 {
+ return ""
+ }
+
+ var parts []string
+ for key, value := range args {
+ if str, ok := value.(string); ok && len(str) > 30 {
+ parts = append(parts, fmt.Sprintf("%s: \"%.30s...\"", key, str))
+ } else {
+ parts = append(parts, fmt.Sprintf("%s: %v", key, value))
+ }
+ }
+ return strings.Join(parts, ", ")
+}
+
+func (d *Del) streamResponseChunks(ctx context.Context, text string) {
+ // Instead of word-by-word streaming, send the full response for better markdown rendering
+ d.emit(StreamMessage{
+ Type: MessageTypeAssistant,
+ Content: text,
+ })
+}
+
+func (d *Del) buildOllamaTools() []api.Tool {
+ var tools []api.Tool
+
+ // Helper function to create property
+ makeProperty := func(propType string, description string) struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ } {
+ return struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ }{
+ Type: api.PropertyType{propType},
+ Description: description,
+ }
+ }
+
+ // read_file tool
+ readFileFunc := api.ToolFunction{
+ Name: "read_file",
+ Description: "Read file contents",
+ }
+ readFileFunc.Parameters.Type = "object"
+ readFileFunc.Parameters.Required = []string{"path"}
+ readFileFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ readFileFunc.Parameters.Properties["path"] = makeProperty("string", "Path to the file to read")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: readFileFunc,
+ })
+
+ // list_dir tool
+ listDirFunc := api.ToolFunction{
+ Name: "list_dir",
+ Description: "List directory contents",
+ }
+ listDirFunc.Parameters.Type = "object"
+ listDirFunc.Parameters.Required = []string{}
+ listDirFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ listDirFunc.Parameters.Properties["path"] = makeProperty("string", "Path to the directory to list (defaults to current directory)")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: listDirFunc,
+ })
+
+ // run_command tool
+ runCommandFunc := api.ToolFunction{
+ Name: "run_command",
+ Description: "Execute shell commands",
+ }
+ runCommandFunc.Parameters.Type = "object"
+ runCommandFunc.Parameters.Required = []string{"command"}
+ runCommandFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ runCommandFunc.Parameters.Properties["command"] = makeProperty("string", "Shell command to execute")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: runCommandFunc,
+ })
+
+ // git_status tool
+ gitStatusFunc := api.ToolFunction{
+ Name: "git_status",
+ Description: "Check git repository status",
+ }
+ gitStatusFunc.Parameters.Type = "object"
+ gitStatusFunc.Parameters.Required = []string{}
+ gitStatusFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: gitStatusFunc,
+ })
+
+ // write_file tool
+ writeFileFunc := api.ToolFunction{
+ Name: "write_file",
+ Description: "Write content to files",
+ }
+ writeFileFunc.Parameters.Type = "object"
+ writeFileFunc.Parameters.Required = []string{"path", "content"}
+ writeFileFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ writeFileFunc.Parameters.Properties["path"] = makeProperty("string", "Path to the file to write")
+ writeFileFunc.Parameters.Properties["content"] = makeProperty("string", "Content to write to the file")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: writeFileFunc,
+ })
// analyze_code tool
analyzeCodeFunc := api.ToolFunction{
@@ -906,6 +1590,262 @@ func (d *Del) buildOllamaTools() []api.Tool {
Function: searchCodeFunc,
})
+ // edit_file tool
+ editFileFunc := api.ToolFunction{
+ Name: "edit_file",
+ Description: "Performs exact string replacements in files",
+ }
+ editFileFunc.Parameters.Type = "object"
+ editFileFunc.Parameters.Required = []string{"file_path", "old_string", "new_string"}
+ editFileFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ editFileFunc.Parameters.Properties["file_path"] = makeProperty("string", "Absolute path to the file to edit")
+ editFileFunc.Parameters.Properties["old_string"] = makeProperty("string", "Text to replace")
+ editFileFunc.Parameters.Properties["new_string"] = makeProperty("string", "Replacement text")
+ editFileFunc.Parameters.Properties["replace_all"] = makeProperty("boolean", "Replace all occurrences (default: false)")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: editFileFunc,
+ })
+
+ // multi_edit tool
+ multiEditFunc := api.ToolFunction{
+ Name: "multi_edit",
+ Description: "Multiple edits to a single file in one operation",
+ }
+ multiEditFunc.Parameters.Type = "object"
+ multiEditFunc.Parameters.Required = []string{"file_path", "edits"}
+ multiEditFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ multiEditFunc.Parameters.Properties["file_path"] = makeProperty("string", "Absolute path to the file to edit")
+ multiEditFunc.Parameters.Properties["edits"] = makeProperty("array", "Array of edit operations to perform sequentially")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: multiEditFunc,
+ })
+
+ // glob tool
+ globFunc := api.ToolFunction{
+ Name: "glob",
+ Description: "Fast file pattern matching tool",
+ }
+ globFunc.Parameters.Type = "object"
+ globFunc.Parameters.Required = []string{"pattern"}
+ globFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ globFunc.Parameters.Properties["pattern"] = makeProperty("string", "Glob pattern to match files (e.g., '**/*.js')")
+ globFunc.Parameters.Properties["path"] = makeProperty("string", "Directory to search in (defaults to current directory)")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: globFunc,
+ })
+
+ // grep tool
+ grepFunc := api.ToolFunction{
+ Name: "grep",
+ Description: "Fast content search using regular expressions",
+ }
+ grepFunc.Parameters.Type = "object"
+ grepFunc.Parameters.Required = []string{"pattern"}
+ grepFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ grepFunc.Parameters.Properties["pattern"] = makeProperty("string", "Regular expression pattern to search for")
+ grepFunc.Parameters.Properties["path"] = makeProperty("string", "Directory to search in (defaults to current directory)")
+ grepFunc.Parameters.Properties["include"] = makeProperty("string", "File pattern to include in search (e.g., '*.js')")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: grepFunc,
+ })
+
+ // bash tool (enhanced)
+ bashFunc := api.ToolFunction{
+ Name: "bash",
+ Description: "Execute bash commands with timeout and security measures",
+ }
+ bashFunc.Parameters.Type = "object"
+ bashFunc.Parameters.Required = []string{"command"}
+ bashFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ bashFunc.Parameters.Properties["command"] = makeProperty("string", "Bash command to execute")
+ bashFunc.Parameters.Properties["timeout"] = makeProperty("number", "Timeout in milliseconds (max 600000ms)")
+ bashFunc.Parameters.Properties["description"] = makeProperty("string", "5-10 word description of what the command does")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: bashFunc,
+ })
+
+ // todo_read tool
+ todoReadFunc := api.ToolFunction{
+ Name: "todo_read",
+ Description: "Read the current todo list for the session",
+ }
+ todoReadFunc.Parameters.Type = "object"
+ todoReadFunc.Parameters.Required = []string{}
+ todoReadFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: todoReadFunc,
+ })
+
+ // todo_write tool
+ todoWriteFunc := api.ToolFunction{
+ Name: "todo_write",
+ Description: "Create and manage structured task list",
+ }
+ todoWriteFunc.Parameters.Type = "object"
+ todoWriteFunc.Parameters.Required = []string{"todos"}
+ todoWriteFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ todoWriteFunc.Parameters.Properties["todos"] = makeProperty("array", "Array of todo items with id, content, status, and priority")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: todoWriteFunc,
+ })
+
+ // exit_plan_mode tool
+ exitPlanModeFunc := api.ToolFunction{
+ Name: "exit_plan_mode",
+ Description: "Exit plan mode after presenting plan to user",
+ }
+ exitPlanModeFunc.Parameters.Type = "object"
+ exitPlanModeFunc.Parameters.Required = []string{"plan"}
+ exitPlanModeFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ exitPlanModeFunc.Parameters.Properties["plan"] = makeProperty("string", "Concise plan with markdown support")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: exitPlanModeFunc,
+ })
+
+ // notebook_read tool
+ notebookReadFunc := api.ToolFunction{
+ Name: "notebook_read",
+ Description: "Read Jupyter notebook cells and outputs",
+ }
+ notebookReadFunc.Parameters.Type = "object"
+ notebookReadFunc.Parameters.Required = []string{"notebook_path"}
+ notebookReadFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ notebookReadFunc.Parameters.Properties["notebook_path"] = makeProperty("string", "Absolute path to .ipynb file")
+ notebookReadFunc.Parameters.Properties["cell_id"] = makeProperty("string", "Specific cell ID to read (optional)")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: notebookReadFunc,
+ })
+
+ // notebook_edit tool
+ notebookEditFunc := api.ToolFunction{
+ Name: "notebook_edit",
+ Description: "Edit Jupyter notebook cell contents",
+ }
+ notebookEditFunc.Parameters.Type = "object"
+ notebookEditFunc.Parameters.Required = []string{"notebook_path", "cell_id", "new_source"}
+ notebookEditFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ notebookEditFunc.Parameters.Properties["notebook_path"] = makeProperty("string", "Absolute path to .ipynb file")
+ notebookEditFunc.Parameters.Properties["cell_id"] = makeProperty("string", "Cell ID to edit")
+ notebookEditFunc.Parameters.Properties["new_source"] = makeProperty("string", "New cell content")
+ notebookEditFunc.Parameters.Properties["cell_type"] = makeProperty("string", "Cell type: 'code' or 'markdown'")
+ notebookEditFunc.Parameters.Properties["edit_mode"] = makeProperty("string", "Edit mode: 'replace', 'insert', or 'delete'")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: notebookEditFunc,
+ })
+
+ // web_fetch tool
+ webFetchFunc := api.ToolFunction{
+ Name: "web_fetch",
+ Description: "Fetch and process web content",
+ }
+ webFetchFunc.Parameters.Type = "object"
+ webFetchFunc.Parameters.Required = []string{"url", "prompt"}
+ webFetchFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ webFetchFunc.Parameters.Properties["url"] = makeProperty("string", "URL to fetch content from")
+ webFetchFunc.Parameters.Properties["prompt"] = makeProperty("string", "Processing prompt for the fetched content")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: webFetchFunc,
+ })
+
+ // web_search tool
+ webSearchFunc := api.ToolFunction{
+ Name: "web_search",
+ Description: "Search the web for current information",
+ }
+ webSearchFunc.Parameters.Type = "object"
+ webSearchFunc.Parameters.Required = []string{"query"}
+ webSearchFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ webSearchFunc.Parameters.Properties["query"] = makeProperty("string", "Search query")
+ webSearchFunc.Parameters.Properties["allowed_domains"] = makeProperty("array", "Domain whitelist")
+ webSearchFunc.Parameters.Properties["blocked_domains"] = makeProperty("array", "Domain blacklist")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: webSearchFunc,
+ })
+
return tools
}
@@ -914,8 +1854,10 @@ func (d *Del) parseTextToolCalls(input string) []ToolCall {
var calls []ToolCall
// Simple command detection based on user input
+ originalInput := input
input = strings.ToLower(strings.TrimSpace(input))
+ // File operations
if input == "list files" || input == "list the files" || input == "ls" {
calls = append(calls, ToolCall{Name: "list_dir", Args: map[string]interface{}{}})
} else if strings.HasPrefix(input, "read ") {
@@ -925,10 +1867,39 @@ func (d *Del) parseTextToolCalls(input string) []ToolCall {
if filename != "" {
calls = append(calls, ToolCall{Name: "read_file", Args: map[string]interface{}{"path": filename}})
}
- } else if input == "git status" || input == "check git status" || input == "check git" {
- calls = append(calls, ToolCall{Name: "git_status", Args: map[string]interface{}{}})
- } else if input == "analyze the code" || input == "analyze code" || input == "analyze this project" {
- calls = append(calls, ToolCall{Name: "analyze_code", Args: map[string]interface{}{}})
+ } else if strings.HasPrefix(input, "write ") {
+ // Basic write file detection - would need more sophisticated parsing for real use
+ parts := strings.Fields(originalInput[6:]) // Skip "write "
+ if len(parts) >= 1 {
+ calls = append(calls, ToolCall{Name: "write_file", Args: map[string]interface{}{
+ "path": parts[0],
+ "content": "# Content would need to be specified in a more sophisticated way",
+ }})
+ }
+ } else if strings.HasPrefix(input, "edit ") {
+ // Basic edit detection
+ filename := strings.TrimPrefix(input, "edit ")
+ filename = strings.TrimSpace(filename)
+ if filename != "" {
+ calls = append(calls, ToolCall{Name: "edit_file", Args: map[string]interface{}{
+ "file_path": filename,
+ "old_string": "# Would need more sophisticated parsing",
+ "new_string": "# Would need more sophisticated parsing",
+ }})
+ }
+
+ // Search operations
+ } else if strings.HasPrefix(input, "find ") || strings.HasPrefix(input, "glob ") {
+ pattern := strings.TrimSpace(strings.TrimPrefix(strings.TrimPrefix(input, "find "), "glob "))
+ if pattern != "" {
+ calls = append(calls, ToolCall{Name: "glob", Args: map[string]interface{}{"pattern": pattern}})
+ }
+ } else if strings.HasPrefix(input, "grep ") {
+ pattern := strings.TrimPrefix(input, "grep ")
+ pattern = strings.TrimSpace(pattern)
+ if pattern != "" {
+ calls = append(calls, ToolCall{Name: "grep", Args: map[string]interface{}{"pattern": pattern}})
+ }
} else if strings.HasPrefix(input, "search ") {
// Extract search pattern
pattern := strings.TrimPrefix(input, "search ")
@@ -936,12 +1907,57 @@ func (d *Del) parseTextToolCalls(input string) []ToolCall {
if pattern != "" {
calls = append(calls, ToolCall{Name: "search_code", Args: map[string]interface{}{"pattern": pattern}})
}
- } else if strings.HasPrefix(input, "run ") {
+
+ // Command execution
+ } else if strings.HasPrefix(input, "run ") || strings.HasPrefix(input, "bash ") || strings.HasPrefix(input, "execute ") {
// Extract command
- command := strings.TrimPrefix(input, "run ")
+ var command string
+ if strings.HasPrefix(input, "run ") {
+ command = strings.TrimPrefix(originalInput, "run ")
+ } else if strings.HasPrefix(input, "bash ") {
+ command = strings.TrimPrefix(originalInput, "bash ")
+ } else {
+ command = strings.TrimPrefix(originalInput, "execute ")
+ }
command = strings.TrimSpace(command)
if command != "" {
- calls = append(calls, ToolCall{Name: "run_command", Args: map[string]interface{}{"command": command}})
+ calls = append(calls, ToolCall{Name: "bash", Args: map[string]interface{}{"command": command}})
+ }
+
+ // Git operations
+ } else if input == "git status" || input == "check git status" || input == "check git" {
+ calls = append(calls, ToolCall{Name: "git_status", Args: map[string]interface{}{}})
+
+ // Code analysis
+ } else if input == "analyze the code" || input == "analyze code" || input == "analyze this project" {
+ calls = append(calls, ToolCall{Name: "analyze_code", Args: map[string]interface{}{}})
+
+ // Todo management
+ } else if input == "show todos" || input == "list todos" || input == "read todos" {
+ calls = append(calls, ToolCall{Name: "todo_read", Args: map[string]interface{}{}})
+
+ // Web operations
+ } else if strings.HasPrefix(input, "fetch ") {
+ url := strings.TrimPrefix(input, "fetch ")
+ url = strings.TrimSpace(url)
+ if url != "" {
+ calls = append(calls, ToolCall{Name: "web_fetch", Args: map[string]interface{}{
+ "url": url,
+ "prompt": "Summarize this web page content",
+ }})
+ }
+ } else if strings.HasPrefix(input, "web search ") || strings.HasPrefix(input, "websearch ") {
+ query := strings.TrimSpace(strings.TrimPrefix(strings.TrimPrefix(input, "web search "), "websearch "))
+ if query != "" {
+ calls = append(calls, ToolCall{Name: "web_search", Args: map[string]interface{}{"query": query}})
+ }
+
+ // Notebook operations
+ } else if strings.HasPrefix(input, "read notebook ") {
+ notebook := strings.TrimPrefix(input, "read notebook ")
+ notebook = strings.TrimSpace(notebook)
+ if notebook != "" {
+ calls = append(calls, ToolCall{Name: "notebook_read", Args: map[string]interface{}{"notebook_path": notebook}})
}
}