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
52 lines
1.9 KiB
Go
52 lines
1.9 KiB
Go
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))
|
|
}
|