Tokenizers can split a multi-byte UTF-8 character across two tokens. If an * SSE delta ends at that boundary, some clients replace the incomplete byte * sequence with U+FFFD and later send the corrupted text back, destroying KV * cache prefix matches. Hold only the trailing incomplete character; the next * generated token will complete it. */
| 1011 | static void request_free(request *r) { |
| 1012 | ds4_tokens_free(&r->prompt); |
| 1013 | for (size_t i = 0; i < r->image_count; i++) |
| 1014 | ds4_vision_embedding_free(&r->images[i].embedding); |
| 1015 | free(r->images); |
| 1016 | free(r->image_markers); |
| 1017 | free(r->model); |
| 1018 | for (int i = 0; i < r->stops.len; i++) free(r->stops.v[i]); |
| 1019 | free(r->stops.v); |
| 1020 | free(r->raw_body); |
| 1021 | free(r->prompt_text); |
| 1022 | stop_list_clear(&r->responses_live_call_ids); |
| 1023 | free(r->responses_live_call_ids.v); |
| 1024 | free(r->responses_live_suffix_text); |
| 1025 | stop_list_clear(&r->anthropic_live_call_ids); |
| 1026 | free(r->anthropic_live_call_ids.v); |
| 1027 | free(r->anthropic_live_suffix_text); |
| 1028 | tool_schema_orders_free(&r->tool_orders); |
| 1029 | memset(r, 0, sizeof(*r)); |
| 1030 | } |
| 1031 | |
| 1032 | static ds4_think_mode think_mode_from_enabled(bool enabled, ds4_think_mode effort) { |
| 1033 | if (!enabled || effort == DS4_THINK_NONE) return DS4_THINK_NONE; |
| 1034 | return effort; |
| 1035 | } |
| 1036 | |
| 1037 | static bool parse_reasoning_effort_name(const char *s, ds4_think_mode *out) { |
| 1038 | if (!s) return false; |