fix(#4): remove dead total_cost_usd field; map model enums to readable names

This commit is contained in:
Nikketryhard
2026-02-14 15:54:03 -06:00
parent dd7b12a97d
commit 2ccc4b46f8
4 changed files with 54 additions and 20 deletions

View File

@@ -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<f64>` 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<f64>` 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) Moot — `total_cost_usd` field was removed in issue #4 fix.
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)

View File

@@ -126,7 +126,6 @@ impl StreamingAccumulator {
cache_read_input_tokens: self.cache_read_input_tokens, cache_read_input_tokens: self.cache_read_input_tokens,
thinking_output_tokens: 0, thinking_output_tokens: 0,
response_output_tokens: 0, response_output_tokens: 0,
total_cost_usd: None,
model: self.model, model: self.model,
stop_reason: self.stop_reason, stop_reason: self.stop_reason,
api_provider: Some("anthropic".to_string()), api_provider: Some("anthropic".to_string()),
@@ -150,7 +149,6 @@ fn extract_usage_from_message(msg: &Value) -> Option<ApiUsage> {
cache_read_input_tokens: usage["cache_read_input_tokens"].as_u64().unwrap_or(0), cache_read_input_tokens: usage["cache_read_input_tokens"].as_u64().unwrap_or(0),
thinking_output_tokens: 0, thinking_output_tokens: 0,
response_output_tokens: 0, response_output_tokens: 0,
total_cost_usd: None,
model: msg["model"].as_str().map(|s| s.to_string()), model: msg["model"].as_str().map(|s| s.to_string()),
stop_reason: msg["stop_reason"].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()), api_provider: Some("anthropic".to_string()),

View File

@@ -86,7 +86,6 @@ impl GrpcUsage {
api_provider: self.api_provider, api_provider: self.api_provider,
grpc_method: Some(grpc_method), grpc_method: Some(grpc_method),
stop_reason: None, stop_reason: None,
total_cost_usd: None,
captured_at: std::time::SystemTime::now() captured_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH) .duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default() .unwrap_or_default()
@@ -419,8 +418,9 @@ fn try_extract_usage(fields: &[ProtoField]) -> Option<GrpcUsage> {
if let ProtoValue::Varint(v) = &field.value { if let ProtoValue::Varint(v) = &field.value {
match field.number { match field.number {
1 => { 1 => {
// Model enum — we don't have the mapping, store as number // Model proto enum → human-readable name
usage.model = Some(format!("model_enum_{v}")); // See docs/ls-binary-analysis.md for full mapping
usage.model = Some(model_enum_name(*v).to_string());
} }
6 => { 6 => {
// APIProvider enum // APIProvider enum
@@ -475,6 +475,50 @@ pub fn parse_grpc_response_for_usage(body: &[u8]) -> Option<GrpcUsage> {
None 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -24,8 +24,7 @@ pub struct ApiUsage {
pub thinking_output_tokens: u64, pub thinking_output_tokens: u64,
/// Google-specific: response output tokens (non-thinking portion) /// Google-specific: response output tokens (non-thinking portion)
pub response_output_tokens: u64, pub response_output_tokens: u64,
/// Total cost in USD (if provided by the API).
pub total_cost_usd: Option<f64>,
/// The actual model that served the request. /// The actual model that served the request.
pub model: Option<String>, pub model: Option<String>,
/// Stop reason / finish reason from the API. /// Stop reason / finish reason from the API.