Skip to content

Commit e1f81fc

Browse files
authored
Merge pull request #99 from AdjectiveAllison/agents_put
add put support for agents api
2 parents fda15b7 + 9756f6b commit e1f81fc

File tree

2 files changed

+549
-0
lines changed

2 files changed

+549
-0
lines changed

acp/internal/server/server.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ type CreateAgentRequest struct {
4040
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` // Optional
4141
}
4242

43+
// UpdateAgentRequest defines the structure of the request body for updating an agent
44+
type UpdateAgentRequest struct {
45+
LLM string `json:"llm"` // Required
46+
SystemPrompt string `json:"systemPrompt"` // Required
47+
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` // Optional
48+
}
49+
4350
// MCPServerConfig defines the configuration for an MCP server
4451
type MCPServerConfig struct {
4552
Transport string `json:"transport"` // Required: "stdio" or "http"
@@ -103,6 +110,7 @@ func (s *APIServer) registerRoutes() {
103110
agents.GET("", s.listAgents)
104111
agents.GET("/:name", s.getAgent)
105112
agents.POST("", s.createAgent)
113+
agents.PUT("/:name", s.updateAgent)
106114
}
107115

108116
// processMCPServers creates MCP servers and their secrets based on the given configuration
@@ -618,6 +626,218 @@ func defaultIfEmpty(val, defaultVal string) string {
618626
return val
619627
}
620628

629+
// updateAgent handles updating an existing agent and its associated MCP servers
630+
func (s *APIServer) updateAgent(c *gin.Context) {
631+
ctx := c.Request.Context()
632+
logger := log.FromContext(ctx)
633+
634+
// Get namespace and name
635+
namespace := c.Query("namespace")
636+
if namespace == "" {
637+
c.JSON(http.StatusBadRequest, gin.H{"error": "namespace query parameter is required"})
638+
return
639+
}
640+
name := c.Param("name")
641+
if name == "" {
642+
c.JSON(http.StatusBadRequest, gin.H{"error": "agent name is required"})
643+
return
644+
}
645+
646+
// Read the raw data for validation
647+
var rawData []byte
648+
if data, err := c.GetRawData(); err == nil {
649+
rawData = data
650+
} else {
651+
c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read request body: " + err.Error()})
652+
return
653+
}
654+
655+
// Parse request
656+
var req UpdateAgentRequest
657+
if err := json.Unmarshal(rawData, &req); err != nil {
658+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body: " + err.Error()})
659+
return
660+
}
661+
662+
// Validate for unknown fields
663+
decoder := json.NewDecoder(bytes.NewReader(rawData))
664+
decoder.DisallowUnknownFields()
665+
if err := decoder.Decode(&req); err != nil {
666+
if strings.Contains(err.Error(), "unknown field") {
667+
c.JSON(http.StatusBadRequest, gin.H{"error": "Unknown field in request: " + err.Error()})
668+
return
669+
}
670+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON format: " + err.Error()})
671+
return
672+
}
673+
674+
// Validate required fields
675+
if req.LLM == "" || req.SystemPrompt == "" {
676+
c.JSON(http.StatusBadRequest, gin.H{"error": "llm and systemPrompt are required"})
677+
return
678+
}
679+
680+
// Fetch current agent
681+
var currentAgent acp.Agent
682+
if err := s.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, &currentAgent); err != nil {
683+
if apierrors.IsNotFound(err) {
684+
c.JSON(http.StatusNotFound, gin.H{"error": "Agent not found"})
685+
return
686+
}
687+
logger.Error(err, "Failed to get agent", "name", name)
688+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get agent: " + err.Error()})
689+
return
690+
}
691+
692+
// Verify LLM exists
693+
if err := s.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: req.LLM}, &acp.LLM{}); err != nil {
694+
if apierrors.IsNotFound(err) {
695+
c.JSON(http.StatusNotFound, gin.H{"error": "LLM not found"})
696+
return
697+
}
698+
logger.Error(err, "Failed to check LLM")
699+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check LLM: " + err.Error()})
700+
return
701+
}
702+
703+
// Track current MCP servers for this agent
704+
currentMCPServers := make(map[string]struct{})
705+
for _, ref := range currentAgent.Spec.MCPServers {
706+
currentMCPServers[ref.Name] = struct{}{}
707+
}
708+
709+
// Process new/updated MCP servers
710+
desiredMCPServers := make(map[string]MCPServerConfig)
711+
for key, config := range req.MCPServers {
712+
mcpName := fmt.Sprintf("%s-%s", name, key)
713+
if err := validateMCPConfig(config); err != nil {
714+
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid MCP server config for '%s': %s", key, err.Error())})
715+
return
716+
}
717+
desiredMCPServers[mcpName] = config
718+
}
719+
720+
// Create or update MCP servers
721+
for mcpName, config := range desiredMCPServers {
722+
secretName := fmt.Sprintf("%s-secrets", mcpName)
723+
var mcpServer acp.MCPServer
724+
err := s.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: mcpName}, &mcpServer)
725+
if apierrors.IsNotFound(err) {
726+
// Create new MCP server and secret
727+
if len(config.Secrets) > 0 {
728+
secret := createSecret(secretName, namespace, config.Secrets)
729+
if err := s.client.Create(ctx, secret); err != nil {
730+
logger.Error(err, "Failed to create secret", "name", secretName)
731+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create secret: " + err.Error()})
732+
return
733+
}
734+
}
735+
mcpServer := createMCPServer(mcpName, namespace, config, secretName)
736+
if err := s.client.Create(ctx, mcpServer); err != nil {
737+
logger.Error(err, "Failed to create MCP server", "name", mcpName)
738+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create MCP server: " + err.Error()})
739+
return
740+
}
741+
} else if err == nil {
742+
// Update existing MCP server
743+
updatedMCP := createMCPServer(mcpName, namespace, config, secretName)
744+
updatedMCP.ObjectMeta = mcpServer.ObjectMeta
745+
if err := s.client.Update(ctx, updatedMCP); err != nil {
746+
logger.Error(err, "Failed to update MCP server", "name", mcpName)
747+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update MCP server: " + err.Error()})
748+
return
749+
}
750+
// Handle secret
751+
if len(config.Secrets) > 0 {
752+
var secret corev1.Secret
753+
err := s.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: secretName}, &secret)
754+
if apierrors.IsNotFound(err) {
755+
secret := createSecret(secretName, namespace, config.Secrets)
756+
if err := s.client.Create(ctx, secret); err != nil {
757+
logger.Error(err, "Failed to create secret", "name", secretName)
758+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create secret: " + err.Error()})
759+
return
760+
}
761+
} else if err == nil {
762+
for k, v := range config.Secrets {
763+
if secret.Data == nil {
764+
secret.Data = make(map[string][]byte)
765+
}
766+
secret.Data[k] = []byte(v)
767+
}
768+
if err := s.client.Update(ctx, &secret); err != nil {
769+
logger.Error(err, "Failed to update secret", "name", secretName)
770+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update secret: " + err.Error()})
771+
return
772+
}
773+
} else {
774+
logger.Error(err, "Failed to get secret", "name", secretName)
775+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get secret: " + err.Error()})
776+
return
777+
}
778+
} else {
779+
// Delete secret if it exists and no secrets are specified
780+
var secret corev1.Secret
781+
if err := s.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: secretName}, &secret); err == nil {
782+
if err := s.client.Delete(ctx, &secret); err != nil {
783+
logger.Error(err, "Failed to delete secret", "name", secretName)
784+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete secret: " + err.Error()})
785+
return
786+
}
787+
}
788+
}
789+
} else {
790+
logger.Error(err, "Failed to get MCP server", "name", mcpName)
791+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get MCP server: " + err.Error()})
792+
return
793+
}
794+
delete(currentMCPServers, mcpName)
795+
}
796+
797+
// Delete removed MCP servers
798+
for mcpName := range currentMCPServers {
799+
var mcpServer acp.MCPServer
800+
if err := s.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: mcpName}, &mcpServer); err == nil {
801+
if err := s.client.Delete(ctx, &mcpServer); err != nil {
802+
logger.Error(err, "Failed to delete MCP server", "name", mcpName)
803+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete MCP server: " + err.Error()})
804+
return
805+
}
806+
}
807+
secretName := fmt.Sprintf("%s-secrets", mcpName)
808+
var secret corev1.Secret
809+
if err := s.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: secretName}, &secret); err == nil {
810+
if err := s.client.Delete(ctx, &secret); err != nil {
811+
logger.Error(err, "Failed to delete secret", "name", secretName)
812+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete secret: " + err.Error()})
813+
return
814+
}
815+
}
816+
}
817+
818+
// Update agent spec
819+
currentAgent.Spec.LLMRef = acp.LocalObjectReference{Name: req.LLM}
820+
currentAgent.Spec.System = req.SystemPrompt
821+
currentAgent.Spec.MCPServers = []acp.LocalObjectReference{}
822+
for mcpName := range desiredMCPServers {
823+
currentAgent.Spec.MCPServers = append(currentAgent.Spec.MCPServers, acp.LocalObjectReference{Name: mcpName})
824+
}
825+
826+
if err := s.client.Update(ctx, &currentAgent); err != nil {
827+
logger.Error(err, "Failed to update agent", "name", name)
828+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update agent: " + err.Error()})
829+
return
830+
}
831+
832+
c.JSON(http.StatusOK, AgentResponse{
833+
Namespace: namespace,
834+
Name: name,
835+
LLM: req.LLM,
836+
SystemPrompt: req.SystemPrompt,
837+
MCPServers: req.MCPServers,
838+
})
839+
}
840+
621841
// createTask handles the creation of a new task
622842
func (s *APIServer) createTask(c *gin.Context) {
623843
ctx := c.Request.Context()

0 commit comments

Comments
 (0)