1use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Thread {
16 pub id: String,
18 pub object: String,
20 pub created_at: i64,
22 pub metadata: serde_json::Value,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "lowercase")]
31pub enum MessageRole {
32 User,
34 Assistant,
36}
37
38pub type Annotation = serde_json::Value;
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct TextContent {
44 pub value: String,
46 pub annotations: Vec<Annotation>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ContentBlock {
55 pub r#type: String,
57 pub text: TextContent,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ThreadMessage {
64 pub id: String,
66 pub object: String,
68 pub created_at: i64,
70 pub thread_id: String,
72 pub role: MessageRole,
74 pub content: Vec<ContentBlock>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub run_id: Option<String>,
79}
80
81impl ThreadMessage {
82 pub fn new_user(id: String, thread_id: String, content: String) -> Self {
84 Self {
85 id,
86 object: "thread.message".to_string(),
87 created_at: unix_now(),
88 thread_id,
89 role: MessageRole::User,
90 content: vec![ContentBlock {
91 r#type: "text".to_string(),
92 text: TextContent {
93 value: content,
94 annotations: vec![],
95 },
96 }],
97 run_id: None,
98 }
99 }
100
101 pub fn new_assistant(id: String, thread_id: String, run_id: String, content: String) -> Self {
103 Self {
104 id,
105 object: "thread.message".to_string(),
106 created_at: unix_now(),
107 thread_id,
108 role: MessageRole::Assistant,
109 content: vec![ContentBlock {
110 r#type: "text".to_string(),
111 text: TextContent {
112 value: content,
113 annotations: vec![],
114 },
115 }],
116 run_id: Some(run_id),
117 }
118 }
119
120 pub fn text_content(&self) -> &str {
122 self.content
123 .first()
124 .map(|b| b.text.value.as_str())
125 .unwrap_or("")
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133#[serde(rename_all = "snake_case")]
134pub enum RunStatus {
135 Queued,
137 InProgress,
139 Completed,
141 Cancelled,
143 Failed,
145 Expired,
147}
148
149impl RunStatus {
150 pub fn is_terminal(&self) -> bool {
152 matches!(
153 self,
154 RunStatus::Completed | RunStatus::Cancelled | RunStatus::Failed | RunStatus::Expired
155 )
156 }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct RunError {
162 pub code: String,
164 pub message: String,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct Run {
171 pub id: String,
173 pub object: String,
175 pub created_at: i64,
177 pub thread_id: String,
179 pub status: RunStatus,
181 pub model: String,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub last_error: Option<RunError>,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
192#[serde(rename_all = "snake_case")]
193pub enum RunStepType {
194 MessageCreation,
196 ToolCalls,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202#[serde(rename_all = "snake_case")]
203pub enum RunStepStatus {
204 InProgress,
206 Completed,
208 Failed,
210 Cancelled,
212}
213
214impl RunStepStatus {
215 pub fn is_terminal(&self) -> bool {
217 matches!(
218 self,
219 RunStepStatus::Completed | RunStepStatus::Failed | RunStepStatus::Cancelled
220 )
221 }
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct MessageCreationStepDetails {
227 pub message_id: String,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct RunStep {
237 pub id: String,
239 pub object: String,
241 pub run_id: String,
243 pub thread_id: String,
245 pub step_type: RunStepType,
247 pub status: RunStepStatus,
249 pub created_at: u64,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub completed_at: Option<u64>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub failed_at: Option<u64>,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub error: Option<String>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub step_details: Option<MessageCreationStepDetails>,
263}
264
265impl RunStep {
266 pub fn new_message_creation(step_id: String, run_id: String, thread_id: String) -> Self {
268 Self {
269 id: step_id,
270 object: "thread.run.step".to_string(),
271 run_id,
272 thread_id,
273 step_type: RunStepType::MessageCreation,
274 status: RunStepStatus::InProgress,
275 created_at: unix_now() as u64,
276 completed_at: None,
277 failed_at: None,
278 error: None,
279 step_details: None,
280 }
281 }
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct CreateMessageRequest {
289 pub role: MessageRole,
291 pub content: String,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct CreateThreadRequest {
298 #[serde(default)]
300 pub messages: Option<Vec<CreateMessageRequest>>,
301 #[serde(default)]
303 pub metadata: Option<serde_json::Value>,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct CreateRunRequest {
309 #[serde(default)]
311 pub model: Option<String>,
312 #[serde(default)]
314 pub instructions: Option<String>,
315 #[serde(default)]
317 pub max_tokens: Option<usize>,
318 #[serde(default)]
320 pub stream: bool,
321}
322
323pub(crate) fn unix_now() -> i64 {
326 std::time::SystemTime::now()
327 .duration_since(std::time::UNIX_EPOCH)
328 .map(|d| d.as_secs() as i64)
329 .unwrap_or(0)
330}
331
332#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn run_status_terminal_set_is_correct() {
340 assert!(RunStatus::Completed.is_terminal());
341 assert!(RunStatus::Cancelled.is_terminal());
342 assert!(RunStatus::Failed.is_terminal());
343 assert!(RunStatus::Expired.is_terminal());
344 assert!(!RunStatus::Queued.is_terminal());
345 assert!(!RunStatus::InProgress.is_terminal());
346 }
347
348 #[test]
349 fn thread_message_new_user_sets_fields() {
350 let msg = ThreadMessage::new_user("msg_1".into(), "thread_1".into(), "hello".into());
351 assert_eq!(msg.role, MessageRole::User);
352 assert_eq!(msg.text_content(), "hello");
353 assert!(msg.run_id.is_none());
354 }
355
356 #[test]
357 fn thread_message_new_assistant_sets_run_id() {
358 let msg = ThreadMessage::new_assistant(
359 "msg_2".into(),
360 "thread_1".into(),
361 "run_1".into(),
362 "hi!".into(),
363 );
364 assert_eq!(msg.role, MessageRole::Assistant);
365 assert_eq!(msg.run_id, Some("run_1".into()));
366 }
367
368 #[test]
369 fn run_status_serde_roundtrip() {
370 let s = serde_json::to_string(&RunStatus::InProgress).expect("serialize");
371 let d: RunStatus = serde_json::from_str(&s).expect("deserialize");
372 assert_eq!(d, RunStatus::InProgress);
373 }
374
375 #[test]
376 fn message_role_serde_lowercase() {
377 let json = serde_json::to_string(&MessageRole::User).expect("serialize");
378 assert_eq!(json, r#""user""#);
379 let back: MessageRole = serde_json::from_str(&json).expect("deserialize");
380 assert_eq!(back, MessageRole::User);
381 }
382}