> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plungeai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# plungeai (Rust)

> The official Rust SDK for the One API — async client over reqwest, serde-typed resources, streaming, pagination, retries and errors, generated from the OpenAPI document.


The official Rust SDK for the PlungeAI One API (`https://api.plungeai.com`). Targets One API `2.4.0`; crate version `1.0.0`. The resource methods and their types are generated from the API's own OpenAPI document (`GET /v1/openapi.json`). Async over `reqwest` (rustls) on the `tokio` runtime.

Release status: `cargo search plungeai`; until it answers, depend on the path `sdk/one-api-rust/` in the platform repository.

## Installation

```sh
cargo add plungeai tokio --features tokio/full
```

## Usage

```rust
use plungeai::{types::*, Client};

#[tokio::main]
async fn main() -> Result<(), plungeai::Error> {
    let client = Client::new()?; // reads PLUNGEAI_API_KEY; or Client::builder().api_key("ozk_…").build()?

    let me = client.whoami().await?;
    let run = client
        .agents()
        .execute("calculator-agent", &AgentExecuteParams { prompt: Some("6*7".into()), ..Default::default() })
        .await?;
    let hits = client
        .discovery()
        .search(&DiscoverySearchParams { q: Some("web search".into()), kind: Some(DiscoverySearchParamsKind::Agents), ..Default::default() })
        .await?;
    Ok(())
}
```

Resources are methods on the client (`client.agents()`, `client.workflows().versions()`); every operation takes its path parameters first, then a reference to its params struct (`plungeai::types::<Resource><Method>Params`, all `Default`). Params and responses are `serde` structs under `plungeai::types`; optional fields are `Option<T>`; response enums carry an `Unknown` variant, so a new server value never breaks a client.

## Streaming responses

```rust
use futures_util::StreamExt;

let mut stream = client
    .workflows()
    .execute_inline_stream(&WorkflowExecuteInlineStreamParams { workflow: workflow.into(), ..Default::default() })
    .await?;
while let Some(event) = stream.next().await {
    let event = event?;
    if event.event.as_deref() == Some("token") {
        let token: serde_json::Value = event.json()?;
        print!("{}", token["delta"]);
    }
}
```

`event.data` is the raw string of the server-sent event; `event.json::<T>()` parses it. Dropping the stream cancels the request. Methods that accept `stream: true` on the API have a `…_stream` twin (`client.agents().execute_stream`, `client.chat().completions().create_stream`) returning the same `Stream`.

## Auto-pagination

```rust
let mut items = client.workflows().list(&WorkflowListParams { kind: Some(WorkflowListParamsKind::Bot), ..Default::default() }).await?.into_stream();
while let Some(w) = items.next().await {
    println!("{:?}", w?);
}

let mut page = client.workflows().list(&WorkflowListParams { limit: Some(50), ..Default::default() }).await?;
loop {
    for w in &page.data {
        println!("{w:?}");
    }
    if !page.has_next_page() {
        break;
    }
    page = page.next_page().await?;
}
```

## Handling errors

```rust
match client.agents().execute("calculator-agent", &AgentExecuteParams { prompt: Some("6*7".into()), ..Default::default() }).await {
    Ok(run) => println!("{run:?}"),
    Err(plungeai::Error::Api(err)) if err.status == 424 => println!("{:?}", err.connect.map(|c| c.link)),
    Err(plungeai::Error::Api(err)) => println!("{} {:?} {:?}", err.status, err.code, err.request_id),
    Err(err) => return Err(err),
}
```

One enum, `plungeai::Error`: `Api(Box<ApiError>)` for a response with an error status (`status`, `code` — the envelope's `error.code`, `message`, `request_id` — the `x-request-id` header, and `connect` on 424 with `provider`, `method`, `link`), `Connection` for a request that never got a response, `Timeout`, and `Decode` for a body that does not match its type. Predicates `err.status()`, `err.is_not_found()`, `err.is_rate_limit()` and so on classify without matching.

## Retries

Failed requests are retried 2 times by default with short exponential backoff (honouring `Retry-After`): connection errors, timeouts, 408, 429 and ≥ 500. 409 and 424 are never retried — on the One API they are semantic answers (`approval_required`, `duplicate_execution_id`, a missing connection), and a retried execute would start a second run. Set the count on the builder or per call:

```rust
let client = Client::builder().max_retries(0).build()?;
client.with_options(RequestOptions::new().max_retries(5)).agents().execute("calculator-agent", &params).await?;
```

`RequestOptions::new().idempotency_key(key)` is sent as `x-trace-id`, the API's idempotency key: it becomes the run's execution id, is re-sent on every retry of the call, and a replay answers 409 `duplicate_execution_id`.

## Timeouts

Each attempt times out after 10 minutes by default and fails with `Error::Timeout` (retried as above). Set it on the builder or per call: `Client::builder().timeout(Duration::from_secs(20))`, `RequestOptions::new().timeout(Duration::from_secs(5))`.

## Raw responses

```rust
let response = client.agents().execute_raw("calculator-agent", &params).await?;
println!("{} {:?}", response.status, response.request_id);
let run: AgentExecuteResponse = response.parse()?;
```

`client.with_options(RequestOptions)` (`header`, `query`, `timeout`, `max_retries`, `idempotency_key`) returns a client whose every call carries those options, and every JSON operation has a `…_raw` form returning the response with its `status`, `headers`, `request_id` and `body` bytes. `client.get(path)`, `client.post(path, &body)` and friends reach routes the generated layer does not cover.

## Models plane

`client.chat().completions().create(...)` (plus `client.embeddings()` and `client.models()`) covers the OpenAI-compatible routes here; any OpenAI client also works with base URL `https://api.plungeai.com/v1` and the same key.

## Requirements

Rust 1.85 or newer (edition 2021); `tokio` runtime. TLS through rustls — no system OpenSSL needed.
