use reqwest::Client;
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use serde::{Deserialize, Serialize};
use futures_util::TryStreamExt;
#[derive(Serialize)]
struct ResponseRequest {
model: String,
input: String,
instructions: String,
stream: bool,
}
#[derive(Deserialize, Debug)]
struct StreamChunk {
output: Option<Vec<OutputItem>>,
usage: Option<UsageInfo>,
#[serde(rename = "type")]
chunk_type: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct UsageInfo {
pub input_tokens: Option<u32>,
pub output_tokens: Option<u32>,
pub total_tokens: Option<u32>,
}
#[derive(Deserialize, Debug)]
struct OutputItem {
content: Option<Vec<ContentItem>>,
}
#[derive(Deserialize, Debug)]
struct ContentItem {
text: Option<String>,
}
fn print_usage(usage: &UsageInfo) {
println!("\n[Usage Information]");
println!("Input tokens: {}", usage.input_tokens.unwrap_or(0));
println!("Output tokens: {}", usage.output_tokens.unwrap_or(0));
println!("Total tokens: {}", usage.total_tokens.unwrap_or(0));
}
async fn get_final_usage(api_key: &str, model: &str, instructions: &str, input: &str) -> Result<Option<UsageInfo>, Box<dyn std::error::Error>> {
let client: Client = Client::new();
let body = ResponseRequest {
model: model.to_string(),
instructions: instructions.to_string(),
input: input.to_string(),
stream: false, };
let res = client
.post("https://api.openai.com/v1/responses")
.header(AUTHORIZATION, format!("Bearer {}", api_key))
.header(CONTENT_TYPE, "application/json")
.json(&body)
.send()
.await?;
if res.status().is_success() {
let response_body = res.text().await?;
if let Ok(value) = serde_json::from_str::<serde_json::Value>(&response_body) {
if let Some(usage_data) = value.get("usage") {
if let Ok(usage) = serde_json::from_value::<UsageInfo>(usage_data.clone()) {
return Ok(Some(usage));
}
}
}
}
Ok(None)
}
#[derive(Debug, Clone)]
pub enum ResponseError {
RequestError(String),
ParseError(String),
NetworkError(String),
Unknown(String),
}
pub async fn send_chat<F>(
instructions: &str,
input: &str,
api_key: &str,
model: &str,
stream: bool,
handler: F
) -> Result<(), Box<dyn std::error::Error>>
where
F: Fn(Option<&UsageInfo>, Option<&ResponseError>, Option<&serde_json::Value>) -> ()
{
let client: Client = Client::new();
let body = ResponseRequest {
model: model.to_string(),
instructions: instructions.to_string(),
input: input.to_string(),
stream,
};
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) => res,
Err(e) => {
let err = ResponseError::NetworkError(e.to_string());
handler(None, Some(&err), None);
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())));
}
};
println!("Response status: {}", res.status());
if !stream {
let response_body = match res.text().await {
Ok(body) => body,
Err(e) => {
let err = ResponseError::NetworkError(format!("Failed to get response body: {}", e));
handler(None, Some(&err), None);
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())));
}
};
if let Ok(value) = serde_json::from_str::<serde_json::Value>(&response_body) {
if let Some(output) = value.get("output") {
if let Some(output_array) = output.as_array() {
for item in output_array {
if let Some(content) = item.get("content") {
if let Some(content_array) = content.as_array() {
for c in content_array {
if let Some(text) = c.get("text").and_then(|t| t.as_str()) {
print!("{}", text);
}
}
}
}
}
}
}
let mut usage_found = false;
if let Some(usage_data) = value.get("usage") {
if let Ok(usage) = serde_json::from_value::<UsageInfo>(usage_data.clone()) {
print_usage(&usage);
handler(Some(&usage), None, None);
usage_found = true;
}
}
if !usage_found {
println!("\n[Usage information not available in response]");
println!("Requesting usage information separately...");
match get_final_usage(api_key, model, instructions, input).await {
Ok(Some(usage)) => {
print_usage(&usage);
handler(Some(&usage), None, None);
},
Ok(None) => {
let err = ResponseError::Unknown("Could not retrieve usage information.".to_string());
handler(None, Some(&err), None);
},
Err(e) => {
let err = ResponseError::Unknown(format!("Error getting usage: {}", e));
handler(None, Some(&err), None);
}
}
}
return Ok(());
} else {
let err = ResponseError::ParseError("Failed to parse non-streaming response".to_string());
handler(None, Some(&err), None);
match get_final_usage(api_key, model, instructions, input).await {
Ok(Some(usage)) => {
print_usage(&usage);
handler(Some(&usage), None, None);
},
Ok(None) => {
let err = ResponseError::Unknown("Could not retrieve usage information.".to_string());
handler(None, Some(&err), None);
},
Err(e) => {
let err = ResponseError::Unknown(format!("Error getting usage: {}", e));
handler(None, Some(&err), None);
}
}
return Ok(());
}
}
let mut stream = res.bytes_stream();
let mut last_usage: Option<UsageInfo> = None;
while let Some(chunk) = stream.try_next().await? {
let text = match std::str::from_utf8(&chunk) {
Ok(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) {
handler(None, None, Some(&value));
let event_type = value.get("type").and_then(|t| t.as_str());
match event_type {
Some("response.output_text.delta") => {
if let Some(delta) = value.get("delta") {
if let Some(text) = delta.get("text").and_then(|t| t.as_str()) {
print!("{}", text);
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), None);
}
}
}
},
Some("response.usage.complete") | Some("response.usage") => {
if let Some(usage_data) = value.get("usage") {
if let Ok(usage) = serde_json::from_value::<UsageInfo>(usage_data.clone()) {
last_usage = Some(usage);
}
} else {
if let Ok(usage) = serde_json::from_value::<UsageInfo>(value.clone()) {
last_usage = Some(usage);
}
}
},
Some("response.done") | Some("done") | Some("completion") => {
if let Some(usage_data) = value.get("usage") {
if let Ok(usage) = serde_json::from_value::<UsageInfo>(usage_data.clone()) {
last_usage = Some(usage.clone());
}
}
},
_ => {
if let Some(usage_data) = value.get("usage") {
if let Ok(usage) = serde_json::from_value::<UsageInfo>(usage_data.clone()) {
last_usage = Some(usage);
}
}
}
}
}
if payload == "[DONE]" ||
payload.contains("\"type\":\"done\"") ||
payload.contains("\"type\":\"response.done\"") {
if payload != "[DONE]" {
if let Ok(value) = serde_json::from_str::<serde_json::Value>(payload) {
if let Some(usage_data) = value.get("usage") {
if let Ok(usage) = serde_json::from_value::<UsageInfo>(usage_data.clone()) {
last_usage = Some(usage);
}
}
}
}
if let Some(usage) = &last_usage {
print_usage(usage);
handler(Some(usage), None, None);
} else {
println!("\n[Usage information not available in stream]");
println!("Requesting usage information separately...");
match get_final_usage(api_key, model, instructions, input).await {
Ok(Some(usage)) => {
print_usage(&usage);
handler(Some(&usage), None, None);
},
Ok(None) => {
let err = ResponseError::Unknown("Could not retrieve usage information.".to_string());
handler(None, Some(&err), None);
},
Err(e) => {
let err = ResponseError::Unknown(format!("Error getting usage: {}", e));
handler(None, Some(&err), None);
}
}
}
println!("\n[Stream finished]");
return Ok(());
}
match serde_json::from_str::<StreamChunk>(payload) {
Ok(parsed) => {
if parsed.chunk_type.as_deref() == Some("response.usage.complete") ||
parsed.chunk_type.as_deref() == Some("response.usage") {
if let Some(usage) = &parsed.usage {
last_usage = Some(usage.clone());
}
}
if let Some(output) = &parsed.output {
for item in output {
if let Some(content) = &item.content {
for c in content {
if let Some(text) = &c.text {
print!("{}", text);
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), None);
}
}
}
}
}
}
},
Err(e) => {
if !e.to_string().contains("EOF while parsing") {
let err = ResponseError::ParseError(format!("Error parsing JSON: {}", e));
handler(None, Some(&err), None);
}
continue;
}
}
}
}
}
println!("\n[Stream ended unexpectedly]");
if let Some(usage) = &last_usage {
print_usage(usage);
handler(Some(usage), None, None);
} else {
println!("Requesting usage information separately...");
match get_final_usage(api_key, model, instructions, input).await {
Ok(Some(usage)) => {
print_usage(&usage);
handler(Some(&usage), None, None);
},
Ok(None) => {
let err = ResponseError::Unknown("Could not retrieve usage information.".to_string());
handler(None, Some(&err), None);
},
Err(e) => {
let err = ResponseError::Unknown(format!("Error getting usage: {}", e));
handler(None, Some(&err), None);
}
}
}
Ok(())
}