1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum RequesterType {
9 User,
10 System,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum AttachmentKind {
17 Document,
18 Image,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct AttachmentMetadata {
24 pub attachment_id: Uuid,
25 pub attachment_kind: AttachmentKind,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub filename: Option<String>,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub content_type: Option<String>,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub size_bytes: Option<u64>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub image_used_in_turn: Option<bool>,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub doc_summary: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct AuditUsageTokens {
41 pub input_tokens: u64,
42 pub output_tokens: u64,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub model: Option<String>,
45}
46
47#[derive(Debug, Clone, Default, Serialize, Deserialize)]
49pub struct LatencyMs {
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub ttft_ms: Option<u64>,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub total_ms: Option<u64>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct LicenseDecision {
59 pub feature: String,
60 pub decision: String,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum QuotaScope {
67 Tokens,
68 WebSearch,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct QuotaDecision {
74 pub decision: String,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub quota_scope: Option<QuotaScope>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub downgrade_from: Option<String>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub downgrade_reason: Option<String>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct PolicyDecisions {
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub license: Option<LicenseDecision>,
88 pub quota: QuotaDecision,
89}
90
91#[derive(Debug, Clone, Default, Serialize, Deserialize)]
93pub struct ToolCalls {
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub file_search_calls: Option<u64>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub web_search_calls: Option<u64>,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case")]
103pub enum TurnAuditEventType {
104 TurnCompleted,
105 TurnFailed,
106}
107
108impl std::fmt::Display for TurnAuditEventType {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 match self {
111 Self::TurnCompleted => f.write_str("turn_completed"),
112 Self::TurnFailed => f.write_str("turn_failed"),
113 }
114 }
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct TurnAuditEvent {
120 pub event_type: TurnAuditEventType,
121 #[serde(with = "time::serde::rfc3339")]
122 pub timestamp: OffsetDateTime,
123 pub tenant_id: Uuid,
124 pub requester_type: RequesterType,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub trace_id: Option<String>,
127
128 pub user_id: Uuid,
129 pub chat_id: Uuid,
130 pub turn_id: Uuid,
131 pub request_id: Uuid,
132 pub selected_model: String,
133 pub effective_model: String,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub policy_version_applied: Option<u64>,
136 pub usage: AuditUsageTokens,
137 pub latency_ms: LatencyMs,
138 pub policy_decisions: PolicyDecisions,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub error_code: Option<String>,
141 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub prompt: Option<String>,
145 #[serde(default, skip_serializing_if = "Option::is_none")]
148 pub response: Option<String>,
149 #[serde(default, skip_serializing_if = "Vec::is_empty")]
150 pub attachments: Vec<AttachmentMetadata>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub tool_calls: Option<ToolCalls>,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
157#[serde(rename_all = "snake_case")]
158pub enum TurnMutationAuditEventType {
159 TurnRetry,
160 TurnEdit,
161}
162
163impl std::fmt::Display for TurnMutationAuditEventType {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 match self {
166 Self::TurnRetry => f.write_str("turn_retry"),
167 Self::TurnEdit => f.write_str("turn_edit"),
168 }
169 }
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct TurnMutationAuditEvent {
179 pub event_type: TurnMutationAuditEventType,
180 #[serde(with = "time::serde::rfc3339")]
181 pub timestamp: OffsetDateTime,
182 pub tenant_id: Uuid,
183 pub requester_type: RequesterType,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub trace_id: Option<String>,
186
187 pub actor_user_id: Uuid,
188 pub chat_id: Uuid,
189 pub turn_id: Uuid,
190 pub original_request_id: Uuid,
191 pub new_request_id: Uuid,
192}
193
194impl TurnMutationAuditEvent {
195 #[must_use]
197 #[allow(clippy::too_many_arguments)]
198 pub fn new_retry(
199 timestamp: OffsetDateTime,
200 tenant_id: Uuid,
201 requester_type: RequesterType,
202 trace_id: Option<String>,
203 actor_user_id: Uuid,
204 chat_id: Uuid,
205 turn_id: Uuid,
206 original_request_id: Uuid,
207 new_request_id: Uuid,
208 ) -> Self {
209 Self {
210 event_type: TurnMutationAuditEventType::TurnRetry,
211 timestamp,
212 tenant_id,
213 requester_type,
214 trace_id,
215 actor_user_id,
216 chat_id,
217 turn_id,
218 original_request_id,
219 new_request_id,
220 }
221 }
222
223 #[must_use]
225 #[allow(clippy::too_many_arguments)]
226 pub fn new_edit(
227 timestamp: OffsetDateTime,
228 tenant_id: Uuid,
229 requester_type: RequesterType,
230 trace_id: Option<String>,
231 actor_user_id: Uuid,
232 chat_id: Uuid,
233 turn_id: Uuid,
234 original_request_id: Uuid,
235 new_request_id: Uuid,
236 ) -> Self {
237 Self {
238 event_type: TurnMutationAuditEventType::TurnEdit,
239 timestamp,
240 tenant_id,
241 requester_type,
242 trace_id,
243 actor_user_id,
244 chat_id,
245 turn_id,
246 original_request_id,
247 new_request_id,
248 }
249 }
250}
251
252pub type TurnRetryAuditEvent = TurnMutationAuditEvent;
254
255pub type TurnEditAuditEvent = TurnMutationAuditEvent;
257
258#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
262pub enum TurnDeleteAuditEventType {
263 #[default]
264 #[serde(rename = "turn_delete")]
265 TurnDelete,
266}
267
268impl std::fmt::Display for TurnDeleteAuditEventType {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 f.write_str("turn_delete")
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct TurnDeleteAuditEvent {
277 pub event_type: TurnDeleteAuditEventType,
278 #[serde(with = "time::serde::rfc3339")]
279 pub timestamp: OffsetDateTime,
280 pub tenant_id: Uuid,
281 pub requester_type: RequesterType,
282 #[serde(default, skip_serializing_if = "Option::is_none")]
283 pub trace_id: Option<String>,
284
285 pub actor_user_id: Uuid,
286 pub chat_id: Uuid,
287 pub turn_id: Uuid,
288 pub request_id: Uuid,
289}