use serde::{Deserialize, Serialize}; use serde_json::Value; use time::OffsetDateTime; use crate::{AgentId, OperationId, WorkspaceId}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum InvocationSource { AdminTestRun, AgentToolCall, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum InvocationLevel { Debug, Info, Warn, Error, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum InvocationStatus { Ok, Error, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum UsagePeriod { #[serde(rename = "30m")] Last30Minutes, #[serde(rename = "1h")] LastHour, #[serde(rename = "6h")] Last6Hours, #[serde(rename = "24h")] Last24Hours, #[serde(rename = "7d")] Last7Days, #[serde(rename = "30d")] Last30Days, #[serde(rename = "90d")] Last90Days, #[serde(rename = "this_month")] ThisMonth, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct InvocationLog { pub id: crate::ids::InvocationLogId, pub workspace_id: WorkspaceId, pub agent_id: Option, pub operation_id: OperationId, pub source: InvocationSource, pub level: InvocationLevel, pub status: InvocationStatus, pub tool_name: String, pub message: String, pub request_id: Option, pub status_code: Option, pub duration_ms: u64, pub error_kind: Option, pub request_preview: Value, pub response_preview: Value, #[serde(with = "time::serde::rfc3339")] pub created_at: OffsetDateTime, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UsageRollup { pub workspace_id: WorkspaceId, pub agent_id: Option, pub operation_id: Option, pub period: UsagePeriod, pub calls_total: u64, pub calls_ok: u64, pub calls_error: u64, pub p50_ms: u64, pub p95_ms: u64, pub p99_ms: u64, } #[cfg(test)] mod tests { use serde_json::json; use time::{OffsetDateTime, format_description::well_known::Rfc3339}; use super::{InvocationLevel, InvocationLog, InvocationSource, InvocationStatus, UsagePeriod}; use crate::{AgentId, OperationId, WorkspaceId, ids::InvocationLogId}; fn timestamp(value: &str) -> OffsetDateTime { OffsetDateTime::parse(value, &Rfc3339).unwrap() } #[test] fn invocation_log_serializes_timestamps_as_rfc3339() { let log = InvocationLog { id: InvocationLogId::new("log_01"), workspace_id: WorkspaceId::new("ws_01"), agent_id: Some(AgentId::new("agent_01")), operation_id: OperationId::new("op_01"), source: InvocationSource::AdminTestRun, level: InvocationLevel::Info, status: InvocationStatus::Ok, tool_name: "create_lead".to_owned(), message: "ok".to_owned(), request_id: Some("req_01".to_owned()), status_code: Some(200), duration_ms: 123, error_kind: None, request_preview: json!({"input": "value"}), response_preview: json!({"ok": true}), created_at: timestamp("2026-04-19T12:34:56Z"), }; let value = serde_json::to_value(&log).unwrap(); assert_eq!(value["created_at"], "2026-04-19T12:34:56Z"); assert_eq!(value["source"], "admin_test_run"); assert_eq!(value["level"], "info"); assert_eq!(value["status"], "ok"); } #[test] fn usage_period_serializes_compact_labels() { let value = serde_json::to_value(UsagePeriod::Last7Days).unwrap(); assert_eq!(value, "7d"); } }