openrouter-client 0.2.0

Idiomatic async Rust SDK for the OpenRouter API.
Documentation
# AGENTS.md

Operating manual for AI coding agents (Claude Code, Cursor, etc.) working
on this repository.

## What this crate is

`openrouter-rust` is an idiomatic, async Rust SDK for the
[OpenRouter](https://openrouter.ai) HTTP API. It is a port of
[openrouter-go](https://github.com/hra42/openrouter-go); behavior and
defaults are kept in sync with the Go SDK on purpose. When you change
retry constants, error classifications, or other defaults, check the Go
SDK first and call out any intentional divergence in the commit message.

The package is published as `openrouter-client` on crates.io because the
`openrouter`, `openrouter-rust`, and `openrouter-rs` names were all
already taken. The library identifier stays `openrouter`, so user code
writes `use openrouter::...`. When you touch README, docs.rs, install
snippets, or other crates.io-facing references, use `openrouter-client`.
When you touch Rust source, doctests, or recipes, use `openrouter`. The
**GitHub repo** is still `openrouter-rust` — keep that in repository
URLs and CI badge sources. The split lives in `Cargo.toml`
(`[package] name` vs `[lib] name`).

## Commands you can run

```bash
cargo build
cargo test --all-features
cargo fmt --all -- --check          # CI-equivalent
cargo clippy --all-targets --all-features -- -D warnings   # CI-equivalent
cargo deny check                    # license/advisory audit
cargo doc --all-features --no-deps
RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps  # strict
cargo run --example build_client
OPENROUTER_API_KEY=… cargo run --example e2e -- chat  # live API
```

CI enforces fmt, clippy (`-D warnings`), tests (ubuntu + macOS), and
`cargo-deny`. MSRV is **1.75** (`rust-toolchain.toml`).

## Live-API testing

Every example, smoke test, or doctest that hits the real OpenRouter API
**must use model `google/gemini-3.1-flash-lite`**. Do not switch to
another model without an explicit ask — this keeps cost predictable and
behavior consistent across smoke tests.

## Layout

- `src/client.rs``Client`, `ClientBuilder`, and the per-endpoint
  methods. `Client` is `Clone`-cheap (`Arc<ClientInner>`).
- `src/error.rs``Error` enum + `Result` alias. `Error::from_response_body`,
  `is_transient`, and `retry_after` are the authoritative classifiers; the
  retry layer reads them directly. Keep them `pub(crate)` and avoid
  duplicating the logic elsewhere.
- `src/retry.rs` — exponential backoff with ±25% jitter, capped at 30s,
  default 3 retries. Constants intentionally mirror the Go SDK.
- `src/request.rs` — the single consumer of the error parser, retry
  runner, and HTTP helpers. New endpoints go through `execute_json` /
  `execute_json_get` / `execute_stream`.
- `src/stream.rs``EventStream` + SSE parser.
- `src/types/` — pure serde models, no I/O. All optional request fields
  use `#[serde(skip_serializing_if = "Option::is_none", default)]`.
- `src/responses.rs``[beta]` Responses API, gated behind the `beta`
  cargo feature.
- `src/oauth.rs`, `src/mcp.rs`, `src/webhooks.rs` — OAuth PKCE helpers,
  MCP tool conversion, broadcast-webhook parsing.
- `tests/` — wiremock-based integration tests, one file per endpoint or
  topic. `tests/endpoint_errors.rs` and `tests/error_mapping.rs` carry
  cross-endpoint error coverage; `tests/concurrency.rs` covers cloned
  clients.
- `examples/` — runnable demos per major surface. `examples/run_all.rs`
  orchestrates them in one process; `examples/e2e.rs` is the Go-parity
  CLI smoke binary.
- `docs/recipes/` — markdown recipes, embedded into rustdoc via
  `#[doc = include_str!]`.

## Conventions

- Lints in `Cargo.toml` deny `rust_2018_idioms` and
  `missing_debug_implementations` and warn on `unreachable_pub`. New
  public types need `Debug`; new modules respect the `unreachable_pub`
  boundary (use `pub(crate)` for internals).
- `rustfmt.toml` is intentionally minimal — defaults apply.
- Tests are colocated in `#[cfg(test)] mod tests` blocks for unit tests;
  HTTP-level tests live in `tests/*.rs` using `wiremock`. Use
  `#[tokio::test(start_paused = true)]` for retry/backoff timing so
  `tokio::time::sleep` is virtual.
- `Error::InvalidInput` and `Error::MissingField` carry `&'static str`  keep those as compile-time literals, not `String`.
- Don't write WHAT comments. Comments earn their place by explaining a
  non-obvious WHY: hidden constraints, subtle invariants, workarounds.

## Branching & commits

- One branch per Linear parent issue (`hra-<n>`). For multi-child issues,
  one commit per child issue, in the order specified in Linear.
- Commit subject: `type(hra-<n>): short summary`. Body: 1–3 short
  paragraphs explaining the why. No trailing AI co-author lines.
- Never use `--no-verify` or skip hooks. If a pre-commit hook fails,
  diagnose and fix the underlying issue.
- Don't `git push --force` or `git reset --hard` without an explicit ask.

## Documentation expectations

- Every public item has a doc comment. Major surfaces (`Client`,
  builders, streaming, errors, retry, multimodal helpers, OAuth,
  webhooks, MCP, beta Responses) include at least one runnable doc
  example (`no_run` is acceptable when the example would hit the
  network).
- Recipes in `docs/recipes/*.md` are linked from the matching modules
  via `#[doc = include_str!]`.
- `cargo doc --no-deps` with `RUSTDOCFLAGS=-D warnings` must be clean.

## When in doubt

- Match the Go SDK's behavior.
- Read `CLAUDE.md` for project-specific reminders surfaced to Claude
  Code.
- Ask the user before publishing, tagging, or force-pushing.