use serde_json::{Value, json};
use super::AnthropicAdapter;
impl AnthropicAdapter {
pub(super) fn response_to_chat_completions(response: Value) -> Value {
let blocks = response
.get("content")
.and_then(|c| c.as_array())
.cloned()
.unwrap_or_default();
let content: String = blocks
.iter()
.filter_map(|b| {
if b.get("type").and_then(|t| t.as_str()) == Some("text") {
b.get("text").and_then(|t| t.as_str()).map(String::from)
} else {
None
}
})
.collect::<Vec<_>>()
.join("");
let thinking_blocks: Vec<Value> = blocks
.iter()
.filter(|b| b.get("type").and_then(|t| t.as_str()) == Some("thinking"))
.cloned()
.collect();
let thinking_parts: Vec<String> = thinking_blocks
.iter()
.filter_map(|b| b.get("thinking").and_then(|t| t.as_str()).map(String::from))
.collect();
let reasoning_content = if thinking_parts.is_empty() {
None
} else {
Some(thinking_parts.join("\n\n"))
};
let tool_calls: Vec<Value> = blocks
.iter()
.filter_map(|b| {
if b.get("type").and_then(|t| t.as_str()) == Some("tool_use") {
let id = b.get("id").and_then(|i| i.as_str()).unwrap_or("");
let name = b.get("name").and_then(|n| n.as_str()).unwrap_or("");
let input = b.get("input").cloned().unwrap_or(json!({}));
Some(json!({
"id": id,
"type": "function",
"function": {
"name": name,
"arguments": serde_json::to_string(&input).unwrap_or_default()
}
}))
} else {
None
}
})
.collect();
let model = response
.get("model")
.and_then(|m| m.as_str())
.unwrap_or("unknown");
let usage = response.get("usage").cloned().unwrap_or(json!({}));
let stop_reason = response
.get("stop_reason")
.and_then(|r| r.as_str())
.unwrap_or("stop");
let finish_reason = match stop_reason {
"end_turn" => "stop",
"max_tokens" => "length",
"tool_use" => "tool_calls",
other => other,
};
let mut message = json!({
"role": "assistant",
"content": content
});
if !tool_calls.is_empty() {
message["tool_calls"] = json!(tool_calls);
}
if let Some(ref reasoning) = reasoning_content {
message["reasoning_content"] = json!(reasoning);
}
if !thinking_blocks.is_empty() {
message["_thinking_blocks"] = json!(thinking_blocks);
}
json!({
"id": response.get("id").cloned().unwrap_or(json!("")),
"object": "chat.completion",
"model": model,
"choices": [{
"index": 0,
"message": message,
"finish_reason": finish_reason
}],
"usage": {
"prompt_tokens": usage.get("input_tokens").cloned().unwrap_or(json!(0)),
"completion_tokens": usage.get("output_tokens").cloned().unwrap_or(json!(0)),
"total_tokens": usage.get("input_tokens").and_then(|i| i.as_u64())
.unwrap_or(0)
+ usage.get("output_tokens").and_then(|o| o.as_u64())
.unwrap_or(0)
}
})
}
pub(super) fn parse_stream_event_impl(
&self,
event_type: &str,
data: &Value,
) -> Option<crate::streaming::StreamEvent> {
use crate::streaming::StreamEvent;
match event_type {
"content_block_delta" => {
let delta = data.get("delta")?;
let delta_type = delta.get("type")?.as_str()?;
match delta_type {
"text_delta" => {
let text = delta.get("text")?.as_str()?;
Some(StreamEvent::TextDelta(text.to_string()))
}
"thinking_delta" => {
let text = delta.get("thinking")?.as_str()?;
Some(StreamEvent::ReasoningDelta(text.to_string()))
}
"input_json_delta" => {
let index =
data.get("index").and_then(|i| i.as_u64()).unwrap_or(0) as usize;
let partial = delta
.get("partial_json")
.and_then(|p| p.as_str())
.unwrap_or("")
.to_string();
Some(StreamEvent::FunctionCallDelta {
index,
delta: partial,
})
}
_ => None,
}
}
"message_stop" => None,
"message_start" => None,
"message_delta" => {
let usage = data.get("usage").cloned();
let stop_reason = data
.get("delta")
.and_then(|d| d.get("stop_reason"))
.and_then(|s| s.as_str())
.map(String::from);
if usage.is_some() || stop_reason.is_some() {
Some(StreamEvent::UsageUpdate { usage, stop_reason })
} else {
None
}
}
"content_block_start" => {
let cb = data.get("content_block")?;
let block_type = cb.get("type").and_then(|t| t.as_str())?;
match block_type {
"thinking" => Some(StreamEvent::ReasoningBlockStart),
"tool_use" => {
let index =
data.get("index").and_then(|i| i.as_u64()).unwrap_or(0) as usize;
let call_id = cb
.get("id")
.and_then(|i| i.as_str())
.unwrap_or("")
.to_string();
let name = cb
.get("name")
.and_then(|n| n.as_str())
.unwrap_or("")
.to_string();
Some(StreamEvent::FunctionCallStart {
index,
call_id,
name,
})
}
_ => None,
}
}
"content_block_stop" => None,
"ping" => None,
"error" => {
let error = data.get("error")?;
let msg = error.get("message")?.as_str()?;
Some(StreamEvent::Error(msg.to_string()))
}
_ => None,
}
}
}