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 ResponseRequest {
model: String,
input: String,
instructions: String,
stream: bool,
}
#[derive(Deserialize, Debug, Clone)]
pub struct UsageInfo {
pub input_tokens: Option<u32>,
pub output_tokens: Option<u32>,
pub total_tokens: Option<u32>,
}
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,
history: &[Message],
handler: F
) -> Result<Vec<Message>, Box<dyn std::error::Error>>
where
F: Fn(Option<&UsageInfo>, Option<&ResponseError>, Option<&serde_json::Value>) -> ()
{
let client: Client = Client::new();
let user_message = Message {
role: "user".to_string(),
content: input.to_string(),
};
let mut updated_history = Vec::new();
let has_system = history.iter().any(|m| m.role == "developer");
if !has_system {
updated_history.push(Message {
role: "developer".to_string(),
content: instructions.to_string(),
});
} else {
updated_history.extend(history.iter()
.filter(|m| m.role == "developer")
.cloned());
}
updated_history.extend(history.iter()
.filter(|m| m.role != "developer")
.cloned());
updated_history.push(user_message);
println!("Current history before API call:");
for (i, msg) in updated_history.iter().enumerate() {
println!(" {}: Role: {}, Content: {}", i, msg.role, msg.content);
}
let mut formatted_input = String::new();
for msg in &updated_history {
formatted_input.push_str(&format!("{}: {}\n", msg.role, msg.content));
}
let body = ResponseRequest {
model: model.to_string(),
instructions: instructions.to_string(),
input: formatted_input,
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 !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 Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, format!("API request failed: {}", error_text))));
}
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())));
}
};
println!("Non-streaming response body: {}", response_body);
if let Ok(value) = serde_json::from_str::<serde_json::Value>(&response_body) {
let mut assistant_response = String::new();
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);
assistant_response.push_str(text);
}
}
} else if let Some(text) = content.as_str() {
print!("{}", text);
assistant_response.push_str(text);
}
} else if let Some(text) = item.get("text").and_then(|t| t.as_str()) {
print!("{}", text);
assistant_response.push_str(text);
}
}
} else if let Some(text) = output.as_str() {
print!("{}", text);
assistant_response.push_str(text);
}
}
if assistant_response.is_empty() {
if let Some(content) = value.get("content").and_then(|c| c.as_str()) {
print!("{}", content);
assistant_response.push_str(content);
} else if let Some(text) = value.get("text").and_then(|t| t.as_str()) {
print!("{}", text);
assistant_response.push_str(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);
}
}
}
if assistant_response.is_empty() {
println!("Initial extraction failed, trying fallback extraction methods");
debug_response_extraction(&response_body);
if let Some(extracted) = extract_assistant_response(&response_body) {
assistant_response = extracted;
} else {
if response_body.contains("assistant:") {
if let Some(start_idx) = response_body.find("assistant:") {
let response = &response_body[start_idx + 10..];
if let Some(end_idx) = response.find('\n') {
assistant_response = response[..end_idx].trim().to_string();
} else {
assistant_response = response.trim().to_string();
}
}
}
}
}
if !assistant_response.is_empty() {
println!("Adding assistant response to history (non-streaming): '{}'", assistant_response);
updated_history.push(Message {
role: "assistant".to_string(),
content: assistant_response,
});
} else {
println!("WARNING: No assistant response found in non-streaming response");
}
println!("Final history after API response:");
for (i, msg) in updated_history.iter().enumerate() {
println!(" {}: Role: {}, Content: {}", i, msg.role, msg.content);
}
return Ok(updated_history);
} else {
let err = ResponseError::ParseError("Failed to parse non-streaming response".to_string());
handler(None, Some(&err), None);
println!("WARNING: Failed to parse response, returning history without assistant response");
return Ok(updated_history);
}
}
let mut stream = res.bytes_stream();
let mut last_usage: Option<UsageInfo> = None;
let mut complete_response = String::new();
while let Some(chunk) = stream.try_next().await? {
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) {
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()) {
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_item.done") => {
println!("Received response.output_item.done event: {:?}", value);
let assistant_response = value
.get("item")
.and_then(|item| item.get("content"))
.and_then(|content| content.as_array())
.and_then(|content_array| content_array.first())
.and_then(|first_item| first_item.get("text"))
.and_then(|text| text.as_str())
.map(|s| s.to_string());
if let Some(assistant_response) = assistant_response {
updated_history.push(Message {
role: "assistant".to_string(),
content: assistant_response,
});
}
},
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") => {
println!("Received response.done event: {:?}", value);
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]");
let mut assistant_response = String::new();
if let Some(extracted) = extract_assistant_message_from_stream(&complete_response) {
assistant_response = extracted;
println!("Extracted assistant response from complete stream: '{}'", assistant_response);
updated_history.push(Message {
role: "assistant".to_string(),
content: assistant_response,
});
} else {
println!("Falling back to pattern matching extraction");
debug_response_extraction(&complete_response);
}
println!("Final history after streaming:");
for (i, msg) in updated_history.iter().enumerate() {
println!(" {}: Role: {}, Content: {}", i, msg.role, msg.content);
}
return Ok(updated_history);
}
}
}
}
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(updated_history)
}
fn extract_assistant_response(response_body: &str) -> Option<String> {
println!("Extracting assistant response from body");
if response_body.len() > 1000 {
println!("Response body preview: {}", &response_body[..500]);
} else {
println!("Response body: {}", response_body);
}
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() {
let mut response = String::new();
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()) {
response.push_str(text);
}
}
} else if let Some(text) = content.as_str() {
response.push_str(text);
}
} else if let Some(text) = item.get("text").and_then(|t| t.as_str()) {
response.push_str(text);
}
}
if !response.is_empty() {
println!("Found response in output array: {}", response);
return Some(response);
}
} else if let Some(text) = output.as_str() {
println!("Found response as direct output string: {}", text);
return Some(text.to_string());
}
}
if let Some(content) = value.get("content") {
if let Some(text) = content.as_str() {
println!("Found response in content field: {}", text);
return Some(text.to_string());
}
}
if let Some(text) = value.get("text").and_then(|t| t.as_str()) {
println!("Found response in text field: {}", text);
return Some(text.to_string());
}
println!("Could not find assistant response in structured JSON");
} else {
println!("Could not parse response body as JSON");
}
if response_body.contains("assistant:") {
if let Some(start_idx) = response_body.find("assistant:") {
let response = response_body[start_idx + 10..].trim().to_string();
println!("Found response using heuristics: {}", response);
return Some(response);
}
}
None
}
fn debug_response_extraction(response_body: &str) {
println!("\n=== DEBUG RESPONSE EXTRACTION ===");
println!("Response body length: {}", response_body.len());
let preview_length = std::cmp::min(response_body.len(), 500);
println!("Response preview: {}", &response_body[..preview_length]);
println!("Contains 'output': {}", response_body.contains("output"));
println!("Contains 'content': {}", response_body.contains("content"));
println!("Contains 'text': {}", response_body.contains("text"));
println!("Contains 'assistant': {}", response_body.contains("assistant"));
if let Ok(value) = serde_json::from_str::<serde_json::Value>(response_body) {
println!("Successfully parsed as JSON");
println!("Top-level keys: {:?}", value.as_object().map(|o| o.keys().collect::<Vec<_>>()));
if let Some(output) = value.get("output") {
println!("Output type: {}", if output.is_array() { "array" }
else if output.is_object() { "object" }
else if output.is_string() { "string" }
else { "other" });
if let Some(output_array) = output.as_array() {
println!("Output array length: {}", output_array.len());
if !output_array.is_empty() {
println!("First output item type: {}",
if output_array[0].is_object() { "object" }
else if output_array[0].is_string() { "string" }
else { "other" });
if let Some(first_item) = output_array.get(0).and_then(|i| i.as_object()) {
println!("First output item keys: {:?}", first_item.keys().collect::<Vec<_>>());
}
}
}
}
if let Some(error) = value.get("error") {
println!("Error information found: {}", error);
}
} else {
println!("Failed to parse as JSON");
}
println!("=== END DEBUG RESPONSE EXTRACTION ===\n");
}
fn extract_assistant_message_from_stream(complete_response: &str) -> Option<String> {
for line in complete_response.lines() {
if line.starts_with("data: ") {
let payload = &line[6..];
if let Ok(value) = serde_json::from_str::<serde_json::Value>(payload) {
if value.get("type").and_then(|t| t.as_str()) == Some("response.output_item.done") {
println!("Found response.output_item.done event");
if let Some(content) = value.get("content") {
if let Some(text) = content.get("text").and_then(|t| t.as_str()) {
println!("Extracted complete text: {}", text);
return Some(text.to_string());
}
}
}
}
}
}
let mut assistant_message = String::new();
for line in complete_response.lines() {
if line.starts_with("data: ") {
let payload = &line[6..];
if let Ok(value) = serde_json::from_str::<serde_json::Value>(payload) {
if value.get("type").and_then(|t| t.as_str()) == 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()) {
assistant_message.push_str(text);
}
}
}
}
}
}
if !assistant_message.is_empty() {
println!("Extracted text from deltas: {}", assistant_message);
Some(assistant_message)
} else {
None
}
}