Files
kandev/apps/backend/internal/agent/settings/controller/custom_tui.go
T
2026-02-19 00:22:48 +00:00

102 lines
2.6 KiB
Go

package controller
import (
"context"
"database/sql"
"errors"
"fmt"
"regexp"
"strings"
"github.com/kandev/kandev/internal/agent/settings/dto"
"github.com/kandev/kandev/internal/agent/settings/models"
)
var nonAlphanumeric = regexp.MustCompile(`[^a-z0-9]+`)
// slugify converts a display name into a URL-friendly slug.
func slugify(displayName string) string {
s := strings.ToLower(strings.TrimSpace(displayName))
s = nonAlphanumeric.ReplaceAllString(s, "-")
s = strings.Trim(s, "-")
return s
}
// CreateCustomTUIAgentRequest is the request to create a custom TUI agent.
type CreateCustomTUIAgentRequest struct {
DisplayName string
Model string
Command string
Description string
}
// CreateCustomTUIAgent registers a new custom TUI agent and persists it to the database.
func (c *Controller) CreateCustomTUIAgent(ctx context.Context, req CreateCustomTUIAgentRequest) (*dto.AgentDTO, error) {
slug := slugify(req.DisplayName)
if slug == "" {
return nil, ErrInvalidSlug
}
if req.Command == "" {
return nil, ErrCommandRequired
}
// Check for conflict with existing registry entry
if c.agentRegistry.Exists(slug) {
return nil, ErrAgentAlreadyExists
}
// Check for conflict with existing DB entry
existing, err := c.repo.GetAgentByName(ctx, slug)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err
}
if existing != nil {
return nil, ErrAgentAlreadyExists
}
// Register in the in-memory registry
if regErr := c.agentRegistry.RegisterCustomTUIAgent(
slug, req.DisplayName, req.Command, req.Description, req.Model, nil,
); regErr != nil {
return nil, fmt.Errorf("failed to register agent: %w", regErr)
}
// Persist to DB
tuiConfig := &models.TUIConfigJSON{
Command: req.Command,
DisplayName: req.DisplayName,
Model: req.Model,
Description: req.Description,
WaitForTerminal: true,
}
agent := &models.Agent{
Name: slug,
TUIConfig: tuiConfig,
}
if err := c.repo.CreateAgent(ctx, agent); err != nil {
// Rollback registry on DB failure
_ = c.agentRegistry.Unregister(slug)
return nil, err
}
// Create default passthrough profile — use model as profile name for distinct dropdown labels
profileName := req.Model
if profileName == "" {
profileName = req.DisplayName
}
profile := &models.AgentProfile{
AgentID: agent.ID,
Name: profileName,
AgentDisplayName: req.DisplayName,
Model: "passthrough",
CLIPassthrough: true,
}
if err := c.repo.CreateAgentProfile(ctx, profile); err != nil {
return nil, err
}
profiles := []*models.AgentProfile{profile}
result := toAgentDTO(agent, profiles)
return &result, nil
}