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 original_request_id: Uuid,
190 pub new_request_id: Uuid,
191}
192
193impl TurnMutationAuditEvent {
194 #[must_use]
196 #[allow(clippy::too_many_arguments)]
197 pub fn new_retry(
198 timestamp: OffsetDateTime,
199 tenant_id: Uuid,
200 requester_type: RequesterType,
201 trace_id: Option<String>,
202 actor_user_id: Uuid,
203 chat_id: Uuid,
204 original_request_id: Uuid,
205 new_request_id: Uuid,
206 ) -> Self {
207 Self {
208 event_type: TurnMutationAuditEventType::TurnRetry,
209 timestamp,
210 tenant_id,
211 requester_type,
212 trace_id,
213 actor_user_id,
214 chat_id,
215 original_request_id,
216 new_request_id,
217 }
218 }
219
220 #[must_use]
222 #[allow(clippy::too_many_arguments)]
223 pub fn new_edit(
224 timestamp: OffsetDateTime,
225 tenant_id: Uuid,
226 requester_type: RequesterType,
227 trace_id: Option<String>,
228 actor_user_id: Uuid,
229 chat_id: Uuid,
230 original_request_id: Uuid,
231 new_request_id: Uuid,
232 ) -> Self {
233 Self {
234 event_type: TurnMutationAuditEventType::TurnEdit,
235 timestamp,
236 tenant_id,
237 requester_type,
238 trace_id,
239 actor_user_id,
240 chat_id,
241 original_request_id,
242 new_request_id,
243 }
244 }
245}
246
247pub type TurnRetryAuditEvent = TurnMutationAuditEvent;
249
250pub type TurnEditAuditEvent = TurnMutationAuditEvent;
252
253#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
257pub enum TurnDeleteAuditEventType {
258 #[default]
259 #[serde(rename = "turn_delete")]
260 TurnDelete,
261}
262
263impl std::fmt::Display for TurnDeleteAuditEventType {
264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265 f.write_str("turn_delete")
266 }
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct TurnDeleteAuditEvent {
272 pub event_type: TurnDeleteAuditEventType,
273 #[serde(with = "time::serde::rfc3339")]
274 pub timestamp: OffsetDateTime,
275 pub tenant_id: Uuid,
276 pub requester_type: RequesterType,
277 #[serde(default, skip_serializing_if = "Option::is_none")]
278 pub trace_id: Option<String>,
279
280 pub actor_user_id: Uuid,
281 pub chat_id: Uuid,
282 pub request_id: Uuid,
283}