diff --git a/crates/openfang-api/src/channel_bridge.rs b/crates/openfang-api/src/channel_bridge.rs index 6affca54..a989ec5b 100644 --- a/crates/openfang-api/src/channel_bridge.rs +++ b/crates/openfang-api/src/channel_bridge.rs @@ -1150,6 +1150,7 @@ pub async fn start_channel_bridge_with_config( sl_config.allowed_channels.clone(), sl_config.auto_thread_reply, sl_config.thread_ttl_hours, + sl_config.unfurl_links, )); adapters.push((adapter, sl_config.default_agent.clone())); } diff --git a/crates/openfang-channels/src/slack.rs b/crates/openfang-channels/src/slack.rs index 42beb105..b0915530 100644 --- a/crates/openfang-channels/src/slack.rs +++ b/crates/openfang-channels/src/slack.rs @@ -39,6 +39,8 @@ pub struct SlackAdapter { thread_ttl: Duration, /// Whether auto-thread-reply is enabled. auto_thread_reply: bool, + /// Whether to unfurl (expand previews for) links in posted messages. + unfurl_links: bool, } impl SlackAdapter { @@ -48,6 +50,7 @@ impl SlackAdapter { allowed_channels: Vec, auto_thread_reply: bool, thread_ttl_hours: u64, + unfurl_links: bool, ) -> Self { let (shutdown_tx, shutdown_rx) = watch::channel(false); Self { @@ -61,6 +64,7 @@ impl SlackAdapter { active_threads: Arc::new(DashMap::new()), thread_ttl: Duration::from_secs(thread_ttl_hours * 3600), auto_thread_reply, + unfurl_links, } } @@ -100,6 +104,8 @@ impl SlackAdapter { let mut body = serde_json::json!({ "channel": channel_id, "text": chunk, + "unfurl_links": self.unfurl_links, + "unfurl_media": self.unfurl_links, }); if let Some(ts) = thread_ts { body["thread_ts"] = serde_json::json!(ts); @@ -691,8 +697,35 @@ mod tests { vec!["C123".to_string()], true, 24, + true, ); assert_eq!(adapter.name(), "slack"); assert_eq!(adapter.channel_type(), ChannelType::Slack); } + + #[test] + fn test_slack_adapter_unfurl_links_enabled() { + let adapter = SlackAdapter::new( + "xapp-test".to_string(), + "xoxb-test".to_string(), + vec![], + true, + 24, + true, + ); + assert!(adapter.unfurl_links); + } + + #[test] + fn test_slack_adapter_unfurl_links_disabled() { + let adapter = SlackAdapter::new( + "xapp-test".to_string(), + "xoxb-test".to_string(), + vec![], + true, + 24, + false, + ); + assert!(!adapter.unfurl_links); + } } diff --git a/crates/openfang-types/src/config.rs b/crates/openfang-types/src/config.rs index 421253b9..48472ff2 100644 --- a/crates/openfang-types/src/config.rs +++ b/crates/openfang-types/src/config.rs @@ -1752,6 +1752,9 @@ pub struct SlackConfig { /// Hours to track a thread after last interaction (default: 24). #[serde(default = "default_thread_ttl")] pub thread_ttl_hours: u64, + /// Whether to unfurl (expand previews for) links in messages (default: true). + #[serde(default = "default_true")] + pub unfurl_links: bool, } impl Default for SlackConfig { @@ -1764,6 +1767,7 @@ impl Default for SlackConfig { overrides: ChannelOverrides::default(), auto_thread_reply: true, thread_ttl_hours: 24, + unfurl_links: true, } } } @@ -3872,4 +3876,22 @@ mod tests { "AZURE_OPENAI_KEY" ); } + + #[test] + fn test_slack_config_unfurl_links_defaults_true() { + let config: SlackConfig = toml::from_str("").unwrap(); + assert!(config.unfurl_links); + } + + #[test] + fn test_slack_config_unfurl_links_explicit_false() { + let config: SlackConfig = toml::from_str("unfurl_links = false").unwrap(); + assert!(!config.unfurl_links); + } + + #[test] + fn test_slack_config_unfurl_links_explicit_true() { + let config: SlackConfig = toml::from_str("unfurl_links = true").unwrap(); + assert!(config.unfurl_links); + } }