|
@@ -14,7 +14,74 @@ import (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// RelayGeminiFileUpload handles file upload to Gemini File API
|
|
// RelayGeminiFileUpload handles file upload to Gemini File API
|
|
|
|
|
+// Supports both simple multipart upload and resumable upload protocol
|
|
|
func RelayGeminiFileUpload(c *gin.Context) {
|
|
func RelayGeminiFileUpload(c *gin.Context) {
|
|
|
|
|
+ // Get API key from channel context
|
|
|
|
|
+ apiKey := common.GetContextKeyString(c, constant.ContextKeyChannelKey)
|
|
|
|
|
+ if apiKey == "" {
|
|
|
|
|
+ logger.LogError(c, "Failed to get Gemini channel API key")
|
|
|
|
|
+ c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
|
|
|
+ "error": gin.H{
|
|
|
|
|
+ "message": "No available Gemini channel found",
|
|
|
|
|
+ "type": "service_unavailable_error",
|
|
|
|
|
+ "code": "no_available_channel",
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if this is a resumable upload request
|
|
|
|
|
+ uploadProtocol := c.GetHeader("X-Goog-Upload-Protocol")
|
|
|
|
|
+ if uploadProtocol == "resumable" {
|
|
|
|
|
+ // Handle resumable upload
|
|
|
|
|
+ handleResumableUpload(c, apiKey)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Handle standard multipart upload
|
|
|
|
|
+ handleMultipartUpload(c, apiKey)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// handleResumableUpload handles Gemini's resumable upload protocol
|
|
|
|
|
+func handleResumableUpload(c *gin.Context, apiKey string) {
|
|
|
|
|
+ // Build upstream URL
|
|
|
|
|
+ url := gemini.BuildGeminiFileURL("/upload/v1beta/files")
|
|
|
|
|
+
|
|
|
|
|
+ // Prepare headers - forward all X-Goog-Upload-* headers
|
|
|
|
|
+ headers := map[string]string{
|
|
|
|
|
+ "x-goog-api-key": apiKey,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Forward all resumable upload headers
|
|
|
|
|
+ resumableHeaders := []string{
|
|
|
|
|
+ "X-Goog-Upload-Protocol",
|
|
|
|
|
+ "X-Goog-Upload-Command",
|
|
|
|
|
+ "X-Goog-Upload-Header-Content-Length",
|
|
|
|
|
+ "X-Goog-Upload-Header-Content-Type",
|
|
|
|
|
+ "X-Goog-Upload-Offset",
|
|
|
|
|
+ "Content-Type",
|
|
|
|
|
+ "Content-Length",
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, header := range resumableHeaders {
|
|
|
|
|
+ if value := c.GetHeader(header); value != "" {
|
|
|
|
|
+ headers[header] = value
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get request body (for start command, it's JSON metadata)
|
|
|
|
|
+ body := c.Request.Body
|
|
|
|
|
+
|
|
|
|
|
+ // Forward request to Gemini
|
|
|
|
|
+ err := gemini.ForwardGeminiFileRequest(c, http.MethodPost, url, body, headers)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ logger.LogError(c, fmt.Sprintf("failed to forward resumable upload request: %s", err.Error()))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// handleMultipartUpload handles standard multipart/form-data upload
|
|
|
|
|
+func handleMultipartUpload(c *gin.Context, apiKey string) {
|
|
|
// Parse multipart form
|
|
// Parse multipart form
|
|
|
form, err := common.ParseMultipartFormReusable(c)
|
|
form, err := common.ParseMultipartFormReusable(c)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -30,20 +97,6 @@ func RelayGeminiFileUpload(c *gin.Context) {
|
|
|
}
|
|
}
|
|
|
defer form.RemoveAll()
|
|
defer form.RemoveAll()
|
|
|
|
|
|
|
|
- // Get API key from channel context (set by setupGeminiFileChannel)
|
|
|
|
|
- apiKey := common.GetContextKeyString(c, constant.ContextKeyChannelKey)
|
|
|
|
|
- if apiKey == "" {
|
|
|
|
|
- logger.LogError(c, "Failed to get Gemini channel API key")
|
|
|
|
|
- c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
|
|
|
- "error": gin.H{
|
|
|
|
|
- "message": "No available Gemini channel found",
|
|
|
|
|
- "type": "service_unavailable_error",
|
|
|
|
|
- "code": "no_available_channel",
|
|
|
|
|
- },
|
|
|
|
|
- })
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
// Rebuild multipart form for upstream request
|
|
// Rebuild multipart form for upstream request
|
|
|
body, contentType, err := gemini.RebuildMultipartForm(form)
|
|
body, contentType, err := gemini.RebuildMultipartForm(form)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -71,7 +124,6 @@ func RelayGeminiFileUpload(c *gin.Context) {
|
|
|
err = gemini.ForwardGeminiFileRequest(c, http.MethodPost, url, body, headers)
|
|
err = gemini.ForwardGeminiFileRequest(c, http.MethodPost, url, body, headers)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
logger.LogError(c, fmt.Sprintf("failed to forward file upload request: %s", err.Error()))
|
|
logger.LogError(c, fmt.Sprintf("failed to forward file upload request: %s", err.Error()))
|
|
|
- // Error response already sent by ForwardGeminiFileRequest
|
|
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|