- Not started
- [~] In progress
- Complete
- Status: [x]
- Issue: Gemini 3 requires
thoughtSignatureon ALL function calls when thinking mode is enabled. We passednullwhen no signature existed. - Fix: Added
skipThoughtSignatureBytessentinel inthought_signature_utils.dart. Applied in_convertAssistantMessagewhenisGemini3Model(modelId)is true and no real signature exists. - Files:
message_content_converter.dart,utils/thought_signature_utils.dart
- Status: [x]
- Issue: We always converted
reasoningContenttothought: trueregardless of source model. - Fix: Added
sourceProvider/sourceModelparameters. Only keeps thinking asthought:truewhen same provider AND model. Otherwise converts to plain text. - Files:
message_content_converter.dart
- Status: [x]
- Issue: We always wrapped tool results as
{'result': content}. - Fix: Added
isErrorparameter totoGeminiFunctionResponse. Uses{'error': value}for errors,{'output': value}for success. - Files:
tool_mapper.dart
- Status: [x]
- Issue: No sanitization of unicode surrogates before sending to Gemini API.
- Fix: Added
sanitizeSurrogates()utility. Applied to all text content: system prompts, user messages, assistant text, and reasoning content. - Files:
utils/sanitize_unicode.dart,message_content_converter.dart
- Status: [x]
- Issue: We captured
__last_text__signatures from responses but never re-injected them. - Fix: When converting assistant messages back to Gemini, looks up
__last_text__signature and attaches to text parts (only when same provider/model). - Files:
message_content_converter.dart
- Status: [x]
- Issue: No base64 format validation on thought signatures.
- Fix: Added
isValidThoughtSignature()andresolveThoughtSignature()utilities. Validates base64 format (length % 4 == 0, valid chars) and gates on same provider/model. - Files:
utils/thought_signature_utils.dart,message_content_converter.dart
- Status: [x]
- Issue: Tool results were text-only, no support for images.
- Fix: Added
imageDataandmodelIdparameters totoGeminiFunctionResponse. For Gemini 3: nests images insidefunctionResponse.partsusingFunctionResponseInlinePart. For older models:partsis null (caller handles separately). - Files:
tool_mapper.dart
- Status: [x] N/A
- Issue: The Dart SDK's
FinishReasonenum already matches all values we handle. No additional values exist ingoogleai_dart3.6.0.
- Status: [x]
- Issue: No stripping of special characters or length capping on tool call IDs.
- Fix: Added
normalizeToolCallId()utility that replaces non-[a-zA-Z0-9_-]chars with_and caps at 64 chars. Controlled bynormalizeToolCallIdsparameter. - Files:
utils/thought_signature_utils.dart,message_content_converter.dart
- Status: [x]
- Issue: Fallback to empty
TextPart('')could cause issues with some models. - Fix: Skip empty/whitespace-only text and reasoning content. Only use empty TextPart fallback when the entire message would be empty (last resort).
- Files:
message_content_converter.dart
- Gap #1: 3 tests (sentinel on G3, no sentinel on non-G3, real sig preserved)
- Gap #2: 3 tests (same model, different model, no provider info)
- Gap #3: 3 tests (success key, error key, round-trip)
- Gap #4: 4 tests (user text, system, assistant, clean passthrough)
- Gap #5: 2 tests (injection, no injection for different model)
- Gap #6: 3 tests (valid base64, invalid base64, cross-provider)
- Gap #7: 2 tests (Gemini 3 inline parts, non-G3 no parts)
- Gap #9: 3 tests (special chars stripped, length capped, clean passthrough)
- Gap #10: 3 tests (empty text filtered, whitespace reasoning filtered, valid fallback)
- OpenAI round-trip: 2 tests (full conversation, argument preservation)
- Multi-turn tool calling with thought signatures (4 turns)
- Error tool results communicated to model
- Unicode CJK content round-trip
- GeminiOpenAIClient drop-in multi-turn
- Cross-provider OpenAI → Gemini context preservation
- Streaming with signature capture