use chrono::{DateTime, Utc};
pub use claudius::ToolParam;
#[cfg(feature = "client")]
mod client;
#[cfg(feature = "client")]
pub use client::Client;
#[cfg(feature = "server")]
mod server;
#[cfg(feature = "server")]
pub use server::router;
macro_rules! typed_string {
($name:ident) => {
impl $name {
pub fn new(s: impl Into<String>) -> Option<Self> {
let s = s.into();
Self::validate(&s)?;
Some(Self(s))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
};
}
#[derive(
Clone,
Debug,
Default,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
serde::Deserialize,
serde::Serialize,
)]
pub struct MessageID(String);
typed_string!(MessageID);
impl MessageID {
pub fn validate(_: &str) -> Option<()> {
Some(())
}
}
#[derive(
Clone,
Debug,
Default,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
serde::Deserialize,
serde::Serialize,
)]
pub struct MailboxName(String);
typed_string!(MailboxName);
impl MailboxName {
pub fn validate(_: &str) -> Option<()> {
Some(())
}
}
#[derive(
Clone,
Debug,
Default,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
serde::Deserialize,
serde::Serialize,
)]
pub struct From(String);
typed_string!(From);
impl From {
pub fn validate(_: &str) -> Option<()> {
Some(())
}
}
#[derive(
Clone,
Debug,
Default,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
serde::Deserialize,
serde::Serialize,
)]
pub struct Body(String);
typed_string!(Body);
impl Body {
pub fn validate(_: &str) -> Option<()> {
Some(())
}
}
#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Message {
pub msg_id: MessageID,
pub date: DateTime<Utc>,
pub from: From,
pub body: Body,
pub wrap: bool,
pub tools: Vec<claudius::ToolParam>,
}
#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Mailbox {
pub name: MailboxName,
pub messages: Vec<Message>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
pub struct QueryParameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub search: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keywords: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_keywords: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mailbox: Option<MailboxName>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mailboxes: Option<Vec<MailboxName>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<From>,
#[serde(skip_serializing_if = "Option::is_none")]
pub since: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub until: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_per_inbox: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_across_inboxes: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort: Option<SortOrder>,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
pub enum SortOrder {
DateAsc,
DateDesc,
Relevance,
}
#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct QueryResult {
mailboxes: Vec<Mailbox>,
}
impl QueryResult {
pub fn new(mailboxes: Vec<Mailbox>) -> Self {
Self { mailboxes }
}
pub fn mailboxes(&self) -> &[Mailbox] {
&self.mailboxes
}
pub fn into_mailboxes(self) -> Vec<Mailbox> {
self.mailboxes
}
}
#[async_trait::async_trait]
pub trait MailboxProvider {
type Error: std::error::Error;
async fn query(&mut self, query: QueryParameters) -> Result<QueryResult, Self::Error>;
async fn tool_call(
&mut self,
request: ToolCallRequest,
) -> Result<ToolCallResponse, Self::Error>;
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ToolCallRequest {
pub message_id: MessageID,
pub name: String,
pub input: serde_json::Value,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
pub struct ToolCallResponse {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl ToolCallResponse {
pub fn success() -> Self {
Self {
success: true,
message: None,
}
}
pub fn success_with_message(message: impl Into<String>) -> Self {
Self {
success: true,
message: Some(message.into()),
}
}
pub fn failure(message: impl Into<String>) -> Self {
Self {
success: false,
message: Some(message.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use claudius::ToolParam;
#[test]
fn message_with_tool_param_tools() {
let tool = ToolParam {
name: "done".to_string(),
description: Some("Mark as done".to_string()),
input_schema: serde_json::json!({"type": "object", "properties": {}}),
cache_control: None,
strict: None,
};
let message = Message {
msg_id: MessageID::new("msg-1").unwrap(),
date: Utc::now(),
from: From::new("test@example.com").unwrap(),
body: Body::new("Test").unwrap(),
wrap: false,
tools: vec![tool],
};
assert_eq!(message.tools.len(), 1);
assert_eq!(message.tools[0].name, "done");
assert_eq!(
message.tools[0].description,
Some("Mark as done".to_string())
);
println!("input_schema: {:?}", message.tools[0].input_schema);
}
#[test]
fn tool_call_request_with_input() {
let request = ToolCallRequest {
message_id: MessageID::new("msg-1").unwrap(),
name: "defer".to_string(),
input: serde_json::json!({"days": 3}),
};
assert_eq!(request.name, "defer");
assert_eq!(request.input["days"], 3);
println!("request: {:?}", request);
}
}