From 35c3d80ab9ea2def6bf7ca0a26c09263257e3485 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sat, 20 Jun 2026 14:46:39 +0800 Subject: [PATCH] feat(translator): add support for handling video URLs in Gemini requests - Updated `ConvertOpenAIRequestToGemini` to process `video_url` message types, extracting and embedding video metadata (MIME type and base64 data) into Gemini request parts. - Added corresponding unit tests to validate `video_url` handling, ensuring proper extraction and data preservation. Closes: #3920 --- .../chat-completions/gemini_openai_request.go | 12 +++++++ .../gemini_openai_request_test.go | 32 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go b/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go index f8ab5ddc..28086f52 100644 --- a/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go +++ b/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go @@ -196,6 +196,18 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool) p++ } } + case "video_url": + videoURL := item.Get("video_url.url").String() + if len(videoURL) > 5 { + pieces := strings.SplitN(videoURL[5:], ";", 2) + if len(pieces) == 2 && len(pieces[1]) > 7 { + mime := pieces[0] + data := pieces[1][7:] + node, _ = sjson.SetBytes(node, "parts."+itoa(p)+".inlineData.mime_type", mime) + node, _ = sjson.SetBytes(node, "parts."+itoa(p)+".inlineData.data", data) + p++ + } + } case "file": filename := item.Get("file.filename").String() fileData := item.Get("file.file_data").String() diff --git a/internal/translator/gemini/openai/chat-completions/gemini_openai_request_test.go b/internal/translator/gemini/openai/chat-completions/gemini_openai_request_test.go index 27e999e3..ad79869c 100644 --- a/internal/translator/gemini/openai/chat-completions/gemini_openai_request_test.go +++ b/internal/translator/gemini/openai/chat-completions/gemini_openai_request_test.go @@ -59,6 +59,38 @@ func TestConvertOpenAIRequestToGeminiPreservesInputAudio(t *testing.T) { } } +func TestConvertOpenAIRequestToGeminiPreservesVideoURL(t *testing.T) { + inputJSON := `{ + "model": "gemini-3-flash", + "messages": [ + { + "role": "user", + "content": [ + {"type": "video_url", "video_url": {"url": "data:video/mp4;base64,AAAAIGZ0eXBtcDQy"}}, + {"type": "text", "text": "Describe the video"} + ] + } + ] + }` + + result := ConvertOpenAIRequestToGemini("gemini-3-flash", []byte(inputJSON), false) + resultJSON := gjson.ParseBytes(result) + parts := resultJSON.Get("contents.0.parts").Array() + + if len(parts) != 2 { + t.Fatalf("parts length = %d, want 2. parts=%s", len(parts), resultJSON.Get("contents.0.parts").Raw) + } + if got := parts[0].Get("inlineData.mime_type").String(); got != "video/mp4" { + t.Fatalf("video mime_type = %q, want %q", got, "video/mp4") + } + if got := parts[0].Get("inlineData.data").String(); got != "AAAAIGZ0eXBtcDQy" { + t.Fatalf("video data = %q, want %q", got, "AAAAIGZ0eXBtcDQy") + } + if got := parts[1].Get("text").String(); got != "Describe the video" { + t.Fatalf("text part = %q, want prompt text", got) + } +} + func TestConvertOpenAIRequestToGeminiCleansToolSchemaRequiredFields(t *testing.T) { inputJSON := `{ "model": "gemini-2.0-flash", -- 2.51.2