use std::sync::Arc;
use crate::core::auto_task;
use crate::core::models::{CostEvent, EventType};
use crate::pricing::engine::PricingEngine;
use crate::transport::buffer::EventBuffer;
pub struct TrackedGemini {
pricing: Arc<PricingEngine>,
}
impl TrackedGemini {
pub fn new(pricing: Arc<PricingEngine>) -> Self {
Self { pricing }
}
pub fn record_response(
&self,
model: &str,
prompt_token_count: u32,
candidates_token_count: u32,
cached_content_token_count: Option<u32>,
latency_ms: Option<u64>,
) -> CostEvent {
let cached_for_pricing = cached_content_token_count.map(i64::from).unwrap_or(0);
let cost_result = self.pricing.get_cost_sync(
model,
i64::from(prompt_token_count),
i64::from(candidates_token_count),
cached_for_pricing,
0,
);
let task_type = format!("gemini.{}", model);
let mut task = crate::core::models::Task::new(&task_type);
task.metadata
.insert("session".to_string(), serde_json::Value::Bool(true));
task.metadata.insert(
"initiated_by".to_string(),
serde_json::Value::String(task_type.clone()),
);
let mut event = CostEvent::new(&task.task_id, EventType::LlmCall);
event.provider = Some("google".to_string());
event.model = Some(model.to_string());
event.input_tokens = Some(i64::from(prompt_token_count));
event.output_tokens = Some(i64::from(candidates_token_count));
event.cached_tokens = cached_content_token_count.map(i64::from);
event.latency_ms = latency_ms.map(|ms| ms as i64);
event.cost_usd = cost_result.cost_usd;
event.cost_confidence = cost_result.cost_confidence;
event.pricing_source = Some(cost_result.pricing_source);
event.pricing_version = Some(cost_result.pricing_version);
event.details.insert(
"prompt_token_count".to_string(),
serde_json::Value::Number(serde_json::Number::from(prompt_token_count)),
);
event.details.insert(
"candidates_token_count".to_string(),
serde_json::Value::Number(serde_json::Number::from(candidates_token_count)),
);
if let Some(cached) = cached_content_token_count {
event.details.insert(
"cached_content_token_count".to_string(),
serde_json::Value::Number(serde_json::Number::from(cached)),
);
}
crate::dev_console::log_event(&event, &task.task_type);
event
}
pub fn record_response_buffered(
&self,
model: &str,
prompt_token_count: u32,
candidates_token_count: u32,
cached_content_token_count: Option<u32>,
latency_ms: Option<u64>,
buffer: &mut EventBuffer,
) -> CostEvent {
let event = self.record_response(
model,
prompt_token_count,
candidates_token_count,
cached_content_token_count,
latency_ms,
);
let mut task = crate::core::models::Task::new(&format!("gemini.{}", model));
task.metadata
.insert("session".to_string(), serde_json::Value::Bool(true));
task.metadata.insert(
"initiated_by".to_string(),
serde_json::Value::String(format!("gemini.{}", model)),
);
auto_task::finalize_auto_task(&mut task, Some(&event), "success", buffer);
buffer.add_event(event.clone());
event
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::models::{CostConfidence, EventType};
use rust_decimal::Decimal;
#[test]
fn test_record_response_basic() {
let pricing = Arc::new(PricingEngine::new());
let tracked = TrackedGemini::new(pricing);
let event = tracked.record_response("gemini-1.5-pro", 2000, 800, None, Some(950));
assert_eq!(event.event_type, EventType::LlmCall);
assert_eq!(event.provider.as_deref(), Some("google"));
assert_eq!(event.model.as_deref(), Some("gemini-1.5-pro"));
assert_eq!(event.input_tokens, Some(2000));
assert_eq!(event.output_tokens, Some(800));
assert!(event.cached_tokens.is_none());
assert_eq!(event.latency_ms, Some(950));
assert!(!event.task_id.is_empty());
}
#[test]
fn test_record_response_with_cached_content() {
let pricing = Arc::new(PricingEngine::new());
let tracked = TrackedGemini::new(pricing);
let event = tracked.record_response("gemini-1.5-pro", 2000, 800, Some(300), None);
assert_eq!(event.cached_tokens, Some(300));
assert_eq!(
event.details.get("prompt_token_count"),
Some(&serde_json::Value::Number(serde_json::Number::from(
2000u32
))),
);
assert_eq!(
event.details.get("candidates_token_count"),
Some(&serde_json::Value::Number(serde_json::Number::from(800u32))),
);
assert_eq!(
event.details.get("cached_content_token_count"),
Some(&serde_json::Value::Number(serde_json::Number::from(300u32))),
);
}
#[test]
fn test_record_response_unknown_model() {
let pricing = Arc::new(PricingEngine::new());
let tracked = TrackedGemini::new(pricing);
let event = tracked.record_response("unknown-gemini-model-xyz", 500, 200, None, None);
assert_eq!(event.cost_confidence, CostConfidence::Unknown);
assert_eq!(event.cost_usd, Decimal::ZERO);
}
#[test]
fn test_record_response_buffered() {
let pricing = Arc::new(PricingEngine::new());
let tracked = TrackedGemini::new(pricing);
let mut buffer = EventBuffer::new().unwrap();
let event = tracked.record_response_buffered(
"gemini-1.5-pro",
2000,
800,
None,
Some(600),
&mut buffer,
);
assert_eq!(event.event_type, EventType::LlmCall);
assert_eq!(event.provider.as_deref(), Some("google"));
assert_eq!(buffer.event_count(), 1);
assert_eq!(buffer.task_count(), 1);
}
#[test]
fn test_gemini_details_fields_present() {
let pricing = Arc::new(PricingEngine::new());
let tracked = TrackedGemini::new(pricing);
let event = tracked.record_response("gemini-1.5-pro", 1500, 600, None, None);
assert!(event.details.contains_key("prompt_token_count"));
assert!(event.details.contains_key("candidates_token_count"));
assert!(!event.details.contains_key("cached_content_token_count"));
}
}