agent_runtime_types/
lib.rs1use std::{fmt, str::FromStr, sync::Arc};
7
8use serde::{Deserialize, Serialize};
9
10macro_rules! identifier {
11 ($name:ident, $label:literal) => {
12 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
13 #[serde(transparent)]
14 pub struct $name(String);
15
16 impl $name {
17 pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
18 let value = value.into();
19 validate($label, &value)?;
20 Ok(Self(value))
21 }
22
23 pub fn as_str(&self) -> &str {
24 &self.0
25 }
26 }
27
28 impl fmt::Display for $name {
29 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30 self.0.fmt(formatter)
31 }
32 }
33
34 impl FromStr for $name {
35 type Err = IdentifierError;
36
37 fn from_str(value: &str) -> Result<Self, Self::Err> {
38 Self::new(value)
39 }
40 }
41 };
42}
43
44identifier!(ExecutionId, "execution_id");
45identifier!(ConversationId, "conversation_id");
46identifier!(RuntimeInstanceId, "runtime_instance_id");
47identifier!(WorkspaceId, "workspace_id");
48identifier!(ToolCallId, "tool_call_id");
49identifier!(OperationId, "operation_id");
50identifier!(EventId, "event_id");
51identifier!(DefinitionId, "definition_id");
52
53#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
56#[serde(transparent)]
57pub struct DelegationLeaseRef(String);
58
59impl DelegationLeaseRef {
60 pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
61 let value = value.into();
62 if !value.starts_with("edl_") {
63 return Err(IdentifierError {
64 label: "delegation_lease_ref",
65 reason: "must be an opaque edl_ reference",
66 });
67 }
68 validate("delegation_lease_ref", &value)?;
69 Ok(Self(value))
70 }
71
72 pub fn as_str(&self) -> &str {
73 &self.0
74 }
75}
76
77impl fmt::Debug for DelegationLeaseRef {
78 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79 formatter.write_str("DelegationLeaseRef([REDACTED OPAQUE REFERENCE])")
80 }
81}
82
83impl fmt::Display for DelegationLeaseRef {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 formatter.write_str("[REDACTED OPAQUE REFERENCE]")
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase", deny_unknown_fields)]
91pub struct CallerScope {
92 pub subject: String,
93 pub tenant_id: String,
94 pub project_id: String,
95 #[serde(default)]
96 pub capabilities: Vec<String>,
97}
98
99impl CallerScope {
100 pub fn validate(&self) -> Result<(), IdentifierError> {
101 validate("subject", &self.subject)?;
102 validate("tenant_id", &self.tenant_id)?;
103 validate("project_id", &self.project_id)?;
104 Ok(())
105 }
106}
107
108#[derive(Clone)]
111pub struct CredentialHandle(Arc<str>);
112
113impl CredentialHandle {
114 pub fn new(value: impl Into<Arc<str>>) -> Result<Self, IdentifierError> {
115 let value = value.into();
116 if value.is_empty() || value.len() > 16 * 1024 || value.chars().any(char::is_control) {
117 return Err(IdentifierError {
118 label: "credential",
119 reason: "must contain 1..=16384 non-control bytes",
120 });
121 }
122 Ok(Self(value))
123 }
124
125 pub fn expose(&self) -> &str {
126 &self.0
127 }
128}
129
130impl fmt::Debug for CredentialHandle {
131 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
132 formatter.write_str("CredentialHandle([REDACTED])")
133 }
134}
135
136#[derive(Clone, Debug)]
137pub struct RequestAuthority {
138 pub caller: CallerScope,
139 pub credential: CredentialHandle,
140}
141
142impl ExecutionId {
143 pub fn random() -> Self {
144 Self(uuid::Uuid::new_v4().to_string())
145 }
146}
147
148impl OperationId {
149 pub fn random() -> Self {
150 Self(uuid::Uuid::new_v4().to_string())
151 }
152}
153
154impl EventId {
155 pub fn random() -> Self {
156 Self(uuid::Uuid::new_v4().to_string())
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct IdentifierError {
162 label: &'static str,
163 reason: &'static str,
164}
165
166impl fmt::Display for IdentifierError {
167 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
168 write!(formatter, "{} {}", self.label, self.reason)
169 }
170}
171
172impl std::error::Error for IdentifierError {}
173
174fn validate(label: &'static str, value: &str) -> Result<(), IdentifierError> {
175 if value.is_empty() {
176 return Err(IdentifierError {
177 label,
178 reason: "must not be empty",
179 });
180 }
181 if value.len() > 256 {
182 return Err(IdentifierError {
183 label,
184 reason: "must not exceed 256 bytes",
185 });
186 }
187 if value.chars().any(char::is_control) {
188 return Err(IdentifierError {
189 label,
190 reason: "must not contain control characters",
191 });
192 }
193 Ok(())
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn identifiers_reject_empty_and_control_characters() {
202 assert!(ExecutionId::new("").is_err());
203 assert!(ExecutionId::new("bad\nvalue").is_err());
204 assert_eq!(
205 ExecutionId::new("execution-1").unwrap().as_str(),
206 "execution-1"
207 );
208 }
209}