use reqwest::Client;
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use serde::{Deserialize, Serialize};
use futures_util::TryStreamExt;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Message {
pub role: String,
pub content: String,
}
#[derive(Serialize)]
struct Request {
model: String,
input: String,
instructions: String,
stream: bool,
tools: Vec<Tool>,
previous_response_id: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Tool {
#[serde(rename = "type")]
pub tool_type: String,
}
#[derive(Deserialize, Debug, Clone, Serialize)]
pub struct UsageInfo {
pub input_tokens: Option<u32>,
pub input_tokens_details: Option<InputTokensDetails>,
pub output_tokens: Option<u32>,
pub output_tokens_details: Option<OutputTokensDetails>,
pub total_tokens: Option<u32>,
}
#[derive(Deserialize, Debug, Clone, Serialize)]
pub struct InputTokensDetails {
pub cached_tokens: Option<u32>,
}
#[derive(Deserialize, Debug, Clone, Serialize)]
pub struct OutputTokensDetails {
pub reasoning_tokens: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ResponseError {
RequestError(String),
ParseError(String),
NetworkError(String),
Unknown(String),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum SendChatResult {
Ok(SendChatOK), Err(ResponseError),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SendChatOK {
pub model: String,
pub message: String,
pub id: String,
pub usage: UsageInfo,
pub tools: Vec<Tool>,
}
pub async fn send_chat<F>(
instructions: &str,
input: &str,
api_key: &str,
model: &str,
handler: F,
previous_response_id: Option<&str>, ) -> SendChatResult
where
F: Fn(Option<&UsageInfo>, Option<&ResponseError>, Option<&serde_json::Value>) -> ()
{
let client: Client = Client::new();
let mut tools = vec![];
match model {
"gpt-4.1-nano" => {
tools = vec![];
},
"gpt-4.1-mini" => {
tools = vec![Tool { tool_type: "web_search_preview".to_string() }];
},
"gpt-4.1" => {
tools = vec![Tool { tool_type: "web_search_preview".to_string() }];
},
"gpt-4o-mini" => {
tools = vec![Tool { tool_type: "web_search_preview".to_string() }];
},
"gpt-4o-mini-search-preview" => {
tools = vec![Tool { tool_type: "web_search_preview".to_string() }];
},
"gpt-4o" => {
tools = vec![Tool { tool_type: "web_search_preview".to_string() }];
},
"gpt-4o-search-preview" => {
tools = vec![Tool { tool_type: "web_search_preview".to_string() }];
}
_ => {}
}
let body = Request {
model: model.to_string(),
instructions: instructions.to_string(),
input: input.to_string(),
stream:true,
tools: tools,
previous_response_id: previous_response_id.map(|id| id.to_string()),
};
let res = match client
.post("https://api.openai.com/v1/responses")
.header(AUTHORIZATION, format!("Bearer {}", api_key))
.header(CONTENT_TYPE, "application/json")
.json(&body)
.send()
.await {
Ok(res) => {
println!("***Response status: {}", res.status());
res
},
Err(e) => {
let err = ResponseError::NetworkError(e.to_string());
handler(None, Some(&err), None);
return SendChatResult::Err(err);
}
};
println!("Response status: {}", res.status());
if !res.status().is_success() {
let error_text = match res.text().await {
Ok(text) => text,
Err(e) => format!("Failed to get error response: {}", e),
};
println!("Error response: {}", error_text);
return SendChatResult::Err(ResponseError::NetworkError(format!("API request failed: {}", error_text)));
}
let mut stream = res.bytes_stream();
let mut complete_response = String::new();
let mut final_response = SendChatOK { message: "".to_string(), id: "".to_string(), model: model.to_string(), usage: UsageInfo { input_tokens: None, input_tokens_details: None, output_tokens: None, output_tokens_details: None, total_tokens: None }, tools: vec![] };
loop {
match stream.try_next().await {
Ok(Some(chunk)) => {
let text = match std::str::from_utf8(&chunk) {
Ok(t) => {
println!("chunk: {}", t);
complete_response.push_str(t);
t
},
Err(e) => {
let err = ResponseError::ParseError(format!("Failed to parse UTF-8: {}", e));
handler(None, Some(&err), None);
continue;
}
};
for line in text.lines() {
if line.starts_with("data: ") {
let payload = &line[6..];
if let Ok(value) = serde_json::from_str::<serde_json::Value>(payload) {
match value.get("type").and_then(|t| t.as_str()) {
Some("response.output_text.delta") => {
if let Some(delta) = value.get("delta") {
println!("***delta: {:?}", delta);
handler(None, None, Some(&delta));
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) {
let err = ResponseError::Unknown(format!("Failed to flush stdout: {}", e));
handler(None, Some(&err), Some(&delta));
}
}
},
Some("response.output_text.done") => {
println!("\n=== Response Output Text Done Event ===");
if let Ok(pretty_json) = serde_json::to_string_pretty(&value) {
println!("{}", pretty_json);
} else {
println!("Raw value: {:?}", value);
}
},
Some("response.content_part.done") => {
println!("\n=== Response Content Part Done Event ===");
if let Ok(pretty_json) = serde_json::to_string_pretty(&value) {
println!("{}", pretty_json);
} else {
println!("Raw value: {:?}", value);
}
if let Some(part) = value.get("part") {
if let Some(text) = part.get("text") {
if let Some(message) = text.as_str() {
final_response.message = message.to_string();
}
}
}
println!("===============================\n");
},
Some("response.output_item.done") => {
println!("\n=== Response Output Item Done Event ===");
if let Ok(pretty_json) = serde_json::to_string_pretty(&value) {
println!("{}", pretty_json);
} else {
println!("Raw value: {:?}", value);
}
println!("===============================\n");
},
Some("response.completed") => {
println!("\n=== Response Completed Event ===");
if let Ok(pretty_json) = serde_json::to_string_pretty(&value) {
println!("{}", pretty_json);
} else {
println!("Raw value: {:?}", value);
}
if let Some(response_value) = value.get("response") {
if let Some(id) = response_value.get("id").and_then(|id| id.as_str()) {
final_response.id = id.to_string();
}
if let Some(usage) = response_value.get("usage").and_then(|usage| serde_json::from_value::<UsageInfo>(usage.clone()).ok()) {
final_response.usage = usage;
}
if let Some(tools) = response_value.get("tools").and_then(|tools| serde_json::from_value::<Vec<Tool>>(tools.clone()).ok()) {
final_response.tools = tools;
}
}
println!("final_response: {:?}", final_response);
println!("===============================\n");
return SendChatResult::Ok(final_response);
},
_ => {
println!("value is {:?}", value);
}
}
}
}
}
},
Ok(None) => {
println!("\n[Stream ended unexpectedly]");
return SendChatResult::Err(ResponseError::Unknown("Stream ended unexpectedly".to_string()));
},
Err(e) => {
let err = ResponseError::NetworkError(e.to_string());
handler(None, Some(&err), None);
return SendChatResult::Err(err);
}
}
}
}