From 8337e3facf7d3858a3c5ca74cd27284c917af08e Mon Sep 17 00:00:00 2001 From: Yuanhao Li Date: Fri, 23 Oct 2026 23:39:16 +0200 Subject: [PATCH 1/2] release: yoagent 0.25.13, yoagent-rutis 0.8.2 Also: the price audit's Mistral Large 4 sale exception is gone. Its launch sale ended (~2026-10-20); models.dev and Mistral's own page (checked 2026-10-23: $1.36 in, $4.18 out) list the regular rate the crate already carries. No price changes; the note and `verified` date are updated, and the offline fixture and its check follow. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG --- CHANGELOG.md | 6 ++++-- Cargo.toml | 2 +- integrations/yoagent-rutis/Cargo.toml | 2 +- skills/yoagent/SKILL.md | 11 ++++++++++- src/provider/prices.json | 4 ++-- tests/fixtures/models_dev_slice.json | 6 +++--- tests/price_audit.rs | 22 ++++++++++++---------- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 969a249..a27d6b0 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 7e3ec50..90c3b37 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 b8d1d85..bc0a5d1 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 04d0174..a80f7b1 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 979dc88..b4d3fde 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 4d6d950..5231523 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 a7328ad..c40ea46 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. From 770a3feac13498d2af40958332a3e8fb11ee96ce Mon Sep 17 00:00:00 2001 From: Yuanhao Li Date: Fri, 23 Oct 2026 23:53:23 +0200 Subject: [PATCH 2/2] test: price_fetch_test no longer expects Mistral Large 4 at its sale price The models.dev fixture now carries the regular rate (the sale ended), so the fixture and the bundled snapshot agree for it. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG --- tests/price_fetch_test.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tests/price_fetch_test.rs b/tests/price_fetch_test.rs index 4f98d4b..34e3375 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()), "{:?}",