@@ -156,6 +156,20 @@ void common_chat_msg_parser::add_reasoning_content(const std::string &reasoning_
156156 result_.reasoning_content += reasoning_content;
157157}
158158
159+ void common_chat_msg_parser::mark_reasoning_active (const std::string & end_tag) {
160+ result_.reasoning_status .detected = true ;
161+ result_.reasoning_status .active = true ;
162+ if (!end_tag.empty ()) {
163+ result_.reasoning_status .end_tag = end_tag;
164+ }
165+ }
166+
167+ void common_chat_msg_parser::mark_reasoning_closed () {
168+ if (result_.reasoning_status .detected ) {
169+ result_.reasoning_status .active = false ;
170+ }
171+ }
172+
159173bool common_chat_msg_parser::add_tool_call (const std::string & name, const std::string & id, const std::string & arguments) {
160174 if (name.empty ()) {
161175 return false ;
@@ -329,11 +343,13 @@ bool common_chat_msg_parser::try_parse_reasoning(const std::string & start_think
329343 const size_t saved_pos = pos_;
330344 const size_t saved_content_size = result_.content .size ();
331345 const size_t saved_reasoning_size = result_.reasoning_content .size ();
346+ const auto saved_reasoning_status = result_.reasoning_status ;
332347
333348 auto restore_state = [&]() {
334349 move_to (saved_pos);
335350 result_.content .resize (saved_content_size);
336351 result_.reasoning_content .resize (saved_reasoning_size);
352+ result_.reasoning_status = saved_reasoning_status;
337353 };
338354
339355 // Allow leading whitespace to be preserved as content when reasoning is present at the start
@@ -370,9 +386,11 @@ bool common_chat_msg_parser::try_parse_reasoning(const std::string & start_think
370386 if (whitespace_end > pos_) {
371387 add_content (input_.substr (pos_, whitespace_end - pos_));
372388 }
389+ mark_reasoning_active (end_think);
373390 set_reasoning_prefix (cursor);
374391 cursor += start_think.size ();
375392 } else if (syntax_.thinking_forced_open ) {
393+ mark_reasoning_active (end_think);
376394 cursor = whitespace_end;
377395 } else {
378396 restore_state ();
@@ -398,8 +416,10 @@ bool common_chat_msg_parser::try_parse_reasoning(const std::string & start_think
398416
399417 if (end_pos > cursor) {
400418 handle_reasoning (input_.substr (cursor, end_pos - cursor), /* closed */ true );
419+ mark_reasoning_closed ();
401420 } else {
402421 handle_reasoning (" " , /* closed */ true );
422+ mark_reasoning_closed ();
403423 }
404424
405425 cursor = end_pos + end_think.size ();
@@ -420,6 +440,7 @@ bool common_chat_msg_parser::try_parse_reasoning(const std::string & start_think
420440 move_to (input_.size ());
421441 return true ;
422442 }
443+ mark_reasoning_active (end_think);
423444 set_reasoning_prefix (cursor);
424445 cursor += start_think.size ();
425446 continue ;
@@ -1492,17 +1513,24 @@ common_chat_msg common_chat_parse(const std::string & input, bool is_partial, co
14921513 return common_chat_peg_parse (syntax.parser , input, is_partial, syntax);
14931514 }
14941515 common_chat_msg_parser builder (input, is_partial, syntax);
1516+ bool partial_exception_caught = false ;
14951517 try {
14961518 common_chat_parse (builder);
14971519 } catch (const common_chat_msg_partial_exception & ex) {
14981520 LOG_DBG (" Partial parse: %s\n " , ex.what ());
1521+ partial_exception_caught = true ;
14991522 if (!is_partial) {
15001523 builder.clear_tools ();
15011524 builder.move_to (0 );
15021525 common_chat_parse_content_only (builder);
15031526 }
15041527 }
15051528 auto msg = builder.result ();
1529+ // Mark tool_call_in_progress if we caught a partial exception during partial parsing
1530+ // and there are tool calls in progress (indicates incomplete tool call parsing)
1531+ if (is_partial && partial_exception_caught && !msg.tool_calls .empty ()) {
1532+ msg.tool_call_in_progress = true ;
1533+ }
15061534 if (!is_partial) {
15071535 LOG_DBG (" Parsed message: %s\n " , common_chat_msgs_to_json_oaicompat<json>({msg}).at (0 ).dump ().c_str ());
15081536 }
0 commit comments