my-chatgpt 0.1.8

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;

#[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>) -> ()
{
    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);
            }
        };

    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") {
                                        if let Some(_text) = delta.get("text").and_then(|t| t.as_str()) {
                                            println!("***delta: {:?}", 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);
                                    }
                                    let message = value.get("part")
                                        .and_then(|part| part.get("text"))
                                        .and_then(|text| text.as_str())
                                        .unwrap_or("")
                                        .to_string();
                                    final_response.message = message;
                                    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") {
                                        // Get ID from outer 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);
                                },
                                _ => {
                                    // Check if there's usage data regardless of event type
                                    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);
            }
        }
    }
}