From afd9ad0560d76c2a6d161dad52553b10ff428905 Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Thu, 27 Mar 2025 22:35:48 +0100 Subject: rework llm --- internal/llm/tools/write.go | 186 ++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 95 deletions(-) (limited to 'internal/llm/tools/write.go') diff --git a/internal/llm/tools/write.go b/internal/llm/tools/write.go index b3972cedf..003753d08 100644 --- a/internal/llm/tools/write.go +++ b/internal/llm/tools/write.go @@ -6,17 +6,13 @@ import ( "fmt" "os" "path/filepath" - "strings" "time" - "github.com/cloudwego/eino/components/tool" - "github.com/cloudwego/eino/schema" + "github.com/kujtimiihoxha/termai/internal/config" "github.com/kujtimiihoxha/termai/internal/permission" ) -type writeTool struct { - workingDir string -} +type writeTool struct{} const ( WriteToolName = "write" @@ -27,139 +23,139 @@ type WriteParams struct { Content string `json:"content"` } -func (b *writeTool) Info(ctx context.Context) (*schema.ToolInfo, error) { - return &schema.ToolInfo{ - Name: WriteToolName, - Desc: "Write a file to the local filesystem. Overwrites the existing file if there is one.\n\nBefore using this tool:\n\n1. Use the ReadFile tool to understand the file's contents and context\n\n2. Directory Verification (only applicable when creating new files):\n - Use the LS tool to verify the parent directory exists and is the correct location", - ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ - "file_path": { - Type: "string", - Desc: "The absolute path to the file to write (must be absolute, not relative)", - Required: true, +type WritePermissionsParams struct { + FilePath string `json:"file_path"` + Content string `json:"content"` +} + +func (w *writeTool) Info() ToolInfo { + return ToolInfo{ + Name: WriteToolName, + Description: writeDescription(), + Parameters: map[string]any{ + "file_path": map[string]any{ + "type": "string", + "description": "The path to the file to write", }, - "content": { - Type: "string", - Desc: "The content to write to the file", - Required: true, + "content": map[string]any{ + "type": "string", + "description": "The content to write to the file", }, - }), - }, nil + }, + Required: []string{"file_path", "content"}, + } } -func (b *writeTool) InvokableRun(ctx context.Context, args string, opts ...tool.Option) (string, error) { +// Run implements Tool. +func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) { var params WriteParams - if err := json.Unmarshal([]byte(args), ¶ms); err != nil { - return "", fmt.Errorf("failed to parse parameters: %w", err) + if err := json.Unmarshal([]byte(call.Input), ¶ms); err != nil { + return NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil } if params.FilePath == "" { - return "file_path is required", nil + return NewTextErrorResponse("file_path is required"), nil + } + + if params.Content == "" { + return NewTextErrorResponse("content is required"), nil } - if !filepath.IsAbs(params.FilePath) { - return fmt.Sprintf("file path must be absolute, got: %s", params.FilePath), nil + // Handle relative paths + filePath := params.FilePath + if !filepath.IsAbs(filePath) { + filePath = filepath.Join(config.WorkingDirectory(), filePath) } - // fileExists := false - // oldContent := "" - fileInfo, err := os.Stat(params.FilePath) + // Check if file exists and is a directory + fileInfo, err := os.Stat(filePath) if err == nil { if fileInfo.IsDir() { - return fmt.Sprintf("path is a directory, not a file: %s", params.FilePath), nil + return NewTextErrorResponse(fmt.Sprintf("Path is a directory, not a file: %s", filePath)), nil } + // Check if file was modified since last read modTime := fileInfo.ModTime() - lastRead := getLastReadTime(params.FilePath) + lastRead := getLastReadTime(filePath) if modTime.After(lastRead) { - return fmt.Sprintf("file %s has been modified since it was last read (mod time: %s, last read: %s)", - params.FilePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339)), nil + return NewTextErrorResponse(fmt.Sprintf("File %s has been modified since it was last read.\nLast modification: %s\nLast read: %s\n\nPlease read the file again before modifying it.", + filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339))), nil } - // oldContentBytes, readErr := os.ReadFile(params.FilePath) - // if readErr != nil { - // oldContent = string(oldContentBytes) - // } + // Optional: Get old content for diff + oldContent, readErr := os.ReadFile(filePath) + if readErr == nil && string(oldContent) == params.Content { + return NewTextErrorResponse(fmt.Sprintf("File %s already contains the exact content. No changes made.", filePath)), nil + } } else if !os.IsNotExist(err) { - return fmt.Sprintf("failed to access file: %s", err), nil + return NewTextErrorResponse(fmt.Sprintf("Failed to access file: %s", err)), nil } + // Create parent directories if needed + dir := filepath.Dir(filePath) + if err = os.MkdirAll(dir, 0o755); err != nil { + return NewTextErrorResponse(fmt.Sprintf("Failed to create parent directories: %s", err)), nil + } p := permission.Default.Request( permission.CreatePermissionRequest{ - Path: b.workingDir, + Path: filePath, ToolName: WriteToolName, - Action: "write", - Description: fmt.Sprintf("Write to file %s", params.FilePath), - Params: map[string]interface{}{ - "file_path": params.FilePath, - "contnet": params.Content, + Action: "create", + Description: fmt.Sprintf("Create file %s", filePath), + Params: WritePermissionsParams{ + FilePath: filePath, + Content: GenerateDiff("", params.Content), }, }, ) if !p { - return "", fmt.Errorf("permission denied") - } - dir := filepath.Dir(params.FilePath) - if err = os.MkdirAll(dir, 0o755); err != nil { - return fmt.Sprintf("failed to create parent directories: %s", err), nil + return NewTextErrorResponse(fmt.Sprintf("Permission denied to create file: %s", filePath)), nil } - err = os.WriteFile(params.FilePath, []byte(params.Content), 0o644) + // Write the file + err = os.WriteFile(filePath, []byte(params.Content), 0o644) if err != nil { - return fmt.Sprintf("failed to write file: %s", err), nil + return NewTextErrorResponse(fmt.Sprintf("Failed to write file: %s", err)), nil } - recordFileWrite(params.FilePath) - - output := "File written: " + params.FilePath + // Record the file write + recordFileWrite(filePath) + recordFileRead(filePath) - // if fileExists && oldContent != params.Content { - // output = generateSimpleDiff(oldContent, params.Content) - // } - - return output, nil + return NewTextResponse(fmt.Sprintf("File successfully written: %s", filePath)), nil } -func generateSimpleDiff(oldContent, newContent string) string { - if oldContent == newContent { - return "[No changes]" - } +func writeDescription() string { + return `File writing tool that creates or updates files in the filesystem, allowing you to save or modify text content. - oldLines := strings.Split(oldContent, "\n") - newLines := strings.Split(newContent, "\n") +WHEN TO USE THIS TOOL: +- Use when you need to create a new file +- Helpful for updating existing files with modified content +- Perfect for saving generated code, configurations, or text data - var diffBuilder strings.Builder - diffBuilder.WriteString(fmt.Sprintf("@@ -%d,+%d @@\n", len(oldLines), len(newLines))) +HOW TO USE: +- Provide the path to the file you want to write +- Include the content to be written to the file +- The tool will create any necessary parent directories - maxLines := max(len(oldLines), len(newLines)) - for i := range maxLines { - oldLine := "" - newLine := "" +FEATURES: +- Can create new files or overwrite existing ones +- Creates parent directories automatically if they don't exist +- Checks if the file has been modified since last read for safety +- Avoids unnecessary writes when content hasn't changed - if i < len(oldLines) { - oldLine = oldLines[i] - } +LIMITATIONS: +- You should read a file before writing to it to avoid conflicts +- Cannot append to files (rewrites the entire file) - if i < len(newLines) { - newLine = newLines[i] - } - if oldLine != newLine { - if i < len(oldLines) { - diffBuilder.WriteString(fmt.Sprintf("- %s\n", oldLine)) - } - if i < len(newLines) { - diffBuilder.WriteString(fmt.Sprintf("+ %s\n", newLine)) - } - } else { - diffBuilder.WriteString(fmt.Sprintf(" %s\n", oldLine)) - } - } - - return diffBuilder.String() +TIPS: +- Use the View tool first to examine existing files before modifying them +- Use the LS tool to verify the correct location when creating new files +- Combine with Glob and Grep tools to find and modify multiple files +- Always include descriptive comments when making changes to existing code` } -func NewWriteTool(workingDir string) tool.InvokableTool { - return &writeTool{ - workingDir: workingDir, - } +func NewWriteTool() BaseTool { + return &writeTool{} } -- cgit v1.2.3