tirea_contract/runtime/run/
options.rs1use crate::storage::RunOrigin;
2
3#[derive(Debug, Clone, Default, PartialEq, Eq)]
5pub struct RunPolicy {
6 allowed_tools: Option<Vec<String>>,
7 excluded_tools: Option<Vec<String>>,
8 allowed_skills: Option<Vec<String>>,
9 excluded_skills: Option<Vec<String>>,
10 allowed_agents: Option<Vec<String>>,
11 excluded_agents: Option<Vec<String>>,
12}
13
14impl RunPolicy {
15 #[must_use]
16 pub fn new() -> Self {
17 Self::default()
18 }
19
20 pub fn allowed_tools(&self) -> Option<&[String]> {
21 self.allowed_tools.as_deref()
22 }
23
24 pub fn excluded_tools(&self) -> Option<&[String]> {
25 self.excluded_tools.as_deref()
26 }
27
28 pub fn allowed_skills(&self) -> Option<&[String]> {
29 self.allowed_skills.as_deref()
30 }
31
32 pub fn excluded_skills(&self) -> Option<&[String]> {
33 self.excluded_skills.as_deref()
34 }
35
36 pub fn allowed_agents(&self) -> Option<&[String]> {
37 self.allowed_agents.as_deref()
38 }
39
40 pub fn excluded_agents(&self) -> Option<&[String]> {
41 self.excluded_agents.as_deref()
42 }
43
44 pub fn set_allowed_tools_if_absent(&mut self, values: Option<&[String]>) {
45 if self.allowed_tools.is_none() {
46 self.allowed_tools = normalize_scope_values(values);
47 }
48 }
49
50 pub fn set_excluded_tools_if_absent(&mut self, values: Option<&[String]>) {
51 if self.excluded_tools.is_none() {
52 self.excluded_tools = normalize_scope_values(values);
53 }
54 }
55
56 pub fn set_allowed_skills_if_absent(&mut self, values: Option<&[String]>) {
57 if self.allowed_skills.is_none() {
58 self.allowed_skills = normalize_scope_values(values);
59 }
60 }
61
62 pub fn set_excluded_skills_if_absent(&mut self, values: Option<&[String]>) {
63 if self.excluded_skills.is_none() {
64 self.excluded_skills = normalize_scope_values(values);
65 }
66 }
67
68 pub fn set_allowed_agents_if_absent(&mut self, values: Option<&[String]>) {
69 if self.allowed_agents.is_none() {
70 self.allowed_agents = normalize_scope_values(values);
71 }
72 }
73
74 pub fn set_excluded_agents_if_absent(&mut self, values: Option<&[String]>) {
75 if self.excluded_agents.is_none() {
76 self.excluded_agents = normalize_scope_values(values);
77 }
78 }
79}
80
81fn normalize_scope_values(values: Option<&[String]>) -> Option<Vec<String>> {
82 let parsed: Vec<String> = values
83 .into_iter()
84 .flatten()
85 .map(|value| value.trim())
86 .filter(|value| !value.is_empty())
87 .map(ToOwned::to_owned)
88 .collect();
89 if parsed.is_empty() {
90 None
91 } else {
92 Some(parsed)
93 }
94}
95
96#[derive(Debug, Clone, Default, PartialEq, Eq)]
98pub struct RunIdentity {
99 pub thread_id: String,
100 pub parent_thread_id: Option<String>,
101 pub run_id: String,
102 pub parent_run_id: Option<String>,
103 pub agent_id: String,
104 pub origin: RunOrigin,
105 pub parent_tool_call_id: Option<String>,
106}
107
108impl RunIdentity {
109 #[must_use]
110 pub fn for_thread(thread_id: impl Into<String>) -> Self {
111 Self {
112 thread_id: thread_id.into(),
113 ..Self::default()
114 }
115 }
116
117 #[must_use]
118 pub fn new(
119 thread_id: String,
120 parent_thread_id: Option<String>,
121 run_id: String,
122 parent_run_id: Option<String>,
123 agent_id: String,
124 origin: RunOrigin,
125 ) -> Self {
126 Self {
127 thread_id,
128 parent_thread_id,
129 run_id,
130 parent_run_id,
131 agent_id,
132 origin,
133 parent_tool_call_id: None,
134 }
135 }
136
137 #[must_use]
138 pub fn with_parent_tool_call_id(mut self, parent_tool_call_id: impl Into<String>) -> Self {
139 let value = parent_tool_call_id.into();
140 if !value.trim().is_empty() {
141 self.parent_tool_call_id = Some(value);
142 }
143 self
144 }
145
146 pub fn thread_id_opt(&self) -> Option<&str> {
147 let thread_id = self.thread_id.trim();
148 if thread_id.is_empty() {
149 None
150 } else {
151 Some(thread_id)
152 }
153 }
154
155 pub fn parent_thread_id_opt(&self) -> Option<&str> {
156 self.parent_thread_id
157 .as_deref()
158 .map(str::trim)
159 .filter(|value| !value.is_empty())
160 }
161
162 pub fn run_id_opt(&self) -> Option<&str> {
163 let run_id = self.run_id.trim();
164 if run_id.is_empty() {
165 None
166 } else {
167 Some(run_id)
168 }
169 }
170
171 pub fn parent_run_id_opt(&self) -> Option<&str> {
172 self.parent_run_id
173 .as_deref()
174 .map(str::trim)
175 .filter(|value| !value.is_empty())
176 }
177
178 pub fn agent_id_opt(&self) -> Option<&str> {
179 let agent_id = self.agent_id.trim();
180 if agent_id.is_empty() {
181 None
182 } else {
183 Some(agent_id)
184 }
185 }
186
187 pub fn parent_tool_call_id_opt(&self) -> Option<&str> {
188 self.parent_tool_call_id
189 .as_deref()
190 .map(str::trim)
191 .filter(|value| !value.is_empty())
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn run_policy_normalizes_values() {
201 let mut policy = RunPolicy::new();
202 policy.set_allowed_tools_if_absent(Some(&[" a ".to_string(), "".to_string()]));
203 assert_eq!(policy.allowed_tools(), Some(&["a".to_string()][..]));
204 }
205
206 #[test]
207 fn run_identity_ignores_blank_parent_tool_call_id() {
208 let identity = RunIdentity::new(
209 "thread-1".to_string(),
210 None,
211 "run-1".to_string(),
212 None,
213 "agent-1".to_string(),
214 RunOrigin::Internal,
215 )
216 .with_parent_tool_call_id(" ");
217 assert!(identity.parent_tool_call_id_opt().is_none());
218 }
219}