yao/openapi/openapi.go
Max 83ebe49036 refactor: unify server lifecycle, migrate gRPC client, and clean up sandbox v2
Server lifecycle:
- Introduce service.Service to manage HTTP + gRPC startup/shutdown
- Fix gRPC mutex deadlock in StartServer when port is occupied
- Add GracefulStop with 5s timeout before forced Stop in grpc.go
- Pre-check HTTP and gRPC port availability in cmd/start.go
- Print gRPC server address in startup access-points block

gRPC client refactor:
- Move token manager and client from tai/grpc/ to grpc/client/
- Add backward-compatible aliases in tai/yao.go and tai/token.go
- Update cmd/run.go to import grpc/client directly (no tai dependency)

Sandbox v2 docker migration:
- Delete sandbox/v2/docker/ (moved to tai repo)
- Update sandbox/docker/build.sh hint to point to tai repo
- Clean up .gitignore entries for removed docker directory
- Temporarily disable SandboxV2Test and BenchmarkSandboxV2 in CI
  (docker images need rebuild after tai repo migration)

Tai integration:
- Add direct-mode registration API handlers in tai/api/
- Add heartbeat handler and token management wrappers
- Update tai/registry and tai/tunnel for latest protocol
- Replace yao-grpc references with tai call in docs

Made-with: Cursor
2026-03-07 17:19:19 +08:00

193 lines
5.4 KiB
Go

package openapi
import (
"path/filepath"
"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/app"
"github.com/yaoapp/yao/openapi/captcha"
"github.com/yaoapp/yao/openapi/chat"
"github.com/yaoapp/yao/openapi/dsl"
"github.com/yaoapp/yao/openapi/file"
"github.com/yaoapp/yao/openapi/hello"
openintegrations "github.com/yaoapp/yao/openapi/integrations"
"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"
"github.com/yaoapp/yao/openapi/oauth/types"
"github.com/yaoapp/yao/openapi/otp"
"github.com/yaoapp/yao/openapi/response"
"github.com/yaoapp/yao/openapi/sandbox"
"github.com/yaoapp/yao/openapi/team"
openapiTrace "github.com/yaoapp/yao/openapi/trace"
"github.com/yaoapp/yao/openapi/user"
taiapi "github.com/yaoapp/yao/tai/api"
taitunnel "github.com/yaoapp/yao/tai/tunnel"
)
// Server is the OpenAPI server
var Server *OpenAPI = nil
// OpenAPI is the OpenAPI server
type OpenAPI struct {
Config *Config // OpenAPI configuration
OAuth types.OAuth // OAuth service interface
}
// Load loads the OpenAPI server from the configuration
func Load(appConfig config.Config) (*OpenAPI, error) {
var configPath string = filepath.Join("openapi", "openapi.yao")
var configRaw, err = application.App.Read(configPath)
if err != nil {
return nil, err
}
// Parse the configuration
var config Config
err = application.Parse(configPath, configRaw, &config)
if err != nil {
return nil, err
}
// Convert the configuration to an OAuth configuration
oauthConfig, err := config.OAuthConfig(appConfig)
if err != nil {
return nil, err
}
// Create the OAuth service
oauthService, err := oauth.NewService(oauthConfig)
if err != nil {
return nil, err
}
// Set the secure cookie configuration for the response package
// This determines whether to use __Host- prefix and Secure flag for cookies
response.SetSecureCookieEnabled(oauthConfig.Security.SecureCookie)
// Load user configurations
err = user.Load(appConfig)
if err != nil {
return nil, err
}
// Load the ACL enforcer
_, err = acl.Load(&acl.Config{
Enabled: true,
PathPrefix: config.BaseURL,
Cache: oauthConfig.Cache,
Provider: oauthConfig.UserProvider,
})
if err != nil {
return nil, err
}
// Initialize OTP service (shares the OAuth store)
otp.NewService(oauthService.GetStore(), oauthService.GetKeyPrefix())
// Create the OpenAPI server
Server = &OpenAPI{Config: &config, OAuth: oauthService}
return Server, nil
}
// Attach attaches the OpenAPI server to the router
func (openapi *OpenAPI) Attach(router *gin.Engine) {
// Ignore if the OpenAPI server is not configured
if openapi.Config == nil {
return
}
// Basic Groups
baseURL := openapi.Config.BaseURL
group := router.Group(baseURL)
// Well-known handlers
openapi.attachWellKnown(router)
// Models ( LLM Agent )
group.GET("/models", openapi.OAuth.Guard, agent.GetModels)
// Get Model Details ( LLM Agent )
group.GET("/models/:model_name", openapi.OAuth.Guard, agent.GetModelDetails)
// OAuth handlers
openapi.attachOAuth(group)
// Hello World handlers
hello.Attach(group.Group("/helloworld"), openapi.OAuth)
// DSL handlers
dsl.Attach(group.Group("/dsl"), openapi.OAuth)
// File handlers
file.Attach(group.Group("/file"), openapi.OAuth)
// Knowledge Base handlers
kb.Attach(group.Group("/kb"), openapi.OAuth)
// Job Management handlers
job.Attach(group.Group("/job"), openapi.OAuth)
// Chat handlers
chat.Attach(group.Group("/chat"), openapi.OAuth)
// Captcha handlers
captcha.Attach(group.Group("/captcha"), openapi.OAuth)
// User handlers
user.Attach(group.Group("/user"), openapi.OAuth)
// Team handlers
team.Attach(group.Group("/team"), openapi.OAuth)
// Messenger webhook handlers
messenger.Attach(group.Group("/messenger"), openapi.OAuth)
// Integrations webhook handlers (public, no OAuth - external platforms push here)
openintegrations.Attach(group.Group("/integrations"))
// 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)
// Trace handlers
openapiTrace.Attach(group.Group("/trace"), openapi.OAuth)
// App handlers (menu, etc.)
app.Attach(group.Group("/app"), openapi.OAuth)
// OTP handlers (passwordless authentication)
otp.Attach(group.Group("/otp"), openapi.OAuth)
// Sandbox handlers (VNC proxy for visual browser automation)
sandbox.SetPathPrefix(baseURL)
sandbox.Attach(group.Group("/sandbox"), openapi.OAuth)
// Tai tunnel WebSocket and reverse proxy routes
group.GET("/ws/tai", taitunnel.HandleControl)
group.GET("/ws/tai/data/:channel_id", taitunnel.HandleData)
group.Any("/tai/:taiID/proxy/*path", taitunnel.HandleProxy)
group.GET("/tai/:taiID/vnc/*path", taitunnel.HandleVNC)
// Tai direct registration API (uses /tai-nodes/ prefix to avoid routing conflict with /tai/:taiID/)
group.POST("/tai-nodes/register", taiapi.HandleRegister)
group.POST("/tai-nodes/heartbeat", taiapi.HandleHeartbeat)
group.DELETE("/tai-nodes/register/:tai_id", taiapi.HandleUnregister)
// Custom handlers (Defined by developer)
}