openai_rust_sdk/models/threads/
thread.rs

1//! Thread-related models and builders
2
3use crate::api::base::Validate;
4use crate::{De, Ser};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8use super::builders::MetadataBuilder;
9use super::message::MessageRequest;
10use super::types::{default_thread_object, SortOrder};
11use super::validation::common::validate_metadata;
12
13/// A conversation thread that can contain multiple messages
14#[derive(Debug, Clone, PartialEq, Ser, De)]
15pub struct Thread {
16    /// The identifier of the thread
17    pub id: String,
18    /// The object type, which is always "thread"
19    #[serde(default = "default_thread_object")]
20    pub object: String,
21    /// The Unix timestamp (in seconds) when the thread was created
22    pub created_at: i64,
23    /// Set of 16 key-value pairs that can be attached to an object
24    #[serde(default)]
25    pub metadata: HashMap<String, String>,
26}
27
28/// Request to create or modify a thread
29#[derive(Debug, Clone, Ser, De)]
30pub struct ThreadRequest {
31    /// A list of messages to start the thread with
32    #[serde(default, skip_serializing_if = "Vec::is_empty")]
33    pub messages: Vec<MessageRequest>,
34    /// Set of 16 key-value pairs that can be attached to an object
35    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
36    pub metadata: HashMap<String, String>,
37}
38
39impl ThreadRequest {
40    /// Create a new thread request builder
41    #[must_use]
42    pub fn builder() -> ThreadRequestBuilder {
43        ThreadRequestBuilder::new()
44    }
45
46    /// Create a new empty thread request
47    #[must_use]
48    pub fn new() -> Self {
49        Self {
50            messages: Vec::new(),
51            metadata: HashMap::new(),
52        }
53    }
54
55    /// Validate the thread request
56    pub fn validate(&self) -> Result<(), String> {
57        // Validate metadata using common function
58        validate_metadata(&self.metadata).map_err(|e| format!("Thread {}", e))?;
59
60        // Validate messages
61        for message in &self.messages {
62            message.validate()?;
63        }
64
65        Ok(())
66    }
67}
68
69impl Validate for ThreadRequest {
70    fn validate(&self) -> Result<(), String> {
71        self.validate()
72    }
73}
74
75impl Default for ThreadRequest {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81/// Builder for creating thread requests
82#[derive(Debug, Clone, Default)]
83pub struct ThreadRequestBuilder {
84    /// The messages to include in the thread
85    messages: Vec<MessageRequest>,
86    /// Set of key-value pairs to attach to this thread
87    metadata: HashMap<String, String>,
88}
89
90impl MetadataBuilder for ThreadRequestBuilder {
91    fn get_metadata_mut(&mut self) -> &mut HashMap<String, String> {
92        &mut self.metadata
93    }
94}
95
96impl ThreadRequestBuilder {
97    /// Create a new builder
98    #[must_use]
99    pub fn new() -> Self {
100        Self::default()
101    }
102
103    /// Add a message to the thread
104    #[must_use]
105    pub fn message(mut self, message: MessageRequest) -> Self {
106        self.messages.push(message);
107        self
108    }
109
110    /// Add multiple messages to the thread
111    #[must_use]
112    pub fn messages(mut self, messages: Vec<MessageRequest>) -> Self {
113        self.messages.extend(messages);
114        self
115    }
116
117    /// Add metadata
118    pub fn metadata_pair(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
119        self.add_metadata_pair(key, value);
120        self
121    }
122
123    /// Set all metadata
124    #[must_use]
125    pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
126        self.set_metadata(metadata);
127        self
128    }
129
130    /// Build the thread request
131    #[must_use]
132    pub fn build(self) -> ThreadRequest {
133        ThreadRequest {
134            messages: self.messages,
135            metadata: self.metadata,
136        }
137    }
138}
139
140// Generate list response for threads
141crate::impl_list_response!(ListThreadsResponse, Thread, "Response from listing threads");
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use crate::models::threads::types::MessageRole;
147
148    #[test]
149    fn test_thread_request_builder() {
150        let request = ThreadRequest::builder()
151            .metadata_pair("purpose", "testing")
152            .build();
153
154        assert_eq!(request.metadata.len(), 1);
155        assert_eq!(
156            request.metadata.get("purpose"),
157            Some(&"testing".to_string())
158        );
159        assert!(request.messages.is_empty());
160    }
161
162    #[test]
163    fn test_thread_request_validation() {
164        // Test valid request
165        let request = ThreadRequest::builder()
166            .metadata_pair("key", "value")
167            .build();
168        assert!(request.validate().is_ok());
169
170        // Test metadata count validation
171        let mut request = ThreadRequest::new();
172        for i in 0..17 {
173            request
174                .metadata
175                .insert(format!("key{}", i), "value".to_string());
176        }
177        assert!(request.validate().is_err());
178    }
179
180    #[test]
181    fn test_thread_request_with_messages() {
182        let message = MessageRequest::new(MessageRole::User, "Hello");
183        let request = ThreadRequest::builder().message(message).build();
184
185        assert_eq!(request.messages.len(), 1);
186        assert_eq!(request.messages[0].content, "Hello");
187    }
188}