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 TrackedOpenAI {
pricing: Arc<PricingEngine>,
}
impl TrackedOpenAI {
pub fn new(pricing: Arc<PricingEngine>) -> Self {
Self { pricing }
}
pub fn record_response(
&self,
model: &str,
input_tokens: u32,
output_tokens: u32,
cached_tokens: Option<u32>,
latency_ms: Option<u64>,
) -> CostEvent {
let cost_result = self.pricing.get_cost_sync(
model,
i64::from(input_tokens),
i64::from(output_tokens),
cached_tokens.map(i64::from).unwrap_or(0),
0,
);
let task_type = format!("openai.{}", 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("openai".to_string());
event.model = Some(model.to_string());
event.input_tokens = Some(i64::from(input_tokens));
event.output_tokens = Some(i64::from(output_tokens));
event.cached_tokens = cached_tokens.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);
crate::dev_console::log_event(&event, &task.task_type);
event
}
pub fn record_response_buffered(
&self,
model: &str,
input_tokens: u32,
output_tokens: u32,
cached_tokens: Option<u32>,
latency_ms: Option<u64>,
buffer: &mut EventBuffer,
) -> CostEvent {
let event = self.record_response(
model,
input_tokens,
output_tokens,
cached_tokens,
latency_ms,
);
let mut task = crate::core::models::Task::new(&format!("openai.{}", model));
task.metadata
.insert("session".to_string(), serde_json::Value::Bool(true));
task.metadata.insert(
"initiated_by".to_string(),
serde_json::Value::String(format!("openai.{}", 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 = TrackedOpenAI::new(pricing);
let event = tracked.record_response("gpt-4o", 1000, 500, None, Some(250));
assert_eq!(event.event_type, EventType::LlmCall);
assert_eq!(event.provider.as_deref(), Some("openai"));
assert_eq!(event.model.as_deref(), Some("gpt-4o"));
assert_eq!(event.input_tokens, Some(1000));
assert_eq!(event.output_tokens, Some(500));
assert!(event.cached_tokens.is_none());
assert_eq!(event.latency_ms, Some(250));
assert!(!event.task_id.is_empty());
assert!(!event.event_id.is_empty());
}
#[test]
fn test_record_response_with_cached_tokens() {
let pricing = Arc::new(PricingEngine::new());
let tracked = TrackedOpenAI::new(pricing);
let event = tracked.record_response("gpt-4o", 1000, 500, Some(200), None);
assert_eq!(event.cached_tokens, Some(200));
assert!(event.latency_ms.is_none());
}
#[test]
fn test_record_response_unknown_model() {
let pricing = Arc::new(PricingEngine::new());
let tracked = TrackedOpenAI::new(pricing);
let event = tracked.record_response("totally-unknown-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 = TrackedOpenAI::new(pricing);
let mut buffer = EventBuffer::new().unwrap();
let event =
tracked.record_response_buffered("gpt-4o", 1000, 500, None, Some(100), &mut buffer);
assert_eq!(event.event_type, EventType::LlmCall);
assert_eq!(event.provider.as_deref(), Some("openai"));
assert_eq!(buffer.event_count(), 1);
assert_eq!(buffer.task_count(), 1);
}
#[test]
fn test_record_response_pricing_populated() {
let pricing = Arc::new(PricingEngine::new());
let tracked = TrackedOpenAI::new(pricing);
let event = tracked.record_response("gpt-4o", 1000, 500, None, None);
if event.cost_confidence == CostConfidence::Computed {
assert!(event.cost_usd > Decimal::ZERO);
assert!(event.pricing_source.is_some());
assert!(event.pricing_version.is_some());
}
}
}