Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ adheres to [Semantic Versioning](https://semver.org/).

### Fixed

- **`claude_sonnet_5` was priced 50% high.** The preset carried Sonnet 4.6's
entire price row — $3/$15 with $0.30/$3.75 cache rates, uniformly 1.5x the
real figures — from v0.9.0 through v0.16.5. Every `cost_usd` and
`session_cost_usd` for that model overstated spend by half, across 18
releases, with nothing in the crate able to detect it.

Now $2/$10 with $0.20/$2.50, verified against Anthropic's pricing page and
models.dev on 2026-08-23.

Corrected on the 0.18.0 line by [#121](https://github.com/yologdev/yoagent/pull/121);
this back-ports it. The drift alarm that catches this class of error
(`tests/price_audit.rs`, which diffs every priced preset against models.dev)
is 0.18.0-only — this line has the corrected numbers but not the guard, so
future drift here is again undetected.

- **Tools with no parameters were uncallable on Anthropic.** A tool whose
schema takes no arguments has no JSON to stream, and Anthropic still emits an
`input_json_delta` carrying `""`. `serde_json::from_str("")` fails with "EOF
Expand Down
22 changes: 17 additions & 5 deletions src/provider/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,15 +509,21 @@ impl ModelConfig {
}

/// Claude Sonnet 5. 1M context; defaults to 64K of the model's 128K max output.
///
/// Rates verified against <https://platform.claude.com/docs/en/about-claude/pricing>
/// and models.dev on 2026-08-23. This preset carried Sonnet 4.6's entire
/// price row — $3/$15 with $0.30/$3.75 cache, uniformly 1.5x high — from
/// v0.9.0 through v0.16.5, overstating every `cost_usd` for the model by
/// 50% with nothing to detect it.
pub fn claude_sonnet_5() -> Self {
Self {
context_window: 1_000_000,
max_tokens: 64_000,
cost: CostConfig {
input_per_million: 3.0,
output_per_million: 15.0,
cache_read_per_million: 0.3,
cache_write_per_million: 3.75,
input_per_million: 2.0,
output_per_million: 10.0,
cache_read_per_million: 0.2,
cache_write_per_million: 2.5,
},
..Self::anthropic("claude-sonnet-5", "Claude Sonnet 5")
}
Expand Down Expand Up @@ -1005,7 +1011,13 @@ mod tests {

let sonnet = ModelConfig::claude_sonnet_5();
assert_eq!(sonnet.id, "claude-sonnet-5");
assert_eq!(sonnet.cost.output_per_million, 15.0);
// All four, not just output. This preset carried Sonnet 4.6's entire
// price row — uniformly 1.5x high — for 18 releases, and pinning one
// field would let a partial correction pass just as quietly.
assert_eq!(sonnet.cost.input_per_million, 2.0);
assert_eq!(sonnet.cost.output_per_million, 10.0);
assert_eq!(sonnet.cost.cache_read_per_million, 0.2);
assert_eq!(sonnet.cost.cache_write_per_million, 2.5);

let haiku = ModelConfig::claude_haiku_4_5();
assert_eq!(haiku.id, "claude-haiku-4-5");
Expand Down
Loading