Files
kandev/apps/backend/internal/agent/settings/controller/controller.go
T
2026-04-15 13:22:32 +01:00

91 lines
3.3 KiB
Go

package controller
import (
"context"
"errors"
"fmt"
"strings"
"github.com/kandev/kandev/internal/agent/discovery"
agentdto "github.com/kandev/kandev/internal/agent/dto"
"github.com/kandev/kandev/internal/agent/hostutility"
"github.com/kandev/kandev/internal/agent/mcpconfig"
"github.com/kandev/kandev/internal/agent/registry"
"github.com/kandev/kandev/internal/agent/settings/modelfetcher"
"github.com/kandev/kandev/internal/agent/settings/store"
"github.com/kandev/kandev/internal/common/logger"
"go.uber.org/zap"
)
// buildCommandString builds a display-friendly command string with proper quoting.
func buildCommandString(cmd []string) string {
var parts []string
for _, arg := range cmd {
if strings.ContainsAny(arg, " \t\n\"'`$\\") {
escaped := strings.ReplaceAll(arg, "\"", "\\\"")
parts = append(parts, "\""+escaped+"\"")
} else {
parts = append(parts, arg)
}
}
return strings.Join(parts, " ")
}
var (
ErrAgentNotFound = errors.New("agent not found")
ErrAgentAlreadyExists = errors.New("agent already exists")
ErrAgentProfileNotFound = errors.New("agent profile not found")
ErrAgentMcpUnsupported = errors.New("mcp not supported by agent")
ErrModelRequired = errors.New("model is required for agent profiles")
ErrLogoNotAvailable = errors.New("logo not available for agent")
ErrInvalidSlug = errors.New("display name must produce a valid slug")
ErrCommandRequired = errors.New("command is required")
)
type Controller struct {
repo store.Repository
discovery *discovery.Registry
agentRegistry *registry.Registry
sessionChecker SessionChecker
mcpService *mcpconfig.Service
modelCache *modelfetcher.Cache
hostUtility *hostutility.Manager
logger *logger.Logger
}
// ErrProfileInUseDetail is returned when a profile cannot be deleted because active sessions exist.
type ErrProfileInUseDetail struct {
ActiveSessions []agentdto.ActiveTaskInfo
}
func (e *ErrProfileInUseDetail) Error() string {
return fmt.Sprintf("agent profile is used by %d active session(s)", len(e.ActiveSessions))
}
type SessionChecker interface {
HasActiveTaskSessionsByAgentProfile(ctx context.Context, agentProfileID string) (bool, error)
DeleteEphemeralTasksByAgentProfile(ctx context.Context, agentProfileID string) (int64, error)
GetActiveTaskInfoByAgentProfile(ctx context.Context, agentProfileID string) ([]agentdto.ActiveTaskInfo, error)
}
func NewController(repo store.Repository, discoveryRegistry *discovery.Registry, agentRegistry *registry.Registry, sessionChecker SessionChecker, log *logger.Logger,
) *Controller {
return &Controller{
repo: repo,
discovery: discoveryRegistry,
agentRegistry: agentRegistry,
sessionChecker: sessionChecker,
mcpService: mcpconfig.NewService(repo),
modelCache: modelfetcher.NewCache(),
logger: log.WithFields(zap.String("component", "agent-settings-controller")),
}
}
// SetHostUtility wires the host utility manager into the controller so that
// endpoints like /agent-models can read the cached capability data. Called
// once at startup after the host utility manager is constructed; leaving this
// unset simply causes the model endpoints to report "not_configured".
func (c *Controller) SetHostUtility(h *hostutility.Manager) {
c.hostUtility = h
}