summaryrefslogtreecommitdiffhomepage
path: root/internal/llm/tools/write.go
diff options
context:
space:
mode:
authorKujtim Hoxha <[email protected]>2025-03-27 22:35:48 +0100
committerKujtim Hoxha <[email protected]>2025-04-01 13:38:54 +0200
commitafd9ad0560d76c2a6d161dad52553b10ff428905 (patch)
tree69f78b05ff0d7952cd3e3c9332f001e66abb2faf /internal/llm/tools/write.go
parent904061c243f70696bfe781e97bf4e392e6954d07 (diff)
downloadopencode-afd9ad0560d76c2a6d161dad52553b10ff428905.tar.gz
opencode-afd9ad0560d76c2a6d161dad52553b10ff428905.zip
rework llm
Diffstat (limited to 'internal/llm/tools/write.go')
-rw-r--r--internal/llm/tools/write.go186
1 files changed, 91 insertions, 95 deletions
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), &params); err != nil {
- return "", fmt.Errorf("failed to parse parameters: %w", err)
+ if err := json.Unmarshal([]byte(call.Input), &params); 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{}
}