2598247ff5
Some OpenAI-compatible gateways fronting Gemini backends emit the native uppercase finish reasons (STOP, MAX_TOKENS) instead of the lowercase OpenAI contract values. Every downstream comparison in Hermes uses lowercase literals, so an uppercase reason silently fell through: a clean STOP completion missed the stop handling and a MAX_TOKENS truncation never entered the length-recovery path. Adds normalize_finish_reason() as the single owner in agent/message_sanitization.py (case fold + alias map: max_tokens->length, end->stop, function_call->tool_calls) and wires it at both wire-intake choke points: ChatCompletionsTransport.normalize_response and the streaming chunk-capture loop in chat_completion_helpers. Non-string and empty values pass through unchanged so existing 'or "stop"' defaults and the Poolside int-reason path keep their behavior.