use super::types::{ChatRequest, ChatResponse, ResponseMessage, StreamEvent, Usage};
use serde_json::{json, Value};
pub fn request_to_value(req: &ChatRequest) -> Value {
let mut v = json!({
"model": req.model,
"messages": req.messages,
});
if let Some(cfg) = &req.config {
if let Some(mt) = cfg.max_tokens {
v["max_tokens"] = json!(mt);
}
if let Some(t) = cfg.temperature {
v["temperature"] = json!(t);
}
if let Some(tp) = cfg.top_p {
v["top_p"] = json!(tp);
}
if let Some(tk) = cfg.top_k {
v["top_k"] = json!(tk);
}
if let Some(stop) = &cfg.stop {
v["stop"] = json!(stop);
}
if let Some(effort) = &cfg.reasoning_effort {
v["reasoning_effort"] = json!(effort);
}
if let Some(mode) = &cfg.reasoning_mode {
v["reasoning_mode"] = json!(mode);
}
}
if let Some(pc) = &req.provider_config {
if let Some(obj) = pc.as_object() {
for (k, val) in obj {
v[k.clone()] = val.clone();
}
}
}
v
}
pub fn value_to_response(v: &Value, provider: &str, latency_ms: u64) -> ChatResponse {
let choice = &v["choices"][0];
let msg = &choice["message"];
let content = msg.get("content").cloned().unwrap_or(Value::Null);
let tool_calls = msg.get("tool_calls").cloned().filter(|v| !v.is_null());
let reasoning = msg
.get("reasoning_content")
.and_then(|r| r.as_str())
.map(String::from);
let usage = extract_usage(&v["usage"]);
ChatResponse {
id: v
.get("id")
.and_then(|id| id.as_str())
.unwrap_or("")
.to_string(),
model: v
.get("model")
.and_then(|m| m.as_str())
.unwrap_or("")
.to_string(),
provider: provider.to_string(),
message: ResponseMessage {
role: "assistant".to_string(),
content,
tool_calls,
},
reasoning,
usage,
latency_ms,
}
}
pub fn extract_usage(usage: &Value) -> Usage {
let input = usage
.get("prompt_tokens")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let output = usage
.get("completion_tokens")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let reasoning = usage
.get("reasoning_tokens")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let total = usage
.get("total_tokens")
.and_then(|v| v.as_u64())
.unwrap_or(input + output);
Usage {
input_tokens: input,
output_tokens: output,
reasoning_tokens: reasoning,
total_tokens: total,
}
}
pub fn chunk_to_events(chunk_json: &str) -> Vec<StreamEvent> {
let mut events = Vec::new();
let parsed: Value = match serde_json::from_str(chunk_json) {
Ok(v) => v,
Err(_) => return events,
};
let delta = &parsed["choices"][0]["delta"];
if let Some(reasoning) = delta.get("reasoning_content").and_then(|r| r.as_str()) {
if !reasoning.is_empty() {
events.push(StreamEvent::Reasoning {
text: reasoning.to_string(),
});
}
}
if let Some(content) = delta.get("content").and_then(|c| c.as_str()) {
if !content.is_empty() {
events.push(StreamEvent::Content {
text: content.to_string(),
});
}
}
if let Some(tool_calls) = delta.get("tool_calls").and_then(|tc| tc.as_array()) {
for tc in tool_calls {
if let (Some(id), Some(name)) = (
tc.get("id").and_then(|i| i.as_str()),
tc.pointer("/function/name").and_then(|n| n.as_str()),
) {
let args = tc
.pointer("/function/arguments")
.and_then(|a| a.as_str())
.unwrap_or("")
.to_string();
events.push(StreamEvent::ToolCall {
id: id.to_string(),
name: name.to_string(),
arguments: args,
});
}
}
}
if let Some(finish) = parsed["choices"][0]
.get("finish_reason")
.and_then(|f| f.as_str())
{
if let Some(usage) = parsed.get("usage") {
events.push(StreamEvent::Usage(extract_usage(usage)));
}
let _ = finish; events.push(StreamEvent::Done {});
}
events
}