From 460aa8c73dcb2edac567b8902afa540761696d59 Mon Sep 17 00:00:00 2001 From: Eureka <111341505+Eureka0w0v0@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:02:18 +0900 Subject: [PATCH] fix(pricing): seed Claude Fable 5.1 / Mythos 5.1 and restore Sonnet 5 to the $2/$10 standard price (#7051) - Add claude-fable-5-1 / claude-mythos-5-1 pricing rows ($10/$50, cache read $0.25 = 0.025x, cache write $12.50). Without a row these requests were stored at $0: the prefix rule only looks for longer dated ids and cannot fall back to claude-fable-5. - Sonnet 5: Anthropic's pricing page now states the $2/$10 introductory price is the standard price and the September 1 increase to $3/$15 will not occur. Update the seed and add a repair_current_model_pricing guard so installs seeded at 3/15/0.30/3.75 are corrected; user-customized rows are left alone. - Tests: seed values for the three rows; the repair guard fixes the old list price and keeps a custom price. Fixes #7050 --- src-tauri/src/database/schema.rs | 43 +++++++++-- src-tauri/src/database/tests.rs | 129 +++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 5 deletions(-) diff --git a/src-tauri/src/database/schema.rs b/src-tauri/src/database/schema.rs index 0af5d99cfe..d6cc047f01 100644 --- a/src-tauri/src/database/schema.rs +++ b/src-tauri/src/database/schema.rs @@ -1604,6 +1604,24 @@ impl Database { /// 注意: model_id 使用短横线格式(如 claude-haiku-4-5),与 API 返回的模型名称标准化后一致 fn seed_model_pricing(conn: &Connection) -> Result<(), AppError> { let pricing_data = [ + // Claude Fable 5.1 / Mythos 5.1(2026-09-01 发布;同 Fable 5 价, + // 但缓存读为 0.025x = $0.25,非 Fable 5 的 $1) + ( + "claude-fable-5-1", + "Claude Fable 5.1", + "10", + "50", + "0.25", + "12.50", + ), + ( + "claude-mythos-5-1", + "Claude Mythos 5.1", + "10", + "50", + "0.25", + "12.50", + ), // Claude Fable 5(Opus 之上的新档) ( "claude-fable-5", @@ -1632,14 +1650,15 @@ impl Database { "0.50", "6.25", ), - // Claude Sonnet 5(list 价,与 Sonnet 4.6 一致;促销 $2/$10 至 2026-08-31 不入表) + // Claude Sonnet 5(官方定价页 2026-09 确认:$2/$10 介绍价转为正式价, + // 原定 09-01 涨至 $3/$15 取消) ( "claude-sonnet-5", "Claude Sonnet 5", - "3", - "15", - "0.30", - "3.75", + "2", + "10", + "0.20", + "2.50", ), // Claude 4.7 系列 ( @@ -2597,6 +2616,20 @@ impl Database { fn repair_current_model_pricing(conn: &Connection) -> Result<(), AppError> { let pricing_fixes = [ + // 2026-09-02 官方定价页确认 Sonnet 5 $2/$10 介绍价转为正式价、原定 09-01 涨至 + // $3/$15 取消:早先按 list 价 seed 的行改回正式价(用户手改过的行不匹配旧值,不动) + ( + "claude-sonnet-5", + "Claude Sonnet 5", + "2", + "10", + "0.20", + "2.50", + "3", + "15", + "0.30", + "3.75", + ), // 2026-08-13 models.dev 审计核价:grok-4.5 的 cached input 官方挂牌为 0.30 // (docs.x.ai 现行价表),与 grok-4.5-build 的实测计费一致;早先按 0.50 // 录入的行在此校正。注意 0.50 是 grok-4.6 的 cached 价,勿两者互串 diff --git a/src-tauri/src/database/tests.rs b/src-tauri/src/database/tests.rs index 26ff7d918d..119d654176 100644 --- a/src-tauri/src/database/tests.rs +++ b/src-tauri/src/database/tests.rs @@ -892,6 +892,135 @@ fn model_pricing_seed_repairs_known_outdated_builtin_prices() { assert_eq!(glm, ("9".to_string(), "9".to_string(), "9".to_string())); } +#[test] +fn model_pricing_seed_includes_claude_5_1_and_standard_sonnet_5_prices() { + let db = Database::memory().expect("create memory db"); + let conn = db.conn.lock().expect("lock conn"); + + for model_id in ["claude-fable-5-1", "claude-mythos-5-1"] { + let price: (String, String, String, String) = conn + .query_row( + "SELECT input_cost_per_million, output_cost_per_million, + cache_read_cost_per_million, cache_creation_cost_per_million + FROM model_pricing WHERE model_id = ?1", + [model_id], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)), + ) + .expect("query Fable 5.1 family price"); + // 缓存读 0.025x = $0.25,不是 Fable 5 的 $1 + assert_eq!( + price, + ( + "10".to_string(), + "50".to_string(), + "0.25".to_string(), + "12.50".to_string(), + ), + "{model_id}" + ); + } + + let sonnet: (String, String, String, String) = conn + .query_row( + "SELECT input_cost_per_million, output_cost_per_million, + cache_read_cost_per_million, cache_creation_cost_per_million + FROM model_pricing WHERE model_id = 'claude-sonnet-5'", + [], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)), + ) + .expect("query Sonnet 5 price"); + // $2/$10 介绍价已转为正式价(原定 2026-09-01 涨至 $3/$15 取消) + assert_eq!( + sonnet, + ( + "2".to_string(), + "10".to_string(), + "0.20".to_string(), + "2.50".to_string(), + ) + ); +} + +#[test] +fn model_pricing_seed_repairs_sonnet_5_list_price_but_keeps_custom_price() { + let db = Database::memory().expect("create memory db"); + + { + let conn = db.conn.lock().expect("lock conn"); + // 旧 seed 按 list 价录入的行 → 应被修正 + conn.execute( + "UPDATE model_pricing + SET input_cost_per_million = '3', + output_cost_per_million = '15', + cache_read_cost_per_million = '0.30', + cache_creation_cost_per_million = '3.75' + WHERE model_id = 'claude-sonnet-5'", + [], + ) + .expect("restore old Sonnet 5 list price"); + } + + db.ensure_model_pricing_seeded() + .expect("ensure pricing seeded"); + + { + let conn = db.conn.lock().expect("lock conn"); + let sonnet: (String, String, String, String) = conn + .query_row( + "SELECT input_cost_per_million, output_cost_per_million, + cache_read_cost_per_million, cache_creation_cost_per_million + FROM model_pricing WHERE model_id = 'claude-sonnet-5'", + [], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)), + ) + .expect("query repaired Sonnet 5 price"); + assert_eq!( + sonnet, + ( + "2".to_string(), + "10".to_string(), + "0.20".to_string(), + "2.50".to_string(), + ) + ); + + // 用户手改过的价(不匹配旧 seed 值)不动 + conn.execute( + "UPDATE model_pricing + SET input_cost_per_million = '9', + output_cost_per_million = '9', + cache_read_cost_per_million = '9', + cache_creation_cost_per_million = '9' + WHERE model_id = 'claude-sonnet-5'", + [], + ) + .expect("set custom Sonnet 5 price"); + } + + db.ensure_model_pricing_seeded() + .expect("ensure pricing seeded again"); + + let conn = db.conn.lock().expect("lock conn"); + let custom: (String, String, String, String) = conn + .query_row( + "SELECT input_cost_per_million, output_cost_per_million, + cache_read_cost_per_million, cache_creation_cost_per_million + FROM model_pricing WHERE model_id = 'claude-sonnet-5'", + [], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)), + ) + .expect("query custom Sonnet 5 price"); + assert_eq!( + custom, + ( + "9".to_string(), + "9".to_string(), + "9".to_string(), + "9".to_string(), + ) + ); +} + #[test] fn ensure_incremental_auto_vacuum_rebuilds_existing_file_db() { let temp = NamedTempFile::new().expect("create temp db file");