0.23.1
Gemini 3's thought signatures — the shot that produced it has to carry it back
If you switched to a Gemini 3 model and hit a red bar, this is why:
Error code: 400 - {'error': {'code': 400,
'message': 'Request contains an invalid argument.', 'status': 'INVALID_ARGUMENT'}}
Which argument? It doesn't say. That was the hard part — Google swallows the real message once the history gets long. The same failure, with only five messages of history, says what it actually means:
Function call is missing a thought_signature in functionCall parts.
Gemini 3 attaches a signature to every tool call it makes:
{"id": "call_297731", "type": "function",
"function": {"name": "get_weather", "arguments": "{\"city\":\"Beijing\"}"},
"extra_content": {"google": {"thought_signature": "EoYCCoMCARFNMg/vBOfJ…"}}}Send that call back without the signature and the whole request is rejected.
Two independent ways this broke
1. We were throwing the signature away. The streaming assembler kept only id, name, and arguments from each fragment and dropped everything else. So even if you used Gemini for the entire conversation, the first tool call was the last thing that worked — every request after it was a 400.
The fix doesn't hard-code a field name, it goes by provenance: whatever the vendor puts on the fragment rides back out untouched. Hard-coding extra_content would just mean fixing this again when the next vendor picks a different name. (This is the third time this exact bug has appeared — v0.22.4 was the reasoning text.)
2. Fast Mode mixes vendors in one history. Tool calls made by your fast model carry no Google signature, so the moment the conversation escalates back to a Gemini base model, same 400. The same thing happens to any conversation where you switched providers partway through — and that history can't be repaired retroactively.
For those, the request is flattened before it goes out: an unsigned tool call becomes plain text ((I called read_file with …)), and its result becomes a plain message. What's lost is the structure, not the content — the model still reads what was called, with what arguments, and what came back. For rounds that already executed, nothing depends on that structure any more.
Rounds that do carry a signature are left exactly as they are, and every other vendor's history is returned untouched — not a copy, the same list. If you're not on Google, this release changes nothing about what goes over the wire.
Verified against a live endpoint
- Full Gemini conversation, real streaming: second shot goes from 400 to a normal answer.
- A real 60-message conversation with 22 tool-call rounds made by a different vendor: 400 as-is, answers normally after flattening.
pytest1005 passed,npm run buildclean across all eleven gates.
Full Changelog: 0.23.0...0.23.1