linger-openai-sdk 0.1.1

Rust-native async SDK for OpenAI APIs with typed requests, streaming, uploads, retries, and pluggable transports.
Documentation
# linger-openai-sdk

Rust-native async SDK for OpenAI APIs.

`linger-openai-sdk` provides typed request builders, typed responses, streaming,
uploads, structured errors, conservative retries, and a pluggable HTTP transport
boundary.

## Installation

```toml
[dependencies]
dotenvy = "0.15"
linger-openai-sdk = "0.1"
tokio = { version = "1", features = ["full"] }
```

Default features enable the `reqwest` transport, `rustls`, and OS/system proxy
discovery. Disable default features only when you want to provide your own
transport:

```toml
[dependencies]
linger-openai-sdk = { version = "0.1", default-features = false }
```

Minimum supported Rust version: `1.82`.

## Quick Start

```rust
use linger_openai_sdk::{
    Client, ClientConfig, CreateResponseRequest, LingerError,
};

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

    let api_key = std::env::var("OPENAI_API_KEY")
        .map_err(|_| LingerError::invalid_config("OPENAI_API_KEY is required"))?;
    let model = std::env::var("OPENAI_MODEL").unwrap_or_else(|_| "gpt-5.4-mini".to_string());

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

    let client = Client::with_config(config.build()?)?;
    let response = client
        .responses()
        .create(
            CreateResponseRequest::builder()
                .model(&model)
                .input("Hello")
                .build()?,
        )
        .await?;

    println!("{}", response.output_text());
    Ok(())
}
```

Run the same core example from this repository:

```bash
echo 'OPENAI_API_KEY="sk-..."' > .env
cargo run --example responses_create
```

The example runs the same simple Responses tests shown below.

On Windows PowerShell:

```powershell
Set-Content -Path .env -Value 'OPENAI_API_KEY="sk-..."'
cargo run --example responses_create
```

Optional `.env` values:

```bash
OPENAI_BASE_URL="https://api.openai.com"
OPENAI_MODEL="gpt-5.4-mini"
```

## Supported APIs

- Responses
- Chat Completions
- Completions
- Audio
- Embeddings
- Files and Uploads
- Images
- Models
- Moderations
- Realtime
- Assistants and Threads
- Vector Stores
- Batches
- Fine-tuning
- Evals
- Containers
- Skills
- ChatKit
- Webhooks

Organization, Admin, and Projects management APIs are outside the current
supported scope.

## Streaming

```rust
use futures_util::StreamExt;
use linger_openai_sdk::{Client, CreateResponseRequest, LingerError};

async fn stream_response(client: &Client) -> Result<(), LingerError> {
    let mut stream = client
        .responses()
        .create_stream(
            CreateResponseRequest::builder()
                .model("gpt-5.4-mini")
                .input("Stream one short sentence.")
                .build()?,
        )
        .await?;

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

    Ok(())
}
```

## Simple Responses Tests

The repository example runs these simple, non-destructive smoke tests:

- create a response
- retrieve the response by id
- list the response input items
- count input tokens
- stream a short response

```rust
use futures_util::StreamExt;
use linger_openai_sdk::{
    Client, CreateResponseInputTokensRequest, CreateResponseRequest, LingerError,
};

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 response = client.responses().retrieve(response_id).await?;
    println!("retrieved response id: {}", response.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("Stream one short sentence.")
                .build()?,
        )
        .await?;

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

    Ok(())
}
```

## Environment Variables

Create a local `.env` file:

```bash
OPENAI_API_KEY="sk-..."
```

`.env` is ignored by git in this repository. Use `.env.example` as the committed
template. You can also set the variable in the current shell:

```bash
export OPENAI_API_KEY="sk-..."
```

```powershell
$env:OPENAI_API_KEY = "sk-..."
```

If you set a persistent User or Machine environment variable on Windows, open a
new terminal before running Cargo so the process inherits the new value.

## Live Smoke Test

```bash
cargo run --example responses_create
```

The example loads `.env`, creates a client, and runs the simple Responses tests
above using `OPENAI_MODEL` or `gpt-5.4-mini`.

If the command returns an OpenAI API error such as `429 insufficient_quota` or a
temporary `5xx` service error, the SDK reached the API and parsed the error
correctly; check quota/billing or retry later for transient service errors. If
it returns a transport error, check local network/proxy settings. The default
features include `system-proxy` so reqwest can discover OS-level proxy settings
where supported.

## Features

| Feature | Default | Description |
| --- | --- | --- |
| `reqwest-transport` | yes | Default HTTP transport backed by `reqwest`. |
| `rustls-tls` | yes | Uses `rustls` for TLS. |
| `system-proxy` | yes | Lets reqwest discover OS/system proxy settings where supported. |
| `native-tls` | no | Uses the platform native TLS backend instead of rustls. |

## More Examples

- [examples/responses_create.rs]examples/responses_create.rs
- [README_EN.md]README_EN.md
- [README_CN.md]README_CN.md

## License

MIT OR Apache-2.0