my-chatgpt 0.2.4

A simple API wrapper for the ChatGPT API
Documentation
use reqwest::Client;
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use serde::{Deserialize, Serialize};
use futures_util::{TryStreamExt};
use std::sync::{Arc, Mutex};

#[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>,
}

// Define a general error type that encapsulates all possible error cases
#[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),  // (message, id)
    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>, // if this is provided, the message will be a continuation of the previous message
) -> SendChatResult
where 
    F: Fn(Option<&UsageInfo>, Option<&ResponseError>, Option<&serde_json::Value>) + Send + Sync + 'static
{
    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() }];
        }
        _ => {}
    }

    // Initialize updated history
    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);
            }
        };

    if !res.status().is_success() {
        let error_text = match res.text().await {
            Ok(text) => text,
            Err(e) => format!("Failed to get error response from openai: {}", e),
        };
        println!("Error response: {}", error_text);
        return SendChatResult::Err(ResponseError::NetworkError(format!("API request failed: {}", error_text)));
    }

    let final_response = Arc::new(Mutex::new(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![] 
    }));

    let stream = res.bytes_stream()
        .map_err(|e| ResponseError::NetworkError(e.to_string()))
        .try_filter_map(|chunk| async move {
            let text = std::str::from_utf8(&chunk)
                .map_err(|e| ResponseError::ParseError(format!("Failed to parse UTF-8: {}", e)))?;
            
            let lines: Vec<String> = text.lines()
                .filter(|line| line.starts_with("data: "))
                .map(|line| line[6..].to_string())
                .collect();

            if lines.is_empty() {
                Ok(None)
            } else {
                Ok(Some(lines))
            }
        })
        .try_for_each({
            let final_response = Arc::clone(&final_response);
            move |lines| {
                let final_response = Arc::clone(&final_response);
                async move {
                    for payload in lines {
                        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") => {
                                    println!("\n=== Response Content delta Event ===");
                                    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), None);
                                        }
                                    }
                                },
                                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() {
                                                if let Ok(mut response) = final_response.lock() {
                                                    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 Ok(mut response) = final_response.lock() {
                                            if let Some(id) = response_value.get("id").and_then(|id| id.as_str()) {
                                                response.id = id.to_string();
                                            }
                                            if let Some(usage) = response_value.get("usage").and_then(|usage| serde_json::from_value::<UsageInfo>(usage.clone()).ok()) {
                                                response.usage = usage;
                                            }
                                            if let Some(tools) = response_value.get("tools").and_then(|tools| serde_json::from_value::<Vec<Tool>>(tools.clone()).ok()) {
                                                response.tools = tools;
                                            }
                                        }
                                    }
                                    
                                    println!("final_response: {:?}", final_response);
                                    println!("===============================\n");
                                    return Ok(());
                                },
                                _ => {
                                    println!("value is {:?}", value);
                                }
                            }
                        }
                    }
                    Ok(())
                }
            }
        })
        .await;

    match stream {
        Ok(_) => {
            if let Ok(response) = Arc::try_unwrap(final_response) {
                if let Ok(response) = response.into_inner() {
                    SendChatResult::Ok(response)
                } else {
                    SendChatResult::Err(ResponseError::Unknown("Failed to get final response".to_string()))
                }
            } else {
                SendChatResult::Err(ResponseError::Unknown("Failed to get final response".to_string()))
            }
        },
        Err(e) => SendChatResult::Err(e)
    }
}