yao/agent/context/openapi.go
Max 8dca4719c0 Remove deprecated agent API files and refactor agent loading logic
- Deleted obsolete agent API files (agent.go, api.go, api_test.go, types.go) to streamline the codebase.
- Refactored the agent loading logic to initialize the API instance correctly, ensuring proper integration with the new structure.
- Updated context handling to improve clarity and maintainability across the agent's functionality.
- Enhanced error handling and cache management in the agent's initialization process.
2025-11-11 11:23:20 +08:00

313 lines
8.1 KiB
Go

package context
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/gin-gonic/gin"
"github.com/yaoapp/gou/plan"
"github.com/yaoapp/gou/store"
"github.com/yaoapp/yao/openapi/oauth/authorized"
)
// NewOpenAPI create a new context from openapi context
func NewOpenAPI(c *gin.Context, cache store.Store) Context {
// Get authorized information
authInfo := authorized.GetInfo(c)
// Extract assistant ID (route parameter takes priority, handled in GetAssistantID)
assistantID, _ := GetAssistantID(c)
// Extract chat ID (may generate from messages if not provided)
// GetChatID internally calls GetChatIDByMessages which auto-caches
chatID, _ := GetChatID(c, cache)
// Parse client information from User-Agent header
userAgent := c.GetHeader("User-Agent")
clientType := getClientType(userAgent)
clientIP := c.ClientIP()
// Create context with extracted parameters
ctx := Context{
Context: c.Request.Context(),
Space: plan.NewMemorySharedSpace(),
Authorized: authInfo,
ChatID: chatID,
AssistantID: assistantID,
Locale: GetLocale(c),
Theme: GetTheme(c),
Referer: GetReferer(c),
Accept: GetAccept(c),
Client: Client{
Type: clientType,
UserAgent: userAgent,
IP: clientIP,
},
}
return ctx
}
// getClientType parses the client type from User-Agent header
func getClientType(userAgent string) string {
if userAgent == "" {
return "web" // Default to web
}
ua := strings.ToLower(userAgent)
// Check for specific client types
switch {
case strings.Contains(ua, "yao-agent") || strings.Contains(ua, "agent"):
return "agent"
case strings.Contains(ua, "yao-jssdk") || strings.Contains(ua, "jssdk"):
return "jssdk"
case strings.Contains(ua, "android"):
return "android"
case strings.Contains(ua, "iphone") || strings.Contains(ua, "ipad") || strings.Contains(ua, "ipod"):
return "ios"
case strings.Contains(ua, "windows"):
return "windows"
case strings.Contains(ua, "mac os x") || strings.Contains(ua, "macintosh"):
return "macos"
case strings.Contains(ua, "linux"):
return "linux"
default:
return "web"
}
}
// getPayloadField reads a string field from request body
func getPayloadField(c *gin.Context, fieldName string) string {
if c.Request.Body == nil {
return ""
}
body, err := io.ReadAll(c.Request.Body)
if err != nil {
return ""
}
// Restore body for further use
c.Request.Body = io.NopCloser(bytes.NewReader(body))
if len(body) == 0 {
return ""
}
// Parse JSON to extract field
var payload map[string]interface{}
if err := json.Unmarshal(body, &payload); err != nil {
return ""
}
if value, ok := payload[fieldName]; ok {
if strValue, ok := value.(string); ok {
return strValue
}
}
return ""
}
// GetAssistantID extracts assistant ID from request with priority:
// 1. Query parameter "assistant_id"
// 2. Header "X-Yao-Assistant"
// 3. Query parameter "model" - splits by "-" takes last field, extracts ID from "yao_xxx" prefix
// 4. Payload "model" field - same parsing as query parameter
func GetAssistantID(c *gin.Context) (string, error) {
// Priority 1: Query parameter assistant_id
if assistantID := c.Query("assistant_id"); assistantID != "" {
return assistantID, nil
}
// Priority 2: Header X-Yao-Assistant
if assistantID := c.GetHeader("X-Yao-Assistant"); assistantID != "" {
return assistantID, nil
}
// Priority 3 & 4: Extract from model parameter (Query or Payload)
model := c.Query("model")
if model == "" {
model = getPayloadField(c, "model")
}
if model != "" {
// Split by "-" and get the last field
parts := strings.Split(model, "-")
lastField := strings.TrimSpace(parts[len(parts)-1])
// Check if it has yao_ prefix
if strings.HasPrefix(lastField, "yao_") {
assistantID := strings.TrimPrefix(lastField, "yao_")
if assistantID != "" {
return assistantID, nil
}
}
}
// If no assistant ID found, return error
return "", fmt.Errorf("assistant_id is required")
}
// GetMessages extracts messages from the request
// Priority:
// 1. Query parameter "messages" (JSON string)
// 2. Request body "messages" field
func GetMessages(c *gin.Context) ([]Message, error) {
// Try query parameter first
if messagesJSON := c.Query("messages"); messagesJSON != "" {
var messages []Message
if err := json.Unmarshal([]byte(messagesJSON), &messages); err == nil && len(messages) > 0 {
return messages, nil
}
}
// Check if request body exists
if c.Request.Body == nil {
return nil, fmt.Errorf("messages field is required")
}
// Try request body
// Read body carefully to allow reuse
body, err := io.ReadAll(c.Request.Body)
if err != nil {
return nil, fmt.Errorf("failed to read request body: %w", err)
}
// Restore body for further processing
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
// If body is empty, return error
if len(body) == 0 {
return nil, fmt.Errorf("messages field is required")
}
var requestBody struct {
Messages []Message `json:"messages"`
}
if err := json.Unmarshal(body, &requestBody); err != nil {
return nil, fmt.Errorf("failed to parse messages from request body: %w", err)
}
if len(requestBody.Messages) == 0 {
return nil, fmt.Errorf("messages field is required and must not be empty")
}
return requestBody.Messages, nil
}
// GetLocale extracts locale from request with priority:
// 1. Query parameter "locale"
// 2. Header "Accept-Language"
func GetLocale(c *gin.Context) string {
// Priority 1: Query parameter
if locale := c.Query("locale"); locale != "" {
return strings.ToLower(locale)
}
// Priority 2: Header Accept-Language
if acceptLang := c.GetHeader("Accept-Language"); acceptLang != "" {
// Parse Accept-Language header (e.g., "en-US,en;q=0.9,zh;q=0.8")
// Take the first language
parts := strings.Split(acceptLang, ",")
if len(parts) > 0 {
// Remove quality value if present
lang := strings.Split(parts[0], ";")[0]
return strings.ToLower(strings.TrimSpace(lang))
}
}
return ""
}
// GetTheme extracts theme from request with priority:
// 1. Query parameter "theme"
// 2. Header "X-Yao-Theme"
func GetTheme(c *gin.Context) string {
// Priority 1: Query parameter
if theme := c.Query("theme"); theme != "" {
return strings.ToLower(theme)
}
// Priority 2: Header
if theme := c.GetHeader("X-Yao-Theme"); theme != "" {
return strings.ToLower(theme)
}
return ""
}
// GetReferer extracts referer from request with priority:
// 1. Query parameter "referer"
// 2. Header "X-Yao-Referer"
// 3. Default to "api"
func GetReferer(c *gin.Context) string {
// Priority 1: Query parameter
if referer := c.Query("referer"); referer != "" {
return validateReferer(referer)
}
// Priority 2: Header
if referer := c.GetHeader("X-Yao-Referer"); referer != "" {
return validateReferer(referer)
}
// Priority 3: Default
return RefererAPI
}
// GetAccept extracts accept type from request with priority:
// 1. Query parameter "accept"
// 2. Header "X-Yao-Accept"
// 3. Parse from client type (User-Agent)
func GetAccept(c *gin.Context) Accept {
// Priority 1: Query parameter
if accept := c.Query("accept"); accept != "" {
return validateAccept(accept)
}
// Priority 2: Header
if accept := c.GetHeader("X-Yao-Accept"); accept != "" {
return validateAccept(accept)
}
// Priority 3: Parse from User-Agent
userAgent := c.GetHeader("User-Agent")
clientType := getClientType(userAgent)
return parseAccept(clientType)
}
// GetChatID get the chat ID from the request
// Priority:
// 1. Query parameter "chat_id"
// 2. Header "X-Yao-Chat"
// 3. Generate from messages using GetChatIDByMessages
func GetChatID(c *gin.Context, cache store.Store) (string, error) {
// Priority 1: Query parameter chat_id
if chatID := c.Query("chat_id"); chatID != "" {
return chatID, nil
}
// Priority 2: Header X-Yao-Chat
if chatID := c.GetHeader("X-Yao-Chat"); chatID != "" {
return chatID, nil
}
// Priority 3: Generate from messages
messages, err := GetMessages(c)
if err != nil {
return "", fmt.Errorf("failed to get messages for chat ID generation: %w", err)
}
chatID, err := GetChatIDByMessages(cache, messages)
if err != nil {
return "", fmt.Errorf("failed to generate chat ID from messages: %w", err)
}
return chatID, nil
}