feat(*): 插件中心与模型协议 - 完善声明式协议运行时并增加功能开放控制

This commit is contained in:
ddCat
2026-08-23 23:19:27 +08:00
parent 7cc6021f6e
commit 919037e5d9
106 changed files with 8817 additions and 500 deletions

View File

@@ -736,13 +736,71 @@ func RegisterSystemProxyRoutes(r *gin.RouterGroup, svc *service.Service) {
})
}
// SystemProxyNoRouteHandler handles the short public proxy form
// /api/{channelId}/{providerPath}. It deliberately runs from NoRoute so it
// cannot shadow existing business routes such as /api/tasks or /api/plugins.
func SystemProxyNoRouteHandler(svc *service.Service) gin.HandlerFunc {
return func(c *gin.Context) {
channelID, providerPath, ok := shortSystemProxyPath(c.Request.URL.Path)
if !ok {
fail(c, http.StatusNotFound, errors.New("请求不存在"))
return
}
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
channel, err := svc.SystemChannel(channelID)
if err != nil {
fail(c, http.StatusNotFound, errors.New("系统渠道不存在或已停用"))
return
}
proxySystemRequestPath(c, svc, user, channel, providerPath)
}
}
func shortSystemProxyPath(rawPath string) (string, string, bool) {
const prefix = "/api/"
if !strings.HasPrefix(rawPath, prefix) {
return "", "", false
}
remainder := strings.TrimPrefix(rawPath, prefix)
separator := strings.IndexByte(remainder, '/')
if separator <= 0 || separator == len(remainder)-1 {
return "", "", false
}
channelID := remainder[:separator]
providerPath := remainder[separator:]
if strings.ContainsAny(channelID, "?#\\") || strings.Contains(providerPath, "\\") || isReservedAPIPathPrefix(channelID) {
return "", "", false
}
return channelID, providerPath, true
}
// NoRoute also sees unmatched descendants of registered API routes. Keep the
// short proxy from interpreting /api/tasks/unknown (or /api/plugins/unknown)
// as a channel request when a business route returns 404.
func isReservedAPIPathPrefix(value string) bool {
switch strings.ToLower(strings.TrimSpace(value)) {
case "admin", "ai", "announcements", "assets", "auth", "canvas-projects", "channels", "features", "files", "model-catalog", "models", "oauth", "plugins", "projects", "protocols", "public", "resources", "sessions", "settings", "skills", "style-profiles", "tasks", "user-data", "voice-profiles", "wallet":
return true
default:
return false
}
}
func proxySystemRequest(c *gin.Context, svc *service.Service, user *model.User, channel *model.ModelChannel) {
proxySystemRequestPath(c, svc, user, channel, c.Param("path"))
}
func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.User, channel *model.ModelChannel, providerPath string) {
startedAt := time.Now()
policy, available := loadRuntimePolicy(c, svc)
if !available || !enforceRateLimit(c, "system-proxy:"+user.ID, policy.Request.SystemRelayPerMinute, time.Minute) {
return
}
path := c.Param("path")
path := providerPath
if path == "" {
path = "/"
}
@@ -796,7 +854,7 @@ func proxySystemRequest(c *gin.Context, svc *service.Service, user *model.User,
for _, key := range []string{"key", "api_key", "access_token", "token"} {
query.Del(key)
}
target := strings.TrimRight(channel.BaseURL, "/") + path
target := service.ChannelAPIURLForProtocol(channel.BaseURL, path, protocol)
if encodedQuery := query.Encode(); encodedQuery != "" {
target += "?" + encodedQuery
}
@@ -852,6 +910,9 @@ func proxySystemRequest(c *gin.Context, svc *service.Service, user *model.User,
service.ApplyDefaultOutboundHeaders(upstreamReq)
if protocol == model.ChannelInterfaceGeminiVeo || protocol == model.ChannelInterfaceGeminiImage {
upstreamReq.Header.Set("x-goog-api-key", channel.APIKey)
} else if protocol == model.ChannelInterfaceClaudeAPI {
upstreamReq.Header.Set("x-api-key", channel.APIKey)
upstreamReq.Header.Set("anthropic-version", "2023-06-01")
} else {
upstreamReq.Header.Set("Authorization", "Bearer "+channel.APIKey)
}