@@ -518,6 +518,10 @@ def __init__(
518518 self .stream_delta_callback = stream_delta_callback
519519 self ._last_reported_tool = None # Track for "new tool" mode
520520
521+ # Tool execution state — allows _vprint during tool execution
522+ # even when stream consumers are registered (no tokens streaming then)
523+ self ._executing_tools = False
524+
521525 # Interrupt mechanism for breaking out of tool loops
522526 self ._interrupt_requested = False
523527 self ._interrupt_message = None # Optional message that triggered interrupt
@@ -1068,12 +1072,16 @@ def _safe_print(*args, **kwargs):
10681072 pass
10691073
10701074 def _vprint (self , * args , force : bool = False , ** kwargs ):
1071- """Verbose print — suppressed when streaming TTS is active .
1075+ """Verbose print — suppressed when actively streaming tokens .
10721076
10731077 Pass ``force=True`` for error/warning messages that should always be
10741078 shown even during streaming playback (TTS or display).
1079+
1080+ During tool execution (``_executing_tools`` is True), printing is
1081+ allowed even with stream consumers registered because no tokens
1082+ are being streamed at that point.
10751083 """
1076- if not force and self ._has_stream_consumers ():
1084+ if not force and self ._has_stream_consumers () and not self . _executing_tools :
10771085 return
10781086 self ._safe_print (* args , ** kwargs )
10791087
@@ -4354,14 +4362,19 @@ def _execute_tool_calls(self, assistant_message, messages: list, effective_task_
43544362 """
43554363 tool_calls = assistant_message .tool_calls
43564364
4357- if not _should_parallelize_tool_batch (tool_calls ):
4358- return self ._execute_tool_calls_sequential (
4365+ # Allow _vprint during tool execution even with stream consumers
4366+ self ._executing_tools = True
4367+ try :
4368+ if not _should_parallelize_tool_batch (tool_calls ):
4369+ return self ._execute_tool_calls_sequential (
4370+ assistant_message , messages , effective_task_id , api_call_count
4371+ )
4372+
4373+ return self ._execute_tool_calls_concurrent (
43594374 assistant_message , messages , effective_task_id , api_call_count
43604375 )
4361-
4362- return self ._execute_tool_calls_concurrent (
4363- assistant_message , messages , effective_task_id , api_call_count
4364- )
4376+ finally :
4377+ self ._executing_tools = False
43654378
43664379 def _invoke_tool (self , function_name : str , function_args : dict , effective_task_id : str ) -> str :
43674380 """Invoke a single tool and return the result string. No display logic.
@@ -5418,14 +5431,17 @@ def run_conversation(
54185431 self ._vprint (f"\n { self .log_prefix } 🔄 Making API call #{ api_call_count } /{ self .max_iterations } ..." )
54195432 self ._vprint (f"{ self .log_prefix } 📊 Request size: { len (api_messages )} messages, ~{ approx_tokens :,} tokens (~{ total_chars :,} chars)" )
54205433 self ._vprint (f"{ self .log_prefix } 🔧 Available tools: { len (self .tools ) if self .tools else 0 } " )
5421- elif not self . _has_stream_consumers () :
5422- # Animated thinking spinner in quiet mode (skip during streaming)
5434+ else :
5435+ # Animated thinking spinner in quiet mode
54235436 face = random .choice (KawaiiSpinner .KAWAII_THINKING )
54245437 verb = random .choice (KawaiiSpinner .THINKING_VERBS )
54255438 if self .thinking_callback :
54265439 # CLI TUI mode: use prompt_toolkit widget instead of raw spinner
5440+ # (works in both streaming and non-streaming modes)
54275441 self .thinking_callback (f"{ face } { verb } ..." )
5428- else :
5442+ elif not self ._has_stream_consumers ():
5443+ # Raw KawaiiSpinner only when no streaming consumers
5444+ # (would conflict with streamed token output)
54295445 spinner_type = random .choice (['brain' , 'sparkle' , 'pulse' , 'moon' , 'star' ])
54305446 thinking_spinner = KawaiiSpinner (f"{ face } { verb } ..." , spinner_type = spinner_type )
54315447 thinking_spinner .start ()
0 commit comments