1use std::any::{Any, TypeId, type_name};
4use std::collections::HashMap;
5use std::hash::{BuildHasherDefault, Hasher};
6
7use crate::tool::ToolExecutionError;
8use rig_core::wasm_compat::{WasmCompatSend, WasmCompatSync};
9
10type AnyMap = HashMap<TypeId, Box<dyn AnyClone>, BuildHasherDefault<IdHasher>>;
11
12#[derive(Default)]
13struct IdHasher(u64);
14
15impl Hasher for IdHasher {
16 fn write_u64(&mut self, id: u64) {
17 self.0 = id;
18 }
19
20 fn write(&mut self, bytes: &[u8]) {
21 for &byte in bytes {
22 self.0 = self.0.rotate_left(8) ^ u64::from(byte);
23 }
24 }
25
26 fn finish(&self) -> u64 {
27 self.0
28 }
29}
30
31trait AnyClone: Any + WasmCompatSend + WasmCompatSync {
32 fn clone_box(&self) -> Box<dyn AnyClone>;
33 fn as_any(&self) -> &dyn Any;
34 fn as_any_mut(&mut self) -> &mut dyn Any;
35 fn into_any(self: Box<Self>) -> Box<dyn Any>;
36 fn type_name(&self) -> &'static str;
37}
38
39impl<T> AnyClone for T
40where
41 T: Clone + WasmCompatSend + WasmCompatSync + 'static,
42{
43 fn clone_box(&self) -> Box<dyn AnyClone> {
44 Box::new(self.clone())
45 }
46
47 fn as_any(&self) -> &dyn Any {
48 self
49 }
50
51 fn as_any_mut(&mut self) -> &mut dyn Any {
52 self
53 }
54
55 fn into_any(self: Box<Self>) -> Box<dyn Any> {
56 self
57 }
58
59 fn type_name(&self) -> &'static str {
60 type_name::<T>()
61 }
62}
63
64impl Clone for Box<dyn AnyClone> {
65 fn clone(&self) -> Self {
66 (**self).clone_box()
67 }
68}
69
70#[derive(Default, Clone)]
72pub(crate) struct TypeMap {
73 map: Option<Box<AnyMap>>,
74}
75
76impl TypeMap {
77 pub(crate) const EMPTY: Self = Self { map: None };
78
79 pub(crate) fn insert<T>(&mut self, value: T) -> Option<T>
80 where
81 T: Clone + WasmCompatSend + WasmCompatSync + 'static,
82 {
83 self.map
84 .get_or_insert_with(Default::default)
85 .insert(TypeId::of::<T>(), Box::new(value))
86 .and_then(|previous| previous.into_any().downcast::<T>().ok())
87 .map(|value| *value)
88 }
89
90 pub(crate) fn get<T>(&self) -> Option<&T>
91 where
92 T: WasmCompatSend + WasmCompatSync + 'static,
93 {
94 self.map
95 .as_ref()
96 .and_then(|map| map.get(&TypeId::of::<T>()))
97 .and_then(|value| (**value).as_any().downcast_ref::<T>())
98 }
99
100 pub(crate) fn get_mut<T>(&mut self) -> Option<&mut T>
101 where
102 T: WasmCompatSend + WasmCompatSync + 'static,
103 {
104 self.map
105 .as_mut()
106 .and_then(|map| map.get_mut(&TypeId::of::<T>()))
107 .and_then(|value| (**value).as_any_mut().downcast_mut::<T>())
108 }
109
110 pub(crate) fn remove<T>(&mut self) -> Option<T>
111 where
112 T: WasmCompatSend + WasmCompatSync + 'static,
113 {
114 self.map
115 .as_mut()
116 .and_then(|map| map.remove(&TypeId::of::<T>()))
117 .and_then(|value| value.into_any().downcast::<T>().ok())
118 .map(|value| *value)
119 }
120
121 pub(crate) fn contains<T>(&self) -> bool
122 where
123 T: WasmCompatSend + WasmCompatSync + 'static,
124 {
125 self.map
126 .as_ref()
127 .is_some_and(|map| map.contains_key(&TypeId::of::<T>()))
128 }
129
130 pub(crate) fn len(&self) -> usize {
131 self.map.as_ref().map_or(0, |map| map.len())
132 }
133
134 fn type_names(&self) -> Vec<&'static str> {
135 self.map
136 .as_ref()
137 .map(|map| map.values().map(|value| (**value).type_name()).collect())
138 .unwrap_or_default()
139 }
140}
141
142#[derive(Default, Clone)]
157pub struct ToolContext {
158 inbound: TypeMap,
159 result: TypeMap,
160}
161
162impl ToolContext {
163 pub const fn new() -> Self {
165 Self {
166 inbound: TypeMap::EMPTY,
167 result: TypeMap::EMPTY,
168 }
169 }
170
171 pub fn insert<T>(&mut self, value: T) -> Option<T>
173 where
174 T: Clone + WasmCompatSend + WasmCompatSync + 'static,
175 {
176 self.inbound.insert(value)
177 }
178
179 pub fn get<T>(&self) -> Option<&T>
181 where
182 T: WasmCompatSend + WasmCompatSync + 'static,
183 {
184 self.inbound.get::<T>()
185 }
186
187 pub fn require<T>(&self) -> Result<&T, MissingToolContext>
189 where
190 T: WasmCompatSend + WasmCompatSync + 'static,
191 {
192 self.get::<T>().ok_or(MissingToolContext(type_name::<T>()))
193 }
194
195 pub fn get_mut<T>(&mut self) -> Option<&mut T>
197 where
198 T: WasmCompatSend + WasmCompatSync + 'static,
199 {
200 self.inbound.get_mut::<T>()
201 }
202
203 pub fn remove<T>(&mut self) -> Option<T>
205 where
206 T: WasmCompatSend + WasmCompatSync + 'static,
207 {
208 self.inbound.remove::<T>()
209 }
210
211 pub fn insert_result<T>(&mut self, value: T) -> Option<T>
213 where
214 T: Clone + WasmCompatSend + WasmCompatSync + 'static,
215 {
216 self.result.insert(value)
217 }
218
219 pub fn result<T>(&self) -> Option<&T>
221 where
222 T: WasmCompatSend + WasmCompatSync + 'static,
223 {
224 self.result.get::<T>()
225 }
226
227 pub fn require_result<T>(&self) -> Result<&T, MissingToolContext>
229 where
230 T: WasmCompatSend + WasmCompatSync + 'static,
231 {
232 self.result::<T>()
233 .ok_or(MissingToolContext(type_name::<T>()))
234 }
235
236 pub fn contains<T>(&self) -> bool
238 where
239 T: WasmCompatSend + WasmCompatSync + 'static,
240 {
241 self.inbound.contains::<T>()
242 }
243
244 pub(crate) fn for_dispatch(&self) -> Self {
251 Self {
252 inbound: self.inbound.clone(),
253 result: TypeMap::EMPTY,
254 }
255 }
256
257 pub(crate) fn accept_dispatch_result(&mut self, dispatched: Self) {
260 self.result = dispatched.result;
261 }
262
263 pub(crate) fn clear_dispatch_result(&mut self) {
265 self.result = TypeMap::EMPTY;
266 }
267
268 pub(crate) fn inbound_only(&self) -> Self {
270 self.for_dispatch()
271 }
272}
273
274impl std::fmt::Debug for ToolContext {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 f.debug_struct("ToolContext")
277 .field("inbound_entries", &self.inbound.len())
278 .field("inbound_types", &self.inbound.type_names())
279 .field("result_entries", &self.result.len())
280 .field("result_types", &self.result.type_names())
281 .finish()
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
287#[error("required tool context value of type `{0}` was not found")]
288pub struct MissingToolContext(pub &'static str);
289
290impl From<MissingToolContext> for ToolExecutionError {
291 fn from(error: MissingToolContext) -> Self {
292 ToolExecutionError::other(error.to_string()).with_source(error)
293 }
294}
295
296#[cfg(not(target_family = "wasm"))]
297const _: fn() = || {
298 fn assert_send_sync<T: Send + Sync>() {}
299 assert_send_sync::<ToolContext>();
300};
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn context_separates_inbound_and_result_values() {
308 let mut context = ToolContext::new();
309 context.insert(42_u32);
310 context.insert_result("request-1".to_string());
311 assert_eq!(context.get::<u32>(), Some(&42));
312 assert_eq!(
313 context.result::<String>().map(String::as_str),
314 Some("request-1")
315 );
316
317 let next = context.for_dispatch();
318 assert_eq!(next.get::<u32>(), Some(&42));
319 assert!(next.result::<String>().is_none());
320 }
321
322 #[test]
323 fn missing_context_converts_into_a_tool_execution_error() {
324 fn require_value(context: &ToolContext) -> Result<u32, ToolExecutionError> {
325 Ok(*context.require::<u32>()?)
326 }
327
328 let error = require_value(&ToolContext::new()).unwrap_err();
329 assert!(error.is::<MissingToolContext>());
330 assert_eq!(
331 error.model_feedback(),
332 Some("required tool context value of type `u32` was not found")
333 );
334 }
335}
336
337#[cfg(test)]
338mod migrated_tests {
339 use super::*;
340
341 #[test]
342 fn insert_and_get_returns_value() {
343 let mut c = ToolContext::new();
344 assert_eq!(c.insert(42u32), None);
345 assert_eq!(c.get::<u32>(), Some(&42));
346 }
347 #[test]
348 fn get_missing_type_returns_none() {
349 assert_eq!(ToolContext::new().get::<u32>(), None);
350 }
351 #[test]
352 fn insert_overwrites_and_returns_previous() {
353 let mut c = ToolContext::new();
354 c.insert(1u32);
355 assert_eq!(c.insert(2u32), Some(1));
356 assert_eq!(c.get::<u32>(), Some(&2));
357 }
358 #[test]
359 fn different_types_are_independent() {
360 let mut c = ToolContext::new();
361 c.insert(42u32);
362 c.insert("hello".to_string());
363 assert_eq!(c.get::<u32>(), Some(&42));
364 assert_eq!(c.get::<String>().map(String::as_str), Some("hello"));
365 }
366 #[test]
367 fn contains_tracks_types() {
368 let mut c = ToolContext::new();
369 c.insert(42u32);
370 assert!(c.contains::<u32>());
371 assert!(!c.contains::<String>());
372 }
373 #[test]
374 fn clone_produces_independent_copy() {
375 let mut c = ToolContext::new();
376 c.insert(42u32);
377 let mut clone = c.clone();
378 clone.insert(99u32);
379 assert_eq!(c.get::<u32>(), Some(&42));
380 assert_eq!(clone.get::<u32>(), Some(&99));
381 }
382 #[test]
383 fn clone_deep_copies_heap_values() {
384 let mut c = ToolContext::new();
385 c.insert(vec![1u8, 2, 3]);
386 let mut clone = c.clone();
387 clone.get_mut::<Vec<u8>>().unwrap().push(4);
388 assert_eq!(c.get::<Vec<u8>>(), Some(&vec![1, 2, 3]));
389 assert_eq!(clone.get::<Vec<u8>>(), Some(&vec![1, 2, 3, 4]));
390 }
391 #[test]
392 fn clone_preserves_intentionally_shared_value_state() {
393 let shared = std::sync::Arc::new(std::sync::Mutex::new(1_u32));
394 let mut context = ToolContext::new();
395 context.insert(shared.clone());
396
397 let snapshot = context.for_dispatch();
398 *snapshot
399 .get::<std::sync::Arc<std::sync::Mutex<u32>>>()
400 .expect("shared value")
401 .lock()
402 .expect("shared value lock") = 2;
403
404 assert_eq!(*shared.lock().expect("shared value lock"), 2);
405 assert!(context.contains::<std::sync::Arc<std::sync::Mutex<u32>>>());
406 }
407 #[test]
408 fn empty_context_is_default_and_allocation_free() {
409 let c = ToolContext::default();
410 assert!(!c.contains::<u32>());
411 assert!(c.inbound.map.is_none());
412 assert!(c.result.map.is_none());
413 }
414 #[test]
415 fn get_mut_modifies_in_place() {
416 let mut c = ToolContext::new();
417 c.insert(42u32);
418 *c.get_mut::<u32>().unwrap() = 99;
419 assert_eq!(c.get::<u32>(), Some(&99));
420 }
421 #[test]
422 fn remove_returns_value_and_clears_entry() {
423 let mut c = ToolContext::new();
424 c.insert(42u32);
425 assert_eq!(c.remove::<u32>(), Some(42));
426 assert!(!c.contains::<u32>());
427 }
428 #[test]
429 fn remove_missing_type_returns_none() {
430 assert_eq!(ToolContext::new().remove::<u32>(), None);
431 }
432 #[test]
433 fn require_present_returns_value() {
434 let mut c = ToolContext::new();
435 c.insert(42u32);
436 assert_eq!(c.require::<u32>().copied(), Ok(42));
437 }
438 #[test]
439 fn require_missing_names_type() {
440 let e = ToolContext::new().require::<u32>().unwrap_err();
441 assert!(e.to_string().contains("u32"));
442 }
443 #[test]
444 fn result_metadata_round_trips_and_requires() {
445 #[derive(Clone, Debug, PartialEq)]
446 struct Id(u32);
447 let mut c = ToolContext::new();
448 c.insert_result(Id(7));
449 assert_eq!(c.result::<Id>(), Some(&Id(7)));
450 assert_eq!(c.require_result::<Id>(), Ok(&Id(7)));
451 assert!(c.get::<Id>().is_none());
452 }
453 #[test]
454 fn debug_reports_types_without_values() {
455 #[derive(Clone)]
456 struct Secret(&'static str);
457 let mut c = ToolContext::new();
458 c.insert(42u32);
459 c.insert_result(Secret("do-not-print"));
460 let d = format!("{c:?}");
461 assert!(d.contains("u32"));
462 assert!(d.contains("Secret"));
463 assert!(!d.contains("do-not-print"));
464 assert_eq!(c.result::<Secret>().map(|s| s.0), Some("do-not-print"));
465 }
466 #[test]
467 fn dispatch_snapshot_isolates_inbound_and_publishes_only_result_metadata() {
468 let mut c = ToolContext::new();
469 c.insert(7u32);
470 c.insert_result("old".to_string());
471 let mut d = c.for_dispatch();
472 assert_eq!(d.get::<u32>(), Some(&7));
473 assert!(d.result::<String>().is_none());
474 *d.get_mut::<u32>().expect("snapshot value") = 8;
475 d.insert_result("new".to_string());
476
477 c.accept_dispatch_result(d);
478 assert_eq!(c.get::<u32>(), Some(&7));
479 assert_eq!(c.result::<String>().map(String::as_str), Some("new"));
480 }
481 #[test]
482 fn many_distinct_types_round_trip_through_type_id_hasher() {
483 #[derive(Clone, PartialEq, Debug)]
484 struct A(u8);
485 #[derive(Clone, PartialEq, Debug)]
486 struct B(u16);
487 let mut c = ToolContext::new();
488 c.insert(A(1));
489 c.insert(B(2));
490 c.insert(3u32);
491 c.insert("four".to_string());
492 assert_eq!(c.get::<A>(), Some(&A(1)));
493 assert_eq!(c.get::<B>(), Some(&B(2)));
494 assert_eq!(c.get::<u32>(), Some(&3));
495 assert_eq!(c.get::<String>().map(String::as_str), Some("four"));
496 }
497}