1use strum_macros::IntoStaticStr;
16
17pub trait Attributable {
19 fn role(&self) -> Role;
20 fn alias(&self) -> &str;
21}
22
23impl<T: Attributable + ?Sized> Attributable for std::sync::Arc<T> {
24 fn role(&self) -> Role {
25 (**self).role()
26 }
27 fn alias(&self) -> &str {
28 (**self).alias()
29 }
30}
31
32impl<T: Attributable + ?Sized> Attributable for Box<T> {
33 fn role(&self) -> Role {
34 (**self).role()
35 }
36 fn alias(&self) -> &str {
37 (**self).alias()
38 }
39}
40
41impl<T: Attributable + ?Sized> Attributable for &T {
42 fn role(&self) -> Role {
43 (**self).role()
44 }
45 fn alias(&self) -> &str {
46 (**self).alias()
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Role {
53 Swarm,
54 Agent,
55 Channel(ChannelKind),
56 Tool(ToolKind),
57 Cron(CronKind),
58 Provider(ProviderKind),
59 Memory(MemoryKind),
60 PeerGroup,
61 Skill,
62 Mcp,
63 Sop,
64 Session,
65 System,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
70#[strum(serialize_all = "snake_case")]
71pub enum ChannelKind {
72 #[strum(serialize = "acp")]
73 AcpChannel,
74 Bluesky,
75 #[strum(serialize = "clawdtalk")]
76 ClawdTalk,
77 Cli,
78 #[strum(serialize = "dingtalk")]
79 DingTalk,
80 Discord,
81 Email,
82 GmailPush,
83 #[strum(serialize = "imessage")]
84 IMessage,
85 Irc,
86 Lark,
87 Line,
88 Linq,
89 Matrix,
90 Mattermost,
91 #[strum(serialize = "mochat")]
92 MoChat,
93 NextcloudTalk,
94 Nostr,
95 Notion,
96 Qq,
97 Reddit,
98 Signal,
99 Slack,
100 Telegram,
101 Twitter,
102 VoiceCall,
103 VoiceWake,
104 Wati,
105 #[strum(serialize = "wecom")]
106 WeCom,
107 #[strum(serialize = "wecom_ws")]
108 WeComWs,
109 Webhook,
110 Wechat,
111 WhatsappBusiness,
112 WhatsappWeb,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
118#[strum(serialize_all = "snake_case")]
119pub enum ToolKind {
120 Shell,
121 HttpRequest,
122 HttpServer,
123 FetchUrl,
124 Search,
125 Memory,
126 SpawnSubagent,
127 SopList,
128 SopExecute,
129 SopApprove,
130 SopAdvance,
131 SopStatus,
132 SopHistory,
133 Wait,
134 Plugin,
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
139#[strum(serialize_all = "snake_case")]
140pub enum CronKind {
141 Interval,
142 At,
143 Cron,
144 Once,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum ProviderKind {
152 Model(ModelProviderKind),
153 Tts(TtsProviderKind),
154 Transcription(TranscriptionProviderKind),
155 Tunnel(TunnelProviderKind),
156}
157
158impl ProviderKind {
159 #[must_use]
160 pub fn type_str(self) -> &'static str {
161 match self {
162 Self::Model(k) => k.into(),
163 Self::Tts(k) => k.into(),
164 Self::Transcription(k) => k.into(),
165 Self::Tunnel(k) => k.into(),
166 }
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
171#[strum(serialize_all = "snake_case")]
172pub enum ModelProviderKind {
173 Anthropic,
174 #[strum(serialize = "openai")]
175 OpenAi,
176 #[strum(serialize = "openai_codex")]
177 OpenAiCodex,
178 Azure,
179 Together,
180 Bedrock,
181 Ollama,
182 Gemini,
183 GeminiCli,
184 GoogleAi,
185 Mistral,
186 Groq,
187 OpenRouter,
188 Telnyx,
189 Copilot,
190 Glm,
191 KiloCli,
192 Router,
193 Reliable,
194 Moonshot,
195 Qwen,
196 Minimax,
197 Zai,
198 Doubao,
199 Yi,
200 Hunyuan,
201 Qianfan,
202 Baichuan,
203 Fireworks,
204 Deepseek,
205 AtomicChat,
206 Cohere,
207 Perplexity,
208 Xai,
209 Cerebras,
210 Sambanova,
211 Hyperbolic,
212 Deepinfra,
213 Huggingface,
214 Ai21,
215 Reka,
216 Baseten,
217 Nscale,
218 Anyscale,
219 Nebius,
220 Friendli,
221 Stepfun,
222 Aihubmix,
223 Siliconflow,
224 Astrai,
225 Avian,
226 Deepmyst,
227 Venice,
228 Novita,
229 Nvidia,
230 Vercel,
231 Cloudflare,
232 Ovh,
233 Lmstudio,
234 Llamacpp,
235 Sglang,
236 Vllm,
237 Osaurus,
238 Litellm,
239 Lepton,
240 Synthetic,
241 Opencode,
242 Custom,
243 Plugin,
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
247#[strum(serialize_all = "snake_case")]
248pub enum TtsProviderKind {
249 #[strum(serialize = "openai")]
250 OpenAi,
251 #[strum(serialize = "elevenlabs")]
252 ElevenLabs,
253 Cartesia,
254 Google,
255 Edge,
256 Piper,
257 Plugin,
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
261#[strum(serialize_all = "snake_case")]
262pub enum TranscriptionProviderKind {
263 Whisper,
264 #[strum(serialize = "openai")]
265 OpenAi,
266 Deepgram,
267 Groq,
268 AssemblyAi,
269 Google,
270 Plugin,
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
274#[strum(serialize_all = "snake_case")]
275pub enum TunnelProviderKind {
276 Ngrok,
277 Cloudflared,
278 OpenVpn,
279 Pinggy,
280 Tailscale,
281 None,
282 Custom,
283 Plugin,
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
287#[strum(serialize_all = "snake_case")]
288pub enum MemoryKind {
289 Sqlite,
290 Json,
291 InMemory,
292 Markdown,
293 AgentScopedMarkdown,
294 AgentScoped,
295 Qdrant,
296 Postgres,
297 Lucid,
298 None,
299 Plugin,
300}
301
302impl Role {
303 #[must_use]
307 pub fn composite_prefix(self) -> Option<&'static str> {
308 match self {
309 Self::Channel(_) => Some("channel"),
310 Self::Provider(ProviderKind::Model(_)) => Some("model_provider"),
311 Self::Provider(ProviderKind::Tts(_)) => Some("tts_provider"),
312 Self::Provider(ProviderKind::Transcription(_)) => Some("transcription_provider"),
313 Self::Provider(ProviderKind::Tunnel(_)) => Some("tunnel_provider"),
314 _ => None,
315 }
316 }
317
318 #[must_use]
321 pub fn composite_type(self) -> Option<&'static str> {
322 match self {
323 Self::Channel(k) => Some(k.into()),
324 Self::Provider(p) => Some(p.type_str()),
325 _ => None,
326 }
327 }
328
329 #[must_use]
333 pub fn attribution_field(self) -> Option<&'static str> {
334 match self {
335 Self::Agent => Some("agent_alias"),
336 Self::Tool(_) => Some("tool"),
337 Self::Cron(_) => Some("cron_job_id"),
338 Self::Memory(_) => Some("memory_namespace"),
339 Self::PeerGroup => Some("peer_group"),
340 Self::Skill => Some("skill_bundle"),
341 Self::Mcp => Some("mcp_bundle"),
342 Self::Sop => Some("sop_name"),
343 Self::Session => Some("session_key"),
344 _ => None,
345 }
346 }
347
348 #[must_use]
352 pub fn family_str(self) -> &'static str {
353 match self {
354 Self::Swarm => "swarm",
355 Self::Agent => "agent",
356 Self::Channel(_) => "channel",
357 Self::Tool(_) => "tool",
358 Self::Cron(_) => "cron",
359 Self::Provider(ProviderKind::Model(_)) => "provider.model",
360 Self::Provider(ProviderKind::Tts(_)) => "provider.tts",
361 Self::Provider(ProviderKind::Transcription(_)) => "provider.transcription",
362 Self::Provider(ProviderKind::Tunnel(_)) => "provider.tunnel",
363 Self::Memory(_) => "memory",
364 Self::PeerGroup => "peer_group",
365 Self::Skill => "skill",
366 Self::Mcp => "mcp",
367 Self::Sop => "sop",
368 Self::Session => "session",
369 Self::System => "system",
370 }
371 }
372
373 #[must_use]
378 pub fn default_category(self) -> &'static str {
379 match self {
380 Self::Swarm | Self::Agent => "agent",
381 Self::Channel(_) => "channel",
382 Self::Tool(_) => "tool",
383 Self::Cron(_) => "cron",
384 Self::Provider(ProviderKind::Model(_)) => "model_provider",
385 Self::Provider(ProviderKind::Tts(_)) => "tts_provider",
386 Self::Provider(ProviderKind::Transcription(_)) => "transcription_provider",
387 Self::Provider(ProviderKind::Tunnel(_)) => "tunnel_provider",
388 Self::Memory(_) => "memory",
389 Self::Session => "session",
390 Self::Sop => "sop",
391 Self::PeerGroup | Self::Skill | Self::Mcp | Self::System => "system",
392 }
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn channel_kind_snake_case() {
402 assert_eq!(<&'static str>::from(ChannelKind::Telegram), "telegram");
403 assert_eq!(
404 <&'static str>::from(ChannelKind::WhatsappBusiness),
405 "whatsapp_business"
406 );
407 }
408
409 #[test]
410 fn provider_kind_delegates_to_inner() {
411 assert_eq!(
412 ProviderKind::Model(ModelProviderKind::Anthropic).type_str(),
413 "anthropic"
414 );
415 assert_eq!(
416 ProviderKind::Tts(TtsProviderKind::ElevenLabs).type_str(),
417 "elevenlabs"
418 );
419 }
420
421 #[test]
422 fn role_composite_prefix() {
423 assert_eq!(
424 Role::Channel(ChannelKind::Discord).composite_prefix(),
425 Some("channel")
426 );
427 assert_eq!(
428 Role::Provider(ProviderKind::Model(ModelProviderKind::Anthropic)).composite_prefix(),
429 Some("model_provider"),
430 );
431 assert!(Role::Agent.composite_prefix().is_none());
432 }
433
434 #[test]
435 fn role_attribution_field() {
436 assert_eq!(Role::Agent.attribution_field(), Some("agent_alias"));
437 assert_eq!(
438 Role::Tool(ToolKind::Shell).attribution_field(),
439 Some("tool")
440 );
441 assert!(
442 Role::Channel(ChannelKind::Telegram)
443 .attribution_field()
444 .is_none()
445 );
446 }
447}