From ffce9185bb0efb357d054ad589d6adb193b13eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Thu, 11 Jun 2026 01:16:20 +0800 Subject: [PATCH] Add files via upload --- internal/multiagent/eino_middleware.go | 18 +--- internal/multiagent/plantask_local_backend.go | 71 ++++++++++++++++ .../multiagent/plantask_local_backend_test.go | 83 +++++++++++++++++++ 3 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 internal/multiagent/plantask_local_backend.go create mode 100644 internal/multiagent/plantask_local_backend_test.go diff --git a/internal/multiagent/eino_middleware.go b/internal/multiagent/eino_middleware.go index 062faf6b..1a1d2106 100644 --- a/internal/multiagent/eino_middleware.go +++ b/internal/multiagent/eino_middleware.go @@ -43,22 +43,6 @@ func sanitizeEinoPathSegment(s string) string { return s } -// localPlantaskBackend wraps the eino-ext local backend with plantask.Delete (Local has no Delete). -type localPlantaskBackend struct { - *localbk.Local -} - -func (l *localPlantaskBackend) Delete(ctx context.Context, req *plantask.DeleteRequest) error { - if l == nil || l.Local == nil || req == nil { - return nil - } - p := strings.TrimSpace(req.FilePath) - if p == "" { - return nil - } - return os.Remove(p) -} - func splitToolsForToolSearch(all []tool.BaseTool, alwaysVisible int) (static []tool.BaseTool, dynamic []tool.BaseTool, ok bool) { if alwaysVisible <= 0 || len(all) <= alwaysVisible+1 { return all, nil, false @@ -238,7 +222,7 @@ func prependEinoMiddlewares( if mk := os.MkdirAll(baseDir, 0o755); mk != nil { return nil, nil, toolSearchActive, fmt.Errorf("plantask mkdir: %w", mk) } - ptBE := &localPlantaskBackend{Local: einoLoc} + ptBE := newLocalPlantaskBackend(einoLoc) pt, perr := plantask.New(ctx, &plantask.Config{Backend: ptBE, BaseDir: baseDir}) if perr != nil { return nil, nil, toolSearchActive, fmt.Errorf("plantask: %w", perr) diff --git a/internal/multiagent/plantask_local_backend.go b/internal/multiagent/plantask_local_backend.go new file mode 100644 index 00000000..bcb23ec5 --- /dev/null +++ b/internal/multiagent/plantask_local_backend.go @@ -0,0 +1,71 @@ +package multiagent + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + localbk "github.com/cloudwego/eino-ext/adk/backend/local" + "github.com/cloudwego/eino/adk/middlewares/plantask" +) + +// localPlantaskBackend adapts eino-ext local filesystem backend for Eino plantask. +// +// plantask TaskCreate/TaskList list a directory via LsInfo, then Read using each entry's Path. +// local.LsInfo returns basenames only (e.g. ".highwatermark"), while local.Read expects a +// resolvable path — causing "file not found: .highwatermark" on the second TaskCreate. +type localPlantaskBackend struct { + *localbk.Local +} + +func newLocalPlantaskBackend(loc *localbk.Local) *localPlantaskBackend { + if loc == nil { + return nil + } + return &localPlantaskBackend{Local: loc} +} + +// LsInfo lists files under req.Path and returns absolute paths suitable for subsequent Read calls. +func (l *localPlantaskBackend) LsInfo(ctx context.Context, req *plantask.LsInfoRequest) ([]plantask.FileInfo, error) { + if l == nil || l.Local == nil { + return nil, fmt.Errorf("plantask backend: local nil") + } + if req == nil || strings.TrimSpace(req.Path) == "" { + return nil, fmt.Errorf("plantask backend: list path empty") + } + files, err := l.Local.LsInfo(ctx, req) + if err != nil { + return nil, err + } + if len(files) == 0 { + return files, nil + } + base := filepath.Clean(req.Path) + out := make([]plantask.FileInfo, len(files)) + for i, f := range files { + out[i] = f + name := strings.TrimSpace(f.Path) + if name == "" { + continue + } + if filepath.IsAbs(name) { + out[i].Path = filepath.Clean(name) + continue + } + out[i].Path = filepath.Join(base, name) + } + return out, nil +} + +func (l *localPlantaskBackend) Delete(ctx context.Context, req *plantask.DeleteRequest) error { + if l == nil || l.Local == nil || req == nil { + return nil + } + p := strings.TrimSpace(req.FilePath) + if p == "" { + return nil + } + return os.Remove(p) +} diff --git a/internal/multiagent/plantask_local_backend_test.go b/internal/multiagent/plantask_local_backend_test.go new file mode 100644 index 00000000..35365844 --- /dev/null +++ b/internal/multiagent/plantask_local_backend_test.go @@ -0,0 +1,83 @@ +package multiagent + +import ( + "context" + "os" + "path/filepath" + "testing" + + localbk "github.com/cloudwego/eino-ext/adk/backend/local" + "github.com/cloudwego/eino/adk/filesystem" + "github.com/cloudwego/eino/adk/middlewares/plantask" +) + +func TestLocalPlantaskBackendLsInfoReturnsFullPaths(t *testing.T) { + t.Parallel() + ctx := context.Background() + baseDir := t.TempDir() + + loc, err := localbk.NewBackend(ctx, &localbk.Config{}) + if err != nil { + t.Fatalf("NewBackend: %v", err) + } + be := newLocalPlantaskBackend(loc) + + hwPath := filepath.Join(baseDir, ".highwatermark") + if err := os.WriteFile(hwPath, []byte("1"), 0o600); err != nil { + t.Fatalf("write highwatermark: %v", err) + } + + files, err := be.LsInfo(ctx, &plantask.LsInfoRequest{Path: baseDir}) + if err != nil { + t.Fatalf("LsInfo: %v", err) + } + if len(files) != 1 { + t.Fatalf("expected 1 file, got %d", len(files)) + } + if files[0].Path != hwPath { + t.Fatalf("expected full path %q, got %q", hwPath, files[0].Path) + } + + content, err := be.Read(ctx, &plantask.ReadRequest{FilePath: files[0].Path}) + if err != nil { + t.Fatalf("Read via LsInfo path: %v", err) + } + if content.Content != "1" { + t.Fatalf("unexpected content: %q", content.Content) + } +} + +func TestLocalPlantaskBackendSecondTaskCreateScenario(t *testing.T) { + t.Parallel() + ctx := context.Background() + baseDir := t.TempDir() + + loc, err := localbk.NewBackend(ctx, &localbk.Config{}) + if err != nil { + t.Fatalf("NewBackend: %v", err) + } + be := newLocalPlantaskBackend(loc) + + hwPath := filepath.Join(baseDir, ".highwatermark") + if err := loc.Write(ctx, &filesystem.WriteRequest{FilePath: hwPath, Content: "1"}); err != nil { + t.Fatalf("seed highwatermark: %v", err) + } + + files, err := be.LsInfo(ctx, &plantask.LsInfoRequest{Path: baseDir}) + if err != nil { + t.Fatalf("LsInfo: %v", err) + } + var hwFile string + for _, f := range files { + if filepath.Base(f.Path) == ".highwatermark" { + hwFile = f.Path + break + } + } + if hwFile == "" { + t.Fatal("highwatermark not listed") + } + if _, err := be.Read(ctx, &plantask.ReadRequest{FilePath: hwFile}); err != nil { + t.Fatalf("Read highwatermark (second TaskCreate path): %v", err) + } +}