@@ -6,16 +6,13 @@ import (
66 "io"
77 "net/http"
88 "strings"
9+
10+ "github.com/coder/coder/v2/internal/googleopenai"
911)
1012
1113// OpenAI-compatible providers share an API shape but differ in the exact JSON
1214// they accept. These patches adjust Fantasy's serialized request body at the
1315// transport boundary so higher-level generation code can stay provider agnostic.
14- //
15- // googleOpenAICompatDummyThoughtSignature is Google's documented last-resort
16- // bypass for callers that cannot preserve a real Gemini thought signature.
17- // See https://ai.google.dev/gemini-api/docs/thought-signatures.
18- const googleOpenAICompatDummyThoughtSignature = "skip_thought_signature_validator"
1916
2017func withOpenAICompatRequestPatches (
2118 client * http.Client ,
@@ -91,8 +88,8 @@ func patchOpenAICompatChatCompletionsBody(body []byte, baseURL string, modelID s
9188 }
9289
9390 changed := rewriteOpenAICompatSingleToolChoice (payload )
94- if shouldAddGoogleOpenAICompatThoughtSignatures (baseURL , modelID ) {
95- changed = addGoogleOpenAICompatThoughtSignatures (payload ) || changed
91+ if googleopenai . ShouldPatchOpenAICompatRequest (baseURL , modelID ) {
92+ changed = googleopenai . AddThoughtSignaturesToLatestTurn (payload ) || changed
9693 }
9794 if ! changed {
9895 return body
@@ -144,93 +141,3 @@ func rewriteOpenAICompatSingleToolChoice(payload map[string]any) bool {
144141 payload ["tool_choice" ] = "required"
145142 return true
146143}
147-
148- // shouldAddGoogleOpenAICompatThoughtSignatures detects direct Gemini OpenAI
149- // endpoints and Coder AI Bridge Gemini routes. Other gateways, such as Vercel,
150- // keep their own provider-specific compatibility behavior.
151- func shouldAddGoogleOpenAICompatThoughtSignatures (baseURL string , modelID string ) bool {
152- parsed , ok := parseProviderBaseURL (baseURL )
153- if ! ok {
154- return false
155- }
156- host := strings .ToLower (parsed .Hostname ())
157- path := strings .ToLower (parsed .EscapedPath ())
158- if host == "generativelanguage.googleapis.com" && strings .Contains (path , "/openai" ) {
159- return true
160- }
161- return host == "coder-aibridge" && isGeminiModelID (modelID )
162- }
163-
164- func isGeminiModelID (modelID string ) bool {
165- modelID = strings .ToLower (strings .TrimSpace (modelID ))
166- return strings .HasPrefix (modelID , "gemini-" ) || strings .Contains (modelID , "/gemini-" )
167- }
168-
169- // addGoogleOpenAICompatThoughtSignatures adds a dummy thought signature to the
170- // first tool call on each assistant tool-call message in the latest user turn.
171- // Gemini validates tool-call history with thought signatures, but
172- // OpenAI-compatible serialization can drop the original provider metadata.
173- func addGoogleOpenAICompatThoughtSignatures (payload map [string ]any ) bool {
174- messages , ok := payload ["messages" ].([]any )
175- if ! ok {
176- return false
177- }
178-
179- currentTurnStart := - 1
180- for i , raw := range messages {
181- message , ok := raw .(map [string ]any )
182- if ! ok {
183- continue
184- }
185- if role , _ := message ["role" ].(string ); role == "user" {
186- currentTurnStart = i
187- }
188- }
189-
190- if currentTurnStart == - 1 {
191- return false
192- }
193-
194- changed := false
195- for _ , raw := range messages [currentTurnStart + 1 :] {
196- message , ok := raw .(map [string ]any )
197- if ! ok || ! isOpenAICompatAssistantRole (message ["role" ]) {
198- continue
199- }
200- toolCalls , ok := message ["tool_calls" ].([]any )
201- if ! ok || len (toolCalls ) == 0 {
202- continue
203- }
204- firstToolCall , ok := toolCalls [0 ].(map [string ]any )
205- if ! ok {
206- continue
207- }
208- if ensureGoogleOpenAICompatThoughtSignature (firstToolCall ) {
209- changed = true
210- }
211- }
212- return changed
213- }
214-
215- func isOpenAICompatAssistantRole (role any ) bool {
216- roleValue , _ := role .(string )
217- return roleValue == "assistant" || roleValue == "model"
218- }
219-
220- func ensureGoogleOpenAICompatThoughtSignature (toolCall map [string ]any ) bool {
221- extraContent , _ := toolCall ["extra_content" ].(map [string ]any )
222- google , _ := extraContent ["google" ].(map [string ]any )
223- if signature , _ := google ["thought_signature" ].(string ); signature != "" {
224- return false
225- }
226- if extraContent == nil {
227- extraContent = map [string ]any {}
228- toolCall ["extra_content" ] = extraContent
229- }
230- if google == nil {
231- google = map [string ]any {}
232- extraContent ["google" ] = google
233- }
234- google ["thought_signature" ] = googleOpenAICompatDummyThoughtSignature
235- return true
236- }
0 commit comments