ai_crew_sync/tools/
attachments.rs1use rmcp::{
2 ErrorData, Json, handler::server::wrapper::Parameters, service::RequestContext, tool,
3 tool_router,
4};
5use schemars::JsonSchema;
6use serde::Deserialize;
7
8use super::{Bus, auth_of};
9use crate::{
10 error::BusError,
11 model::{AttachmentContent, AttachmentMeta},
12 store::attachments,
13};
14
15#[derive(Debug, Deserialize, JsonSchema)]
16pub struct AttachFileArgs {
17 #[serde(default)]
19 pub task: Option<String>,
20 #[serde(default)]
23 pub message_id: Option<i64>,
24 pub filename: String,
25 #[serde(default)]
27 pub content_type: Option<String>,
28 pub data_base64: String,
30}
31
32#[derive(Debug, Deserialize, JsonSchema)]
33pub struct GetAttachmentArgs {
34 pub id: i64,
36}
37
38#[tool_router(router = attachments_router, vis = "pub")]
39impl Bus {
40 #[tool(
41 description = "Attach a small file (diff, log, config — max 256 KiB) to a task or to \
42 a message you sent. Teammates see it listed on the task/message and \
43 download it with get_attachment. To share a file with a new message, \
44 pass `attachments` to post_message instead."
45 )]
46 async fn attach_file(
47 &self,
48 ctx: RequestContext<rmcp::RoleServer>,
49 Parameters(args): Parameters<AttachFileArgs>,
50 ) -> Result<Json<AttachmentMeta>, ErrorData> {
51 let auth = auth_of(&ctx)?;
52 let att = attachments::decode_input(&args.filename, args.content_type, &args.data_base64)?;
53 let meta = match (&args.task, args.message_id) {
54 (Some(_), Some(_)) | (None, None) => {
55 return Err(BusError::invalid(
56 "set either `task` (a task key) or `message_id`, not both",
57 )
58 .into());
59 }
60 (Some(task), None) => attachments::attach_to_task(&self.db, &auth, task, &att).await?,
61 (None, Some(message_id)) => {
62 attachments::attach_to_message(&self.db, &auth, message_id, &att).await?
63 }
64 };
65 Ok(Json(meta))
66 }
67
68 #[tool(
69 description = "Download an attachment by id (returns metadata plus base64 content). \
70 Attachments on direct messages are only visible to the DM's two \
71 parties; everything else is team-visible."
72 )]
73 async fn get_attachment(
74 &self,
75 ctx: RequestContext<rmcp::RoleServer>,
76 Parameters(args): Parameters<GetAttachmentArgs>,
77 ) -> Result<Json<AttachmentContent>, ErrorData> {
78 let auth = auth_of(&ctx)?;
79 Ok(Json(
80 attachments::get_attachment(&self.db, &auth, args.id).await?,
81 ))
82 }
83}