mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
fd960921ca
- Remove deprecated GenerateWithLoopAndStreaming and TreeManager AppendFantasyMessage / AddFantasyMessages / GetFantasyMessages to close the SDK leakage caused by the kit.TreeManager type alias - Switch extensionAPI method signatures to local Extension* aliases so pkg.go.dev signatures no longer expose internal package names - Bundle runNormalMode dependencies into a runModeDeps struct, shrinking the runNonInteractive and runInteractive call sites from 40+ positional args to (ctx, deps) - Add generic subscribeTyped[E Event] helper and collapse ~30 typed OnXxx wrappers in pkg/kit/events.go onto it (public signatures unchanged) - Extract setupBashPipes / interpretBashExit in internal/core/bash.go to deduplicate the buffered and streaming execution paths - Extract resolveAutoRouteAPIKey and wrapProviderErr helpers in internal/models/providers.go and uniformly apply them across every createXxxProvider site - Reimplement internal/extensions/watcher.go as a thin wrapper over the general-purpose internal/watcher.ContentWatcher, eliminating ~130 LOC of duplicated fsnotify logic while preserving the existing test API - Add ctx.Err() pre-flight checks in executeRead / Write / Edit / Ls so cancellation actually short-circuits pure file-IO tools
107 lines
2.7 KiB
Go
107 lines
2.7 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"charm.land/fantasy"
|
|
)
|
|
|
|
type lsArgs struct {
|
|
Path string `json:"path,omitempty"`
|
|
Limit int `json:"limit,omitempty"`
|
|
}
|
|
|
|
// NewLsTool creates the ls core tool.
|
|
func NewLsTool(opts ...ToolOption) fantasy.AgentTool {
|
|
cfg := ApplyOptions(opts)
|
|
return &coreTool{
|
|
info: fantasy.ToolInfo{
|
|
Name: "ls",
|
|
Description: "List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles. Output is truncated to 500 entries or 50KB.",
|
|
Parameters: map[string]any{
|
|
"path": map[string]any{
|
|
"type": "string",
|
|
"description": "Directory to list (default: current directory)",
|
|
},
|
|
"limit": map[string]any{
|
|
"type": "number",
|
|
"description": "Maximum number of entries to return (default: 500)",
|
|
},
|
|
},
|
|
Required: []string{},
|
|
Parallel: true,
|
|
},
|
|
handler: func(ctx context.Context, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
|
|
return executeLs(ctx, call, cfg.WorkDir)
|
|
},
|
|
}
|
|
}
|
|
|
|
func executeLs(ctx context.Context, call fantasy.ToolCall, workDir string) (fantasy.ToolResponse, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return fantasy.ToolResponse{}, err
|
|
}
|
|
var args lsArgs
|
|
_ = parseArgs(call.Input, &args) // optional args
|
|
|
|
limit := 500
|
|
if args.Limit > 0 {
|
|
limit = args.Limit
|
|
}
|
|
|
|
dirPath := "."
|
|
if args.Path != "" {
|
|
resolved, err := resolvePathWithWorkDir(args.Path, workDir)
|
|
if err != nil {
|
|
return fantasy.NewTextErrorResponse(fmt.Sprintf("invalid path: %v", err)), nil
|
|
}
|
|
dirPath = resolved
|
|
} else if workDir != "" {
|
|
dirPath = workDir
|
|
}
|
|
|
|
info, err := os.Stat(dirPath)
|
|
if err != nil {
|
|
return fantasy.NewTextErrorResponse(fmt.Sprintf("cannot access '%s': %v", args.Path, err)), nil
|
|
}
|
|
if !info.IsDir() {
|
|
return fantasy.NewTextErrorResponse(fmt.Sprintf("'%s' is not a directory", args.Path)), nil
|
|
}
|
|
|
|
entries, err := os.ReadDir(dirPath)
|
|
if err != nil {
|
|
return fantasy.NewTextErrorResponse(fmt.Sprintf("failed to read directory: %v", err)), nil
|
|
}
|
|
|
|
// Sort alphabetically (case-insensitive)
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
return strings.ToLower(entries[i].Name()) < strings.ToLower(entries[j].Name())
|
|
})
|
|
|
|
var result strings.Builder
|
|
count := 0
|
|
for _, entry := range entries {
|
|
if count >= limit {
|
|
fmt.Fprintf(&result, "\n[truncated: showing %d of %d entries]", limit, len(entries))
|
|
break
|
|
}
|
|
name := entry.Name()
|
|
if entry.IsDir() {
|
|
name += "/"
|
|
}
|
|
result.WriteString(name + "\n")
|
|
count++
|
|
}
|
|
|
|
output := result.String()
|
|
if output == "" {
|
|
return fantasy.NewTextResponse("(empty directory)"), nil
|
|
}
|
|
|
|
return fantasy.NewTextResponse(strings.TrimRight(output, "\n")), nil
|
|
}
|