@@ -508,8 +508,20 @@ def _retrieve_failure(query: str, n: int = 3) -> list[str]:
508508_last_prompt_tokens : int = 0
509509_last_completion_tokens : int = 0
510510_active_usage_run_id : ContextVar [str | None ] = ContextVar ("active_usage_run_id" , default = None )
511+ _stream_event_callback : ContextVar [Any ] = ContextVar ("stream_event_callback" , default = None )
511512_turn_cost_log : list [dict ] = [] # {"tokens":int, "time":float, "tool_calls":int}
512513
514+
515+ def _emit_stream_event (event : dict [str , Any ]) -> None :
516+ """Forward typed stream progress without letting a client affect the turn."""
517+ callback = _stream_event_callback .get ()
518+ if not callable (callback ):
519+ return
520+ try :
521+ callback (event )
522+ except Exception :
523+ pass
524+
513525def _track_tool_performance (action : str , result : str , elapsed : float ) -> None :
514526 stats = _tool_stats .setdefault (action , {"calls" :0 ,"successes" :0 ,"total_time" :0.0 })
515527 stats ["calls" ] += 1
@@ -997,6 +1009,13 @@ def _spinner_worker(stop_event: threading.Event) -> None:
9971009
9981010def _call_llm_with_spinner (messages : list [dict ], model : str | None = None ) -> str :
9991011 global _SPINNER_STOP , _SPINNER_THREAD
1012+ streaming = callable (_stream_event_callback .get ())
1013+ if streaming :
1014+ return _get_llm_response (
1015+ messages , model = model , stream = True ,
1016+ on_chunk = lambda chunk : _emit_stream_event ({"event" : "content" , "chunk" : str (chunk )}),
1017+ on_stream_end = lambda : _emit_stream_event ({"event" : "model_complete" }),
1018+ )
10001019 _SPINNER_STOP .clear ()
10011020 _SPINNER_THREAD = threading .Thread (target = _spinner_worker , args = (_SPINNER_STOP ,), daemon = True )
10021021 _SPINNER_THREAD .start ()
@@ -3303,12 +3322,18 @@ def _record_turn_receipt(receipt: ExecutionReceipt) -> dict[str, Any]:
33033322 "execution.receipt" , receipt .as_dict (), user_id = tasks .user_id ,
33043323 workspace_id = tasks .workspace_id , session_id = tasks .session_id , task_id = task_id ,
33053324 )
3306- return {
3325+ result = {
33073326 "receipt_id" : receipt .receipt_id , "operation_id" : receipt .operation_id ,
33083327 "action" : receipt .action , "args" : receipt .args , "result" : receipt .result ,
33093328 "success" : receipt .success , "authorized" : receipt .authorized ,
33103329 "acceptance" : receipt .acceptance , "failure" : receipt .failure ,
33113330 }
3331+ _emit_stream_event ({"event" : "tool_receipt" , "tool_receipt" : result })
3332+ _emit_stream_event ({"event" : "tasks" , "tasks" : [
3333+ {"id" : item ["id" ], "description" : item ["description" ], "status" : item ["status" ]}
3334+ for item in tasks .tasks
3335+ ]})
3336+ return result
33123337
33133338
33143339def _execute_durable_task (task : dict [str , Any ]) -> dict [str , Any ]:
@@ -3449,7 +3474,8 @@ def run() -> None:
34493474 return value
34503475
34513476
3452- def _get_llm_response (messages : list [dict [str , str ]], model : str | None = None , stream : bool = False ) -> str :
3477+ def _get_llm_response (messages : list [dict [str , str ]], model : str | None = None , stream : bool = False ,
3478+ on_chunk : Any = None , on_stream_end : Any = None ) -> str :
34533479 global _last_prompt_tokens , _last_completion_tokens , _total_prompt_tokens , _total_completion_tokens
34543480 if llm_provider is None :
34553481 return "[Error] LLM provider not initialised"
@@ -3459,15 +3485,29 @@ def _get_llm_response(messages: list[dict[str, str]], model: str | None = None,
34593485 workspace_id = memory_bank .workspace_id , session_id = memory_bank .session_id ,
34603486 run_id = _active_usage_run_id .get (), surface = _EXECUTION_SURFACE ):
34613487 if stream and hasattr (llm_provider , 'chat_stream' ):
3462- # Streaming usage frames are persisted by the streaming implementation.
3488+ before = memory_bank .store .usage_totals (
3489+ user_id = memory_bank .user_id , workspace_id = memory_bank .workspace_id ,
3490+ session_id = memory_bank .session_id , run_id = _active_usage_run_id .get (),
3491+ )
34633492 collected : list [str ] = []
34643493 for chunk in llm_provider .chat_stream (messages , model or DEEPSEEK_MODEL ):
34653494 collected .append (chunk )
3466- sys .stdout .write (chunk )
3467- sys .stdout .flush ()
3495+ if on_chunk :
3496+ on_chunk (chunk )
3497+ else :
3498+ sys .stdout .write (chunk )
3499+ sys .stdout .flush ()
34683500 text = "" .join (collected ).strip ()
3469- _last_prompt_tokens = 0
3470- _last_completion_tokens = len (text ) // 4 # presentation-only until a usage frame arrives
3501+ after = memory_bank .store .usage_totals (
3502+ user_id = memory_bank .user_id , workspace_id = memory_bank .workspace_id ,
3503+ session_id = memory_bank .session_id , run_id = _active_usage_run_id .get (),
3504+ )
3505+ _last_prompt_tokens = max (0 , int (after ["prompt_tokens" ]) - int (before ["prompt_tokens" ]))
3506+ _last_completion_tokens = max (0 , int (after ["completion_tokens" ]) - int (before ["completion_tokens" ]))
3507+ _total_prompt_tokens += _last_prompt_tokens
3508+ _total_completion_tokens += _last_completion_tokens
3509+ if on_stream_end :
3510+ on_stream_end ()
34713511 else :
34723512 text , usage_dict = _bounded_provider_call (
34733513 lambda : llm_provider .chat (messages , model or DEEPSEEK_MODEL )
0 commit comments