1use ferrin_spec::ProviderOptions;
4use serde::Deserialize;
5use serde::Serialize;
6
7use crate::part::AssistantPart;
8use crate::part::ToolApprovalResponse;
9use crate::part::ToolPart;
10use crate::part::UserPart;
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(tag = "role", rename_all = "lowercase")]
15#[non_exhaustive]
16pub enum Message {
17 System(SystemMessage),
19 User(UserMessage),
21 Assistant(AssistantMessage),
23 Tool(ToolMessage),
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30#[non_exhaustive]
31pub enum Role {
32 System,
34 User,
36 Assistant,
38 Tool,
40}
41
42impl Role {
43 #[must_use]
45 pub fn as_str(self) -> &'static str {
46 match self {
47 Self::System => "system",
48 Self::User => "user",
49 Self::Assistant => "assistant",
50 Self::Tool => "tool",
51 }
52 }
53}
54
55impl std::fmt::Display for Role {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.write_str(self.as_str())
58 }
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct SystemMessage {
64 pub content: String,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub provider_options: Option<ProviderOptions>,
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73pub struct UserMessage {
74 pub content: UserContent,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub provider_options: Option<ProviderOptions>,
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub struct AssistantMessage {
84 pub content: AssistantContent,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub provider_options: Option<ProviderOptions>,
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct ToolMessage {
94 pub content: Vec<ToolPart>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub provider_options: Option<ProviderOptions>,
99}
100
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103#[serde(untagged)]
104pub enum UserContent {
105 Text(String),
107 Parts(Vec<UserPart>),
109}
110
111impl UserContent {
112 #[must_use]
114 pub fn is_empty(&self) -> bool {
115 match self {
116 Self::Text(text) => text.is_empty(),
117 Self::Parts(parts) => parts.is_empty(),
118 }
119 }
120
121 #[must_use]
123 pub fn as_text(&self) -> Option<&str> {
124 match self {
125 Self::Text(text) => Some(text),
126 Self::Parts(_) => None,
127 }
128 }
129
130 #[must_use]
132 pub fn into_parts(self) -> Vec<UserPart> {
133 match self {
134 Self::Text(text) => vec![UserPart::text(text)],
135 Self::Parts(parts) => parts,
136 }
137 }
138}
139
140impl From<&str> for UserContent {
141 fn from(text: &str) -> Self {
142 Self::Text(text.to_owned())
143 }
144}
145
146impl From<String> for UserContent {
147 fn from(text: String) -> Self {
148 Self::Text(text)
149 }
150}
151
152impl From<Vec<UserPart>> for UserContent {
153 fn from(parts: Vec<UserPart>) -> Self {
154 Self::Parts(parts)
155 }
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160#[serde(untagged)]
161pub enum AssistantContent {
162 Text(String),
164 Parts(Vec<AssistantPart>),
166}
167
168impl AssistantContent {
169 #[must_use]
171 pub fn is_empty(&self) -> bool {
172 match self {
173 Self::Text(text) => text.is_empty(),
174 Self::Parts(parts) => parts.is_empty(),
175 }
176 }
177
178 #[must_use]
180 pub fn as_text(&self) -> Option<&str> {
181 match self {
182 Self::Text(text) => Some(text),
183 Self::Parts(_) => None,
184 }
185 }
186
187 #[must_use]
189 pub fn into_parts(self) -> Vec<AssistantPart> {
190 match self {
191 Self::Text(text) => vec![AssistantPart::text(text)],
192 Self::Parts(parts) => parts,
193 }
194 }
195
196 #[must_use]
198 pub fn as_parts(&self) -> Option<&[AssistantPart]> {
199 match self {
200 Self::Text(_) => None,
201 Self::Parts(parts) => Some(parts),
202 }
203 }
204}
205
206impl From<&str> for AssistantContent {
207 fn from(text: &str) -> Self {
208 Self::Text(text.to_owned())
209 }
210}
211
212impl From<String> for AssistantContent {
213 fn from(text: String) -> Self {
214 Self::Text(text)
215 }
216}
217
218impl From<Vec<AssistantPart>> for AssistantContent {
219 fn from(parts: Vec<AssistantPart>) -> Self {
220 Self::Parts(parts)
221 }
222}
223
224impl Message {
225 #[must_use]
227 pub fn system(content: impl Into<String>) -> Self {
228 Self::System(SystemMessage {
229 content: content.into(),
230 provider_options: None,
231 })
232 }
233
234 #[must_use]
236 pub fn user(text: impl Into<String>) -> Self {
237 Self::User(UserMessage {
238 content: UserContent::Text(text.into()),
239 provider_options: None,
240 })
241 }
242
243 #[must_use]
245 pub fn user_parts(parts: impl IntoIterator<Item = impl Into<UserPart>>) -> Self {
246 Self::User(UserMessage {
247 content: UserContent::Parts(parts.into_iter().map(Into::into).collect()),
248 provider_options: None,
249 })
250 }
251
252 #[must_use]
254 pub fn assistant(text: impl Into<String>) -> Self {
255 Self::Assistant(AssistantMessage {
256 content: AssistantContent::Text(text.into()),
257 provider_options: None,
258 })
259 }
260
261 #[must_use]
263 pub fn assistant_parts(parts: impl IntoIterator<Item = impl Into<AssistantPart>>) -> Self {
264 Self::Assistant(AssistantMessage {
265 content: AssistantContent::Parts(parts.into_iter().map(Into::into).collect()),
266 provider_options: None,
267 })
268 }
269
270 #[must_use]
272 pub fn tool(parts: impl IntoIterator<Item = impl Into<ToolPart>>) -> Self {
273 Self::Tool(ToolMessage {
274 content: parts.into_iter().map(Into::into).collect(),
275 provider_options: None,
276 })
277 }
278
279 #[must_use]
281 pub fn role(&self) -> Role {
282 match self {
283 Self::System(_) => Role::System,
284 Self::User(_) => Role::User,
285 Self::Assistant(_) => Role::Assistant,
286 Self::Tool(_) => Role::Tool,
287 }
288 }
289
290 #[must_use]
293 pub fn is_empty(&self) -> bool {
294 match self {
295 Self::System(message) => message.content.is_empty(),
296 Self::User(message) => message.content.is_empty(),
297 Self::Assistant(message) => message.content.is_empty(),
298 Self::Tool(message) => message.content.is_empty(),
299 }
300 }
301
302 #[must_use]
304 pub fn provider_options(&self) -> Option<&ProviderOptions> {
305 match self {
306 Self::System(message) => message.provider_options.as_ref(),
307 Self::User(message) => message.provider_options.as_ref(),
308 Self::Assistant(message) => message.provider_options.as_ref(),
309 Self::Tool(message) => message.provider_options.as_ref(),
310 }
311 }
312
313 #[must_use]
315 pub fn with_provider_options(mut self, options: ProviderOptions) -> Self {
316 let slot = match &mut self {
317 Self::System(message) => &mut message.provider_options,
318 Self::User(message) => &mut message.provider_options,
319 Self::Assistant(message) => &mut message.provider_options,
320 Self::Tool(message) => &mut message.provider_options,
321 };
322 *slot = Some(options);
323 self
324 }
325
326 #[must_use]
328 pub fn as_system(&self) -> Option<&SystemMessage> {
329 match self {
330 Self::System(message) => Some(message),
331 _ => None,
332 }
333 }
334
335 #[must_use]
337 pub fn as_user(&self) -> Option<&UserMessage> {
338 match self {
339 Self::User(message) => Some(message),
340 _ => None,
341 }
342 }
343
344 #[must_use]
346 pub fn as_assistant(&self) -> Option<&AssistantMessage> {
347 match self {
348 Self::Assistant(message) => Some(message),
349 _ => None,
350 }
351 }
352
353 #[must_use]
355 pub fn as_tool(&self) -> Option<&ToolMessage> {
356 match self {
357 Self::Tool(message) => Some(message),
358 _ => None,
359 }
360 }
361}
362
363impl From<SystemMessage> for Message {
364 fn from(message: SystemMessage) -> Self {
365 Self::System(message)
366 }
367}
368
369impl From<UserMessage> for Message {
370 fn from(message: UserMessage) -> Self {
371 Self::User(message)
372 }
373}
374
375impl From<AssistantMessage> for Message {
376 fn from(message: AssistantMessage) -> Self {
377 Self::Assistant(message)
378 }
379}
380
381impl From<ToolMessage> for Message {
382 fn from(message: ToolMessage) -> Self {
383 Self::Tool(message)
384 }
385}
386
387pub trait MessagesExt {
389 fn push_approval_response(&mut self, response: ToolApprovalResponse);
392
393 fn pending_approval_requests(&self) -> Vec<&crate::part::ToolApprovalRequest>;
395}
396
397impl MessagesExt for Vec<Message> {
398 fn push_approval_response(&mut self, response: ToolApprovalResponse) {
399 if let Some(Message::Tool(tool)) = self.last_mut() {
400 tool.content.push(ToolPart::ToolApprovalResponse(response));
401 } else {
402 self.push(Message::tool([ToolPart::ToolApprovalResponse(response)]));
403 }
404 }
405
406 fn pending_approval_requests(&self) -> Vec<&crate::part::ToolApprovalRequest> {
407 let answered: std::collections::HashSet<&ferrin_spec::ApprovalId> = self
408 .iter()
409 .filter_map(Message::as_tool)
410 .flat_map(|tool| tool.content.iter())
411 .filter_map(ToolPart::as_tool_approval_response)
412 .map(|response| &response.approval_id)
413 .collect();
414 self.iter()
415 .filter_map(Message::as_assistant)
416 .filter_map(|assistant| assistant.content.as_parts())
417 .flatten()
418 .filter_map(AssistantPart::as_tool_approval_request)
419 .filter(|request| !answered.contains(&request.approval_id))
420 .collect()
421 }
422}