@@ -655,8 +655,32 @@ impl LlmClient for OpenAiClient {
655655 if let Some ( message) = choice. message {
656656 if let Some ( reasoning) = message. reasoning_content {
657657 reasoning_content_accum. push_str ( & reasoning) ;
658+ // For models like kimi that use reasoning_content for actual text output,
659+ // emit it as TextDelta when delta_content is empty (None or "")
660+ let delta_content_empty = delta_content
661+ . as_deref ( )
662+ . map_or ( true , str:: is_empty) ;
663+ if delta_content_empty {
664+ if first_token_ms. is_none ( ) {
665+ first_token_ms = Some (
666+ request_started_at. elapsed ( ) . as_millis ( )
667+ as u64 ,
668+ ) ;
669+ }
670+ if let Some ( delta) = Self :: merge_stream_text (
671+ & mut text_content,
672+ & reasoning,
673+ ) {
674+ let _ = tx
675+ . send ( StreamEvent :: ReasoningDelta (
676+ delta,
677+ ) )
678+ . await ;
679+ }
680+ }
658681 }
659- if delta_content. as_deref ( ) . map_or ( true , str:: is_empty) {
682+ if delta_content. as_deref ( ) . map_or ( true , str:: is_empty)
683+ {
660684 if let Some ( content) = message
661685 . content
662686 . filter ( |value| !value. is_empty ( ) )
@@ -694,6 +718,30 @@ impl LlmClient for OpenAiClient {
694718 if let Some ( delta) = choice. delta {
695719 if let Some ( ref rc) = delta. reasoning_content {
696720 reasoning_content_accum. push_str ( rc) ;
721+ // For models like kimi that use reasoning_content for actual text output,
722+ // emit it as TextDelta when content is empty (None or "")
723+ let content_empty = delta
724+ . content
725+ . as_deref ( )
726+ . map_or ( true , |s| s. is_empty ( ) ) ;
727+ if content_empty {
728+ if first_token_ms. is_none ( ) {
729+ first_token_ms = Some (
730+ request_started_at. elapsed ( ) . as_millis ( )
731+ as u64 ,
732+ ) ;
733+ }
734+ if let Some ( delta) = Self :: merge_stream_text (
735+ & mut text_content,
736+ rc,
737+ ) {
738+ let _ = tx
739+ . send ( StreamEvent :: ReasoningDelta (
740+ delta,
741+ ) )
742+ . await ;
743+ }
744+ }
697745 }
698746
699747 if let Some ( content) = delta. content {
@@ -833,6 +881,23 @@ impl LlmClient for OpenAiClient {
833881 if let Some ( delta) = choice. delta {
834882 if let Some ( ref rc) = delta. reasoning_content {
835883 reasoning_content_accum. push_str ( rc) ;
884+ // For models like kimi that use reasoning_content for actual text output,
885+ // emit it as TextDelta when content is empty (None or "")
886+ let content_empty =
887+ delta. content . as_deref ( ) . map_or ( true , |s| s. is_empty ( ) ) ;
888+ if content_empty {
889+ if first_token_ms. is_none ( ) {
890+ first_token_ms = Some (
891+ request_started_at. elapsed ( ) . as_millis ( ) as u64 ,
892+ ) ;
893+ }
894+ if let Some ( delta) =
895+ Self :: merge_stream_text ( & mut text_content, rc)
896+ {
897+ let _ =
898+ tx. send ( StreamEvent :: ReasoningDelta ( delta) ) . await ;
899+ }
900+ }
836901 }
837902 if let Some ( content) = delta. content {
838903 if first_token_ms. is_none ( ) {
@@ -857,10 +922,7 @@ impl LlmClient for OpenAiClient {
857922 usage. total_tokens = response. usage . total_tokens ;
858923 // MiniMax: fall back to total_characters when total_tokens is 0.
859924 if usage. total_tokens == 0 {
860- usage. total_tokens = response
861- . usage
862- . total_characters
863- . unwrap_or ( 0 ) ;
925+ usage. total_tokens = response. usage . total_characters . unwrap_or ( 0 ) ;
864926 }
865927 usage. cache_read_tokens = response
866928 . usage
0 commit comments