relay_gemini_file.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package controller
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strings"
  6. "github.com/QuantumNous/new-api/common"
  7. "github.com/QuantumNous/new-api/constant"
  8. "github.com/QuantumNous/new-api/logger"
  9. "github.com/QuantumNous/new-api/relay/channel/gemini"
  10. "github.com/gin-gonic/gin"
  11. )
  12. // RelayGeminiFileUpload handles file upload to Gemini File API
  13. // Supports both simple multipart upload and resumable upload protocol
  14. func RelayGeminiFileUpload(c *gin.Context) {
  15. // Get API key from channel context
  16. apiKey := common.GetContextKeyString(c, constant.ContextKeyChannelKey)
  17. if apiKey == "" {
  18. logger.LogError(c, "Failed to get Gemini channel API key")
  19. c.JSON(http.StatusServiceUnavailable, gin.H{
  20. "error": gin.H{
  21. "message": "No available Gemini channel found",
  22. "type": "service_unavailable_error",
  23. "code": "no_available_channel",
  24. },
  25. })
  26. return
  27. }
  28. // Check if this is a resumable upload request
  29. uploadProtocol := c.GetHeader("X-Goog-Upload-Protocol")
  30. if uploadProtocol == "resumable" {
  31. // Handle resumable upload
  32. handleResumableUpload(c, apiKey)
  33. return
  34. }
  35. // Handle standard multipart upload
  36. handleMultipartUpload(c, apiKey)
  37. }
  38. // handleResumableUpload handles Gemini's resumable upload protocol
  39. func handleResumableUpload(c *gin.Context, apiKey string) {
  40. // Build upstream URL
  41. url := gemini.BuildGeminiFileURL("/upload/v1beta/files")
  42. // Prepare headers - forward all X-Goog-Upload-* headers
  43. headers := map[string]string{
  44. "x-goog-api-key": apiKey,
  45. }
  46. // Forward all resumable upload headers
  47. resumableHeaders := []string{
  48. "X-Goog-Upload-Protocol",
  49. "X-Goog-Upload-Command",
  50. "X-Goog-Upload-Header-Content-Length",
  51. "X-Goog-Upload-Header-Content-Type",
  52. "X-Goog-Upload-Offset",
  53. "Content-Type",
  54. "Content-Length",
  55. }
  56. for _, header := range resumableHeaders {
  57. if value := c.GetHeader(header); value != "" {
  58. headers[header] = value
  59. }
  60. }
  61. // Get request body (for start command, it's JSON metadata)
  62. body := c.Request.Body
  63. // Forward request to Gemini
  64. err := gemini.ForwardGeminiFileRequest(c, http.MethodPost, url, body, headers)
  65. if err != nil {
  66. logger.LogError(c, fmt.Sprintf("failed to forward resumable upload request: %s", err.Error()))
  67. return
  68. }
  69. }
  70. // handleMultipartUpload handles standard multipart/form-data upload
  71. func handleMultipartUpload(c *gin.Context, apiKey string) {
  72. // Parse multipart form
  73. form, err := common.ParseMultipartFormReusable(c)
  74. if err != nil {
  75. logger.LogError(c, fmt.Sprintf("failed to parse multipart form: %s", err.Error()))
  76. c.JSON(http.StatusBadRequest, gin.H{
  77. "error": gin.H{
  78. "message": fmt.Sprintf("failed to parse multipart form: %s", err.Error()),
  79. "type": "invalid_request_error",
  80. "code": "invalid_multipart_form",
  81. },
  82. })
  83. return
  84. }
  85. defer form.RemoveAll()
  86. // Rebuild multipart form for upstream request
  87. body, contentType, err := gemini.RebuildMultipartForm(form)
  88. if err != nil {
  89. logger.LogError(c, fmt.Sprintf("failed to rebuild multipart form: %s", err.Error()))
  90. c.JSON(http.StatusInternalServerError, gin.H{
  91. "error": gin.H{
  92. "message": fmt.Sprintf("failed to rebuild multipart form: %s", err.Error()),
  93. "type": "internal_error",
  94. "code": "form_rebuild_error",
  95. },
  96. })
  97. return
  98. }
  99. // Build upstream URL
  100. url := gemini.BuildGeminiFileURL("/upload/v1beta/files")
  101. // Prepare headers
  102. headers := map[string]string{
  103. "Content-Type": contentType,
  104. "x-goog-api-key": apiKey,
  105. }
  106. // Forward request to Gemini
  107. err = gemini.ForwardGeminiFileRequest(c, http.MethodPost, url, body, headers)
  108. if err != nil {
  109. logger.LogError(c, fmt.Sprintf("failed to forward file upload request: %s", err.Error()))
  110. return
  111. }
  112. }
  113. // RelayGeminiFileList lists files from Gemini File API
  114. func RelayGeminiFileList(c *gin.Context) {
  115. // Get API key from channel context
  116. apiKey := common.GetContextKeyString(c, constant.ContextKeyChannelKey)
  117. if apiKey == "" {
  118. logger.LogError(c, "API key not found in context")
  119. c.JSON(http.StatusUnauthorized, gin.H{
  120. "error": gin.H{
  121. "message": "API key not found",
  122. "type": "authentication_error",
  123. "code": "invalid_api_key",
  124. },
  125. })
  126. return
  127. }
  128. // Build upstream URL with query parameters
  129. url := gemini.BuildGeminiFileURL("/v1beta/files")
  130. // Add query parameters if present
  131. queryParams := []string{}
  132. if pageSize := c.Query("pageSize"); pageSize != "" {
  133. queryParams = append(queryParams, fmt.Sprintf("pageSize=%s", pageSize))
  134. }
  135. if pageToken := c.Query("pageToken"); pageToken != "" {
  136. queryParams = append(queryParams, fmt.Sprintf("pageToken=%s", pageToken))
  137. }
  138. if len(queryParams) > 0 {
  139. url = fmt.Sprintf("%s?%s", url, strings.Join(queryParams, "&"))
  140. }
  141. // Prepare headers
  142. headers := map[string]string{
  143. "x-goog-api-key": apiKey,
  144. }
  145. // Forward request to Gemini
  146. err := gemini.ForwardGeminiFileRequest(c, http.MethodGet, url, nil, headers)
  147. if err != nil {
  148. logger.LogError(c, fmt.Sprintf("failed to forward file list request: %s", err.Error()))
  149. return
  150. }
  151. }