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