feat: agents discovery
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kandev/kandev/internal/agent/discovery"
|
||||
"github.com/kandev/kandev/internal/agent/settings/dto"
|
||||
"github.com/kandev/kandev/internal/agent/settings/models"
|
||||
"github.com/kandev/kandev/internal/agent/settings/store"
|
||||
"github.com/kandev/kandev/internal/common/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAgentNotFound = errors.New("agent not found")
|
||||
ErrAgentProfileNotFound = errors.New("agent profile not found")
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
repo store.Repository
|
||||
discovery *discovery.Registry
|
||||
logger *logger.Logger
|
||||
}
|
||||
|
||||
func NewController(repo store.Repository, discoveryRegistry *discovery.Registry, log *logger.Logger) *Controller {
|
||||
return &Controller{
|
||||
repo: repo,
|
||||
discovery: discoveryRegistry,
|
||||
logger: log.WithFields(zap.String("component", "agent-settings-controller")),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) ListDiscovery(ctx context.Context) (*dto.ListDiscoveryResponse, error) {
|
||||
results, err := c.discovery.Detect(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := make([]dto.AgentDiscoveryDTO, 0, len(results))
|
||||
for _, result := range results {
|
||||
payload = append(payload, dto.AgentDiscoveryDTO{
|
||||
Name: result.Name,
|
||||
SupportsMCP: result.SupportsMCP,
|
||||
MCPConfigPath: result.MCPConfigPath,
|
||||
InstallationPaths: result.InstallationPaths,
|
||||
Available: result.Available,
|
||||
MatchedPath: result.MatchedPath,
|
||||
})
|
||||
}
|
||||
return &dto.ListDiscoveryResponse{Agents: payload, Total: len(payload)}, nil
|
||||
}
|
||||
|
||||
func (c *Controller) ListAgents(ctx context.Context) (*dto.ListAgentsResponse, error) {
|
||||
agents, err := c.repo.ListAgents(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := make([]dto.AgentDTO, 0, len(agents))
|
||||
for _, agent := range agents {
|
||||
profiles, err := c.repo.ListAgentProfiles(ctx, agent.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload = append(payload, toAgentDTO(agent, profiles))
|
||||
}
|
||||
return &dto.ListAgentsResponse{Agents: payload, Total: len(payload)}, nil
|
||||
}
|
||||
|
||||
func (c *Controller) GetAgent(ctx context.Context, id string) (*dto.AgentDTO, error) {
|
||||
agent, err := c.repo.GetAgent(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrAgentNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
profiles, err := c.repo.ListAgentProfiles(ctx, agent.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := toAgentDTO(agent, profiles)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
type CreateAgentRequest struct {
|
||||
Name string
|
||||
WorkspaceID *string
|
||||
Profiles []CreateAgentProfileRequest
|
||||
}
|
||||
|
||||
type CreateAgentProfileRequest struct {
|
||||
Name string
|
||||
Model string
|
||||
AutoApprove bool
|
||||
DangerouslySkipPermissions bool
|
||||
Plan string
|
||||
}
|
||||
|
||||
func (c *Controller) CreateAgent(ctx context.Context, req CreateAgentRequest) (*dto.AgentDTO, error) {
|
||||
if req.Name == "" {
|
||||
return nil, fmt.Errorf("name is required")
|
||||
}
|
||||
existing, err := c.repo.GetAgentByName(ctx, req.Name)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, err
|
||||
}
|
||||
if err == nil && existing != nil {
|
||||
return nil, fmt.Errorf("agent already configured: %s", req.Name)
|
||||
}
|
||||
discoveryResults, err := c.discovery.Detect(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var matched *discovery.Availability
|
||||
for _, result := range discoveryResults {
|
||||
if result.Name == req.Name {
|
||||
matched = &result
|
||||
break
|
||||
}
|
||||
}
|
||||
if matched == nil {
|
||||
return nil, fmt.Errorf("unknown agent: %s", req.Name)
|
||||
}
|
||||
if !matched.Available {
|
||||
return nil, fmt.Errorf("agent not installed: %s", req.Name)
|
||||
}
|
||||
agent := &models.Agent{
|
||||
Name: matched.Name,
|
||||
WorkspaceID: req.WorkspaceID,
|
||||
SupportsMCP: matched.SupportsMCP,
|
||||
MCPConfigPath: matched.MCPConfigPath,
|
||||
}
|
||||
if err := c.repo.CreateAgent(ctx, agent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profiles := make([]*models.AgentProfile, 0, len(req.Profiles))
|
||||
for _, profileReq := range req.Profiles {
|
||||
profile := &models.AgentProfile{
|
||||
AgentID: agent.ID,
|
||||
Name: profileReq.Name,
|
||||
Model: profileReq.Model,
|
||||
AutoApprove: profileReq.AutoApprove,
|
||||
DangerouslySkipPermissions: profileReq.DangerouslySkipPermissions,
|
||||
Plan: profileReq.Plan,
|
||||
}
|
||||
if err := c.repo.CreateAgentProfile(ctx, profile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profiles = append(profiles, profile)
|
||||
}
|
||||
result := toAgentDTO(agent, profiles)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
type UpdateAgentRequest struct {
|
||||
ID string
|
||||
WorkspaceID *string
|
||||
SupportsMCP *bool
|
||||
MCPConfigPath *string
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateAgent(ctx context.Context, req UpdateAgentRequest) (*dto.AgentDTO, error) {
|
||||
agent, err := c.repo.GetAgent(ctx, req.ID)
|
||||
if err != nil {
|
||||
return nil, ErrAgentNotFound
|
||||
}
|
||||
if req.WorkspaceID != nil {
|
||||
agent.WorkspaceID = req.WorkspaceID
|
||||
}
|
||||
if req.SupportsMCP != nil {
|
||||
agent.SupportsMCP = *req.SupportsMCP
|
||||
}
|
||||
if req.MCPConfigPath != nil {
|
||||
agent.MCPConfigPath = *req.MCPConfigPath
|
||||
}
|
||||
if err := c.repo.UpdateAgent(ctx, agent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profiles, err := c.repo.ListAgentProfiles(ctx, agent.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := toAgentDTO(agent, profiles)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *Controller) DeleteAgent(ctx context.Context, id string) error {
|
||||
if err := c.repo.DeleteAgent(ctx, id); err != nil {
|
||||
if strings.Contains(err.Error(), "agent not found") {
|
||||
return ErrAgentNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateProfileRequest struct {
|
||||
AgentID string
|
||||
Name string
|
||||
Model string
|
||||
AutoApprove bool
|
||||
DangerouslySkipPermissions bool
|
||||
Plan string
|
||||
}
|
||||
|
||||
func (c *Controller) CreateProfile(ctx context.Context, req CreateProfileRequest) (*dto.AgentProfileDTO, error) {
|
||||
profile := &models.AgentProfile{
|
||||
AgentID: req.AgentID,
|
||||
Name: req.Name,
|
||||
Model: req.Model,
|
||||
AutoApprove: req.AutoApprove,
|
||||
DangerouslySkipPermissions: req.DangerouslySkipPermissions,
|
||||
Plan: req.Plan,
|
||||
}
|
||||
if err := c.repo.CreateAgentProfile(ctx, profile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := toProfileDTO(profile)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
type UpdateProfileRequest struct {
|
||||
ID string
|
||||
Name *string
|
||||
Model *string
|
||||
AutoApprove *bool
|
||||
DangerouslySkipPermissions *bool
|
||||
Plan *string
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateProfile(ctx context.Context, req UpdateProfileRequest) (*dto.AgentProfileDTO, error) {
|
||||
profile, err := c.repo.GetAgentProfile(ctx, req.ID)
|
||||
if err != nil {
|
||||
return nil, ErrAgentProfileNotFound
|
||||
}
|
||||
if req.Name != nil {
|
||||
profile.Name = *req.Name
|
||||
}
|
||||
if req.Model != nil {
|
||||
profile.Model = *req.Model
|
||||
}
|
||||
if req.AutoApprove != nil {
|
||||
profile.AutoApprove = *req.AutoApprove
|
||||
}
|
||||
if req.DangerouslySkipPermissions != nil {
|
||||
profile.DangerouslySkipPermissions = *req.DangerouslySkipPermissions
|
||||
}
|
||||
if req.Plan != nil {
|
||||
profile.Plan = *req.Plan
|
||||
}
|
||||
if err := c.repo.UpdateAgentProfile(ctx, profile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := toProfileDTO(profile)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *Controller) DeleteProfile(ctx context.Context, id string) error {
|
||||
if err := c.repo.DeleteAgentProfile(ctx, id); err != nil {
|
||||
if strings.Contains(err.Error(), "agent profile not found") {
|
||||
return ErrAgentProfileNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toAgentDTO(agent *models.Agent, profiles []*models.AgentProfile) dto.AgentDTO {
|
||||
profileDTOs := make([]dto.AgentProfileDTO, 0, len(profiles))
|
||||
for _, profile := range profiles {
|
||||
profileDTOs = append(profileDTOs, toProfileDTO(profile))
|
||||
}
|
||||
return dto.AgentDTO{
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
WorkspaceID: agent.WorkspaceID,
|
||||
SupportsMCP: agent.SupportsMCP,
|
||||
MCPConfigPath: agent.MCPConfigPath,
|
||||
Profiles: profileDTOs,
|
||||
CreatedAt: agent.CreatedAt,
|
||||
UpdatedAt: agent.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func toProfileDTO(profile *models.AgentProfile) dto.AgentProfileDTO {
|
||||
return dto.AgentProfileDTO{
|
||||
ID: profile.ID,
|
||||
AgentID: profile.AgentID,
|
||||
Name: profile.Name,
|
||||
Model: profile.Model,
|
||||
AutoApprove: profile.AutoApprove,
|
||||
DangerouslySkipPermissions: profile.DangerouslySkipPermissions,
|
||||
Plan: profile.Plan,
|
||||
CreatedAt: profile.CreatedAt,
|
||||
UpdatedAt: profile.UpdatedAt,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user