# 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
| `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