use serde_json::{json, Value};
use crate::error::Result;
use crate::transport::{RequestOptions, Transport};
use crate::types::LlmTraceParams;
pub struct Llm {
transport: Transport,
api_key: String,
}
impl Llm {
pub fn new(transport: Transport, api_key: String) -> Self {
Self { transport, api_key }
}
pub fn capture_trace(&self, params: &LlmTraceParams) -> Result<Value> {
let body = json!({
"api_key": self.api_key,
"traceId": params.trace_id,
"spanId": params.span_id,
"parentSpanId": params.parent_span_id.clone().unwrap_or_default(),
"model": params.model,
"inputText": params.input_text,
"outputText": params.output_text,
"promptTokens": params.prompt_tokens.unwrap_or(0),
"completionTokens": params.completion_tokens.unwrap_or(0),
"durationMs": params.duration_ms.unwrap_or(0),
"costUsd": params.cost_usd.unwrap_or(0.0),
"properties": params.properties.clone().unwrap_or_default(),
"metadata": params.metadata.clone().unwrap_or_default(),
"timestamp": params.timestamp.clone().unwrap_or_else(now_iso8601),
});
let opts = RequestOptions::post("/llm/trace", body)
.header("X-BillDog-API-Key", &self.api_key)
.gzip(false);
self.transport.request(&opts)
}
}
fn now_iso8601() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let days = (secs / 86_400) as i64;
let rem = secs % 86_400;
let (hour, min, sec) = (rem / 3600, (rem % 3600) / 60, rem % 60);
let z = days + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if m <= 2 { y + 1 } else { y };
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.000Z",
year, m, d, hour, min, sec
)
}