diff --git a/CHANGELOG.md b/CHANGELOG.md index 969a2490..a27d6b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,14 @@ All notable changes to `yoagent` are documented here. The format loosely follows [Keep a Changelog](https://keepachangelog.com/), and the project adheres to [Semantic Versioning](https://semver.org/). -## Unreleased +## 0.25.13 (2026-10-23) + +Two provider fixes; no API change. - **Bedrock, Gemini and Vertex no longer send an empty message** (#369). An assistant turn that errored, was refused or was cancelled with no content (the loop's error message is one empty text block, which the builders filter) — or whose only content is thinking the provider does not replay (on Gemini and Vertex any thinking; on Bedrock unsigned reasoning to a Claude model, or another API's) — went out as `content: []` / `parts: []`, which both APIs reject (as did a user message with only empty text); the Anthropic provider already skipped such an assistant turn. So the prompt after a failed or cancelled run was rejected too. Empty messages of either role are now dropped before consecutive same-role messages are merged (#368), so the user turns around a dropped one go back as one. - **Bedrock, Gemini and Vertex: a parallel tool call's results go back in one message** (#223). Each tool result was sent as its own user-role message, and a steering message, stop marker or compaction summary after them added another, while Bedrock's Converse requires roles to alternate and Gemini requires a turn's function responses in one content, as many as its function calls. So the request after any turn with two or more tool calls (the default parallel strategy) could be rejected. The three request builders now merge consecutive same-role messages, keeping every block in order: the results in call order, then whatever followed; on Gemini and Vertex a merged turn's function responses come first, then any images and text (images among the responses are a reported cause of Gemini's response-count 400). Pinned by request-body tests; not run against a live endpoint. -## yoagent-rutis Unreleased +## yoagent-rutis 0.8.2 (2026-10-23) yoagent-rutis only; requires yoagent 0.25, rutis 0.12 and Rust 1.88, as 0.8.1. Additive. diff --git a/Cargo.toml b/Cargo.toml index 7e3ec508..90c3b37b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yoagent" -version = "0.25.12" +version = "0.25.13" edition = "2021" # MSRV-aware dependency resolution (cargo 1.84+): prefer dependency versions # whose own rust-version fits ours. Without it CI re-resolves to whatever is diff --git a/integrations/yoagent-rutis/Cargo.toml b/integrations/yoagent-rutis/Cargo.toml index b8d1d856..bc0a5d11 100644 --- a/integrations/yoagent-rutis/Cargo.toml +++ b/integrations/yoagent-rutis/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yoagent-rutis" -version = "0.8.1" +version = "0.8.2" edition = "2021" resolver = "3" description = "Extend yoagent agents at runtime with rutis plugins, as one yoagent Extension: tools, tool policy, output filters, turn notes, input checks, verifiers and events" diff --git a/skills/yoagent/SKILL.md b/skills/yoagent/SKILL.md index 04d0174a..a80f7b17 100644 --- a/skills/yoagent/SKILL.md +++ b/skills/yoagent/SKILL.md @@ -5,7 +5,7 @@ description: Use when building, extending or upgrading an agent on the yoagent R # yoagent: which feature for which job -Written for **yoagent 0.25.12** (and yoagent-rutis 0.8.1); the recipes +Written for **yoagent 0.25.13** (and yoagent-rutis 0.8.2); the recipes below are compiled by `tests/skill_test.rs`, line for line. Check what you build against: `cargo tree -p yoagent` or `Cargo.lock`. If you are on an older version, read [What changed](#what-changed) first. @@ -198,6 +198,11 @@ and its examples. Newest first; the full list is `CHANGELOG.md`. +- **0.25.13** — Bedrock, Gemini and Vertex: a parallel tool call's + results go back in one message (consecutive same-role messages are + merged; on Gemini/Vertex the function responses come first), and an + empty message (an errored or cancelled turn) is no longer sent, so the + request after a multi-tool turn or a failed run is accepted. - **0.25.12** — `provider::api_key_variables()`: every environment variable yoagent reads a credential from (providers', Bedrock's `AWS_*`, the `YOAGENT_API_KEY` / `API_KEY` fallback, the decision presets'). To @@ -274,6 +279,10 @@ Newest first; the full list is `CHANGELOG.md`. - **0.24.2** — built-in tool sandbox fixes (path escapes, `search` flag injection, Gemini key out of the URL, tool panics contained). - **0.24.1** — `retry_safe_events`. +- **yoagent-rutis 0.8.2** — feature `embedded-adapters`: + `adapters::files()` is the pi and DSH adapter files built into the + binary, for hosts installed without the crate's source (write them under + one folder per version, then `npm ci` in its `pi/` and `dsh/`). - **yoagent-rutis 0.8.1** — `probe::pi_with` / `probe::dsh_with` with `ProbeOptions::default().env(name, value)`: variables for the probe's runtime (an empty value blanks one; the DSH probe's own homes win). diff --git a/src/provider/prices.json b/src/provider/prices.json index 979dc886..b4d3fdef 100644 --- a/src/provider/prices.json +++ b/src/provider/prices.json @@ -267,9 +267,9 @@ "input": 1.36, "output": 4.18, "cache_read": 0.14, - "note": "Regular price. Mistral's launch sale (50% off for two weeks from 2026-10-06) bills half of it until about 2026-10-20.", + "note": "Regular price; Mistral's launch sale (50% off from 2026-10-06) has ended.", "source": "https://docs.mistral.ai/models/mistral-large-4-0", - "verified": "2026-10-18" + "verified": "2026-10-23" } }, "qwen": { diff --git a/tests/fixtures/models_dev_slice.json b/tests/fixtures/models_dev_slice.json index 4d6d9505..52315238 100644 --- a/tests/fixtures/models_dev_slice.json +++ b/tests/fixtures/models_dev_slice.json @@ -403,9 +403,9 @@ }, "status": "beta", "cost": { - "input": 0.68, - "output": 2.09, - "cache_read": 0.07 + "input": 1.36, + "output": 4.18, + "cache_read": 0.14 }, "canonical_model_id": "mistral/mistral-large-4" } diff --git a/tests/price_audit.rs b/tests/price_audit.rs index a7328adc..c40ea463 100644 --- a/tests/price_audit.rs +++ b/tests/price_audit.rs @@ -241,7 +241,8 @@ const PEAK_IS_TWICE_UPSTREAM: &[&str] = &["deepseek"]; const PROMO_IS_HALF_REGULAR: &[(&str, &str)] = &[ ("google", "gemini-4-argon"), ("google", "gemini-3.8-flash"), - ("mistral", "mistral-large-4"), + // Mistral Large 4 left on 2026-10-23: its launch sale ended, and + // models.dev lists the regular rate the crate carries. ]; /// Whether the crate's `input`/`output`/`cache_read` are twice models.dev's @@ -909,11 +910,12 @@ fn the_audit_logic_runs_offline_against_the_fixture() { // Muse Spark 1.2, and the three DeepSeek entries; and Mistral Large 4's, // unset on both sides. assert_eq!(o.absent, 7); - // A promotional entry is compared at twice models.dev's rate. + // Mistral Large 4's launch sale is over: compared at models.dev's own + // rate, with no promotion note. assert!( - o.notes + !o.notes .iter() - .any(|n| n.starts_with("mistral/mistral-large-4: regular rate")), + .any(|n| n.starts_with("mistral/mistral-large-4")), "{:?}", o.notes ); @@ -960,17 +962,17 @@ fn the_audit_logic_runs_offline_against_the_fixture() { .any(|d| d.contains("twice models.dev's off-peak")), "{off_peak:?}" ); - // A promotional entry carried at the promotional rate is drift: the - // crate carries the regular one. - let promo = drift_after(&|p| { + // An entry left at a sale price after the sale ended is drift, compared + // directly (Mistral Large 4 left `PROMO_IS_HALF_REGULAR` on 2026-10-23). + let stale_sale = drift_after(&|p| { if p.model == "mistral-large-4" { p.cost.input_per_million = 0.68; } }); - assert_eq!(promo.len(), 1, "{promo:?}"); + assert_eq!(stale_sale.len(), 1, "{stale_sale:?}"); assert!( - promo[0].contains("twice models.dev's off-peak or promotional rate"), - "{promo:?}" + stale_sale[0].contains("input is 0.68 in the crate, 1.36 in models.dev"), + "{stale_sale:?}" ); // Without the recorded gap, models.dev's `reasoning` rate is unknown // structure. diff --git a/tests/price_fetch_test.rs b/tests/price_fetch_test.rs index 4f98d4bb..34e3375a 100644 --- a/tests/price_fetch_test.rs +++ b/tests/price_fetch_test.rs @@ -151,18 +151,8 @@ fn models_dev_fixture_maps_into_our_schema() { // Against the built-in data: every overlapping model agrees as billed // (gpt-5.5's implicit cache_write equals its explicit input-rate one), so // only new models are reported. + // (Mistral Large 4 agrees too since its launch sale ended, 2026-10-23.) let changes = t.changes_from(&PriceTable::builtin()); - // Except a model on a vendor promotion: the snapshot carries the regular - // rate, models.dev the promotional half (what a live fetch installs). - let promo = |c: &yoagent::provider::prices::PriceChange| { - c.provider == "mistral" && c.model == "mistral-large-4" - }; - let mistral = changes.iter().find(|c| promo(c)).expect("the promotion"); - assert_eq!( - mistral.after.as_ref().map(|a| a.input_per_million), - Some(0.68) - ); - let changes: Vec<_> = changes.into_iter().filter(|c| !promo(c)).collect(); assert!( changes.iter().all(|c| c.before.is_none()), "{:?}",