starweaver_context/
context_handle.rs1use std::{
2 collections::BTreeSet,
3 sync::{Arc, Mutex, MutexGuard},
4};
5
6use starweaver_usage::Usage;
7
8use crate::{AgentContext, AgentEvent, Task, TaskManager, ToolSearchInvalidation, ToolSearchState};
9
10pub const CONTEXT_HANDOFF_CAPABILITY: &str = "starweaver.context.handoff";
12pub const CONTEXT_TASKS_CAPABILITY: &str = "starweaver.context.tasks";
14pub const CONTEXT_USAGE_CAPABILITY: &str = "starweaver.context.usage";
16pub const CONTEXT_TOOL_SEARCH_CAPABILITY: &str = "starweaver.context.tool_search";
18
19#[derive(Clone)]
21pub struct AgentContextHandle {
22 inner: Arc<Mutex<AgentContext>>,
23}
24
25impl AgentContextHandle {
26 #[must_use]
28 pub fn new(context: AgentContext) -> Self {
29 Self {
30 inner: Arc::new(Mutex::new(context)),
31 }
32 }
33
34 #[must_use]
36 pub fn snapshot(&self) -> AgentContext {
37 lock(&self.inner).clone()
38 }
39
40 pub fn replace(&self, context: AgentContext) {
42 *lock(&self.inner) = context;
43 }
44
45 pub fn update(&self, update: impl FnOnce(&mut AgentContext)) {
47 update(&mut lock(&self.inner));
48 }
49}
50
51impl Default for AgentContextHandle {
52 fn default() -> Self {
53 Self::new(AgentContext::default())
54 }
55}
56
57impl std::fmt::Debug for AgentContextHandle {
58 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 formatter
60 .debug_struct("AgentContextHandle")
61 .field("snapshot", &self.snapshot())
62 .finish()
63 }
64}
65
66#[derive(Clone, Debug)]
67struct HandoffMutation {
68 handoff_message: Option<String>,
69 auto_load_files: Vec<String>,
70}
71
72#[derive(Clone)]
74pub struct ContextHandoffHandle {
75 inner: Arc<Mutex<HandoffMutation>>,
76}
77
78impl ContextHandoffHandle {
79 #[must_use]
81 pub fn from_context(context: &AgentContext) -> Self {
82 Self {
83 inner: Arc::new(Mutex::new(HandoffMutation {
84 handoff_message: context.handoff_message.clone(),
85 auto_load_files: context.tools.auto_load_files.clone(),
86 })),
87 }
88 }
89
90 pub fn set_handoff(&self, rendered: String, auto_load_files: &[String]) {
92 let mut mutation = lock(&self.inner);
93 mutation.handoff_message = Some(rendered);
94 for file in auto_load_files {
95 if !mutation.auto_load_files.contains(file) {
96 mutation.auto_load_files.push(file.clone());
97 }
98 }
99 }
100
101 pub fn apply_to(&self, context: &mut AgentContext) {
103 let mutation = lock(&self.inner);
104 context
105 .handoff_message
106 .clone_from(&mutation.handoff_message);
107 context
108 .tools
109 .auto_load_files
110 .clone_from(&mutation.auto_load_files);
111 }
112}
113
114#[derive(Clone)]
116pub struct TaskContextHandle {
117 inner: Arc<Mutex<TaskManager>>,
118}
119
120impl TaskContextHandle {
121 #[must_use]
123 pub fn from_context(context: &AgentContext) -> Self {
124 Self {
125 inner: Arc::new(Mutex::new(context.tools.tasks.clone())),
126 }
127 }
128
129 pub fn update<R>(&self, update: impl FnOnce(&mut TaskManager) -> R) -> (R, Vec<Task>) {
131 let mut manager = lock(&self.inner);
132 let result = update(&mut manager);
133 let snapshot = manager.list_all();
134 drop(manager);
135 (result, snapshot)
136 }
137
138 #[must_use]
140 pub fn snapshot(&self) -> Vec<Task> {
141 lock(&self.inner).list_all()
142 }
143
144 pub fn apply_to(&self, context: &mut AgentContext) {
146 context.tools.tasks = lock(&self.inner).clone();
147 let snapshot = context.tools.tasks.list_all();
148 context.set_tasks(snapshot);
149 context.publish_task_snapshot_event();
150 }
151}
152
153#[derive(Clone)]
155pub struct UsageContextHandle {
156 inner: Arc<Mutex<Usage>>,
157}
158
159impl UsageContextHandle {
160 #[must_use]
162 pub fn from_context(context: &AgentContext) -> Self {
163 Self {
164 inner: Arc::new(Mutex::new(context.usage.clone())),
165 }
166 }
167
168 pub fn add_usage(&self, usage: &Usage) {
170 lock(&self.inner).add_assign(usage);
171 }
172
173 fn apply_to(&self, context: &mut AgentContext) {
174 context.usage = lock(&self.inner).clone();
175 }
176}
177
178#[derive(Clone, Debug)]
179struct ToolSearchMutation {
180 state: ToolSearchState,
181 events: Vec<AgentEvent>,
182}
183
184#[derive(Clone)]
186pub struct ToolSearchContextHandle {
187 inner: Arc<Mutex<ToolSearchMutation>>,
188}
189
190impl ToolSearchContextHandle {
191 #[must_use]
193 pub fn from_context(context: &AgentContext) -> Self {
194 Self {
195 inner: Arc::new(Mutex::new(ToolSearchMutation {
196 state: context.tool_search_state(),
197 events: Vec::new(),
198 })),
199 }
200 }
201
202 #[must_use]
204 pub fn state(&self) -> ToolSearchState {
205 lock(&self.inner).state.clone()
206 }
207
208 pub fn record_loaded(
210 &self,
211 tools: impl IntoIterator<Item = impl Into<String>>,
212 namespaces: impl IntoIterator<Item = impl Into<String>>,
213 ) {
214 let mut mutation = lock(&self.inner);
215 for tool in tools {
216 push_unique(&mut mutation.state.loaded_tools, tool.into());
217 }
218 for namespace in namespaces {
219 push_unique(&mut mutation.state.loaded_namespaces, namespace.into());
220 }
221 }
222
223 #[must_use]
225 pub fn clear_loaded(&self) -> ToolSearchInvalidation {
226 let mut mutation = lock(&self.inner);
227 ToolSearchInvalidation {
228 removed_tools: std::mem::take(&mut mutation.state.loaded_tools),
229 removed_namespaces: std::mem::take(&mut mutation.state.loaded_namespaces),
230 }
231 }
232
233 pub fn publish_event(&self, event: AgentEvent) {
235 lock(&self.inner).events.push(event);
236 }
237
238 pub fn apply_to(&self, context: &mut AgentContext) {
240 let mutation = lock(&self.inner);
241 context
242 .tools
243 .loaded_tool_names
244 .clone_from(&mutation.state.loaded_tools);
245 context
246 .tools
247 .loaded_tool_namespaces
248 .clone_from(&mutation.state.loaded_namespaces);
249 for event in &mutation.events {
250 context.publish_event(event.clone());
251 }
252 }
253}
254
255#[derive(Clone)]
257pub struct ContextMutationHandles {
258 handoff: ContextHandoffHandle,
259 tasks: TaskContextHandle,
260 usage: UsageContextHandle,
261 tool_search: ToolSearchContextHandle,
262}
263
264impl ContextMutationHandles {
265 #[must_use]
267 pub fn from_context(context: &AgentContext) -> Self {
268 Self {
269 handoff: ContextHandoffHandle::from_context(context),
270 tasks: TaskContextHandle::from_context(context),
271 usage: UsageContextHandle::from_context(context),
272 tool_search: ToolSearchContextHandle::from_context(context),
273 }
274 }
275
276 pub fn insert_grants(
278 &self,
279 dependencies: &mut crate::DependencyStore,
280 requested: &BTreeSet<String>,
281 ) {
282 for capability in requested {
283 match capability.as_str() {
284 CONTEXT_HANDOFF_CAPABILITY => dependencies.insert(self.handoff.clone()),
285 CONTEXT_TASKS_CAPABILITY => dependencies.insert(self.tasks.clone()),
286 CONTEXT_USAGE_CAPABILITY => dependencies.insert(self.usage.clone()),
287 CONTEXT_TOOL_SEARCH_CAPABILITY => dependencies.insert(self.tool_search.clone()),
288 _ => {}
289 }
290 }
291 }
292
293 pub fn apply_to(&self, context: &mut AgentContext, authorized: &BTreeSet<String>) {
295 for capability in authorized {
296 match capability.as_str() {
297 CONTEXT_HANDOFF_CAPABILITY => self.handoff.apply_to(context),
298 CONTEXT_TASKS_CAPABILITY => self.tasks.apply_to(context),
299 CONTEXT_USAGE_CAPABILITY => self.usage.apply_to(context),
300 CONTEXT_TOOL_SEARCH_CAPABILITY => self.tool_search.apply_to(context),
301 _ => {}
302 }
303 }
304 }
305}
306
307impl std::fmt::Debug for ContextHandoffHandle {
308 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 formatter.write_str("ContextHandoffHandle")
310 }
311}
312
313impl std::fmt::Debug for TaskContextHandle {
314 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315 formatter.write_str("TaskContextHandle")
316 }
317}
318
319impl std::fmt::Debug for UsageContextHandle {
320 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 formatter.write_str("UsageContextHandle")
322 }
323}
324
325impl std::fmt::Debug for ToolSearchContextHandle {
326 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 formatter.write_str("ToolSearchContextHandle")
328 }
329}
330
331impl std::fmt::Debug for ContextMutationHandles {
332 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333 formatter.write_str("ContextMutationHandles")
334 }
335}
336
337fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
338 match mutex.lock() {
339 Ok(guard) => guard,
340 Err(error) => error.into_inner(),
341 }
342}
343
344fn push_unique(values: &mut Vec<String>, value: String) {
345 if !values.contains(&value) {
346 values.push(value);
347 }
348}