win 435ae221bc
Some checks failed
CI / test (push) Failing after 1m32s
CI / golangci-lint (push) Failing after 31s
Security Scan / backend-security (push) Failing after 1m32s
Security Scan / frontend-security (push) Failing after 9s
x
2026-04-16 19:11:47 +08:00

125 lines
3.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package routes
import (
"bytes"
"context"
"io"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
const (
anthropicEventLoggingURL = "https://api.anthropic.com/api/event_logging/batch"
eventLoggingForwardTimeout = 8 * time.Second
claudeCodeGrowthBookDateUpdated = "1970-01-01T00:00:00Z"
)
// RegisterCommonRoutes 注册通用路由(健康检查、状态等)
func RegisterCommonRoutes(r *gin.Engine) {
// 健康检查
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
// Claude Code 遥测日志:清理敏感字段后转发给 Anthropic。
// 删除 baseUrl/gateway 字段防止网关地址暴露(见 FINGERPRINT_SECURITY_REPORT.md §GAP-1/2
// 转发而非丢弃,避免"高流量零遥测"异常被检测。
r.POST("/api/event_logging/batch", func(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil || len(body) == 0 {
c.Status(http.StatusOK)
return
}
sanitized := sanitizeEventBatch(body)
ctx, cancel := context.WithTimeout(c.Request.Context(), eventLoggingForwardTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, anthropicEventLoggingURL, bytes.NewReader(sanitized))
if err != nil {
c.Status(http.StatusOK)
return
}
req.Header.Set("Content-Type", "application/json")
// 透传客户端的 Authorization headerOAuth Bearer token
if auth := c.GetHeader("Authorization"); auth != "" {
req.Header.Set("Authorization", auth)
}
resp, err := http.DefaultClient.Do(req)
if err == nil {
resp.Body.Close()
}
c.Status(http.StatusOK)
})
// Claude Code 启动预检:本地 CLI 会在启动早期请求该端点。
r.GET("/api/claude_cli/bootstrap", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"client_data": nil,
"additional_model_options": []any{},
"additional_model_costs": gin.H{},
})
})
// Claude Code 组织级策略限制:源码 schema 为 { restrictions: { key: { allowed: boolean } } }。
// 空对象表示当前没有下发任何限制。
r.GET("/api/claude_code/policy_limits", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"restrictions": gin.H{},
})
})
// GrowthBook 特性拉取:真实 Claude Code 远端评估会命中 /api/eval/:clientKey
// SDK 也支持 /api/features/:clientKey并通过 x-sse-support 探测是否可订阅 SSE。
r.GET("/api/features/:clientKey", func(c *gin.Context) {
c.Header("x-sse-support", "enabled")
c.JSON(http.StatusOK, gin.H{
"features": gin.H{},
"dateUpdated": claudeCodeGrowthBookDateUpdated,
})
})
r.POST("/api/eval/:clientKey", func(c *gin.Context) {
c.Header("x-sse-support", "enabled")
c.JSON(http.StatusOK, gin.H{
"features": gin.H{},
"dateUpdated": claudeCodeGrowthBookDateUpdated,
})
})
writeGrowthBookSSE := func(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("X-Accel-Buffering", "no")
c.Status(http.StatusOK)
c.SSEvent("features", gin.H{
"features": gin.H{},
"dateUpdated": claudeCodeGrowthBookDateUpdated,
})
if flusher, ok := c.Writer.(http.Flusher); ok {
flusher.Flush()
}
}
// 真实 Claude Code SDK 使用 /sub/:clientKey 订阅特性更新。
r.GET("/sub/:clientKey", writeGrowthBookSSE)
// 兼容当前内部 bootstrap 预热器仍在使用的旧路径,避免本地联调时 404。
r.GET("/sub/features/:clientKey", writeGrowthBookSSE)
// Setup status endpoint (always returns needs_setup: false in normal mode)
// This is used by the frontend to detect when the service has restarted after setup
r.GET("/setup/status", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"needs_setup": false,
"step": "completed",
},
})
})
}