systemprompt_identifiers/
actor.rs1use std::fmt;
12
13use serde::{Deserialize, Serialize};
14
15use crate::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 fn agent(user_id: UserId, agent_id: impl Into<String>) -> Self {
70 Self {
71 user_id,
72 kind: ActorKind::Agent {
73 agent_id: agent_id.into(),
74 },
75 }
76 }
77
78 #[must_use]
79 pub fn audit_columns(&self) -> (&str, &str) {
80 (self.kind.as_str(), self.kind.actor_id(&self.user_id))
81 }
82
83 #[must_use]
84 pub fn from_tool_name(user_id: UserId, agent_id: Option<&str>, tool_name: &str) -> Self {
85 if let Some(rest) = tool_name.strip_prefix("mcp__")
86 && let Some(server) = rest.split("__").next()
87 && !server.is_empty()
88 {
89 return Self::mcp(user_id, server);
90 }
91 match agent_id {
92 Some(id) if !id.is_empty() => Self::agent(user_id, id),
93 _ => Self::user(user_id),
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(tag = "kind", rename_all = "snake_case")]
100pub enum ActorKind {
101 User,
102 Anonymous,
103 System,
104 Job { job_name: String },
105 Mcp { server_name: String },
106 Agent { agent_id: String },
107}
108
109impl ActorKind {
110 #[must_use]
111 pub const fn as_str(&self) -> &'static str {
112 match self {
113 Self::User => "user",
114 Self::Anonymous => "anonymous",
115 Self::System => "system",
116 Self::Job { .. } => "job",
117 Self::Mcp { .. } => "mcp",
118 Self::Agent { .. } => "agent",
119 }
120 }
121
122 #[must_use]
123 pub fn actor_id<'a>(&'a self, user_id: &'a UserId) -> &'a str {
124 match self {
125 Self::User | Self::Anonymous | Self::System => user_id.as_str(),
126 Self::Job { job_name } => job_name.as_str(),
127 Self::Mcp { server_name } => server_name.as_str(),
128 Self::Agent { agent_id } => agent_id.as_str(),
129 }
130 }
131}
132
133impl ActorKind {
134 #[must_use]
135 pub const fn tag(&self) -> ActorKindTag {
136 match self {
137 Self::User => ActorKindTag::User,
138 Self::Anonymous => ActorKindTag::Anonymous,
139 Self::System => ActorKindTag::System,
140 Self::Job { .. } => ActorKindTag::Job,
141 Self::Mcp { .. } => ActorKindTag::Mcp,
142 Self::Agent { .. } => ActorKindTag::Agent,
143 }
144 }
145}
146
147impl fmt::Display for ActorKind {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 f.write_str(self.as_str())
150 }
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
160#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
161#[cfg_attr(feature = "sqlx", sqlx(type_name = "TEXT", rename_all = "snake_case"))]
162#[serde(rename_all = "snake_case")]
163pub enum ActorKindTag {
164 User,
165 Anonymous,
166 System,
167 Job,
168 Mcp,
169 Agent,
170}
171
172impl ActorKindTag {
173 #[must_use]
174 pub const fn as_str(self) -> &'static str {
175 match self {
176 Self::User => "user",
177 Self::Anonymous => "anonymous",
178 Self::System => "system",
179 Self::Job => "job",
180 Self::Mcp => "mcp",
181 Self::Agent => "agent",
182 }
183 }
184}
185
186impl fmt::Display for ActorKindTag {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 f.write_str(self.as_str())
189 }
190}