This commit adds support for the OpenAI Responses API in both streaming and non-streaming modes. It also implements proactive routing for gpt-5 and codex models and cleans up unused 'session' variable warnings across the dashboard source files.
This commit introduces:
- AES-256-GCM encryption for LLM provider API keys in the database.
- HMAC-SHA256 signed session tokens with activity-based refresh logic.
- Standardized frontend XSS protection using a global escapeHtml utility.
- Hardened security headers and request body size limits.
- Improved database integrity with foreign key enforcement and atomic transactions.
- Integration tests for the full encrypted key storage and proxy usage lifecycle.
This commit fixes the Gemini API 'Invalid value at thought_signature' error by ensuring synthetic 'call_' IDs are not passed into the TYPE_BYTES field. It also adds a pre-pass to correctly resolve function names from tool call IDs for tool responses.
- Ensure ALL assistant messages in history have reasoning_content and string content.
- Use a single space as a professional minimal placeholder for missing reasoning.
- Log full offending request bodies at ERROR level for detailed debugging.
- Fix DeepSeek R1 (reasoner) 400 errors by ensuring assistant messages with
tool_calls in history always have non-null 'content' and 'reasoning_content'.
- Implement deterministic tool call ID truncation (max 40 chars) for OpenAI
compatibility (fixes errors when history contains long Gemini signatures).
- Automatic transition from 'max_tokens' to 'max_completion_tokens' for newer
OpenAI models (o1, o3, gpt-5-nano).
- Added 'reasoning' and 'thought' aliases to reasoning_content for robust
deserialization from various clients.
Newer OpenAI models (o1, o3, gpt-5) have deprecated 'max_tokens' in favor of
'max_completion_tokens'. The provider now automatically maps this parameter
to ensure compatibility and avoid 400 errors.
OpenAI has a strict 40-character limit for tool call IDs. Long IDs (like
Gemini's 56-char thought signatures) are now deterministically truncated
to 40 characters when sending history back to OpenAI-compatible providers.
- Move DeepSeek R1 reasoning_content and content:"" fixes to DeepSeekProvider only.
- Restore OpenAI-standard null content for tool calls in helpers.rs.
- Fixes 400 Bad Request for gpt-5-nano and other strict OpenAI models.
- Ensure assistant tool calls always have content: "" instead of null.
- Add debug-level logging of the full request body when DeepSeek returns 400.
- Improved R1 placeholder injection to include content sanitation.
- Added aliases 'reasoning' and 'thought' to reasoning_content field for better client compatibility.
- Implemented automatic 'Thinking...' placeholder injection for deepseek-reasoner history messages that contain tool_calls but lack reasoning_content.
- Restored strict parameter sanitation for deepseek-reasoner.
- Introduce AuthInfo struct for shared auth state.
- Populate AuthInfo in rate_limit_middleware and store in request extensions.
- Update AuthenticatedClient extractor to use pre-resolved AuthInfo.
- Simplify chat_completions by removing redundant DB lookups.
Gemini often sends tool calls in one chunk and then 'STOP' in a final chunk.
If we pass the raw 'stop' at the end, clients stop and ignore the previously
received tool calls. We now track if any tools were seen and override the
final 'stop' to 'tool_calls'.
Gemini often reports 'STOP' even when tool calls are generated. To remain
OpenAI-compatible and ensure clients execute tools and continue, we must
report 'tool_calls' as the finish_reason when tools are present.
- Remove from as it is rejected by the API inside .
- Ensure is always a JSON object (google.protobuf.Struct), wrapping non-object tool results in .
- Update extraction logic to only look for in sibling fields.
- Ensure is preserved in conversation history for Gemini 3 models.
- Support multiple naming conventions (snake_case and camelCase).
- Implement stable tool call ID tracking during streaming using a stateful map.
- Improve extraction from both Gemini parts and function calls.
- Fix incorrect tool call indices during streaming.
- The native Gemini REST API requires camelCase 'thoughtSignature' as a sibling to functionCall.
- Explicitly rename the field to match this requirement, resolving the 'missing thought_signature' 400 error.
- Only restore thought_signature if the tool call ID doesn't start with 'call_'.
- This ensures proxy-generated UUIDs are never sent back to Gemini as signatures, which was causing base64 decoding failures.
- Extract thought_signature from the Part level during response parsing.
- Provide thought_signature as a sibling to functionCall during request assembly.
- This fully resolves the 'Unknown name thoughtSignature at function_call' error.
- Update get_base_url to only perform replacement if the base_url specifically ends with /v1.
- This prevents malformed URLs like /v1betabeta when the base_url was already configured as v1beta.
- Switch Gemini 3 models to v1beta for both streaming and non-streaming (better reasoning support).
- Increase max_output_tokens cap to 65536 for reasoning-heavy models.
- Elevate API URL and chunk tracing to INFO level for easier production debugging.
- Exclude 'HARM_CATEGORY_CIVIC_INTEGRITY' when using v1 endpoint (v1beta only).
- Filter out empty strings from 'stop_sequences' which are rejected by Gemini.
- Update error probe to use non-streaming endpoint for better JSON error diagnostics.
- Recursively remove '$schema', 'additionalProperties', 'exclusiveMaximum', and 'exclusiveMinimum' from tool definitions.
- These fields are frequently included by clients like opencode but are rejected by the Gemini API with 400 errors.
- Merge all consecutive messages with the same role into a single GeminiContent object.
- Ensure the first message is always 'user' by prepending a placeholder if necessary.
- Add final check for empty contents to prevent sending malformed requests.
- This addresses strict role-sequence requirements in Gemini 2.0/3.0 models.
- Update get_base_url to prefer v1 for Gemini 3.0+ models even if they contain 'preview'.
- Add tracing::debug logs for the final API URLs used in both streaming and non-streaming requests.
- Switch to v1beta endpoint for 'preview' and 'thinking' models.
- Update model version checks to include gemini-3 as a known version.
- Use get_base_url helper to construct dynamic URLs for both streaming and non-streaming requests.
- Map unknown Gemini model names to the configured default model to prevent 400 errors.
- Clamp max_tokens to a safe limit of 8192 for Gemini models.
- Clean up message filtering and role injection for better client compatibility.
- Filter out empty text parts in Gemini requests to avoid 400 errors.
- Inject 'assistant' role into the first streaming chunk for better compatibility with clients like opencode.
- Fallback to tool_call_id for Gemini function responses when name is missing.
Gemini API requires the first message to be from the 'user' role.
This commit ensures that:
- If a conversation starts with a 'model' (assistant) role, a placeholder 'user' message is prepended.
- 'tool' results are correctly mapped to 'user' role parts.
- Sequential messages with the same role are merged.
- Empty content requests are prevented in both sync and stream paths.
This fixes 400 Bad Request errors when clients (like opencode) send
message histories that don't match Gemini's strict role requirements.