| 6330 | tool_calls *calls) { |
| 6331 | static const char tool_start[] = "<tool_call>"; |
| 6332 | static const char tool_end[] = "</tool_call>"; |
| 6333 | static const char arg_key_start[] = "<arg_key>"; |
| 6334 | static const char arg_key_end[] = "</arg_key>"; |
| 6335 | static const char arg_value_start[] = "<arg_value>"; |
| 6336 | static const char arg_value_end[] = "</arg_value>"; |
| 6337 | |
| 6338 | text = text ? text : ""; |
| 6339 | const char *tool_search = text; |
| 6340 | bool recovered_unclosed_tool = false; |
| 6341 | if (require_thinking_closed) { |
| 6342 | const char *think_end = find_tool_structural_text(text, "</think>", true); |
| 6343 | if (!think_end) { |
| 6344 | const char *candidate = strstr(text, tool_start); |
| 6345 | if (!candidate || !strstr(candidate, tool_end)) { |
| 6346 | fprintf(stderr, "ds4-server: thinking not closed, ignoring incomplete GLM tool calls in reasoning\n"); |
| 6347 | ds4_local_unterminated_reasoning(text, content_out, reasoning_out); |
| 6348 | return true; |
| 6349 | } |
| 6350 | tool_search = candidate; |
| 6351 | recovered_unclosed_tool = true; |
| 6352 | } else { |
| 6353 | tool_search = think_end + 8; |
| 6354 | } |
| 6355 | } |
| 6356 | |
| 6357 | const char *start = strstr(tool_search, tool_start); |
| 6358 | if (!start) { |
| 6359 | split_reasoning_content(text, strlen(text), content_out, reasoning_out); |
| 6360 | return true; |
| 6361 | } |
| 6362 | |
| 6363 | const char *raw_block_start = start; |
| 6364 | if (start >= text + 2 && start[-2] == '\n' && start[-1] == '\n') { |
| 6365 | raw_block_start = start - 2; |
| 6366 | } |
| 6367 | size_t content_len = trim_tool_separator_ws(text, 0, |
| 6368 | (size_t)(raw_block_start - text)); |
| 6369 | const char *p = start; |
| 6370 | for (;;) { |
| 6371 | p = skip_ascii_ws(p); |
| 6372 | if (strncmp(p, tool_start, strlen(tool_start)) != 0) break; |
| 6373 | p += strlen(tool_start); |
| 6374 | |