Some checks failed
CI / test (push) Failing after 3s
CI / frontend (push) Failing after 3s
CI / golangci-lint (push) Failing after 3s
Security Scan / backend-security (push) Failing after 3s
Security Scan / frontend-security (push) Failing after 5s
CI / windsurf-platform (macos-latest) (push) Has been cancelled
CI / windsurf-platform (windows-latest) (push) Has been cancelled
268 lines
7.1 KiB
Go
268 lines
7.1 KiB
Go
package handler
|
||
|
||
import (
|
||
"encoding/json"
|
||
"log/slog"
|
||
"net/http"
|
||
|
||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// AntigravityHTTPHandler 处理下游客户端的 HTTP 请求
|
||
// 内部调用 LanguageServerService,再转发到上游 API
|
||
type AntigravityHTTPHandler struct {
|
||
langServerService *service.LanguageServerService
|
||
logger *slog.Logger
|
||
}
|
||
|
||
func NewAntigravityHTTPHandler(
|
||
langServerService *service.LanguageServerService,
|
||
logger *slog.Logger,
|
||
) *AntigravityHTTPHandler {
|
||
return &AntigravityHTTPHandler{
|
||
langServerService: langServerService,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// Cascade 流程 API
|
||
// ============================================================================
|
||
|
||
// StartCascadeRequest HTTP 请求格式
|
||
type StartCascadeRequest struct {
|
||
Model string `json:"model"` // 模型名称
|
||
SystemPrompt string `json:"system_prompt"` // 系统提示
|
||
Metadata map[string]string `json:"metadata"` // 设备指纹等伪装信息
|
||
}
|
||
|
||
// StartCascadeResponse HTTP 响应格式
|
||
type StartCascadeResponse struct {
|
||
CascadeID string `json:"cascade_id"`
|
||
}
|
||
|
||
// POST /api/v1/cascade/start
|
||
// 启动新的 Cascade Agent 会话
|
||
func (h *AntigravityHTTPHandler) StartCascade(c *gin.Context) {
|
||
var req StartCascadeRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
h.logger.Error("invalid request", "error", err)
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"error": "invalid request: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 提取 OAuth token
|
||
token := c.GetHeader("Authorization")
|
||
if token == "" {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"error": "missing authorization header",
|
||
})
|
||
return
|
||
}
|
||
|
||
// 调用内部 LanguageServerService
|
||
cascadeID, err := h.langServerService.StartCascade(
|
||
c.Request.Context(),
|
||
req.Model,
|
||
req.SystemPrompt,
|
||
req.Metadata,
|
||
token,
|
||
)
|
||
if err != nil {
|
||
h.logger.Error("start cascade failed", "error", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
h.logger.Info("cascade started", "cascade_id", cascadeID, "model", req.Model)
|
||
|
||
c.JSON(http.StatusOK, StartCascadeResponse{
|
||
CascadeID: cascadeID,
|
||
})
|
||
}
|
||
|
||
// ============================================================================
|
||
|
||
// SendUserMessageRequest HTTP 请求格式
|
||
type SendUserMessageRequest struct {
|
||
CascadeID string `json:"cascade_id"` // 会话 ID
|
||
Message string `json:"message"` // 用户消息
|
||
Context map[string]string `json:"context"` // 上下文(可选)
|
||
}
|
||
|
||
// CascadeUpdate 流式响应格式(Server-Sent Events)
|
||
type CascadeUpdate struct {
|
||
Type string `json:"type"` // "message_delta", "tool_call", etc.
|
||
Payload string `json:"payload"` // JSON 格式的负载
|
||
}
|
||
|
||
// POST /api/v1/cascade/message (流式)
|
||
// 发送用户消息,接收流式更新
|
||
func (h *AntigravityHTTPHandler) SendUserMessage(c *gin.Context) {
|
||
var req SendUserMessageRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
h.logger.Error("invalid request", "error", err)
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"error": "invalid request: " + err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 提取 OAuth token
|
||
token := c.GetHeader("Authorization")
|
||
if token == "" {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"error": "missing authorization header",
|
||
})
|
||
return
|
||
}
|
||
|
||
// 设置 Server-Sent Events 响应头
|
||
c.Header("Content-Type", "text/event-stream")
|
||
c.Header("Cache-Control", "no-cache")
|
||
c.Header("Connection", "keep-alive")
|
||
|
||
// 调用内部 LanguageServerService,获取流式响应
|
||
updateChan, err := h.langServerService.SendUserMessage(
|
||
c.Request.Context(),
|
||
req.CascadeID,
|
||
req.Message,
|
||
token,
|
||
)
|
||
if err != nil {
|
||
h.logger.Error("send user message failed", "error", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 逐个推送更新到客户端(SSE)
|
||
for update := range updateChan {
|
||
data, _ := json.Marshal(update)
|
||
c.SSEvent("update", string(data))
|
||
c.Writer.Flush()
|
||
}
|
||
|
||
h.logger.Info("cascade message processed", "cascade_id", req.CascadeID)
|
||
}
|
||
|
||
// ============================================================================
|
||
|
||
// POST /api/v1/cascade/cancel
|
||
// 取消 Cascade 调用
|
||
func (h *AntigravityHTTPHandler) CancelCascade(c *gin.Context) {
|
||
var req struct {
|
||
CascadeID string `json:"cascade_id"`
|
||
}
|
||
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"error": "invalid request",
|
||
})
|
||
return
|
||
}
|
||
|
||
if err := h.langServerService.CancelCascade(
|
||
c.Request.Context(),
|
||
req.CascadeID,
|
||
); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
h.logger.Info("cascade cancelled", "cascade_id", req.CascadeID)
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
})
|
||
}
|
||
|
||
// ============================================================================
|
||
// 模型配置 API
|
||
// ============================================================================
|
||
|
||
// ModelConfig 模型配置
|
||
type ModelConfig struct {
|
||
Name string `json:"name"`
|
||
DisplayName string `json:"display_name"`
|
||
MaxTokens int `json:"max_tokens"`
|
||
SupportsThinking bool `json:"supports_thinking"`
|
||
ThinkingBudget int `json:"thinking_budget,omitempty"`
|
||
SupportsImages bool `json:"supports_images"`
|
||
Provider string `json:"provider"` // anthropic, google, openai
|
||
}
|
||
|
||
// GET /api/v1/models
|
||
// 获取可用模型列表
|
||
func (h *AntigravityHTTPHandler) GetModels(c *gin.Context) {
|
||
models, err := h.langServerService.GetAvailableModels(c.Request.Context())
|
||
if err != nil {
|
||
h.logger.Error("get models failed", "error", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"models": models,
|
||
"default_model": "claude-opus-4-7",
|
||
})
|
||
}
|
||
|
||
// ============================================================================
|
||
// 健康检查 API
|
||
// ============================================================================
|
||
|
||
// GET /api/v1/health
|
||
// 健康检查
|
||
func (h *AntigravityHTTPHandler) Health(c *gin.Context) {
|
||
status, err := h.langServerService.GetStatus(c.Request.Context())
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"status": "unhealthy",
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"status": status,
|
||
"version": "1.0.0",
|
||
})
|
||
}
|
||
|
||
// ============================================================================
|
||
|
||
// RegisterRoutes 注册所有 HTTP 路由
|
||
func (h *AntigravityHTTPHandler) RegisterRoutes(router *gin.Engine) {
|
||
api := router.Group("/api/v1")
|
||
|
||
// Cascade 流程
|
||
api.POST("/cascade/start", h.StartCascade)
|
||
api.POST("/cascade/message", h.SendUserMessage)
|
||
api.POST("/cascade/cancel", h.CancelCascade)
|
||
|
||
// 模型列表
|
||
api.GET("/models", h.GetModels)
|
||
|
||
// 健康检查
|
||
api.GET("/health", h.Health)
|
||
|
||
h.logger.Info("antigravity http handler registered",
|
||
"routes", []string{
|
||
"/api/v1/cascade/start",
|
||
"/api/v1/cascade/message",
|
||
"/api/v1/cascade/cancel",
|
||
"/api/v1/models",
|
||
"/api/v1/health",
|
||
})
|
||
}
|