systemprompt_identifiers/
actor.rs1use std::fmt;
12
13use serde::{Deserialize, Serialize};
14
15use crate::{AgentId, UserId};
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct Actor {
19 pub user_id: UserId,
20 pub kind: ActorKind,
21}
22
23impl Actor {
24 #[must_use]
25 pub const fn user(user_id: UserId) -> Self {
26 Self {
27 user_id,
28 kind: ActorKind::User,
29 }
30 }
31
32 #[must_use]
33 pub const fn anonymous(user_id: UserId) -> Self {
34 Self {
35 user_id,
36 kind: ActorKind::Anonymous,
37 }
38 }
39
40 #[must_use]
41 pub const fn system(user_id: UserId) -> Self {
42 Self {
43 user_id,
44 kind: ActorKind::System,
45 }
46 }
47
48 #[must_use]
49 pub fn job(user_id: UserId, job_name: impl Into<String>) -> Self {
50 Self {
51 user_id,
52 kind: ActorKind::Job {
53 job_name: job_name.into(),
54 },
55 }
56 }
57
58 #[must_use]
59 pub fn mcp(user_id: UserId, server_name: impl Into<String>) -> Self {
60 Self {
61 user_id,
62 kind: ActorKind::Mcp {
63 server_name: server_name.into(),
64 },
65 }
66 }
67
68 #[must_use]
69 pub const fn agent(user_id: UserId, agent_id: AgentId) -> Self {
70 Self {
71 user_id,
72 kind: ActorKind::Agent { agent_id },
73 }
74 }
75
76 #[must_use]
77 pub fn audit_columns(&self) -> (&str, &str) {
78 (self.kind.as_str(), self.kind.actor_id(&self.user_id))
79 }
80
81 #[must_use]
82 pub fn from_tool_name(user_id: UserId, agent_id: Option<&AgentId>, tool_name: &str) -> Self {
83 if let Some(rest) = tool_name.strip_prefix("mcp__")
84 && let Some(server) = rest.split("__").next()
85 && !server.is_empty()
86 {
87 return Self::mcp(user_id, server);
88 }
89 match agent_id {
90 Some(id) if !id.as_str().is_empty() => Self::agent(user_id, id.clone()),
91 _ => Self::user(user_id),
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(tag = "kind", rename_all = "snake_case")]
98pub enum ActorKind {
99 User,
100 Anonymous,
101 System,
102 Job { job_name: String },
103 Mcp { server_name: String },
104 Agent { agent_id: AgentId },
105}
106
107impl ActorKind {
108 #[must_use]
109 pub const fn as_str(&self) -> &'static str {
110 match self {
111 Self::User => "user",
112 Self::Anonymous => "anonymous",
113 Self::System => "system",
114 Self::Job { .. } => "job",
115 Self::Mcp { .. } => "mcp",
116 Self::Agent { .. } => "agent",
117 }
118 }
119
120 #[must_use]
121 pub fn actor_id<'a>(&'a self, user_id: &'a UserId) -> &'a str {
122 match self {
123 Self::User | Self::Anonymous | Self::System => user_id.as_str(),
124 Self::Job { job_name } => job_name.as_str(),
125 Self::Mcp { server_name } => server_name.as_str(),
126 Self::Agent { agent_id } => agent_id.as_str(),
127 }
128 }
129}
130
131impl ActorKind {
132 #[must_use]
133 pub const fn tag(&self) -> ActorKindTag {
134 match self {
135 Self::User => ActorKindTag::User,
136 Self::Anonymous => ActorKindTag::Anonymous,
137 Self::System => ActorKindTag::System,
138 Self::Job { .. } => ActorKindTag::Job,
139 Self::Mcp { .. } => ActorKindTag::Mcp,
140 Self::Agent { .. } => ActorKindTag::Agent,
141 }
142 }
143}
144
145impl fmt::Display for ActorKind {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 f.write_str(self.as_str())
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
158#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
159#[cfg_attr(feature = "sqlx", sqlx(type_name = "TEXT", rename_all = "snake_case"))]
160#[serde(rename_all = "snake_case")]
161pub enum ActorKindTag {
162 User,
163 Anonymous,
164 System,
165 Job,
166 Mcp,
167 Agent,
168}
169
170impl ActorKindTag {
171 #[must_use]
172 pub const fn as_str(self) -> &'static str {
173 match self {
174 Self::User => "user",
175 Self::Anonymous => "anonymous",
176 Self::System => "system",
177 Self::Job => "job",
178 Self::Mcp => "mcp",
179 Self::Agent => "agent",
180 }
181 }
182}
183
184impl fmt::Display for ActorKindTag {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 f.write_str(self.as_str())
187 }
188}