openrouter-client 0.2.0

Idiomatic async Rust SDK for the OpenRouter API.
Documentation
# Tool / function calling

Declare tools via [`Tool::function`](crate::Tool::function), attach them
to a [`ChatCompletionRequest`](crate::ChatCompletionRequest) with
[`with_tools`](crate::ChatCompletionRequest::with_tools), and steer
selection with [`ToolChoice`](crate::ToolChoice).

```rust,no_run
use openrouter::{ChatCompletionRequest, Client, FunctionDef, Message, Tool, ToolChoice};

#[tokio::main]
async fn main() -> openrouter::Result<()> {
    let client = Client::builder()
        .api_key(std::env::var("OPENROUTER_API_KEY").unwrap())
        .build()?;

    let tool = Tool::function(FunctionDef {
        name: "get_weather".into(),
        description: Some("Return the current weather for a city.".into()),
        parameters: Some(serde_json::json!({
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        })),
        strict: None,
    });

    let req = ChatCompletionRequest::new(
        "google/gemini-3.1-flash-lite",
        vec![Message::user("Weather in Berlin?")],
    )
    .with_tools(vec![tool])
    .with_tool_choice(ToolChoice::auto());

    let resp = client.chat_complete(req).await?;
    if let Some(calls) = resp.choices.first()
        .and_then(|c| c.message.as_ref())
        .and_then(|m| m.tool_calls.clone())
    {
        for call in calls {
            println!(
                "{} {}",
                call.function.name.as_deref().unwrap_or(""),
                call.function.arguments.as_deref().unwrap_or(""),
            );
        }
    }
    Ok(())
}
```

## Streaming tool calls

When streaming, tool-call arguments are emitted as deltas. Feed the
deltas into [`ToolCallAccumulator`](crate::ToolCallAccumulator) — it
will buffer fragments and emit complete tool calls keyed by index.