feat(config): add Lark/Feishu channel config support

* feat(config): add Lark/Feishu channel config support

- Add LarkConfig struct with app_id, app_secret, encrypt_key, verification_token, allowed_users, use_feishu fields
- Add lark field to ChannelsConfig
- Export LarkConfig in config/mod.rs
- Add 5 tests for LarkConfig serialization/deserialization

Related to #164

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: apply cargo fmt formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Argenis 2026-02-16 00:16:04 -05:00 committed by GitHub
parent c8ca6ff059
commit 0e0b3644a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 100 additions and 1 deletions

5
.cargo/config.toml Normal file
View file

@ -0,0 +1,5 @@
[target.x86_64-unknown-linux-musl]
rustflags = ["-C", "link-arg=-static"]
[target.aarch64-unknown-linux-musl]
rustflags = ["-C", "link-arg=-static"]

View file

@ -3,7 +3,7 @@ pub mod schema;
pub use schema::{ pub use schema::{
AutonomyConfig, BrowserConfig, ChannelsConfig, ComposioConfig, Config, DelegateAgentConfig, AutonomyConfig, BrowserConfig, ChannelsConfig, ComposioConfig, Config, DelegateAgentConfig,
DiscordConfig, DockerRuntimeConfig, GatewayConfig, HeartbeatConfig, IMessageConfig, DiscordConfig, DockerRuntimeConfig, GatewayConfig, HeartbeatConfig, IMessageConfig,
IdentityConfig, MatrixConfig, MemoryConfig, ModelRouteConfig, ObservabilityConfig, IdentityConfig, LarkConfig, MatrixConfig, MemoryConfig, ModelRouteConfig, ObservabilityConfig,
ReliabilityConfig, RuntimeConfig, SecretsConfig, SlackConfig, TelegramConfig, TunnelConfig, ReliabilityConfig, RuntimeConfig, SecretsConfig, SlackConfig, TelegramConfig, TunnelConfig,
WebhookConfig, WebhookConfig,
}; };

View file

@ -741,6 +741,7 @@ pub struct ChannelsConfig {
pub whatsapp: Option<WhatsAppConfig>, pub whatsapp: Option<WhatsAppConfig>,
pub email: Option<crate::channels::email_channel::EmailConfig>, pub email: Option<crate::channels::email_channel::EmailConfig>,
pub irc: Option<IrcConfig>, pub irc: Option<IrcConfig>,
pub lark: Option<LarkConfig>,
} }
impl Default for ChannelsConfig { impl Default for ChannelsConfig {
@ -756,6 +757,7 @@ impl Default for ChannelsConfig {
whatsapp: None, whatsapp: None,
email: None, email: None,
irc: None, irc: None,
lark: None,
} }
} }
} }
@ -850,6 +852,28 @@ fn default_irc_port() -> u16 {
6697 6697
} }
/// Lark/Feishu configuration for messaging integration
/// Lark is the international version, Feishu is the Chinese version
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LarkConfig {
/// App ID from Lark/Feishu developer console
pub app_id: String,
/// App Secret from Lark/Feishu developer console
pub app_secret: String,
/// Encrypt key for webhook message decryption (optional)
#[serde(default)]
pub encrypt_key: Option<String>,
/// Verification token for webhook validation (optional)
#[serde(default)]
pub verification_token: Option<String>,
/// Allowed user IDs or union IDs (empty = deny all, "*" = allow all)
#[serde(default)]
pub allowed_users: Vec<String>,
/// Whether to use the Feishu (Chinese) endpoint instead of Lark (International)
#[serde(default)]
pub use_feishu: bool,
}
// ── Config impl ────────────────────────────────────────────────── // ── Config impl ──────────────────────────────────────────────────
impl Default for Config { impl Default for Config {
@ -1216,6 +1240,7 @@ mod tests {
whatsapp: None, whatsapp: None,
email: None, email: None,
irc: None, irc: None,
lark: None,
}, },
memory: MemoryConfig::default(), memory: MemoryConfig::default(),
tunnel: TunnelConfig::default(), tunnel: TunnelConfig::default(),
@ -1464,6 +1489,7 @@ default_temperature = 0.7
whatsapp: None, whatsapp: None,
email: None, email: None,
irc: None, irc: None,
lark: None,
}; };
let toml_str = toml::to_string_pretty(&c).unwrap(); let toml_str = toml::to_string_pretty(&c).unwrap();
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap(); let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
@ -1622,6 +1648,7 @@ channel_id = "C123"
}), }),
email: None, email: None,
irc: None, irc: None,
lark: None,
}; };
let toml_str = toml::to_string_pretty(&c).unwrap(); let toml_str = toml::to_string_pretty(&c).unwrap();
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap(); let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
@ -2052,6 +2079,72 @@ default_temperature = 0.7
assert!(g.paired_tokens.is_empty()); assert!(g.paired_tokens.is_empty());
} }
// ── Lark config ───────────────────────────────────────────────
#[test]
fn lark_config_serde() {
let lc = LarkConfig {
app_id: "cli_123456".into(),
app_secret: "secret_abc".into(),
encrypt_key: Some("encrypt_key".into()),
verification_token: Some("verify_token".into()),
allowed_users: vec!["user_123".into(), "user_456".into()],
use_feishu: true,
};
let json = serde_json::to_string(&lc).unwrap();
let parsed: LarkConfig = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.app_id, "cli_123456");
assert_eq!(parsed.app_secret, "secret_abc");
assert_eq!(parsed.encrypt_key.as_deref(), Some("encrypt_key"));
assert_eq!(parsed.verification_token.as_deref(), Some("verify_token"));
assert_eq!(parsed.allowed_users.len(), 2);
assert!(parsed.use_feishu);
}
#[test]
fn lark_config_toml_roundtrip() {
let lc = LarkConfig {
app_id: "cli_123456".into(),
app_secret: "secret_abc".into(),
encrypt_key: Some("encrypt_key".into()),
verification_token: Some("verify_token".into()),
allowed_users: vec!["*".into()],
use_feishu: false,
};
let toml_str = toml::to_string(&lc).unwrap();
let parsed: LarkConfig = toml::from_str(&toml_str).unwrap();
assert_eq!(parsed.app_id, "cli_123456");
assert_eq!(parsed.app_secret, "secret_abc");
assert!(!parsed.use_feishu);
}
#[test]
fn lark_config_deserializes_without_optional_fields() {
let json = r#"{"app_id":"cli_123","app_secret":"secret"}"#;
let parsed: LarkConfig = serde_json::from_str(json).unwrap();
assert!(parsed.encrypt_key.is_none());
assert!(parsed.verification_token.is_none());
assert!(parsed.allowed_users.is_empty());
assert!(!parsed.use_feishu);
}
#[test]
fn lark_config_defaults_to_lark_endpoint() {
let json = r#"{"app_id":"cli_123","app_secret":"secret"}"#;
let parsed: LarkConfig = serde_json::from_str(json).unwrap();
assert!(
!parsed.use_feishu,
"use_feishu should default to false (Lark)"
);
}
#[test]
fn lark_config_with_wildcard_allowed_users() {
let json = r#"{"app_id":"cli_123","app_secret":"secret","allowed_users":["*"]}"#;
let parsed: LarkConfig = serde_json::from_str(json).unwrap();
assert_eq!(parsed.allowed_users, vec!["*"]);
}
// ══════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════
// AGENT DELEGATION CONFIG TESTS // AGENT DELEGATION CONFIG TESTS
// ══════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════

View file

@ -1126,6 +1126,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
whatsapp: None, whatsapp: None,
email: None, email: None,
irc: None, irc: None,
lark: None,
}; };
loop { loop {