use serde_json::Value;
use crate::content::ContentPart;
use crate::message::{MessageContent, ModelMessage, Role};
use crate::result::ReasoningPart;
pub(crate) fn response_tool_call_input(input: &Value, invalid: Option<bool>) -> Value {
if invalid == Some(true) && !matches!(input, Value::Object(_) | Value::Array(_) | Value::Null) {
Value::Object(serde_json::Map::new())
} else {
input.clone()
}
}
pub(crate) fn extract_reasoning_signature(provider_metadata: Option<&Value>) -> Option<String> {
let metadata = provider_metadata?;
["anthropic", "bedrock", "amazonBedrock"]
.iter()
.find_map(|ns| metadata.get(ns)?.get("signature")?.as_str())
.map(str::to_owned)
}
#[derive(Default)]
pub(crate) struct ResponseMessageBuilder {
parts: Vec<ContentPart>,
text_buf: String,
text_provider_options: Option<Value>,
reasoning_buf: String,
reasoning_provider_options: Option<Value>,
reasoning: Vec<ReasoningPart>,
}
pub(crate) struct ResponseMessages {
pub messages: Vec<ModelMessage>,
pub reasoning: Vec<ReasoningPart>,
}
impl ResponseMessageBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn text_start(&mut self, provider_metadata: Option<Value>) {
self.flush_reasoning();
self.flush_text();
self.text_provider_options = provider_metadata;
}
pub fn text_delta(&mut self, delta: &str, provider_metadata: Option<Value>) {
self.flush_reasoning();
self.text_buf.push_str(delta);
if provider_metadata.is_some() {
self.text_provider_options = provider_metadata;
}
}
pub fn text_end(&mut self, provider_metadata: Option<Value>) {
if provider_metadata.is_some() {
self.text_provider_options = provider_metadata;
}
self.flush_text();
}
pub fn reasoning_start(&mut self, provider_metadata: Option<Value>) {
self.flush_text();
self.flush_reasoning();
self.reasoning_provider_options = provider_metadata;
}
pub fn reasoning_delta(&mut self, delta: &str, provider_metadata: Option<Value>) {
self.flush_text();
self.reasoning_buf.push_str(delta);
if provider_metadata.is_some() {
self.reasoning_provider_options = provider_metadata;
}
}
pub fn reasoning_end(&mut self, provider_metadata: Option<Value>) {
self.flush_text();
if provider_metadata.is_some() {
self.reasoning_provider_options = provider_metadata;
}
self.flush_reasoning();
}
pub fn text(&mut self, text: &str, provider_metadata: Option<&Value>) {
if !text.is_empty() {
self.parts.push(ContentPart::Text {
text: text.to_owned(),
provider_options: provider_metadata.cloned(),
});
}
}
pub fn reasoning(&mut self, text: &str, provider_metadata: Option<&Value>) {
self.reasoning.push(ReasoningPart {
text: text.to_owned(),
});
let signature = extract_reasoning_signature(provider_metadata);
self.parts.push(ContentPart::Reasoning {
text: text.to_owned(),
signature,
provider_options: provider_metadata.cloned(),
});
}
pub fn tool_call(&mut self, call: &crate::tool::ToolCall) {
self.flush_text();
self.flush_reasoning();
self.parts.push(ContentPart::ToolCall {
tool_call_id: call.tool_call_id.clone(),
tool_name: call.tool_name.clone(),
input: response_tool_call_input(&call.input, call.invalid),
provider_executed: call.provider_executed,
thought_signature: call.thought_signature.clone(),
provider_options: call.provider_metadata.clone(),
});
}
#[allow(clippy::too_many_arguments)]
pub fn tool_result(
&mut self,
tool_call_id: String,
tool_name: String,
result: Value,
is_error: Option<bool>,
preliminary: Option<bool>,
dynamic: Option<bool>,
provider_options: Option<Value>,
) {
if preliminary == Some(true) {
return;
}
self.flush_text();
self.flush_reasoning();
self.parts.push(ContentPart::ToolResult {
tool_call_id,
tool_name: Some(tool_name),
result,
is_error,
preliminary,
dynamic,
provider_options,
});
}
pub fn finish(mut self) -> ResponseMessages {
self.flush_text();
self.flush_reasoning();
let messages = if self.parts.is_empty() {
Vec::new()
} else {
vec![ModelMessage {
role: Role::Assistant,
content: MessageContent::Parts(self.parts),
}]
};
ResponseMessages {
messages,
reasoning: self.reasoning,
}
}
fn flush_text(&mut self) {
if !self.text_buf.is_empty() {
self.parts.push(ContentPart::Text {
text: std::mem::take(&mut self.text_buf),
provider_options: self.text_provider_options.take(),
});
} else {
self.text_provider_options = None;
}
}
fn flush_reasoning(&mut self) {
if self.reasoning_buf.is_empty() && self.reasoning_provider_options.is_none() {
return;
}
let text = std::mem::take(&mut self.reasoning_buf);
if !text.is_empty() {
self.reasoning.push(ReasoningPart { text: text.clone() });
}
let provider_options = self.reasoning_provider_options.take();
let signature = extract_reasoning_signature(provider_options.as_ref());
self.parts.push(ContentPart::Reasoning {
text,
signature,
provider_options,
});
}
}