use super::chat_completions::chat_tool_call_response_item;
use super::*;
use serde_json::Value;
impl StreamParser {
pub(super) fn reasoning_summary_delta_from_event<'a>(
&self,
value: &'a Value,
item_type: &str,
) -> Option<&'a str> {
(item_type == "response.reasoning_summary_text.delta")
.then(|| value.pointer("/delta").and_then(Value::as_str))
.flatten()
.or_else(|| {
value
.pointer("/choices/0/delta/reasoning_content")
.and_then(Value::as_str)
})
.or_else(|| {
value
.pointer("/choices/0/message/reasoning_content")
.and_then(Value::as_str)
})
}
pub(super) fn reasoning_summary_done_from_event<'a>(
&self,
value: &'a Value,
item_type: &str,
) -> Option<(&'a str, Option<&'a str>)> {
if item_type != "response.reasoning_summary_text.done" {
return None;
}
let summary = value.pointer("/text").and_then(Value::as_str)?;
let item_id = value
.pointer("/item/id")
.and_then(Value::as_str)
.or_else(|| value.get("item_id").and_then(Value::as_str))
.or_else(|| value.get("id").and_then(Value::as_str));
Some((summary, item_id))
}
pub(super) fn reconcile_reasoning_summary_complete(
&mut self,
summary: &str,
item_id: Option<&str>,
) -> Vec<ProviderEvent> {
if summary.trim().is_empty() {
return Vec::new();
}
let key = item_id
.filter(|id| !id.trim().is_empty())
.map(|id| format!("id:{id}"))
.unwrap_or_else(|| "legacy".to_string());
if item_id.is_some() {
self.saw_identified_reasoning_completion = true;
}
if self
.completed_reasoning_summary_keys
.get(&key)
.is_some_and(|previous| previous == summary)
{
return Vec::new();
}
self.completed_reasoning_summary_keys
.insert(key, summary.to_string());
let mut events = Vec::new();
if item_id.is_none() {
if let Some(suffix) = summary.strip_prefix(&self.reasoning_summary_text) {
if !suffix.is_empty() {
self.reasoning_summary_text.push_str(suffix);
events.push(ProviderEvent::ReasoningSummaryDelta(suffix.to_string()));
}
} else {
self.reasoning_summary_text.clear();
self.reasoning_summary_text.push_str(summary);
}
}
if let Some(item_id) = item_id {
events.push(ProviderEvent::ReasoningSummaryCompleteIdentified(
ReasoningSummary {
text: summary.to_string(),
item_id: Some(item_id.to_string()),
turn_id: None,
},
));
} else {
events.push(ProviderEvent::ReasoningSummaryComplete(summary.to_string()));
}
events
}
pub(super) fn process_chat_content_delta(
&mut self,
delta: &str,
) -> (Option<String>, Option<String>) {
if self.thinking_complete {
return (None, (!delta.is_empty()).then(|| delta.to_string()));
}
self.content_buffer.push_str(delta);
if let Some(close_index) = self.content_buffer.find("</think>") {
let reasoning = self.content_buffer[..close_index].to_string();
let visible_start = close_index + "</think>".len();
let visible = self.content_buffer[visible_start..].to_string();
self.content_buffer.clear();
self.thinking_complete = true;
return (
(!reasoning.is_empty()).then_some(reasoning),
(!visible.is_empty()).then_some(visible),
);
}
(None, None)
}
pub(super) fn flush_pending_chat_content(&mut self, events: &mut Vec<ProviderEvent>) {
if self.content_buffer.is_empty() {
return;
}
let text = std::mem::take(&mut self.content_buffer);
self.thinking_complete = true;
if self.gemma_inline_tool_calls_enabled
&& let Some(tool_calls) = self.parse_gemma_inline_tool_calls(&text)
{
events.push(ProviderEvent::ResponseItem(chat_tool_call_response_item(
&tool_calls,
)));
events.extend(tool_calls.into_iter().map(ProviderEvent::ToolCall));
} else {
self.emitted_text_delta = true;
events.push(ProviderEvent::TextDelta(text));
}
}
}
pub(super) fn reasoning_summary_text(item: &Value) -> Option<String> {
if item.get("type").and_then(Value::as_str) != Some("reasoning") {
return None;
}
let parts = item
.get("summary")
.and_then(Value::as_array)?
.iter()
.filter(|part| part.get("type").and_then(Value::as_str) == Some("summary_text"))
.filter_map(|part| part.get("text").and_then(Value::as_str))
.filter(|text| !text.trim().is_empty())
.map(ToString::to_string)
.collect::<Vec<_>>();
(!parts.is_empty()).then(|| parts.join("\n"))
}