Skip to main content

agent_inbox_protocol/
lib.rs

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            /// Creates a new instance if the input is valid.
16            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            /// Returns the inner string as a slice.
23            pub fn as_str(&self) -> &str {
24                &self.0
25            }
26        }
27    };
28}
29
30///////////////////////////////////////////// MessageID /////////////////////////////////////////////
31
32#[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//////////////////////////////////////////// MailboxName ///////////////////////////////////////////
54
55#[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/////////////////////////////////////////////// From ///////////////////////////////////////////////
77
78#[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/////////////////////////////////////////////// Body ///////////////////////////////////////////////
100
101#[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////////////////////////////////////////////// Message /////////////////////////////////////////////
123
124#[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////////////////////////////////////////////// Mailbox /////////////////////////////////////////////
135
136#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
137pub struct Mailbox {
138    pub name: MailboxName,
139    pub messages: Vec<Message>,
140}
141
142////////////////////////////////////////// QueryParameters /////////////////////////////////////////
143
144#[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/////////////////////////////////////////// QueryResults ///////////////////////////////////////////
178
179#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
180pub struct QueryResult {
181    mailboxes: Vec<Mailbox>,
182}
183
184impl QueryResult {
185    /// Creates a new query result from a list of mailboxes.
186    pub fn new(mailboxes: Vec<Mailbox>) -> Self {
187        Self { mailboxes }
188    }
189
190    /// Returns the mailboxes in this query result.
191    pub fn mailboxes(&self) -> &[Mailbox] {
192        &self.mailboxes
193    }
194
195    /// Consumes the query result and returns the mailboxes.
196    pub fn into_mailboxes(self) -> Vec<Mailbox> {
197        self.mailboxes
198    }
199}
200
201////////////////////////////////////////// MailboxProvider /////////////////////////////////////////
202
203#[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////////////////////////////////////////// ToolCallRequest /////////////////////////////////////////
214
215/// Request to perform a tool call on a message.
216#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
217pub struct ToolCallRequest {
218    /// The message ID to target.
219    pub message_id: MessageID,
220    /// The name of the tool to perform.
221    pub name: String,
222    /// The input payload for the tool, matching the tool's input_schema.
223    pub input: serde_json::Value,
224}
225
226////////////////////////////////////////// ToolCallResponse ////////////////////////////////////////
227
228/// Response from a tool call request.
229#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
230pub struct ToolCallResponse {
231    /// Whether the tool call succeeded.
232    pub success: bool,
233    /// Optional message describing the result.
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub message: Option<String>,
236}
237
238impl ToolCallResponse {
239    /// Creates a successful response.
240    pub fn success() -> Self {
241        Self {
242            success: true,
243            message: None,
244        }
245    }
246
247    /// Creates a successful response with a message.
248    pub fn success_with_message(message: impl Into<String>) -> Self {
249        Self {
250            success: true,
251            message: Some(message.into()),
252        }
253    }
254
255    /// Creates a failure response.
256    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        // Verify input_schema is as expected.
295        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        // Verify input can be serialized.
309        println!("request: {:?}", request);
310    }
311}