gemini_upstream_url.go 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. package service
  2. import (
  3. "errors"
  4. "fmt"
  5. "strings"
  6. )
  7. // geminiAIStudioActions 是允许出现在上游 URL 里的 action 集合,与 ForwardNative
  8. // 的入站校验保持一致,同时避免 compat 侧把可变字符串直接拼进 path。
  9. var geminiAIStudioActions = map[string]struct{}{
  10. "generateContent": {},
  11. "streamGenerateContent": {},
  12. "countTokens": {},
  13. }
  14. // buildGeminiAIStudioModelActionURL 组装 AI Studio 的
  15. // /v1beta/models/{model}:{action} 上游 URL。
  16. //
  17. // model 是客户端可控的(native 路由取自 URL 片段,compat 路由取自请求体的 model
  18. // 字段,之后可能再经渠道映射),因此必须先过路径片段护栏才能拼进 path,
  19. // 见 upstream_path_guard.go。新增 AI Studio 端点请一律走本函数。
  20. func buildGeminiAIStudioModelActionURL(baseURL, model, action string, stream bool) (string, error) {
  21. trimmedBase := strings.TrimRight(strings.TrimSpace(baseURL), "/")
  22. if trimmedBase == "" {
  23. return "", errors.New("gemini base url is required")
  24. }
  25. trimmedModel := strings.TrimSpace(model)
  26. if trimmedModel == "" {
  27. return "", errors.New("gemini model is required")
  28. }
  29. if err := validateUpstreamPathSegment("gemini model", trimmedModel); err != nil {
  30. return "", err
  31. }
  32. trimmedAction := strings.TrimSpace(action)
  33. if _, ok := geminiAIStudioActions[trimmedAction]; !ok {
  34. return "", fmt.Errorf("unsupported gemini action: %s", trimmedAction)
  35. }
  36. fullURL := fmt.Sprintf("%s/v1beta/models/%s:%s", trimmedBase, trimmedModel, trimmedAction)
  37. if stream {
  38. fullURL += "?alt=sse"
  39. }
  40. return fullURL, nil
  41. }
  42. // IsSafeGeminiModelPathSegment 供 handler 层在解析出 URL 里的模型名后立刻校验,
  43. // 让客户端拿到明确的 400,而不是等到构造上游请求时才报错。
  44. func IsSafeGeminiModelPathSegment(model string) bool {
  45. return isSafeUpstreamPathSegment(strings.TrimSpace(model))
  46. }