linger-openai-sdk 0.1.1

Rust-native async SDK for OpenAI APIs with typed requests, streaming, uploads, retries, and pluggable transports.
Documentation
use futures_util::StreamExt;
use linger_openai_sdk::{
    Client, ClientConfig, CreateResponseInputTokensRequest, CreateResponseRequest, LingerError,
};

const DEFAULT_MODEL: &str = "gpt-5.4-mini";

#[tokio::main]
async fn main() -> Result<(), LingerError> {
    let _ = dotenvy::dotenv();

    let client = client_from_env()?;
    let model = std::env::var("OPENAI_MODEL").unwrap_or_else(|_| DEFAULT_MODEL.to_string());

    let response_id = create_response(&client, &model).await?;
    retrieve_response(&client, &response_id).await?;
    list_input_items(&client, &response_id).await?;
    count_input_tokens(&client, &model).await?;
    stream_response(&client, &model).await?;

    Ok(())
}

async fn create_response(client: &Client, model: &str) -> Result<String, LingerError> {
    let response = client
        .responses()
        .create(
            CreateResponseRequest::builder()
                .model(model)
                .input("Hello")
                .build()?,
        )
        .await?;

    println!("response id: {}", response.id);
    println!("response text: {}", response.output_text());
    Ok(response.id)
}

async fn retrieve_response(client: &Client, response_id: &str) -> Result<(), LingerError> {
    let retrieved = client.responses().retrieve(response_id).await?;
    println!("retrieved response id: {}", retrieved.id);
    Ok(())
}

async fn list_input_items(client: &Client, response_id: &str) -> Result<(), LingerError> {
    let input_items = client.responses().list_input_items(response_id).await?;
    println!("input item count: {}", input_items.data.len());
    Ok(())
}

async fn count_input_tokens(client: &Client, model: &str) -> Result<(), LingerError> {
    let tokens = client
        .responses()
        .count_input_tokens(
            CreateResponseInputTokensRequest::builder()
                .model(model)
                .input("Count the input tokens in this short sentence.")
                .build()?,
        )
        .await?;

    println!("input tokens: {}", tokens.input_tokens);
    Ok(())
}

async fn stream_response(client: &Client, model: &str) -> Result<(), LingerError> {
    let mut stream = client
        .responses()
        .create_stream(
            CreateResponseRequest::builder()
                .model(model)
                .input("你好,你能帮我做什么?")
                .build()?,
        )
        .await?;

    print!("stream text: ");
    while let Some(item) = stream.next().await {
        if let Some(delta) = item?.output_text_delta() {
            print!("{delta}");
        }
    }
    println!();

    Ok(())
}

fn client_from_env() -> Result<Client, LingerError> {
    let api_key = std::env::var("OPENAI_API_KEY")
        .map_err(|_| LingerError::invalid_config("OPENAI_API_KEY is required"))?;

    let mut builder = ClientConfig::builder().api_key(api_key);
    if let Ok(base_url) = std::env::var("OPENAI_BASE_URL") {
        builder = builder.base_url(base_url);
    }

    Client::with_config(builder.build()?)
}