fix(#4): remove dead total_cost_usd field; map model enums to readable names
This commit is contained in:
@@ -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)
|
|
||||||
|
|||||||
@@ -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()),
|
||||||
|
|||||||
@@ -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::*;
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user