Skip to content

[BUG]: Gemini 3 Pro function calling fails - missing thoughtSignature support #4832

Description

@linhlban150612

[BUG]: Gemini 3 Pro function calling fails - missing thoughtSignature support

Description

When using Gemini 3 Pro models (gemini-3-pro-preview) with function calling/tool use, requests fail with error:

Unable to submit request because function call `default_api:webfetch` in the 4. content block is missing a `thought_signature`.

Root Cause

Gemini 3 Pro requires thoughtSignature to be preserved and passed back during multi-step function calling. This is a mandatory validation introduced in Gemini 3 (unlike Gemini 2.5 where it was optional).

How Gemini 3 Thought Signatures Work

  1. When model responds with a functionCall, it includes a thoughtSignature field
  2. Client MUST preserve this signature and include it in the next request when sending functionResponse
  3. If signature is missing → API returns 400 error

Reference: https://ai.google.dev/gemini-api/docs/thought-signatures

Expected Flow

Turn 1, Step 1:
  Request: user_prompt
  Response: functionCall + thoughtSignature

Turn 1, Step 2:
  Request: user_prompt + (functionCall + thoughtSignature) + functionResponse  ← signature MUST be here
  Response: text_output or another functionCall + signature

Current Behavior in OpenCode

OpenCode does not preserve thoughtSignature from Gemini API responses when building subsequent requests in conversation history.

The contents array in requests to Gemini API is missing the thoughtSignature field in functionCall parts.

Affected Models

  • gemini-3-pro-preview (mandatory - will error)
  • gemini-2.5-pro / gemini-2.5-flash (optional but recommended for reasoning quality)

Proposed Fix

Option 1: Preserve thoughtSignature in Message History

When storing model responses that contain functionCall parts, also store the thoughtSignature field. When building the next request, include the signature in the corresponding content part:

{
  "contents": [
    {
      "role": "model",
      "parts": [
        {
          "functionCall": { "name": "webfetch", "args": {...} },
          "thoughtSignature": "<preserved_signature>"  // ← Add this
        }
      ]
    }
  ]
}

Option 2: Dummy Signature Workaround

According to Google's documentation, for cases where signatures cannot be preserved (e.g., transferring from another model), you can use dummy signatures:

"skip_thought_signature_validator"

or

"context_engineering_is_the_way_to_go"

This skips validation but may degrade reasoning quality.

Steps to Reproduce

  1. Configure opencode with Gemini 3 Pro (gemini-3-pro-preview)
  2. Ask a question that triggers tool/function calling (e.g., web search, file operations)
  3. When model makes a function call and receives response, the next request fails

Environment

  • OpenCode version: latest
  • Model: gemini-3-pro-preview
  • Provider: Google AI / Vertex AI

Additional Context

  • This issue affects any multi-step agentic workflow with Gemini 3
  • The Gemini CLI and official Google Gen AI SDKs handle this automatically via chat history management
  • OpenCode's custom provider/plugin architecture needs to explicitly handle this

Related Links

Activity

  1. github-actions commented on Nov 27, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Both issues indicate problems with how OpenCode handles function calling/tool use responses from LLM providers, which is the core issue described here regarding thoughtSignature handling.

    Feel free to ignore if none of these address your specific case.

  2. self-assigned this
    on Nov 29, 2025
  3. rekram1-node commented on Nov 29, 2025

    @rekram1-node
    Collaborator

    I can't replicate issue with your reproduction steps

  4. andreigiura commented on Dec 2, 2025

    @andreigiura

    @rekram1-node i think this happens when using gemini 3 pro with openrouter

  5. rekram1-node commented on Dec 2, 2025

    @rekram1-node
    Collaborator

    what version of OC are you using

  6. s16173760 commented on Dec 7, 2025

    @s16173760

    I suggest using this method first. langgenius/dify-official-plugins#2120

  7. andreigiura commented on Dec 10, 2025

    @andreigiura

    @rekram1-node i am using the latest version 1.0.141. The error i get is:
    {"code":400,"message":"Provider returned error","type":null,"param":null,"metadata":{"raw":"{\n "error": {\n "code": 400,\n "message": "Corrupted thought signature.",\n "status": "INVALID_ARGUMENT"\n }\n}\n","provider_name":"Google AI Studio"}}

  8. rekram1-node commented on Dec 10, 2025

    @rekram1-node
    Collaborator

    @andreigiura under what circumstances? Any reproduction steps?

  9. andreigiura commented on Dec 10, 2025

    @andreigiura

    @rekram1-node i have tried to build a hello world app. then tried to enhence its design. it builds the hello world with react for example, but at some point it fails and never recover. just need to work with it a little bit in order to reproduce it. it only happes on openrouter provider, not directly with google gemini

  10. rekram1-node commented on Dec 10, 2025

    @rekram1-node
    Collaborator

    Oh well that makes sense then

  11. rekram1-node commented on Dec 10, 2025

    @rekram1-node
    Collaborator

    OpenRouter just did some bug fixes for their provider and I merged their fix in today. it will be in next release

  12. matthewijordan commented on Dec 15, 2025

    @matthewijordan

    @rekram1-node have you got a link to the changes? I've just jumped to the latest release and I'm still seeing this issue

  13. rekram1-node commented on Dec 15, 2025

    @rekram1-node
    Collaborator

    @matthewijordan you are encountering issues with gemini specifically through openrouter? Can you explain steps to reproduce?

  14. rekram1-node commented on Dec 15, 2025

    @rekram1-node
    Collaborator

    here is one of the last fixes they did:
    OpenRouterTeam/ai-sdk-provider#288

  15. ramarivera commented on Dec 23, 2025

    @ramarivera

    Not sure if related at all, but when using Zen + Gemini 3 Pro I get

    Request contains an invalid argument.
    

    on every request 🤔
    Not sure where logs would get stored, but lemme know if I can provide more info?

  16. 8 remaining items

  17. jakobbjelver commented on Feb 18, 2026

    @jakobbjelver

    I am also getting the same error. Version 1.2.6 with Google Gemini Pro and Flash using OpenRouter. No external plugins or anything, vanilla OpenCode config.

    This is what I can see from the logs:
    { "error": { "code": 400, "message": "Unable to submit request because Thought signature is not valid.. Learn more: https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/gemini", "status": "INVALID_ARGUMENT" } }

    Anyone got an update on this? Seems it should have been fixed?

  18. VitorNaidek commented on Feb 24, 2026

    @VitorNaidek

    I am also receiving this error when using gemini-3-flash-preview through the ollama cloud. I did not encounter the same issue with other ollama models.

  19. BrianJM commented on Feb 24, 2026

    @BrianJM

    I am also getting the same error. Version 1.2.6 with Google Gemini Pro and Flash using OpenRouter. No external plugins or anything, vanilla OpenCode config.

    This is what I can see from the logs: { "error": { "code": 400, "message": "Unable to submit request because Thought signature is not valid.. Learn more: https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/gemini", "status": "INVALID_ARGUMENT" } }

    Anyone got an update on this? Seems it should have been fixed?

    I am also receiving the same issue with Gemini models on OpenRouter.

  20. nguyenvulong commented on Mar 17, 2026

    @nguyenvulong

    Same problem with Google Vertex AI with Gemini 3.1 Pro Preview
    I just used very few skills from Claude. No other additional plugins were used.

    Unable to submit request because function call default_api:read in the 73. content block is missing a thought_signature. Learn more: https://docs.cloud.google.com/vertex-ai/generative-ai/docs/thought-signatures

  21. CCRcmcpe commented on Mar 25, 2026

    @CCRcmcpe

    Same issue with Vertex Gemini 3.1 Pro Preview on clean opencode installation

  22. gintasz commented on Apr 15, 2026

    @gintasz

    Same issue, still getting a whole bunch of random thought signature errors on opencode 1.4.3, at least 20 times today, gemini 3.1 pro preview on openrouter.

  23. czln commented on May 4, 2026

    @czln

    Still having the same issue at latest version1.14.33. I'm using gemini over my own proxy on my server, which I believe all content has been routed to the gemini server. I myself would not change the json content.

  24. rekram1-node commented on May 4, 2026

    @rekram1-node
    Collaborator

    openrouter sdk had bugs we updated sdk recently.

    For ur custom one kinda need more info for ur proxy

  25. czln commented on May 11, 2026

    @czln

    @rekram1-node
    well, it's a really simple proxy. I can just paste part of my source code here:

    func main() {
    	// Parse a default target for the proxy constructor
    	defaultTarget, err := url.Parse(targetLLMServerURL)
    	if err != nil {
    		log.Fatalf("FATAL: Failed to parse default target URL: %v", err)
    	}
    
    	proxy := httputil.NewSingleHostReverseProxy(defaultTarget)
    
    	// 3. Customize the proxy's Director
    	// The Director modifies the request *before* it's sent to the target.
    	originalDirector := proxy.Director
    	proxy.Director = func(r *http.Request) {
    		// Run the original director logic (sets up scheme, host, etc.)
    		originalDirector(r)
    
    		// Read the full request body and replace it so it can be read again by the proxy.
    		bodyBytes, err := io.ReadAll(r.Body)
    		if err != nil {
    			log.Printf("ERROR: Failed to read request body: %v", err)
    			// leave request as-is; proxy will attempt to forward whatever remains
    			return
    		}
    		// Close original and set a new ReadCloser for downstream
    		_ = r.Body.Close()
    		r.Body = io.NopCloser(bytes.NewReader(bodyBytes))
    		r.ContentLength = int64(len(bodyBytes))
    		r.Header.Set("Content-Length", strconv.Itoa(len(bodyBytes)))
    
    		// Try to parse model from JSON body
    		var reqData map[string]interface{}
    		if len(bodyBytes) > 0 {
    			if err := json.Unmarshal(bodyBytes, &reqData); err != nil {
    				log.Printf("WARN: Failed to unmarshal request body; using default target: %v", err)
    			}
    		}
    
    		// Determine target URL based on model (fallback to default)
    		targetURL := defaultTarget
    		if modelVal, ok := reqData["model"].(string); ok {
    			targetURL = getUrl(modelVal)
    		} else {
    			log.Printf("INFO: 'model' field not found or not a string; using default target")
    		}
    
    		// Update request URL/host to route to selected target
    		r.URL.Scheme = targetURL.Scheme
    		r.URL.Host = targetURL.Host
    		// Use the target's path as the request path (so full paths like /v1/chat/completions are honored)
    		r.URL.Path = targetURL.Path
    		r.Host = targetURL.Host
    
    		// (Optional) If the target server needs a *different* API key,
    		// you can swap it out here.
    		if targetServerAPIKey != "" {
    			r.Header.Set("Authorization", "Bearer "+targetServerAPIKey)
    		}
    		// If targetServerAPIKey is empty, the client's original
    		// Authorization header is passed through automatically.
    
    		log.Printf("Proxying request to: %s", targetURL.String())
    	}
    
    	// 4. Define our main handler
    	http.HandleFunc("/chat/completions", authAndProxyHandler(proxy))
    
    	// 5. Start the HTTP server
    	log.Printf("Starting HTTP proxy server on http://localhost%s", serverAddr)
    	log.Printf("Proxying requests to %s", targetLLMServerURL)
    	log.Printf("Expecting 'Authorization: Bearer %s'", validClientAuthToken)
    
    	err = http.ListenAndServe(serverAddr, nil)
    	if err != nil {
    		log.Fatalf("FATAL: Failed to start server: %v", err)
    	}
    }
    
    // authAndProxyHandler returns a handler that first checks authentication
    // and then proxies the request if auth is successful.
    func authAndProxyHandler(proxy *httputil.ReverseProxy) http.HandlerFunc {
    	return func(w http.ResponseWriter, r *http.Request) {
    		// --- 1. Method Check ---
    		if r.Method != http.MethodPost && r.Method != http.MethodOptions {
    			log.Printf("REJECT: Invalid method: %s", r.Method)
    			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    			return
    		}
    
    		// --- 2. Auth Check ---
    		authHeader := r.Header.Get("Authorization")
    		if authHeader == "" {
    			log.Println("REJECT: Missing Authorization header")
    			http.Error(w, "Authorization header required", http.StatusUnauthorized)
    			return
    		}
    
    		// Check for "Bearer <token>" format
    		tokenParts := strings.Split(authHeader, " ")
    		if len(tokenParts) != 2 || strings.ToLower(tokenParts[0]) != "bearer" {
    			log.Println("REJECT: Invalid Authorization header format")
    			http.Error(w, "Invalid Authorization header format", http.StatusUnauthorized)
    			return
    		}
    
    		token := tokenParts[1]
    
    		// Validate the token
    		if token != validClientAuthToken {
    			log.Println("REJECT: Invalid token")
    			http.Error(w, "Invalid token", http.StatusForbidden)
    			return
    		}
    
    		log.Printf("ACCEPT: Auth successful. Proxying request for %s", r.RemoteAddr)
    
    		// --- 3. Proxy the Request ---
    		// If auth is OK, let the proxy handle the rest.
    		// It forwards the request and streams back the response.
    		proxy.ServeHTTP(w, r)
    	}
    }

    the content of http request would never be changed

  26. rmbusddollar commented on May 11, 2026

    @rmbusddollar

    Have this been fixed?

  27. rekram1-node commented on May 11, 2026

    @rekram1-node
    Collaborator

    if ur still having the issue send me a session u got it in? opencode export > session.json

  28. cyberfox1 commented on Aug 12, 2026

    @cyberfox1

    Still get this issue, latest version of opencode and 3.5 flash.

  29. rekram1-node commented on Aug 12, 2026

    @rekram1-node
    Collaborator

    @cyberfox1 As the msg above states, plz:

    if ur still having the issue send me a session u got it in? opencode export > session.json

  30. cyberfox1 commented on Aug 12, 2026

    @cyberfox1

    @cyberfox1 As the msg above states, plz:

    if ur still having the issue send me a session u got it in? opencode export > session.json

    I looked into this a bit more it seems my provider had switched from google to openai-compatible, switching it back to google resolved it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions