yoagent 0.15.0

Simple, effective agent loop with tool execution and event streaming
Documentation
# Structured Outputs

Get a **typed, schema-validated reply** instead of free text. The JSON Schema
is enforced natively by the provider — not by prompt begging.

```rust
use yoagent::{Agent, provider::ModelConfig};

#[derive(serde::Deserialize)]
struct Invoice {
    vendor: String,
    total_cents: u64,
    line_items: Vec<String>,
}

let mut agent = Agent::from_config(ModelConfig::anthropic("claude-sonnet-5", "Sonnet 5"));

let invoice: Invoice = agent
    .prompt_structured(
        "Extract the invoice from the attached text: ...",
        serde_json::json!({
            "type": "object",
            "properties": {
                "vendor": {"type": "string"},
                "total_cents": {"type": "integer"},
                "line_items": {"type": "array", "items": {"type": "string"}}
            },
            "required": ["vendor", "total_cents", "line_items"]
        }),
    )
    .await?;
```

Derive the schema however you like — by hand as above, or with the
[`schemars`](https://crates.io/crates/schemars) crate (convert with
`serde_json::to_value(schemars::schema_for!(Invoice))`). Mind the provider
dialects: OpenAI strict mode requires `additionalProperties: false` and every
property listed in `required`; Gemini rejects `$defs`/`$ref`. Schemas are
passed through as given.

## How each provider enforces it

| Protocol | Mechanism |
|----------|-----------|
| Anthropic | Forced tool call — a synthetic tool is built from your schema and `tool_choice` forces it; the loop unwraps the call back into text |
| OpenAI-compatible | `response_format: {type: "json_schema", strict: true}` |
| Google Gemini | `generationConfig.responseSchema` + JSON mime type (note: Gemini uses an OpenAPI-style schema dialect — your schema is passed through as given) |
| OpenAI Responses / Azure / Vertex / Bedrock | Not yet wired — a warning is logged and the model replies as free text, which still must parse into `T` |

## Semantics & caveats

- `prompt_structured` runs the loop to completion internally and returns the
  parsed `T` — there is no event receiver for this call.
- Three error shapes: `Provider { message }` when the API call itself failed
  (auth, network, a schema-induced 400 — retrying the parse is pointless);
  `Parse { source, raw }` when the model's text didn't deserialize (the raw
  text is preserved so you can retry or salvage); `NoOutput` when the run
  produced no text. Only messages produced by **this call** are considered —
  stale output from earlier turns is never parsed.
- On Anthropic the forced tool call preempts regular tools for that request,
  and **disables extended thinking** for that request (forced tool choice and
  thinking are mutually exclusive at the API level — a warning is logged).
  Treat structured prompts as **extraction/finalization calls**, not agentic
  tool-using turns.
- Markdown code fences around the JSON are stripped defensively before
  parsing.