Files
clawgo/pkg/tools/memory_get.go

124 lines
2.7 KiB
Go

package tools
import (
"bufio"
"context"
"fmt"
"os"
"path/filepath"
"strings"
)
type MemoryGetTool struct {
workspace string
}
func NewMemoryGetTool(workspace string) *MemoryGetTool {
return &MemoryGetTool{workspace: workspace}
}
func (t *MemoryGetTool) Name() string {
return "memory_get"
}
func (t *MemoryGetTool) Description() string {
return "Read safe snippets from MEMORY.md or memory/*.md using optional line range."
}
func (t *MemoryGetTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"path": map[string]interface{}{
"type": "string",
"description": "Relative path to MEMORY.md or memory/*.md",
},
"from": map[string]interface{}{
"type": "integer",
"description": "Start line (1-indexed)",
"default": 1,
},
"lines": map[string]interface{}{
"type": "integer",
"description": "Number of lines to read",
"default": 80,
},
},
"required": []string{"path"},
}
}
func (t *MemoryGetTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) {
rawPath, _ := args["path"].(string)
rawPath = strings.TrimSpace(rawPath)
if rawPath == "" {
return "", fmt.Errorf("path is required")
}
from := 1
if v, ok := args["from"].(float64); ok && int(v) > 0 {
from = int(v)
}
lines := 80
if v, ok := args["lines"].(float64); ok && int(v) > 0 {
lines = int(v)
}
if lines > 500 {
lines = 500
}
fullPath := filepath.Clean(filepath.Join(t.workspace, rawPath))
if !t.isAllowedMemoryPath(fullPath) {
return "", fmt.Errorf("path not allowed: %s", rawPath)
}
f, err := os.Open(fullPath)
if err != nil {
return "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
lineNo := 0
end := from + lines - 1
var out strings.Builder
for scanner.Scan() {
lineNo++
if lineNo < from {
continue
}
if lineNo > end {
break
}
out.WriteString(fmt.Sprintf("%d: %s\n", lineNo, scanner.Text()))
}
if err := scanner.Err(); err != nil {
return "", err
}
content := strings.TrimSpace(out.String())
if content == "" {
return fmt.Sprintf("No content in range for %s (from=%d, lines=%d)", rawPath, from, lines), nil
}
rel, _ := filepath.Rel(t.workspace, fullPath)
return fmt.Sprintf("Source: %s#L%d-L%d\n%s", rel, from, end, content), nil
}
func (t *MemoryGetTool) isAllowedMemoryPath(fullPath string) bool {
workspaceMemory := filepath.Join(t.workspace, "MEMORY.md")
if fullPath == workspaceMemory {
return true
}
memoryDir := filepath.Join(t.workspace, "memory")
rel, err := filepath.Rel(memoryDir, fullPath)
if err != nil {
return false
}
if strings.HasPrefix(rel, "..") {
return false
}
return strings.HasSuffix(strings.ToLower(fullPath), ".md")
}