From 6eb217b242576b8d8c4bb0a6bdb4f76fbef75c5c Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 13 Jul 2026 17:55:33 +0800 Subject: [PATCH] revert(proxy): drop the 1-hour cache TTL option and TTL-bucketed write accounting The forced 1-hour cache_control TTL (schema v14) was a mistake. Injected breakpoints return to Anthropic's standard 5-minute TTL, caller-owned markers are preserved verbatim instead of having their TTLs rewritten, and the 5m/1h cache-write buckets are removed from usage parsing and pricing (back to the single aggregate cache-creation rate). The cache TTL selector is removed from the rectifier settings panel along with its i18n keys in all four locales. SCHEMA_VERSION returns to 13: the unreleased cache_creation_1h_tokens column and the v13->v14 migration are removed. The feature never shipped in a release and the introducing commit was never pushed, so no databases were stamped v14 outside this machine (local DB verified at user_version 13). --- src-tauri/src/commands/settings.rs | 9 -- src-tauri/src/database/mod.rs | 2 +- src-tauri/src/database/schema.rs | 29 +----- src-tauri/src/proxy/cache_injector.rs | 97 ++++-------------- src-tauri/src/proxy/forwarder.rs | 5 +- .../providers/transform_codex_anthropic.rs | 17 +--- src-tauri/src/proxy/response_processor.rs | 12 +-- src-tauri/src/proxy/thinking_optimizer.rs | 2 - src-tauri/src/proxy/types.rs | 8 -- src-tauri/src/proxy/usage/calculator.rs | 46 +-------- src-tauri/src/proxy/usage/logger.rs | 6 +- src-tauri/src/proxy/usage/parser.rs | 85 +--------------- src-tauri/src/services/session_usage.rs | 2 - src-tauri/src/services/session_usage_codex.rs | 2 - .../src/services/session_usage_gemini.rs | 2 - .../src/services/session_usage_opencode.rs | 2 - src-tauri/src/services/usage_stats.rs | 99 ++++--------------- .../settings/RectifierConfigPanel.tsx | 26 ----- src/i18n/locales/en.json | 5 +- src/i18n/locales/ja.json | 5 +- src/i18n/locales/zh-TW.json | 5 +- src/i18n/locales/zh.json | 5 +- src/lib/api/settings.ts | 1 - 23 files changed, 56 insertions(+), 416 deletions(-) diff --git a/src-tauri/src/commands/settings.rs b/src-tauri/src/commands/settings.rs index 8e4c08964..ec508d792 100644 --- a/src-tauri/src/commands/settings.rs +++ b/src-tauri/src/commands/settings.rs @@ -661,15 +661,6 @@ pub async fn set_optimizer_config( state: tauri::State<'_, crate::AppState>, config: crate::proxy::types::OptimizerConfig, ) -> Result { - // Validate cache_ttl: only allow known values - match config.cache_ttl.as_str() { - "5m" | "1h" => {} - other => { - return Err(format!( - "Invalid cache_ttl value: '{other}'. Allowed values: '5m', '1h'" - )) - } - } state .db .set_optimizer_config(&config) diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs index fa0652312..d7ba00104 100644 --- a/src-tauri/src/database/mod.rs +++ b/src-tauri/src/database/mod.rs @@ -52,7 +52,7 @@ use std::sync::Mutex; /// 当前 Schema 版本号 /// 每次修改表结构时递增,并在 schema.rs 中添加相应的迁移逻辑 -pub(crate) const SCHEMA_VERSION: i32 = 14; +pub(crate) const SCHEMA_VERSION: i32 = 13; /// 安全地序列化 JSON,避免 unwrap panic pub(crate) fn to_json_string(value: &T) -> Result { diff --git a/src-tauri/src/database/schema.rs b/src-tauri/src/database/schema.rs index ea0de90c2..14bb0637c 100644 --- a/src-tauri/src/database/schema.rs +++ b/src-tauri/src/database/schema.rs @@ -189,7 +189,6 @@ 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', @@ -486,11 +485,6 @@ 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}" @@ -1360,22 +1354,6 @@ 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 返回的模型名称标准化后一致 @@ -2788,7 +2766,7 @@ mod tests { Database::apply_schema_migrations_on_conn(&conn)?; - assert_eq!(Database::get_user_version(&conn)?, 14); + assert_eq!(Database::get_user_version(&conn)?, 13); assert!(Database::has_column( &conn, "proxy_request_logs", @@ -2799,11 +2777,6 @@ 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'", diff --git a/src-tauri/src/proxy/cache_injector.rs b/src-tauri/src/proxy/cache_injector.rs index dd84d955e..5ed524684 100644 --- a/src-tauri/src/proxy/cache_injector.rs +++ b/src-tauri/src/proxy/cache_injector.rs @@ -22,19 +22,9 @@ pub fn inject(body: &mut Value, config: &OptimizerConfig) { ); } - // 升级已有断点的 TTL - upgrade_existing_ttl(body, &config.cache_ttl); - let mut budget = 4_usize.saturating_sub(existing); if budget == 0 { - if existing > 0 { - log::info!( - "[OPT] cache: ttl-upgrade({existing}->{},existing={existing})", - config.cache_ttl - ); - } else { - log::info!("[OPT] cache: no-op(existing={existing})"); - } + log::info!("[OPT] cache: no-op(existing={existing})"); return; } @@ -46,10 +36,7 @@ pub fn inject(body: &mut Value, config: &OptimizerConfig) { if let Some(last) = tools.last_mut() { if last.get("cache_control").is_none() { if let Some(o) = last.as_object_mut() { - o.insert( - "cache_control".to_string(), - make_cache_control(&config.cache_ttl), - ); + o.insert("cache_control".to_string(), make_cache_control()); } budget -= 1; injected.push("tools"); @@ -73,10 +60,7 @@ pub fn inject(body: &mut Value, config: &OptimizerConfig) { if let Some(last) = system.last_mut() { if last.get("cache_control").is_none() { if let Some(o) = last.as_object_mut() { - o.insert( - "cache_control".to_string(), - make_cache_control(&config.cache_ttl), - ); + o.insert("cache_control".to_string(), make_cache_control()); } budget -= 1; injected.push("system"); @@ -90,7 +74,7 @@ pub fn inject(body: &mut Value, config: &OptimizerConfig) { if budget > 0 { if let Some(messages) = body.get_mut("messages").and_then(|m| m.as_array_mut()) { for message in messages.iter_mut().rev() { - if inject_message_breakpoint(message, &config.cache_ttl) { + if inject_message_breakpoint(message) { budget -= 1; injected.push("msgs-latest"); break; @@ -108,7 +92,7 @@ pub fn inject(body: &mut Value, config: &OptimizerConfig) { } user_count += 1; if user_count == 2 { - if inject_message_breakpoint(message, &config.cache_ttl) { + if inject_message_breakpoint(message) { injected.push("msgs-prior-user"); } break; @@ -122,11 +106,11 @@ pub fn inject(body: &mut Value, config: &OptimizerConfig) { "[OPT] cache: {}bp({},{},pre={existing})", injected.len(), injected.join("+"), - config.cache_ttl, + "5m", ); } -fn inject_message_breakpoint(message: &mut Value, ttl: &str) -> bool { +fn inject_message_breakpoint(message: &mut Value) -> bool { let Some(content) = message.get_mut("content").and_then(Value::as_array_mut) else { return false; }; @@ -144,16 +128,12 @@ fn inject_message_breakpoint(message: &mut Value, ttl: &str) -> bool { let Some(object) = block.as_object_mut() else { return false; }; - object.insert("cache_control".to_string(), make_cache_control(ttl)); + object.insert("cache_control".to_string(), make_cache_control()); true } -fn make_cache_control(ttl: &str) -> Value { - if ttl == "5m" { - json!({"type": "ephemeral"}) - } else { - json!({"type": "ephemeral", "ttl": ttl}) - } +fn make_cache_control() -> Value { + json!({"type": "ephemeral"}) } fn count_existing(body: &Value) -> usize { @@ -187,40 +167,6 @@ fn count_existing(body: &Value) -> usize { count } -fn upgrade_existing_ttl(body: &mut Value, ttl: &str) { - let upgrade = |val: &mut Value| { - if let Some(cc) = val.get_mut("cache_control").and_then(|c| c.as_object_mut()) { - if ttl == "5m" { - cc.remove("ttl"); - } else { - cc.insert("ttl".to_string(), json!(ttl)); - } - } - }; - - if let Some(tools) = body.get_mut("tools").and_then(|t| t.as_array_mut()) { - for tool in tools.iter_mut() { - upgrade(tool); - } - } - - if let Some(system) = body.get_mut("system").and_then(|s| s.as_array_mut()) { - for block in system.iter_mut() { - upgrade(block); - } - } - - if let Some(messages) = body.get_mut("messages").and_then(|m| m.as_array_mut()) { - for msg in messages.iter_mut() { - if let Some(content) = msg.get_mut("content").and_then(|c| c.as_array_mut()) { - for block in content.iter_mut() { - upgrade(block); - } - } - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -231,7 +177,6 @@ mod tests { enabled: true, thinking_optimizer: true, cache_injection: true, - cache_ttl: "1h".to_string(), } } @@ -262,7 +207,7 @@ mod tests { // tools last element assert!(body["tools"][1].get("cache_control").is_some()); - assert_eq!(body["tools"][1]["cache_control"]["ttl"], "1h"); + assert!(body["tools"][1]["cache_control"].get("ttl").is_none()); // system last element assert!(body["system"][0].get("cache_control").is_some()); // assistant last non-thinking block @@ -296,26 +241,26 @@ mod tests { } #[test] - fn test_existing_four_breakpoints_only_upgrades_ttl() { + fn test_existing_four_breakpoints_preserve_caller_ttl() { let mut body = json!({ "model": "test", "tools": [ - {"name": "t1", "cache_control": {"type": "ephemeral", "ttl": "5m"}}, - {"name": "t2", "cache_control": {"type": "ephemeral", "ttl": "5m"}} + {"name": "t1", "cache_control": {"type": "ephemeral", "ttl": "1h"}}, + {"name": "t2", "cache_control": {"type": "ephemeral", "ttl": "1h"}} ], "system": [ - {"type": "text", "text": "sys", "cache_control": {"type": "ephemeral", "ttl": "5m"}} + {"type": "text", "text": "sys", "cache_control": {"type": "ephemeral", "ttl": "1h"}} ], "messages": [ {"role": "assistant", "content": [ - {"type": "text", "text": "ok", "cache_control": {"type": "ephemeral", "ttl": "5m"}} + {"type": "text", "text": "ok", "cache_control": {"type": "ephemeral", "ttl": "1h"}} ]} ] }); inject(&mut body, &default_config()); - // All TTLs upgraded to 1h, no new breakpoints + // Existing markers are caller-owned; only newly injected markers are fixed to 5m. assert_eq!(body["tools"][0]["cache_control"]["ttl"], "1h"); assert_eq!(body["tools"][1]["cache_control"]["ttl"], "1h"); assert_eq!(body["system"][0]["cache_control"]["ttl"], "1h"); @@ -393,18 +338,14 @@ mod tests { } #[test] - fn test_ttl_5m_no_ttl_field() { - let config = OptimizerConfig { - cache_ttl: "5m".to_string(), - ..default_config() - }; + fn test_standard_five_minute_cache_control_omits_ttl() { let mut body = json!({ "model": "test", "tools": [{"name": "tool1"}], "messages": [{"role": "user", "content": [{"type": "text", "text": "hi"}]}] }); - inject(&mut body, &config); + inject(&mut body, &default_config()); let cc = &body["tools"][0]["cache_control"]; assert_eq!(cc["type"], "ephemeral"); diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index 7759b61cb..1df83e2d7 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -2859,13 +2859,13 @@ fn responses_error_envelope_message(body: &[u8]) -> Option { /// Prompt caching is part of the Codex→Anthropic protocol bridge rather than an /// optional Bedrock optimizer. Codex requests do not contain Anthropic /// `cache_control`, so keep bridge caching on by default while still honoring the -/// dedicated cache-injection switch and configured TTL. +/// dedicated cache-injection switch. Injected breakpoints always use Anthropic's +/// standard 5-minute TTL. fn codex_anthropic_cache_config(config: &OptimizerConfig) -> OptimizerConfig { OptimizerConfig { enabled: true, thinking_optimizer: false, cache_injection: config.cache_injection, - cache_ttl: config.cache_ttl.clone(), } } @@ -4233,7 +4233,6 @@ mod tests { let default = codex_anthropic_cache_config(&OptimizerConfig::default()); assert!(default.enabled); assert!(default.cache_injection); - assert_eq!(default.cache_ttl, "1h"); let disabled = codex_anthropic_cache_config(&OptimizerConfig { cache_injection: false, diff --git a/src-tauri/src/proxy/providers/transform_codex_anthropic.rs b/src-tauri/src/proxy/providers/transform_codex_anthropic.rs index 0cbd457f1..a45e99aed 100644 --- a/src-tauri/src/proxy/providers/transform_codex_anthropic.rs +++ b/src-tauri/src/proxy/providers/transform_codex_anthropic.rs @@ -191,11 +191,6 @@ pub(crate) fn build_responses_usage_from_anthropic(usage: Option<&Value>) -> Val if cache_creation > 0 { result["cache_creation_input_tokens"] = json!(cache_creation); } - if let Some(cache_creation_details) = u.get("cache_creation") { - // Preserve Anthropic's TTL buckets as a compatibility extension. The usage - // parser consumes these to distinguish 5-minute and 1-hour write pricing. - result["cache_creation"] = cache_creation_details.clone(); - } result } @@ -2318,11 +2313,7 @@ mod tests { "output_tokens": 5, "output_tokens_details": {"thinking_tokens": 3}, "cache_read_input_tokens": 60, - "cache_creation_input_tokens": 20, - "cache_creation": { - "ephemeral_5m_input_tokens": 5, - "ephemeral_1h_input_tokens": 15 - } + "cache_creation_input_tokens": 20 } }); let result = anthropic_response_to_responses(input).unwrap(); @@ -2340,12 +2331,8 @@ mod tests { result["usage"]["input_tokens_details"]["cache_write_tokens"], 20 ); - // cache_creation is passed through explicitly for downstream billing attribution (counted only once) + // Aggregate cache creation is exposed for downstream billing attribution (counted only once). assert_eq!(result["usage"]["cache_creation_input_tokens"], 20); - assert_eq!( - result["usage"]["cache_creation"]["ephemeral_1h_input_tokens"], - 15 - ); } #[test] diff --git a/src-tauri/src/proxy/response_processor.rs b/src-tauri/src/proxy/response_processor.rs index 20b4eb039..238e14420 100644 --- a/src-tauri/src/proxy/response_processor.rs +++ b/src-tauri/src/proxy/response_processor.rs @@ -645,14 +645,12 @@ async fn log_usage_internal( let request_id = usage.dedup_request_id(); log::debug!( - "[{app_type}] 记录请求日志: id={request_id}, provider={provider_id}, model={model}, streaming={is_streaming}, status={status_code}, latency_ms={latency_ms}, first_token_ms={first_token_ms:?}, session={}, input={}, output={}, cache_read={}, cache_creation={}, cache_creation_5m={}, cache_creation_1h={}", + "[{app_type}] 记录请求日志: id={request_id}, provider={provider_id}, model={model}, streaming={is_streaming}, status={status_code}, latency_ms={latency_ms}, first_token_ms={first_token_ms:?}, session={}, input={}, output={}, cache_read={}, cache_creation={}", session_id.as_deref().unwrap_or("none"), usage.input_tokens, usage.output_tokens, usage.cache_read_tokens, - usage.cache_creation_tokens, - usage.cache_creation_5m_tokens, - usage.cache_creation_1h_tokens + usage.cache_creation_tokens ); if let Err(e) = logger.log_with_calculation( @@ -1006,8 +1004,6 @@ mod tests { output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; @@ -1076,8 +1072,6 @@ mod tests { output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; @@ -1160,8 +1154,6 @@ mod tests { output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; diff --git a/src-tauri/src/proxy/thinking_optimizer.rs b/src-tauri/src/proxy/thinking_optimizer.rs index 90cf892a4..4f8374a5a 100644 --- a/src-tauri/src/proxy/thinking_optimizer.rs +++ b/src-tauri/src/proxy/thinking_optimizer.rs @@ -137,7 +137,6 @@ mod tests { enabled: true, thinking_optimizer: true, cache_injection: true, - cache_ttl: "1h".to_string(), } } @@ -146,7 +145,6 @@ mod tests { enabled: true, thinking_optimizer: false, cache_injection: true, - cache_ttl: "1h".to_string(), } } diff --git a/src-tauri/src/proxy/types.rs b/src-tauri/src/proxy/types.rs index 6dc5bc659..d309000f6 100644 --- a/src-tauri/src/proxy/types.rs +++ b/src-tauri/src/proxy/types.rs @@ -264,13 +264,6 @@ pub struct OptimizerConfig { /// Cache 注入子开关(总开关开启后默认生效) #[serde(default = "default_true")] pub cache_injection: bool, - /// Cache TTL: "5m" | "1h"(默认 "1h") - #[serde(default = "default_cache_ttl")] - pub cache_ttl: String, -} - -fn default_cache_ttl() -> String { - "1h".to_string() } impl Default for OptimizerConfig { @@ -279,7 +272,6 @@ impl Default for OptimizerConfig { enabled: false, thinking_optimizer: true, cache_injection: true, - cache_ttl: "1h".to_string(), } } } diff --git a/src-tauri/src/proxy/usage/calculator.rs b/src-tauri/src/proxy/usage/calculator.rs index 4628a6868..db8def267 100644 --- a/src-tauri/src/proxy/usage/calculator.rs +++ b/src-tauri/src/proxy/usage/calculator.rs @@ -94,20 +94,9 @@ impl CostCalculator { Decimal::from(usage.output_tokens) * pricing.output_cost_per_million / million; let cache_read_cost = Decimal::from(usage.cache_read_tokens) * pricing.cache_read_cost_per_million / million; - let (cache_creation_unspecified, cache_creation_5m, cache_creation_1h) = - usage.normalized_cache_creation_buckets(); - let cache_creation_5m_price = pricing.cache_creation_cost_per_million; - // The stored cache-creation price is the existing 5-minute rate. Anthropic - // prices 5m writes at 1.25x input and 1h writes at 2x input, therefore the - // corresponding 1h price is 8/5 of the configured 5m price. Unknown providers - // without TTL details retain the configured legacy rate. - let cache_creation_1h_price = - cache_creation_5m_price * Decimal::from(8u32) / Decimal::from(5u32); - let cache_creation_cost = (Decimal::from(cache_creation_unspecified) - + Decimal::from(cache_creation_5m)) - * cache_creation_5m_price - / million - + Decimal::from(cache_creation_1h) * cache_creation_1h_price / million; + let cache_creation_cost = Decimal::from(usage.cache_creation_tokens) + * pricing.cache_creation_cost_per_million + / million; // 总成本 = 各项基础成本之和 × 倍率 let base_total = input_cost + output_cost + cache_read_cost + cache_creation_cost; @@ -170,8 +159,6 @@ mod tests { output_tokens: 500, cache_read_tokens: 200, cache_creation_tokens: 100, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; @@ -204,8 +191,6 @@ mod tests { output_tokens: 500, cache_read_tokens: 200, cache_creation_tokens: 100, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; @@ -226,25 +211,6 @@ mod tests { assert_eq!(cost.total_cost, Decimal::from_str("0.010035").unwrap()); } - #[test] - fn test_one_hour_cache_creation_uses_premium_rate() { - let usage = TokenUsage { - cache_creation_tokens: 1_000_000, - cache_creation_5m_tokens: 250_000, - cache_creation_1h_tokens: 750_000, - ..TokenUsage::default() - }; - let pricing = ModelPricing::from_strings("3", "15", "0.3", "3.75").unwrap(); - - let cost = CostCalculator::calculate(&usage, &pricing, Decimal::ONE); - - // 250k × $3.75/M + 750k × $6/M = $5.4375. - assert_eq!( - cost.cache_creation_cost, - Decimal::from_str("5.4375").unwrap() - ); - } - #[test] fn test_cost_multiplier() { let usage = TokenUsage { @@ -252,8 +218,6 @@ mod tests { output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; @@ -276,8 +240,6 @@ mod tests { output_tokens: 500, cache_read_tokens: 0, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; @@ -295,8 +257,6 @@ mod tests { output_tokens: 1, cache_read_tokens: 1, cache_creation_tokens: 1, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; diff --git a/src-tauri/src/proxy/usage/logger.rs b/src-tauri/src/proxy/usage/logger.rs index 985fad40a..072dd5ccf 100644 --- a/src-tauri/src/proxy/usage/logger.rs +++ b/src-tauri/src/proxy/usage/logger.rs @@ -81,12 +81,11 @@ impl<'a> UsageLogger<'a> { "INSERT OR REPLACE INTO proxy_request_logs ( request_id, provider_id, app_type, model, request_model, pricing_model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, - cache_creation_1h_tokens, input_token_semantics, input_cost_usd, output_cost_usd, cache_read_cost_usd, cache_creation_cost_usd, total_cost_usd, latency_ms, first_token_ms, status_code, error_message, session_id, provider_type, is_streaming, cost_multiplier, created_at - ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26)", + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25)", rusqlite::params![ log.request_id, log.provider_id, @@ -98,7 +97,6 @@ impl<'a> UsageLogger<'a> { log.usage.output_tokens, log.usage.cache_read_tokens, log.usage.cache_creation_tokens, - log.usage.cache_creation_1h_tokens, input_token_semantics, input_cost, output_cost, @@ -398,8 +396,6 @@ mod tests { output_tokens: 500, cache_read_tokens: 0, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }; diff --git a/src-tauri/src/proxy/usage/parser.rs b/src-tauri/src/proxy/usage/parser.rs index c28edf8c5..45120b8ae 100644 --- a/src-tauri/src/proxy/usage/parser.rs +++ b/src-tauri/src/proxy/usage/parser.rs @@ -27,22 +27,6 @@ fn openai_cache_write_tokens(usage: &Value) -> u32 { .unwrap_or(0) as u32 } -fn cache_creation_ttl_tokens(usage: &Value) -> (u32, u32) { - let details = usage - .get("cache_creation") - .or_else(|| usage.pointer("/input_tokens_details/cache_creation")) - .or_else(|| usage.pointer("/prompt_tokens_details/cache_creation")); - let five_minutes = details - .and_then(|value| value.get("ephemeral_5m_input_tokens")) - .and_then(Value::as_u64) - .unwrap_or(0) as u32; - let one_hour = details - .and_then(|value| value.get("ephemeral_1h_input_tokens")) - .and_then(Value::as_u64) - .unwrap_or(0) as u32; - (five_minutes, one_hour) -} - /// Session 日志 request_id 前缀,与 `session_usage.rs` 中的格式保持一致 pub const SESSION_REQUEST_ID_PREFIX: &str = "session:"; @@ -53,13 +37,6 @@ pub struct TokenUsage { pub output_tokens: u32, pub cache_read_tokens: u32, pub cache_creation_tokens: u32, - /// Anthropic cache-write TTL detail. The aggregate above remains the stable - /// public/storage metric; these buckets make 1-hour writes billable at 2x input - /// instead of the 5-minute 1.25x rate. - #[serde(default)] - pub cache_creation_5m_tokens: u32, - #[serde(default)] - pub cache_creation_1h_tokens: u32, /// 从响应中提取的实际模型名称(如果可用) pub model: Option, /// 从响应中提取的消息 ID(用于跨源去重) @@ -70,23 +47,6 @@ pub struct TokenUsage { } impl TokenUsage { - /// Return mutually exclusive cache-write buckets, clamped to the aggregate. - /// Providers that do not expose TTL detail remain in `unspecified` and keep the - /// legacy cache-creation price. - pub fn normalized_cache_creation_buckets(&self) -> (u32, u32, u32) { - let one_hour = self - .cache_creation_1h_tokens - .min(self.cache_creation_tokens); - let five_minutes = self - .cache_creation_5m_tokens - .min(self.cache_creation_tokens.saturating_sub(one_hour)); - let unspecified = self - .cache_creation_tokens - .saturating_sub(one_hour) - .saturating_sub(five_minutes); - (unspecified, five_minutes, one_hour) - } - /// 生成与 session 日志共享的 request_id,用于跨源去重。 /// 有 message_id 时返回 `session:{id}`,否则回退到随机 UUID。 pub fn dedup_request_id(&self) -> String { @@ -123,7 +83,6 @@ impl TokenUsage { /// 从 Claude API 非流式响应解析 pub fn from_claude_response(body: &Value) -> Option { let usage = body.get("usage")?; - let (cache_creation_5m_tokens, cache_creation_1h_tokens) = cache_creation_ttl_tokens(usage); // 提取响应中的模型名称 let model = body .get("model") @@ -145,8 +104,6 @@ impl TokenUsage { .get("cache_creation_input_tokens") .and_then(|v| v.as_u64()) .unwrap_or(0) as u32, - cache_creation_5m_tokens, - cache_creation_1h_tokens, model, message_id, }) @@ -177,8 +134,6 @@ impl TokenUsage { } } if let Some(msg_usage) = event.get("message").and_then(|m| m.get("usage")) { - let (cache_creation_5m, cache_creation_1h) = - cache_creation_ttl_tokens(msg_usage); // 从 message_start 获取 input_tokens(原生 Claude API) if let Some(input) = msg_usage.get("input_tokens").and_then(|v| v.as_u64()) @@ -195,14 +150,10 @@ impl TokenUsage { .and_then(|v| v.as_u64()) .unwrap_or(0) as u32; - usage.cache_creation_5m_tokens = cache_creation_5m; - usage.cache_creation_1h_tokens = cache_creation_1h; } } "message_delta" => { if let Some(delta_usage) = event.get("usage") { - let (delta_cache_creation_5m, delta_cache_creation_1h) = - cache_creation_ttl_tokens(delta_usage); // 从 message_delta 获取 output_tokens if let Some(output) = delta_usage.get("output_tokens").and_then(|v| v.as_u64()) @@ -241,14 +192,6 @@ impl TokenUsage { } if let Some(cache_creation) = delta_cache_creation { usage.cache_creation_tokens = cache_creation; - if delta_cache_creation_5m > 0 - || delta_cache_creation_1h > 0 - { - usage.cache_creation_5m_tokens = - delta_cache_creation_5m; - usage.cache_creation_1h_tokens = - delta_cache_creation_1h; - } } } } @@ -263,10 +206,6 @@ impl TokenUsage { if usage.cache_creation_tokens == 0 { if let Some(cache_creation) = delta_cache_creation { usage.cache_creation_tokens = cache_creation; - if delta_cache_creation_5m > 0 || delta_cache_creation_1h > 0 { - usage.cache_creation_5m_tokens = delta_cache_creation_5m; - usage.cache_creation_1h_tokens = delta_cache_creation_1h; - } } } } @@ -298,8 +237,6 @@ impl TokenUsage { output_tokens: usage.get("completion_tokens")?.as_u64()? as u32, cache_read_tokens: 0, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: None, message_id: None, }) @@ -333,15 +270,12 @@ impl TokenUsage { let cached_tokens = openai_cache_read_tokens(usage); let cache_write_tokens = openai_cache_write_tokens(usage); - let (cache_creation_5m_tokens, cache_creation_1h_tokens) = cache_creation_ttl_tokens(usage); Some(Self { input_tokens: input_tokens? as u32, output_tokens: output_tokens? as u32, cache_read_tokens: cached_tokens, cache_creation_tokens: cache_write_tokens, - cache_creation_5m_tokens, - cache_creation_1h_tokens, model, message_id: None, }) @@ -360,7 +294,6 @@ impl TokenUsage { // 获取 cached_tokens (可能在 cache_read_input_tokens 或 input_tokens_details 中) let cached_tokens = openai_cache_read_tokens(usage); let cache_write_tokens = openai_cache_write_tokens(usage); - let (cache_creation_5m_tokens, cache_creation_1h_tokens) = cache_creation_ttl_tokens(usage); // 调整 input_tokens: OpenAI total input 同时包含 cache read/write 两桶。 let adjusted_input = input_tokens @@ -378,8 +311,6 @@ impl TokenUsage { output_tokens, cache_read_tokens: cached_tokens, cache_creation_tokens: cache_write_tokens, - cache_creation_5m_tokens, - cache_creation_1h_tokens, model, message_id: None, }) @@ -460,7 +391,6 @@ impl TokenUsage { // 获取 cached_tokens (可能在 prompt_tokens_details 中) let cached_tokens = openai_cache_read_tokens(usage); let cache_write_tokens = openai_cache_write_tokens(usage); - let (cache_creation_5m_tokens, cache_creation_1h_tokens) = cache_creation_ttl_tokens(usage); // 提取响应中的模型名称 let model = body @@ -473,8 +403,6 @@ impl TokenUsage { output_tokens: completion_tokens as u32, cache_read_tokens: cached_tokens, cache_creation_tokens: cache_write_tokens, - cache_creation_5m_tokens, - cache_creation_1h_tokens, model, message_id: None, }) @@ -520,8 +448,6 @@ impl TokenUsage { .and_then(|v| v.as_u64()) .unwrap_or(0) as u32, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model, message_id: None, }) @@ -573,8 +499,6 @@ impl TokenUsage { output_tokens: total_output, cache_read_tokens: total_cache_read, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model, message_id: None, }) @@ -597,11 +521,7 @@ mod tests { "input_tokens": 100, "output_tokens": 50, "cache_read_input_tokens": 20, - "cache_creation_input_tokens": 10, - "cache_creation": { - "ephemeral_5m_input_tokens": 4, - "ephemeral_1h_input_tokens": 6 - } + "cache_creation_input_tokens": 10 } }); @@ -610,9 +530,6 @@ mod tests { assert_eq!(usage.output_tokens, 50); assert_eq!(usage.cache_read_tokens, 20); assert_eq!(usage.cache_creation_tokens, 10); - assert_eq!(usage.cache_creation_5m_tokens, 4); - assert_eq!(usage.cache_creation_1h_tokens, 6); - assert_eq!(usage.normalized_cache_creation_buckets(), (0, 4, 6)); assert_eq!(usage.model, Some("claude-sonnet-4-20250514".to_string())); } diff --git a/src-tauri/src/services/session_usage.rs b/src-tauri/src/services/session_usage.rs index 3c5c83660..7d8756722 100644 --- a/src-tauri/src/services/session_usage.rs +++ b/src-tauri/src/services/session_usage.rs @@ -448,8 +448,6 @@ fn insert_session_log_entry( output_tokens: msg.output_tokens, cache_read_tokens: msg.cache_read_tokens, cache_creation_tokens: msg.cache_creation_tokens, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: Some(msg.model.clone()), message_id: None, }; diff --git a/src-tauri/src/services/session_usage_codex.rs b/src-tauri/src/services/session_usage_codex.rs index 7bf33257f..5757f9ded 100644 --- a/src-tauri/src/services/session_usage_codex.rs +++ b/src-tauri/src/services/session_usage_codex.rs @@ -634,8 +634,6 @@ fn insert_codex_session_entry( output_tokens: delta.output, cache_read_tokens: delta.cached_input, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: Some(model.to_string()), message_id: None, }; diff --git a/src-tauri/src/services/session_usage_gemini.rs b/src-tauri/src/services/session_usage_gemini.rs index 3e81a3613..c904726f2 100644 --- a/src-tauri/src/services/session_usage_gemini.rs +++ b/src-tauri/src/services/session_usage_gemini.rs @@ -270,8 +270,6 @@ fn insert_gemini_session_entry( output_tokens, cache_read_tokens: tokens.cached, cache_creation_tokens: 0, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: Some(model.to_string()), message_id: None, }; diff --git a/src-tauri/src/services/session_usage_opencode.rs b/src-tauri/src/services/session_usage_opencode.rs index f4ec116ab..83574bb62 100644 --- a/src-tauri/src/services/session_usage_opencode.rs +++ b/src-tauri/src/services/session_usage_opencode.rs @@ -362,8 +362,6 @@ fn insert_opencode_message( output_tokens: output_with_reasoning, cache_read_tokens: msg.cache_read_tokens, cache_creation_tokens: msg.cache_write_tokens, - cache_creation_5m_tokens: 0, - cache_creation_1h_tokens: 0, model: Some(msg.model_id.clone()), message_id: None, }; diff --git a/src-tauri/src/services/usage_stats.rs b/src-tauri/src/services/usage_stats.rs index 0fb576b84..d0760f2c4 100644 --- a/src-tauri/src/services/usage_stats.rs +++ b/src-tauri/src/services/usage_stats.rs @@ -137,9 +137,6 @@ pub struct RequestLogDetail { pub output_tokens: u32, pub cache_read_tokens: u32, pub cache_creation_tokens: u32, - /// Internal TTL pricing bucket; aggregate UI metrics remain unchanged. - #[serde(skip)] - pub cache_creation_1h_tokens: u32, /// Internal storage semantics; omitted from the UI/API payload. #[serde(skip)] pub input_token_semantics: i64, @@ -162,12 +159,12 @@ pub struct RequestLogDetail { pub pricing_model: Option, } -/// 把 27 列的查询结果映射为 `RequestLogDetail`。 +/// 把 26 列的查询结果映射为 `RequestLogDetail`。 /// -/// 调用方的 SELECT **必须**按以下顺序返回 27 列: +/// 调用方的 SELECT **必须**按以下顺序返回 26 列: /// `request_id, provider_id, provider_name, app_type, model, request_model, /// cost_multiplier, input_tokens, output_tokens, cache_read_tokens, -/// cache_creation_tokens, cache_creation_1h_tokens, input_cost_usd, output_cost_usd, cache_read_cost_usd, +/// cache_creation_tokens, input_cost_usd, output_cost_usd, cache_read_cost_usd, /// cache_creation_cost_usd, total_cost_usd, is_streaming, latency_ms, /// first_token_ms, duration_ms, status_code, error_message, created_at, /// data_source, pricing_model, input_token_semantics` @@ -188,22 +185,21 @@ fn row_to_request_log_detail(row: &rusqlite::Row<'_>) -> rusqlite::Result(8)? as u32, cache_read_tokens: row.get::<_, i64>(9)? as u32, cache_creation_tokens: row.get::<_, i64>(10)? as u32, - cache_creation_1h_tokens: row.get::<_, i64>(11)? as u32, - input_cost_usd: row.get(12)?, - output_cost_usd: row.get(13)?, - cache_read_cost_usd: row.get(14)?, - cache_creation_cost_usd: row.get(15)?, - total_cost_usd: row.get(16)?, - is_streaming: row.get::<_, i64>(17)? != 0, - latency_ms: row.get::<_, i64>(18)? as u64, - first_token_ms: row.get::<_, Option>(19)?.map(|v| v as u64), - duration_ms: row.get::<_, Option>(20)?.map(|v| v as u64), - status_code: row.get::<_, i64>(21)? as u16, - error_message: row.get(22)?, - created_at: row.get(23)?, - data_source: row.get(24)?, - pricing_model: row.get(25)?, - input_token_semantics: row.get::<_, i64>(26)?, + input_cost_usd: row.get(11)?, + output_cost_usd: row.get(12)?, + cache_read_cost_usd: row.get(13)?, + cache_creation_cost_usd: row.get(14)?, + total_cost_usd: row.get(15)?, + is_streaming: row.get::<_, i64>(16)? != 0, + latency_ms: row.get::<_, i64>(17)? as u64, + first_token_ms: row.get::<_, Option>(18)?.map(|v| v as u64), + duration_ms: row.get::<_, Option>(19)?.map(|v| v as u64), + status_code: row.get::<_, i64>(20)? as u16, + error_message: row.get(21)?, + created_at: row.get(22)?, + data_source: row.get(23)?, + pricing_model: row.get(24)?, + input_token_semantics: row.get::<_, i64>(25)?, }) } @@ -1534,7 +1530,6 @@ impl Database { "SELECT l.request_id, l.provider_id, {logs_pname} as provider_name, l.app_type, l.model, l.request_model, l.cost_multiplier, l.input_tokens, l.output_tokens, l.cache_read_tokens, l.cache_creation_tokens, - l.cache_creation_1h_tokens, l.input_cost_usd, l.output_cost_usd, l.cache_read_cost_usd, l.cache_creation_cost_usd, l.total_cost_usd, l.is_streaming, l.latency_ms, l.first_token_ms, l.duration_ms, l.status_code, l.error_message, l.created_at, l.data_source, l.pricing_model, @@ -1579,7 +1574,6 @@ impl Database { "SELECT l.request_id, l.provider_id, {detail_pname} as provider_name, l.app_type, l.model, l.request_model, l.cost_multiplier, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, - cache_creation_1h_tokens, input_cost_usd, output_cost_usd, cache_read_cost_usd, cache_creation_cost_usd, total_cost_usd, is_streaming, latency_ms, first_token_ms, duration_ms, status_code, error_message, created_at, l.data_source, l.pricing_model, @@ -1736,7 +1730,6 @@ impl Database { "SELECT request_id, provider_id, NULL AS provider_name, app_type, model, request_model, cost_multiplier, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, - cache_creation_1h_tokens, input_cost_usd, output_cost_usd, cache_read_cost_usd, cache_creation_cost_usd, total_cost_usd, is_streaming, latency_ms, first_token_ms, duration_ms, status_code, error_message, created_at, @@ -1843,17 +1836,9 @@ impl Database { let cache_read_cost = rust_decimal::Decimal::from(log.cache_read_tokens as u64) * pricing.cache_read / million; - let cache_creation_1h_tokens = - log.cache_creation_1h_tokens.min(log.cache_creation_tokens) as u64; - let cache_creation_standard_tokens = - (log.cache_creation_tokens as u64).saturating_sub(cache_creation_1h_tokens); - let cache_creation_1h_price = pricing.cache_creation * rust_decimal::Decimal::from(8u32) - / rust_decimal::Decimal::from(5u32); - let cache_creation_cost = rust_decimal::Decimal::from(cache_creation_standard_tokens) + let cache_creation_cost = rust_decimal::Decimal::from(log.cache_creation_tokens as u64) * pricing.cache_creation - / million - + rust_decimal::Decimal::from(cache_creation_1h_tokens) * cache_creation_1h_price - / million; + / million; // 总成本 = 基础成本之和 × 倍率 let base_total = input_cost + output_cost + cache_read_cost + cache_creation_cost; let total_cost = base_total * multiplier; @@ -2883,50 +2868,6 @@ mod tests { Ok(()) } - #[test] - fn test_backfill_preserves_one_hour_cache_write_pricing() -> Result<(), AppError> { - let db = Database::memory()?; - - { - let conn = lock_conn!(db.conn); - insert_usage_log( - &conn, - "claude-cache-one-hour", - "claude", - "p1", - "claude-haiku-4-5", - "proxy", - 1000, - 0, - 0, - 0, - 1_000_000, - 200, - "0", - )?; - conn.execute( - "UPDATE proxy_request_logs - SET cache_creation_1h_tokens = 1000000 - WHERE request_id = 'claude-cache-one-hour'", - [], - )?; - } - - assert_eq!(db.backfill_missing_usage_costs()?, 1); - - let conn = lock_conn!(db.conn); - let (cache_creation_cost, total_cost): (String, String) = conn.query_row( - "SELECT cache_creation_cost_usd, total_cost_usd - FROM proxy_request_logs WHERE request_id = 'claude-cache-one-hour'", - [], - |row| Ok((row.get(0)?, row.get(1)?)), - )?; - assert_eq!(cache_creation_cost, "2.000000"); - assert_eq!(total_cost, "2.000000"); - - Ok(()) - } - #[test] fn test_get_usage_summary() -> Result<(), AppError> { let db = Database::memory()?; diff --git a/src/components/settings/RectifierConfigPanel.tsx b/src/components/settings/RectifierConfigPanel.tsx index 2f9d5579c..28947899b 100644 --- a/src/components/settings/RectifierConfigPanel.tsx +++ b/src/components/settings/RectifierConfigPanel.tsx @@ -22,7 +22,6 @@ export function RectifierConfigPanel() { enabled: false, thinkingOptimizer: true, cacheInjection: true, - cacheTtl: "1h", }); const [isLoading, setIsLoading] = useState(true); @@ -204,31 +203,6 @@ export function RectifierConfigPanel() { } /> - - {optimizerConfig.cacheInjection && ( -
-
- -
- -
- )} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index e5653879b..4dd0b9a6d 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -392,10 +392,7 @@ "thinkingOptimizer": "Thinking Optimization", "thinkingOptimizerDescription": "Automatically enable Adaptive Thinking for Opus/Sonnet, and inject Extended Thinking for legacy models", "cacheInjection": "Cache Injection", - "cacheInjectionDescription": "Automatically inject cache breakpoints at key positions in requests to reduce duplicate token billing", - "cacheTtl": "Cache TTL", - "cacheTtl5m": "5 minutes", - "cacheTtl1h": "1 hour" + "cacheInjectionDescription": "Automatically inject standard 5-minute cache breakpoints at key positions to reduce duplicate token billing" }, "logConfig": { "title": "Log Management", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index df6cb7787..94dfe2c5d 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -392,10 +392,7 @@ "thinkingOptimizer": "Thinking 最適化", "thinkingOptimizerDescription": "Opus/Sonnet に Adaptive Thinking を自動的に有効化し、レガシーモデルに Extended Thinking を注入", "cacheInjection": "Cache 注入", - "cacheInjectionDescription": "リクエストの重要な位置に Cache ブレークポイントを自動注入し、重複トークンの課金を削減", - "cacheTtl": "Cache TTL", - "cacheTtl5m": "5 分", - "cacheTtl1h": "1 時間" + "cacheInjectionDescription": "リクエストの重要な位置に標準の 5 分間 Cache ブレークポイントを自動注入し、重複トークンの課金を削減" }, "logConfig": { "title": "ログ管理", diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json index 9b4adeed5..8cab77aad 100644 --- a/src/i18n/locales/zh-TW.json +++ b/src/i18n/locales/zh-TW.json @@ -392,10 +392,7 @@ "thinkingOptimizer": "Thinking 最佳化", "thinkingOptimizerDescription": "自動為 Opus/Sonnet 啟用 Adaptive Thinking,為舊模型注入 Extended Thinking", "cacheInjection": "Cache 注入", - "cacheInjectionDescription": "自動在請求關鍵位置注入 Cache 斷點,減少重複 token 計費", - "cacheTtl": "Cache TTL", - "cacheTtl5m": "5 分鐘", - "cacheTtl1h": "1 小時" + "cacheInjectionDescription": "自動在請求關鍵位置注入標準 5 分鐘 Cache 斷點,減少重複 token 計費" }, "logConfig": { "title": "日誌管理", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index d7cb2e15d..b0f60dd3b 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -392,10 +392,7 @@ "thinkingOptimizer": "Thinking 优化", "thinkingOptimizerDescription": "自动为 Opus/Sonnet 启用 Adaptive Thinking,为旧模型注入 Extended Thinking", "cacheInjection": "Cache 注入", - "cacheInjectionDescription": "自动在请求关键位置注入 Cache 断点,减少重复 token 计费", - "cacheTtl": "Cache TTL", - "cacheTtl5m": "5 分钟", - "cacheTtl1h": "1 小时" + "cacheInjectionDescription": "自动在请求关键位置注入标准 5 分钟 Cache 断点,减少重复 token 计费" }, "logConfig": { "title": "日志管理", diff --git a/src/lib/api/settings.ts b/src/lib/api/settings.ts index 9c394c00b..e76ea5dc6 100644 --- a/src/lib/api/settings.ts +++ b/src/lib/api/settings.ts @@ -333,7 +333,6 @@ export interface OptimizerConfig { enabled: boolean; thinkingOptimizer: boolean; cacheInjection: boolean; - cacheTtl: string; } export interface LogConfig {