1use chrono::{DateTime, Utc};
2pub use claudius::ToolParam;
3#[cfg(feature = "client")]
4mod client;
5#[cfg(feature = "client")]
6pub use client::Client;
7#[cfg(feature = "server")]
8mod server;
9#[cfg(feature = "server")]
10pub use server::router;
11
12macro_rules! typed_string {
13 ($name:ident) => {
14 impl $name {
15 pub fn new(s: impl Into<String>) -> Option<Self> {
17 let s = s.into();
18 Self::validate(&s)?;
19 Some(Self(s))
20 }
21
22 pub fn as_str(&self) -> &str {
24 &self.0
25 }
26 }
27 };
28}
29
30#[derive(
33 Clone,
34 Debug,
35 Default,
36 Eq,
37 PartialEq,
38 Ord,
39 PartialOrd,
40 Hash,
41 serde::Deserialize,
42 serde::Serialize,
43)]
44pub struct MessageID(String);
45typed_string!(MessageID);
46
47impl MessageID {
48 pub fn validate(_: &str) -> Option<()> {
49 Some(())
50 }
51}
52
53#[derive(
56 Clone,
57 Debug,
58 Default,
59 Eq,
60 PartialEq,
61 Ord,
62 PartialOrd,
63 Hash,
64 serde::Deserialize,
65 serde::Serialize,
66)]
67pub struct MailboxName(String);
68typed_string!(MailboxName);
69
70impl MailboxName {
71 pub fn validate(_: &str) -> Option<()> {
72 Some(())
73 }
74}
75
76#[derive(
79 Clone,
80 Debug,
81 Default,
82 Eq,
83 PartialEq,
84 Ord,
85 PartialOrd,
86 Hash,
87 serde::Deserialize,
88 serde::Serialize,
89)]
90pub struct From(String);
91typed_string!(From);
92
93impl From {
94 pub fn validate(_: &str) -> Option<()> {
95 Some(())
96 }
97}
98
99#[derive(
102 Clone,
103 Debug,
104 Default,
105 Eq,
106 PartialEq,
107 Ord,
108 PartialOrd,
109 Hash,
110 serde::Deserialize,
111 serde::Serialize,
112)]
113pub struct Body(String);
114typed_string!(Body);
115
116impl Body {
117 pub fn validate(_: &str) -> Option<()> {
118 Some(())
119 }
120}
121
122#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
125pub struct Message {
126 pub msg_id: MessageID,
127 pub date: DateTime<Utc>,
128 pub from: From,
129 pub body: Body,
130 pub wrap: bool,
131 pub tools: Vec<claudius::ToolParam>,
132}
133
134#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
137pub struct Mailbox {
138 pub name: MailboxName,
139 pub messages: Vec<Message>,
140}
141
142#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
145pub struct QueryParameters {
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub search: Option<String>,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub keywords: Option<Vec<String>>,
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub not_keywords: Option<Vec<String>>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub mailbox: Option<MailboxName>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub mailboxes: Option<Vec<MailboxName>>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub from: Option<From>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub since: Option<DateTime<Utc>>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub until: Option<DateTime<Utc>>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub max_per_inbox: Option<u32>,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub max_across_inboxes: Option<u32>,
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub sort: Option<SortOrder>,
168}
169
170#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
171pub enum SortOrder {
172 DateAsc,
173 DateDesc,
174 Relevance,
175}
176
177#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
180pub struct QueryResult {
181 mailboxes: Vec<Mailbox>,
182}
183
184impl QueryResult {
185 pub fn new(mailboxes: Vec<Mailbox>) -> Self {
187 Self { mailboxes }
188 }
189
190 pub fn mailboxes(&self) -> &[Mailbox] {
192 &self.mailboxes
193 }
194
195 pub fn into_mailboxes(self) -> Vec<Mailbox> {
197 self.mailboxes
198 }
199}
200
201#[async_trait::async_trait]
204pub trait MailboxProvider {
205 type Error: std::error::Error;
206 async fn query(&mut self, query: QueryParameters) -> Result<QueryResult, Self::Error>;
207 async fn tool_call(
208 &mut self,
209 request: ToolCallRequest,
210 ) -> Result<ToolCallResponse, Self::Error>;
211}
212
213#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
217pub struct ToolCallRequest {
218 pub message_id: MessageID,
220 pub name: String,
222 pub input: serde_json::Value,
224}
225
226#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
230pub struct ToolCallResponse {
231 pub success: bool,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub message: Option<String>,
236}
237
238impl ToolCallResponse {
239 pub fn success() -> Self {
241 Self {
242 success: true,
243 message: None,
244 }
245 }
246
247 pub fn success_with_message(message: impl Into<String>) -> Self {
249 Self {
250 success: true,
251 message: Some(message.into()),
252 }
253 }
254
255 pub fn failure(message: impl Into<String>) -> Self {
257 Self {
258 success: false,
259 message: Some(message.into()),
260 }
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267 use claudius::ToolParam;
268
269 #[test]
270 fn message_with_tool_param_tools() {
271 let tool = ToolParam {
272 name: "done".to_string(),
273 description: Some("Mark as done".to_string()),
274 input_schema: serde_json::json!({"type": "object", "properties": {}}),
275 cache_control: None,
276 strict: None,
277 };
278
279 let message = Message {
280 msg_id: MessageID::new("msg-1").unwrap(),
281 date: Utc::now(),
282 from: From::new("test@example.com").unwrap(),
283 body: Body::new("Test").unwrap(),
284 wrap: false,
285 tools: vec![tool],
286 };
287
288 assert_eq!(message.tools.len(), 1);
289 assert_eq!(message.tools[0].name, "done");
290 assert_eq!(
291 message.tools[0].description,
292 Some("Mark as done".to_string())
293 );
294 println!("input_schema: {:?}", message.tools[0].input_schema);
296 }
297
298 #[test]
299 fn tool_call_request_with_input() {
300 let request = ToolCallRequest {
301 message_id: MessageID::new("msg-1").unwrap(),
302 name: "defer".to_string(),
303 input: serde_json::json!({"days": 3}),
304 };
305
306 assert_eq!(request.name, "defer");
307 assert_eq!(request.input["days"], 3);
308 println!("request: {:?}", request);
310 }
311}