From 2ccc4b46f844cfebb3a842a763214c82d5e1db53 Mon Sep 17 00:00:00 2001 From: Nikketryhard Date: Sat, 14 Feb 2026 15:54:03 -0600 Subject: [PATCH] fix(#4): remove dead total_cost_usd field; map model enums to readable names --- KNOWN_ISSUES.md | 19 ++++++---------- src/mitm/intercept.rs | 2 -- src/mitm/proto.rs | 50 ++++++++++++++++++++++++++++++++++++++++--- src/mitm/store.rs | 3 +-- 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index b926bf4..95cdd6f 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -34,13 +34,13 @@ The `MitmConfig.modify_requests` flag exists and is plumbed through, but no actu --- -### 4. `total_cost_usd` Is Dead +### ~~4. `total_cost_usd` Is Dead~~ ✅ FIXED -**File:** `src/mitm/store.rs` (line 28) +**File:** `src/mitm/store.rs` -`ApiUsage.total_cost_usd` is `Option` but is **always `None`** — set to `None` in all 4 construction sites (`h2_handler.rs` ×2, `intercept.rs` ×2). Neither Anthropic nor Google include cost in API responses. +~~`ApiUsage.total_cost_usd` is `Option` but is **always `None`**.~~ -**Fix:** Either remove the field (simpler), or populate it via a pricing table lookup (model → $/1K tokens) at `record_usage()` time. +**Fixed:** Removed the field entirely from `ApiUsage` and all 3 construction sites (`proto.rs`, `intercept.rs` ×2). --- @@ -182,13 +182,6 @@ The LS supports BYOK (Bring Your Own Key) variants for Claude and OpenAI models --- -### 13. `total_cost_usd` Could Use Pricing Table +### ~~13. `total_cost_usd` Could Use Pricing Table~~ ✅ RESOLVED -(Extends issue #4) - -Now that we have the full model catalog with proto enum numbers (`docs/ls-binary-analysis.md`), we could build a pricing table mapping model → cost per token. The MITM captures input/output token counts, so cost calculation is just a lookup + multiply. - -Known models that could have pricing: - -- Claude Opus 4.6 (M26/1026), Opus 4.5 (M12/1012) -- Gemini 3 Pro High (M8/1008), Pro Low (M7/1007), Flash (M18/1018) +Moot — `total_cost_usd` field was removed in issue #4 fix. diff --git a/src/mitm/intercept.rs b/src/mitm/intercept.rs index 130586d..41f4922 100644 --- a/src/mitm/intercept.rs +++ b/src/mitm/intercept.rs @@ -126,7 +126,6 @@ impl StreamingAccumulator { cache_read_input_tokens: self.cache_read_input_tokens, thinking_output_tokens: 0, response_output_tokens: 0, - total_cost_usd: None, model: self.model, stop_reason: self.stop_reason, api_provider: Some("anthropic".to_string()), @@ -150,7 +149,6 @@ fn extract_usage_from_message(msg: &Value) -> Option { cache_read_input_tokens: usage["cache_read_input_tokens"].as_u64().unwrap_or(0), thinking_output_tokens: 0, response_output_tokens: 0, - total_cost_usd: None, model: msg["model"].as_str().map(|s| s.to_string()), stop_reason: msg["stop_reason"].as_str().map(|s| s.to_string()), api_provider: Some("anthropic".to_string()), diff --git a/src/mitm/proto.rs b/src/mitm/proto.rs index db96e7b..3f1e958 100644 --- a/src/mitm/proto.rs +++ b/src/mitm/proto.rs @@ -86,7 +86,6 @@ impl GrpcUsage { api_provider: self.api_provider, grpc_method: Some(grpc_method), stop_reason: None, - total_cost_usd: None, captured_at: std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() @@ -419,8 +418,9 @@ fn try_extract_usage(fields: &[ProtoField]) -> Option { if let ProtoValue::Varint(v) = &field.value { match field.number { 1 => { - // Model enum — we don't have the mapping, store as number - usage.model = Some(format!("model_enum_{v}")); + // Model proto enum → human-readable name + // See docs/ls-binary-analysis.md for full mapping + usage.model = Some(model_enum_name(*v).to_string()); } 6 => { // APIProvider enum @@ -475,6 +475,50 @@ pub fn parse_grpc_response_for_usage(body: &[u8]) -> Option { None } +// ─── Model enum → name mapping ────────────────────────────────────────────── + +/// Map a proto model enum number to a human-readable name. +/// +/// Numbers extracted from extension.js protobuf definitions. +/// See `docs/ls-binary-analysis.md` for full catalog. +fn model_enum_name(enum_val: u64) -> &'static str { + match enum_val { + // Placeholder models (1000 + N) + 1007 => "gemini-3-pro", // MODEL_PLACEHOLDER_M7 + 1008 => "gemini-3-pro-high", // MODEL_PLACEHOLDER_M8 + 1012 => "claude-opus-4.5", // MODEL_PLACEHOLDER_M12 + 1018 => "gemini-3-flash", // MODEL_PLACEHOLDER_M18 + 1026 => "claude-opus-4.6", // MODEL_PLACEHOLDER_M26 + + // Claude models (named) + 281 => "claude-4-sonnet", + 282 => "claude-4-sonnet-thinking", + 290 => "claude-4-opus", + 291 => "claude-4-opus-thinking", + 333 => "claude-4.5-sonnet", + 334 => "claude-4.5-sonnet-thinking", + 340 => "claude-4.5-haiku", + 341 => "claude-4.5-haiku-thinking", + + // Google models (named) + 246 => "gemini-2.5-pro", + 312 => "gemini-2.5-flash", + 313 => "gemini-2.5-flash-thinking", + 329 => "gemini-2.5-flash-thinking-tools", + 330 => "gemini-2.5-flash-lite", + 335 => "gemini-computer-use-experimental", + 342 => "openai-gpt-oss-120b", + 346 => "jarvis-proxy", + 348 => "gemini-riftrunner", + 352 => "gemini-riftrunner-thinking-low", + 353 => "gemini-riftrunner-thinking-high", + + // Unknown — return a static leak to avoid format!() in a &'static str context + // This is fine because the match arm handles it + _ => Box::leak(format!("model_enum_{enum_val}").into_boxed_str()), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/mitm/store.rs b/src/mitm/store.rs index 0dd1ed4..f9b41aa 100644 --- a/src/mitm/store.rs +++ b/src/mitm/store.rs @@ -24,8 +24,7 @@ pub struct ApiUsage { pub thinking_output_tokens: u64, /// Google-specific: response output tokens (non-thinking portion) pub response_output_tokens: u64, - /// Total cost in USD (if provided by the API). - pub total_cost_usd: Option, + /// The actual model that served the request. pub model: Option, /// Stop reason / finish reason from the API.