use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum SlackAction {
PostMessage {
input_message_id: String,
tool_call_id: String,
text: String,
},
AddReaction {
channel: String,
timestamp: String,
name: String,
},
UpdateMessage {
channel: String,
timestamp: String,
text: String,
},
LookupUser { user_id: String },
UploadFile {
channel: String,
thread_ts: Option<String>,
filename: String,
content: Vec<u8>,
initial_comment: Option<String>,
},
}
impl SlackAction {
pub fn kind(&self) -> &'static str {
match self {
Self::PostMessage { .. } => "post_message",
Self::AddReaction { .. } => "add_reaction",
Self::UpdateMessage { .. } => "update_message",
Self::LookupUser { .. } => "lookup_user",
Self::UploadFile { .. } => "upload_file",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum SlackActionOutcome {
MessagePosted { channel: String, timestamp: String },
ReactionAdded { already_reacted: bool },
MessageUpdated { channel: String, timestamp: String },
User {
user_id: String,
display_name: Option<String>,
real_name: Option<String>,
is_bot: bool,
tz: Option<String>,
},
FileUploaded {
file_id: String,
permalink: Option<String>,
},
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum SlackActionError {
#[error(
"this session did not originate from Slack, so there is no Slack endpoint to act as; \
Slack tools only work in a session created by a Slack message"
)]
NoSlackSession,
#[error("the Slack endpoint for this session is no longer available")]
ChannelUnavailable,
#[error("the Slack endpoint for this session has no bot token configured")]
NotConfigured,
#[error("Slack rejected the request: {0}")]
Rejected(String),
#[error("Slack rate limited the request{}", match .retry_after_secs {
Some(secs) => format!(" (retry after {secs}s)"),
None => String::new(),
})]
RateLimited { retry_after_secs: Option<u64> },
#[error("{0}")]
InvalidArgument(String),
#[error("Slack request failed: {0}")]
Transient(String),
}
impl SlackActionError {
pub fn is_tool_error(&self) -> bool {
!matches!(self, Self::Transient(_))
}
}
#[async_trait]
pub trait SlackActionInvoker: Send + Sync {
async fn invoke(&self, action: SlackAction) -> Result<SlackActionOutcome, SlackActionError>;
}
#[derive(Clone)]
pub struct SlackActionInvokerExt(pub std::sync::Arc<dyn SlackActionInvoker>);