yao/tai/workspace/workspace.go
Max 2084e20479 feat(workspace): add workspace ID retrieval and integration into environment configuration
- Implemented GetWorkspaceID method in the context package to retrieve the workspace ID.
- Updated buildContextVariables to include WORKSPACE_ID in the context variables if available.
- Enhanced buildEnv function to set CTX_WORKSPACE_ID in the environment configuration based on the workspace ID.
- Refactored role connector handling to support multiple connectors in the environment setup.
- Added tests for new role connector functionality and workspace ID integration.
2026-04-13 14:47:08 +08:00

215 lines
6 KiB
Go

package workspace
import (
"context"
"io"
"io/fs"
"os"
"strings"
"time"
"github.com/yaoapp/yao/tai/volume"
)
// FS extends Go's fs.FS with write operations.
// Backed by volume.Volume — works for both Remote and Local transparently.
type FS interface {
fs.FS
fs.StatFS
fs.ReadFileFS
fs.ReadDirFS
io.Closer
WriteFile(name string, data []byte, perm os.FileMode) error
Remove(name string) error
RemoveAll(name string) error
Rename(oldname, newname string) error
MkdirAll(name string, perm os.FileMode) error
// Copy copies files between workspace paths and/or host paths.
// Host paths use "local:///" (absolute system path) or "tmp:///" (os.TempDir-relative).
// ws↔ws uses Volume.Copy (server-side for remote volumes, avoiding 2N network round-trips).
// Returns non-nil *SyncResult for host↔workspace and ws↔ws transfers; nil for host↔host.
Copy(src, dst string, opts ...volume.SyncOption) (*volume.SyncResult, error)
// GetRoot returns the absolute path of this workspace's root directory on the host filesystem.
GetRoot() (string, error)
// GetID returns the ID of this workspace.
GetID() (string, error)
}
// New creates an FS backed by the given Volume for the specified session.
func New(vol volume.Volume, sessionID string) FS {
return &workspaceFS{vol: vol, session: sessionID}
}
type workspaceFS struct {
vol volume.Volume
session string
}
func (w *workspaceFS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
}
ctx := context.Background()
info, err := w.vol.Stat(ctx, w.session, name)
if err != nil {
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
}
if info.IsDir {
return &dirFile{w: w, name: name, info: info}, nil
}
data, _, err := w.vol.ReadFile(ctx, w.session, name)
if err != nil {
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
}
return &memFile{name: name, info: info, data: data}, nil
}
func (w *workspaceFS) Stat(name string) (fs.FileInfo, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrInvalid}
}
info, err := w.vol.Stat(context.Background(), w.session, name)
if err != nil {
return nil, &fs.PathError{Op: "stat", Path: name, Err: err}
}
return toFSInfo(name, info), nil
}
func (w *workspaceFS) ReadFile(name string) ([]byte, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{Op: "read", Path: name, Err: fs.ErrInvalid}
}
data, _, err := w.vol.ReadFile(context.Background(), w.session, name)
if err != nil {
return nil, &fs.PathError{Op: "read", Path: name, Err: err}
}
return data, nil
}
func (w *workspaceFS) ReadDir(name string) ([]fs.DirEntry, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrInvalid}
}
entries, err := w.vol.ListDir(context.Background(), w.session, name)
if err != nil {
return nil, &fs.PathError{Op: "readdir", Path: name, Err: err}
}
result := make([]fs.DirEntry, 0, len(entries))
for i := range entries {
result = append(result, &dirEntry{info: &entries[i]})
}
return result, nil
}
func (w *workspaceFS) WriteFile(name string, data []byte, perm os.FileMode) error {
return w.vol.WriteFile(context.Background(), w.session, name, data, perm)
}
func (w *workspaceFS) Remove(name string) error {
return w.vol.Remove(context.Background(), w.session, name, false)
}
func (w *workspaceFS) RemoveAll(name string) error {
return w.vol.Remove(context.Background(), w.session, name, true)
}
func (w *workspaceFS) Rename(oldname, newname string) error {
return w.vol.Rename(context.Background(), w.session, oldname, newname)
}
func (w *workspaceFS) MkdirAll(name string, _ os.FileMode) error {
return w.vol.MkdirAll(context.Background(), w.session, name)
}
func (w *workspaceFS) GetRoot() (string, error) {
return w.vol.Abs(context.Background(), w.session, ".")
}
func (w *workspaceFS) GetID() (string, error) {
return w.session, nil
}
func (w *workspaceFS) Close() error { return nil }
// --- fs.FileInfo adapter ---
type fileInfoAdapter struct {
name string
size int64
mode fs.FileMode
mtime time.Time
isDir bool
}
func toFSInfo(name string, vi *volume.FileInfo) *fileInfoAdapter {
base := name
if idx := strings.LastIndex(name, "/"); idx >= 0 {
base = name[idx+1:]
}
if base == "" {
base = "."
}
return &fileInfoAdapter{
name: base,
size: vi.Size,
mode: vi.Mode,
mtime: vi.Mtime,
isDir: vi.IsDir,
}
}
func (f *fileInfoAdapter) Name() string { return f.name }
func (f *fileInfoAdapter) Size() int64 { return f.size }
func (f *fileInfoAdapter) Mode() fs.FileMode { return f.mode }
func (f *fileInfoAdapter) ModTime() time.Time { return f.mtime }
func (f *fileInfoAdapter) IsDir() bool { return f.isDir }
func (f *fileInfoAdapter) Sys() any { return nil }
// --- fs.DirEntry adapter ---
type dirEntry struct {
info *volume.FileInfo
}
func (d *dirEntry) Name() string { return d.info.Path }
func (d *dirEntry) IsDir() bool { return d.info.IsDir }
func (d *dirEntry) Type() fs.FileMode { return d.info.Mode.Type() }
func (d *dirEntry) Info() (fs.FileInfo, error) { return toFSInfo(d.info.Path, d.info), nil }
// --- in-memory file (for Open on regular files) ---
type memFile struct {
name string
info *volume.FileInfo
data []byte
offset int
}
func (f *memFile) Stat() (fs.FileInfo, error) { return toFSInfo(f.name, f.info), nil }
func (f *memFile) Read(b []byte) (int, error) {
if f.offset >= len(f.data) {
return 0, io.EOF
}
n := copy(b, f.data[f.offset:])
f.offset += n
return n, nil
}
func (f *memFile) Close() error { return nil }
// --- directory file (for Open on directories) ---
type dirFile struct {
w *workspaceFS
name string
info *volume.FileInfo
}
func (d *dirFile) Stat() (fs.FileInfo, error) { return toFSInfo(d.name, d.info), nil }
func (d *dirFile) Read([]byte) (int, error) {
return 0, &fs.PathError{Op: "read", Path: d.name, Err: fs.ErrInvalid}
}
func (d *dirFile) Close() error { return nil }