| 4842 | |
| 4843 | static bool parse_deepseek_generated_message_ex(const char *text, |
| 4844 | bool require_thinking_closed, |
| 4845 | char **content_out, |
| 4846 | char **reasoning_out, |
| 4847 | tool_calls *calls) { |
| 4848 | text = text ? text : ""; |
| 4849 | const char *tool_search = text; |
| 4850 | bool recovered_unclosed_tool = false; |
| 4851 | |
| 4852 | /* When thinking mode is enabled the model is expected to close |
| 4853 | * </think> before it enters the executable assistant surface. DSML inside |
| 4854 | * reasoning is just model text: it may be a mistaken attempt, a quotation, |
| 4855 | * or an explanation of the protocol. Treating it as a real tool call |
| 4856 | * duplicates it into both reasoning and structured tool_calls, and can make |
| 4857 | * clients execute something the assistant had not actually emitted as its |
| 4858 | * post-thinking action. */ |
| 4859 | if (require_thinking_closed) { |
| 4860 | const char *think_end = find_last_substr(text, "</think>"); |
| 4861 | if (!think_end) { |
| 4862 | const char *candidate = find_any_tool_start(text); |
| 4863 | if (!candidate || !find_any_tool_end(candidate)) { |
| 4864 | fprintf(stderr, "ds4-server: thinking not closed, ignoring incomplete DSML in reasoning\n"); |
| 4865 | ds4_local_unterminated_reasoning(text, content_out, reasoning_out); |
| 4866 | return true; |
| 4867 | } |
| 4868 | tool_search = candidate; |
| 4869 | recovered_unclosed_tool = true; |
| 4870 | } else { |
| 4871 | tool_search = think_end + 8; |
| 4872 | } |
| 4873 | } |
| 4874 | |
| 4875 | const char *start = strstr(tool_search, "\n\n" DS4_TOOL_CALLS_START); |
| 4876 | int style = 0; /* 0: DSML, 1: plain XML, 2: DSML with the first vertical bar omitted. */ |
| 4877 | if (!start) start = strstr(tool_search, DS4_TOOL_CALLS_START); |
| 4878 | if (!start) { |
| 4879 | start = strstr(tool_search, "\n\n" DS4_TOOL_CALLS_START_SHORT); |