From d8f267694d8986e4328c69562609848e6be702e7 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 25 Oct 2025 16:02:01 +0800 Subject: [PATCH] Refactor API handler functions for assistants in Neo package - Updated the assistant-related API handler functions in neo/api.go to follow Go naming conventions by capitalizing the function names, making them exported for use in openapi/agent. - This change enhances code readability and consistency across the API implementation. - Added new LLM and MCP server management handlers in their respective packages, improving the overall structure and organization of the OpenAPI module. - Removed the obsolete agent.go file, streamlining the codebase. --- neo/api.go | 36 ++++++++-------- openapi/agent/agent.go | 29 +++++++++++++ openapi/agent/agnet.go | 1 - openapi/llm/llm.go | 94 ++++++++++++++++++++++++++++++++++++++++++ openapi/mcp/mcp.go | 67 ++++++++++++++++++++++++++++++ openapi/openapi.go | 12 ++++++ 6 files changed, 220 insertions(+), 19 deletions(-) create mode 100644 openapi/agent/agent.go delete mode 100644 openapi/agent/agnet.go diff --git a/neo/api.go b/neo/api.go index 0310f352..f9cb8c30 100644 --- a/neo/api.go +++ b/neo/api.go @@ -67,30 +67,30 @@ func (neo *DSL) API(router *gin.Engine, path string) error { // Assistant API endpoints // List assistants example: // curl -X GET 'http://localhost:5099/api/__yao/neo/assistants?page=1&pagesize=20&tags=tag1,tag2&token=xxx' - router.GET(path+"/assistants", append(middlewares, neo.handleAssistantList)...) + router.GET(path+"/assistants", append(middlewares, neo.HandleAssistantList)...) // Get all assistant tags example: // curl -X GET 'http://localhost:5099/api/__yao/neo/assistants/tags?token=xxx' - router.GET(path+"/assistants/tags", append(middlewares, neo.handleAssistantTags)...) + router.GET(path+"/assistants/tags", append(middlewares, neo.HandleAssistantTags)...) // Get assistant details example: // curl -X GET 'http://localhost:5099/api/__yao/neo/assistants/assistant_123?token=xxx' - router.GET(path+"/assistants/:id", append(middlewares, neo.handleAssistantDetail)...) + router.GET(path+"/assistants/:id", append(middlewares, neo.HandleAssistantDetail)...) // Execute assistant API example: // curl -X POST 'http://localhost:5099/api/__yao/neo/assistants/assistant_123/api' \ // -H 'Content-Type: application/json' \ // -d '{"name": "Test", "payload": {"name": "yao", "age": 18}}' - router.POST(path+"/assistants/:id/call", append(middlewares, neo.handleAssistantCall)...) + router.POST(path+"/assistants/:id/call", append(middlewares, neo.HandleAssistantCall)...) // Create/Update assistant example: // curl -X POST 'http://localhost:5099/api/__yao/neo/assistants' \ // -H 'Content-Type: application/json' \ // -d '{"name": "My Assistant", "type": "chat", "tags": ["tag1", "tag2"], "mentionable": true, "avatar": "path/to/avatar.png", "token": "xxx"}' - router.POST(path+"/assistants", append(middlewares, neo.handleAssistantSave)...) + router.POST(path+"/assistants", append(middlewares, neo.HandleAssistantSave)...) // Delete assistant example: // curl -X DELETE 'http://localhost:5099/api/__yao/neo/assistants/assistant_123?token=xxx' - router.DELETE(path+"/assistants/:id", append(middlewares, neo.handleAssistantDelete)...) + router.DELETE(path+"/assistants/:id", append(middlewares, neo.HandleAssistantDelete)...) // Chat management endpoints // List chats example: @@ -1070,8 +1070,8 @@ func (neo *DSL) handleGeneratePrompts(c *gin.Context) { } } -// handleAssistantList handles listing assistants -func (neo *DSL) handleAssistantList(c *gin.Context) { +// HandleAssistantList handles listing assistants (exported for use in openapi/agent) +func (neo *DSL) HandleAssistantList(c *gin.Context) { // Parse filter parameters filter := store.AssistantFilter{ Type: "assistant", @@ -1173,8 +1173,8 @@ func parseBoolValue(value string) *bool { } } -// handleAssistantAPI handles the assistant API -func (neo *DSL) handleAssistantCall(c *gin.Context) { +// HandleAssistantCall handles the assistant API call (exported for use in openapi/agent) +func (neo *DSL) HandleAssistantCall(c *gin.Context) { assistantID := c.Param("id") if assistantID == "" { c.JSON(400, gin.H{"message": "assistant id is required", "code": 400}) @@ -1213,8 +1213,8 @@ func (neo *DSL) handleAssistantCall(c *gin.Context) { c.Done() } -// handleAssistantDetail handles getting a single assistant's details -func (neo *DSL) handleAssistantDetail(c *gin.Context) { +// HandleAssistantDetail handles getting a single assistant's details (exported for use in openapi/agent) +func (neo *DSL) HandleAssistantDetail(c *gin.Context) { assistantID := c.Param("id") if assistantID == "" { c.JSON(400, gin.H{"message": "assistant id is required", "code": 400}) @@ -1252,8 +1252,8 @@ func (neo *DSL) handleAssistantDetail(c *gin.Context) { c.Done() } -// handleAssistantSave handles creating or updating an assistant -func (neo *DSL) handleAssistantSave(c *gin.Context) { +// HandleAssistantSave handles creating or updating an assistant (exported for use in openapi/agent) +func (neo *DSL) HandleAssistantSave(c *gin.Context) { var assistantData map[string]interface{} if err := c.BindJSON(&assistantData); err != nil { c.JSON(400, gin.H{"message": "invalid request body", "code": 400}) @@ -1290,8 +1290,8 @@ func (neo *DSL) handleAssistantSave(c *gin.Context) { c.Done() } -// handleAssistantDelete handles deleting an assistant -func (neo *DSL) handleAssistantDelete(c *gin.Context) { +// HandleAssistantDelete handles deleting an assistant (exported for use in openapi/agent) +func (neo *DSL) HandleAssistantDelete(c *gin.Context) { assistantID := c.Param("id") if assistantID == "" { c.JSON(400, gin.H{"message": "assistant id is required", "code": 400}) @@ -1342,8 +1342,8 @@ func (neo *DSL) handleConnectors(c *gin.Context) { c.Done() } -// handleAssistantTags handles getting all assistant tags -func (neo *DSL) handleAssistantTags(c *gin.Context) { +// HandleAssistantTags handles getting all assistant tags (exported for use in openapi/agent) +func (neo *DSL) HandleAssistantTags(c *gin.Context) { sid := c.GetString("__sid") if sid == "" { c.JSON(400, gin.H{"message": "sid is required", "code": 400}) diff --git a/openapi/agent/agent.go b/openapi/agent/agent.go new file mode 100644 index 00000000..5245fdc1 --- /dev/null +++ b/openapi/agent/agent.go @@ -0,0 +1,29 @@ +package agent + +import ( + "github.com/gin-gonic/gin" + "github.com/yaoapp/yao/neo" + "github.com/yaoapp/yao/openapi/oauth/types" +) + +// Attach attaches the agent (assistant) API handlers to the router with OAuth protection +// This provides OAuth-protected endpoints for assistant management, mirroring the neo assistant API +func Attach(group *gin.RouterGroup, oauth types.OAuth) { + + // Get the Neo instance + n := neo.GetNeo() + + // Create agents group with OAuth guard + agents := group.Group("/agents") + agents.Use(oauth.Guard) + + // Agent CRUD - Standard REST endpoints + agents.GET("/", n.HandleAssistantList) // GET /agents - List agents + agents.POST("/", n.HandleAssistantSave) // POST /agents - Create/Update agent + agents.GET("/tags", n.HandleAssistantTags) // GET /agents/tags - Get all agent tags + agents.GET("/:id", n.HandleAssistantDetail) // GET /agents/:id - Get agent details + agents.DELETE("/:id", n.HandleAssistantDelete) // DELETE /agents/:id - Delete agent + + // Agent Actions + agents.POST("/:id/call", n.HandleAssistantCall) // POST /agents/:id/call - Execute agent API +} diff --git a/openapi/agent/agnet.go b/openapi/agent/agnet.go deleted file mode 100644 index 48831554..00000000 --- a/openapi/agent/agnet.go +++ /dev/null @@ -1 +0,0 @@ -package agent diff --git a/openapi/llm/llm.go b/openapi/llm/llm.go index 006c3ded..075c75a7 100644 --- a/openapi/llm/llm.go +++ b/openapi/llm/llm.go @@ -1 +1,95 @@ package llm + +import ( + "github.com/gin-gonic/gin" + "github.com/yaoapp/gou/connector" + oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/openapi/response" +) + +// Provider represents an LLM provider option +type Provider struct { + Label string `json:"label"` + Value string `json:"value"` + Type string `json:"type"` // "openai" + Builtin bool `json:"builtin"` // true for system built-in, false for user-defined +} + +// Attach attaches the LLM management handlers to the router with OAuth protection +func Attach(group *gin.RouterGroup, oauth oauthTypes.OAuth) { + + // Create providers group with OAuth guard + providers := group.Group("/providers") + providers.Use(oauth.Guard) + + // LLM Providers endpoints + providers.GET("/", listProviders) // GET /providers - List all LLM providers +} + +// listProviders lists all available LLM providers (built-in + user-defined) +func listProviders(c *gin.Context) { + allProviders := make([]Provider, 0) + + // Track which connectors we've already added (to avoid duplicates) + added := make(map[string]bool) + + // 1. Get system built-in OpenAI-compatible LLM connectors + for _, opt := range connector.AIConnectors { + connType := getConnectorType(opt.Value) + // Only include OpenAI-compatible LLM connectors + if connType == "openai" { + allProviders = append(allProviders, Provider{ + Label: opt.Label, + Value: opt.Value, + Type: connType, + Builtin: true, + }) + added[opt.Value] = true + } + } + + // 2. Get user-defined OpenAI-compatible LLM connectors from the global connector registry + // This includes all loaded connectors, both built-in and user-defined + // Only include OpenAI-compatible connectors (standard openai format) + for id, conn := range connector.Connectors { + // Skip if already added + if added[id] { + continue + } + + // Only include OpenAI-compatible LLM connectors + connType := getConnectorType(id) + if connType == "openai" { + meta := conn.GetMetaInfo() + label := meta.Label + if label == "" { + label = id + } + + allProviders = append(allProviders, Provider{ + Label: label, + Value: id, + Type: connType, + Builtin: meta.Builtin, + }) + added[id] = true + } + } + + response.RespondWithSuccess(c, response.StatusOK, allProviders) +} + +// getConnectorType retrieves the connector type by checking the global connector map +func getConnectorType(id string) string { + conn, ok := connector.Connectors[id] + if !ok { + return "unknown" + } + + // Only return openai type (OpenAI-compatible format) + if conn.Is(connector.OPENAI) { + return "openai" + } + + return "unknown" +} diff --git a/openapi/mcp/mcp.go b/openapi/mcp/mcp.go index 87468ebd..de2037c5 100644 --- a/openapi/mcp/mcp.go +++ b/openapi/mcp/mcp.go @@ -1 +1,68 @@ package mcp + +import ( + "github.com/gin-gonic/gin" + "github.com/yaoapp/gou/mcp" + oauthTypes "github.com/yaoapp/yao/openapi/oauth/types" + "github.com/yaoapp/yao/openapi/response" +) + +// Server represents an MCP server option (from user perspective) +type Server struct { + Label string `json:"label"` + Value string `json:"value"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Transport string `json:"transport,omitempty"` // "stdio", "sse", "http" + Builtin bool `json:"builtin"` // true for system built-in, false for user-defined +} + +// Attach attaches the MCP server management handlers to the router with OAuth protection +func Attach(group *gin.RouterGroup, oauth oauthTypes.OAuth) { + + // Create servers group with OAuth guard + servers := group.Group("/servers") + servers.Use(oauth.Guard) + + // MCP Servers endpoints + servers.GET("/", listServers) // GET /servers - List all MCP servers +} + +// listServers lists all available MCP servers (loaded clients from user perspective) +func listServers(c *gin.Context) { + allServers := make([]Server, 0) + + // Get all loaded MCP clients (they are servers from user perspective) + clientIDs := mcp.ListClients() + + for _, id := range clientIDs { + client, err := mcp.Select(id) + if err != nil { + continue + } + + // Get metadata + meta := client.GetMetaInfo() + + label := meta.Label + if label == "" { + label = id + } + + name := id + + // Get transport type (if available from DSL or client info) + transport := "" // Could extract from client implementation if needed + + allServers = append(allServers, Server{ + Label: label, + Value: id, + Name: name, + Description: meta.Description, + Transport: transport, + Builtin: meta.Builtin, + }) + } + + response.RespondWithSuccess(c, response.StatusOK, allServers) +} diff --git a/openapi/openapi.go b/openapi/openapi.go index 5e272909..20967f0a 100644 --- a/openapi/openapi.go +++ b/openapi/openapi.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/yaoapp/gou/application" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/openapi/agent" "github.com/yaoapp/yao/openapi/captcha" "github.com/yaoapp/yao/openapi/chat" "github.com/yaoapp/yao/openapi/dsl" @@ -13,6 +14,8 @@ import ( "github.com/yaoapp/yao/openapi/hello" "github.com/yaoapp/yao/openapi/job" "github.com/yaoapp/yao/openapi/kb" + "github.com/yaoapp/yao/openapi/llm" + "github.com/yaoapp/yao/openapi/mcp" "github.com/yaoapp/yao/openapi/messenger" "github.com/yaoapp/yao/openapi/oauth" "github.com/yaoapp/yao/openapi/oauth/acl" @@ -128,6 +131,15 @@ func (openapi *OpenAPI) Attach(router *gin.Engine) { // Messenger webhook handlers messenger.Attach(group.Group("/messenger"), openapi.OAuth) + // Agent handlers + agent.Attach(group.Group("/agent"), openapi.OAuth) + + // LLM Provider handlers + llm.Attach(group.Group("/llm"), openapi.OAuth) + + // MCP Server handlers + mcp.Attach(group.Group("/mcp"), openapi.OAuth) + // Custom handlers (Defined by developer) }