fix(usage): account for Anthropic cache write TTLs

Parse and retain Anthropic's ephemeral 5-minute and 1-hour cache-creation token buckets while preserving the existing aggregate cache-write metric for compatibility.

Price 1-hour writes at the documented premium relative to the configured 5-minute write rate, clamp inconsistent provider details safely, and include TTL buckets in usage diagnostics.

Persist 1-hour cache-write tokens with schema version 14 so zero-cost backfills and later pricing updates retain the original TTL semantics. Keep session import paths compatible through zero-valued detail fields.
This commit is contained in:
Jason
2026-07-12 12:19:36 +08:00
parent b9263a8040
commit 13e7c1fcc4
11 changed files with 258 additions and 29 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ use std::sync::Mutex;
/// 当前 Schema 版本号
/// 每次修改表结构时递增,并在 schema.rs 中添加相应的迁移逻辑
pub(crate) const SCHEMA_VERSION: i32 = 13;
pub(crate) const SCHEMA_VERSION: i32 = 14;
/// 安全地序列化 JSON,避免 unwrap panic
pub(crate) fn to_json_string<T: Serialize>(value: &T) -> Result<String, AppError> {
+28 -1
View File
@@ -189,6 +189,7 @@ impl Database {
pricing_model TEXT,
input_tokens INTEGER NOT NULL DEFAULT 0, output_tokens INTEGER NOT NULL DEFAULT 0,
cache_read_tokens INTEGER NOT NULL DEFAULT 0, cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
cache_creation_1h_tokens INTEGER NOT NULL DEFAULT 0,
input_token_semantics INTEGER NOT NULL DEFAULT 0,
input_cost_usd TEXT NOT NULL DEFAULT '0', output_cost_usd TEXT NOT NULL DEFAULT '0',
cache_read_cost_usd TEXT NOT NULL DEFAULT '0', cache_creation_cost_usd TEXT NOT NULL DEFAULT '0',
@@ -485,6 +486,11 @@ impl Database {
Self::migrate_v12_to_v13(conn)?;
Self::set_user_version(conn, 13)?;
}
13 => {
log::info!("迁移数据库从 v13 到 v14(记录 1 小时缓存写入 token)");
Self::migrate_v13_to_v14(conn)?;
Self::set_user_version(conn, 14)?;
}
_ => {
return Err(AppError::Database(format!(
"未知的数据库版本 {version},无法迁移到 {SCHEMA_VERSION}"
@@ -1354,6 +1360,22 @@ impl Database {
Ok(())
}
/// v13 -> v14:持久化 Anthropic 1-hour cache-write bucket。
/// 5-minute/unknown writes can be derived from aggregate cache_creation_tokens;
/// storing only the premium bucket keeps existing aggregate APIs stable while
/// allowing later cost backfills to retain correct TTL pricing.
fn migrate_v13_to_v14(conn: &Connection) -> Result<(), AppError> {
if Self::table_exists(conn, "proxy_request_logs")? {
Self::add_column_if_missing(
conn,
"proxy_request_logs",
"cache_creation_1h_tokens",
"INTEGER NOT NULL DEFAULT 0",
)?;
}
Ok(())
}
/// 插入默认模型定价数据
/// 格式: (model_id, display_name, input, output, cache_read, cache_creation)
/// 注意: model_id 使用短横线格式(如 claude-haiku-4-5),与 API 返回的模型名称标准化后一致
@@ -2713,7 +2735,7 @@ mod tests {
Database::apply_schema_migrations_on_conn(&conn)?;
assert_eq!(Database::get_user_version(&conn)?, 13);
assert_eq!(Database::get_user_version(&conn)?, 14);
assert!(Database::has_column(
&conn,
"proxy_request_logs",
@@ -2724,6 +2746,11 @@ mod tests {
"usage_daily_rollups",
"input_token_semantics"
)?);
assert!(Database::has_column(
&conn,
"proxy_request_logs",
"cache_creation_1h_tokens"
)?);
let log_default: i64 = conn.query_row(
"SELECT dflt_value = '0' FROM pragma_table_info('proxy_request_logs')
WHERE name = 'input_token_semantics'",