Files
kandev/apps/backend/internal/agent/handlers/workspace_file_handlers.go
T

154 lines
5.2 KiB
Go

package handlers
import (
"context"
"fmt"
"github.com/kandev/kandev/internal/agent/lifecycle"
"github.com/kandev/kandev/internal/common/logger"
ws "github.com/kandev/kandev/pkg/websocket"
"go.uber.org/zap"
)
// WorkspaceFileHandlers handles workspace file operations
type WorkspaceFileHandlers struct {
lifecycle *lifecycle.Manager
logger *logger.Logger
}
// NewWorkspaceFileHandlers creates new workspace file handlers
func NewWorkspaceFileHandlers(lm *lifecycle.Manager, log *logger.Logger) *WorkspaceFileHandlers {
return &WorkspaceFileHandlers{
lifecycle: lm,
logger: log.WithFields(zap.String("component", "workspace-file-handlers")),
}
}
// RegisterHandlers registers workspace file handlers with the dispatcher
func (h *WorkspaceFileHandlers) RegisterHandlers(d *ws.Dispatcher) {
d.RegisterFunc(ws.ActionWorkspaceFileTreeGet, h.wsGetFileTree)
d.RegisterFunc(ws.ActionWorkspaceFileContentGet, h.wsGetFileContent)
d.RegisterFunc(ws.ActionWorkspaceFilesSearch, h.wsSearchFiles)
}
// wsGetFileTree handles workspace.tree.get action
func (h *WorkspaceFileHandlers) wsGetFileTree(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
var req struct {
SessionID string `json:"session_id"`
Path string `json:"path"`
Depth int `json:"depth"`
}
if err := msg.ParsePayload(&req); err != nil {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeBadRequest, "Invalid payload: "+err.Error(), nil)
}
if req.SessionID == "" {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, "session_id is required", nil)
}
// Get agent execution for this session
execution, found := h.lifecycle.GetExecutionBySessionID(req.SessionID)
if !found {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeNotFound, "No agent found for session", nil)
}
// Get agentctl client
client := execution.GetAgentCtlClient()
if client == nil {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeInternalError, "Agent client not available", nil)
}
// Request file tree from agentctl
response, err := client.RequestFileTree(ctx, req.Path, req.Depth)
if err != nil {
h.logger.Error("failed to get file tree", zap.Error(err), zap.String("session_id", req.SessionID))
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeInternalError, fmt.Sprintf("Failed to get file tree: %v", err), nil)
}
return ws.NewResponse(msg.ID, msg.Action, response)
}
// wsGetFileContent handles workspace.file.get action
func (h *WorkspaceFileHandlers) wsGetFileContent(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
var req struct {
SessionID string `json:"session_id"`
Path string `json:"path"`
}
if err := msg.ParsePayload(&req); err != nil {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeBadRequest, "Invalid payload: "+err.Error(), nil)
}
if req.SessionID == "" {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, "session_id is required", nil)
}
if req.Path == "" {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, "path is required", nil)
}
// Get agent execution for this session
execution, found := h.lifecycle.GetExecutionBySessionID(req.SessionID)
if !found {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeNotFound, "No agent found for session", nil)
}
// Get agentctl client
client := execution.GetAgentCtlClient()
if client == nil {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeInternalError, "Agent client not available", nil)
}
// Request file content from agentctl
response, err := client.RequestFileContent(ctx, req.Path)
if err != nil {
h.logger.Error("failed to get file content", zap.Error(err), zap.String("session_id", req.SessionID), zap.String("path", req.Path))
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeInternalError, fmt.Sprintf("Failed to get file content: %v", err), nil)
}
return ws.NewResponse(msg.ID, msg.Action, response)
}
// wsSearchFiles handles workspace.files.search action
func (h *WorkspaceFileHandlers) wsSearchFiles(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
var req struct {
SessionID string `json:"session_id"`
Query string `json:"query"`
Limit int `json:"limit"`
}
if err := msg.ParsePayload(&req); err != nil {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeBadRequest, "Invalid payload: "+err.Error(), nil)
}
if req.SessionID == "" {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, "session_id is required", nil)
}
// Get agent execution for this session
execution, found := h.lifecycle.GetExecutionBySessionID(req.SessionID)
if !found {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeNotFound, "No agent found for session", nil)
}
// Get agentctl client
client := execution.GetAgentCtlClient()
if client == nil {
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeInternalError, "Agent client not available", nil)
}
// Default limit
limit := req.Limit
if limit <= 0 {
limit = 20
}
// Search files via agentctl
response, err := client.SearchFiles(ctx, req.Query, limit)
if err != nil {
h.logger.Error("failed to search files", zap.Error(err), zap.String("session_id", req.SessionID))
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeInternalError, fmt.Sprintf("Failed to search files: %v", err), nil)
}
return ws.NewResponse(msg.ID, msg.Action, response)
}