Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// geminiAIStudioActions 是允许出现在上游 URL 里的 action 集合,与 ForwardNative
|
||||
// 的入站校验保持一致,同时避免 compat 侧把可变字符串直接拼进 path。
|
||||
var geminiAIStudioActions = map[string]struct{}{
|
||||
"generateContent": {},
|
||||
"streamGenerateContent": {},
|
||||
"countTokens": {},
|
||||
}
|
||||
|
||||
// buildGeminiAIStudioModelActionURL 组装 AI Studio 的
|
||||
// /v1beta/models/{model}:{action} 上游 URL。
|
||||
//
|
||||
// model 是客户端可控的(native 路由取自 URL 片段,compat 路由取自请求体的 model
|
||||
// 字段,之后可能再经渠道映射),因此必须先过路径片段护栏才能拼进 path,
|
||||
// 见 upstream_path_guard.go。新增 AI Studio 端点请一律走本函数。
|
||||
func buildGeminiAIStudioModelActionURL(baseURL, model, action string, stream bool) (string, error) {
|
||||
trimmedBase := strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
if trimmedBase == "" {
|
||||
return "", errors.New("gemini base url is required")
|
||||
}
|
||||
trimmedModel := strings.TrimSpace(model)
|
||||
if trimmedModel == "" {
|
||||
return "", errors.New("gemini model is required")
|
||||
}
|
||||
if err := validateUpstreamPathSegment("gemini model", trimmedModel); err != nil {
|
||||
return "", err
|
||||
}
|
||||
trimmedAction := strings.TrimSpace(action)
|
||||
if _, ok := geminiAIStudioActions[trimmedAction]; !ok {
|
||||
return "", fmt.Errorf("unsupported gemini action: %s", trimmedAction)
|
||||
}
|
||||
|
||||
fullURL := fmt.Sprintf("%s/v1beta/models/%s:%s", trimmedBase, trimmedModel, trimmedAction)
|
||||
if stream {
|
||||
fullURL += "?alt=sse"
|
||||
}
|
||||
return fullURL, nil
|
||||
}
|
||||
|
||||
// IsSafeGeminiModelPathSegment 供 handler 层在解析出 URL 里的模型名后立刻校验,
|
||||
// 让客户端拿到明确的 400,而不是等到构造上游请求时才报错。
|
||||
func IsSafeGeminiModelPathSegment(model string) bool {
|
||||
return isSafeUpstreamPathSegment(strings.TrimSpace(model))
|
||||
}
|
||||
Reference in New Issue
Block a user