use alloc::{borrow::ToOwned, boxed::Box, format, string::String, sync::Arc};
use core::any::{Any, TypeId};
use super::ToolError;
use crate::compat::{HashMap, RwLock, read_lock, write_lock};
#[derive(Clone)]
pub struct SharedState(pub(super) Arc<RwLock<HashMap<String, serde_json::Value>>>);
impl SharedState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn get_state(&self, key: &str, default: serde_json::Value) -> serde_json::Value {
match read_lock(&self.0) {
Ok(guard) => guard.get(key).cloned().unwrap_or(default),
Err(e) => {
tracing::warn!(key, error = %e, "SharedState::get_state: lock poisoned, returning default");
default
}
}
}
pub fn set_state(&self, key: &str, value: serde_json::Value) -> Result<(), ToolError> {
match write_lock(&self.0) {
Ok(mut guard) => {
guard.insert(key.to_owned(), value);
Ok(())
}
Err(e) => {
let msg = format!("SharedState::set_state: lock poisoned for key '{key}': {e}");
tracing::warn!("{msg}");
Err(ToolError::new(msg))
}
}
}
#[must_use]
pub fn remove_state(&self, key: &str) -> bool {
match write_lock(&self.0) {
Ok(mut guard) => guard.remove(key).is_some(),
Err(e) => {
tracing::warn!(key, error = %e, "SharedState::remove_state: lock poisoned");
false
}
}
}
pub fn clear_state(&self) {
match write_lock(&self.0) {
Ok(mut guard) => guard.clear(),
Err(e) => {
tracing::warn!(error = %e, "SharedState::clear_state: lock poisoned");
}
}
}
}
impl Default for SharedState {
fn default() -> Self {
Self(Arc::new(RwLock::new(HashMap::new())))
}
}
impl core::fmt::Debug for SharedState {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SharedState").finish_non_exhaustive()
}
}
pub struct ToolContext {
conversation_id: Option<String>,
pub(super) state: SharedState,
pub(super) extensions: Arc<RwLock<HashMap<TypeId, Box<dyn Any + Send + Sync>>>>,
}
impl Clone for ToolContext {
fn clone(&self) -> Self {
Self {
conversation_id: self.conversation_id.clone(),
state: self.state.clone(),
extensions: Arc::clone(&self.extensions),
}
}
}
impl core::fmt::Debug for ToolContext {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let count = match read_lock(&self.extensions) {
Ok(g) => g.len(),
Err(e) => {
tracing::warn!(error = %e, "ToolContext::fmt: extensions lock poisoned");
0
}
};
f.debug_struct("ToolContext")
.field("conversation_id", &self.conversation_id)
.field("state", &self.state)
.field("extensions_count", &count)
.finish()
}
}
impl Default for ToolContext {
fn default() -> Self {
Self::new()
}
}
impl ToolContext {
#[must_use]
pub fn new() -> Self {
Self {
conversation_id: None,
state: SharedState::new(),
extensions: Arc::new(RwLock::new(HashMap::new())),
}
}
#[must_use]
pub fn with_conversation_id(mut self, conversation_id: impl Into<String>) -> Self {
self.conversation_id = Some(conversation_id.into());
self
}
#[must_use]
pub fn with_caller(&self, conversation_id: impl Into<String>) -> Self {
Self {
conversation_id: Some(conversation_id.into()),
state: self.state.clone(),
extensions: Arc::clone(&self.extensions),
}
}
#[must_use]
pub fn with_shared_state(mut self, state: SharedState) -> Self {
self.state = state;
self
}
#[must_use]
pub fn shared_state(&self) -> SharedState {
self.state.clone()
}
#[must_use]
pub fn conversation_id(&self) -> Option<&str> {
self.conversation_id.as_deref()
}
#[must_use]
pub fn get_state(&self, key: &str, default: serde_json::Value) -> serde_json::Value {
match read_lock(&self.state.0) {
Ok(guard) => guard.get(key).cloned().unwrap_or(default),
Err(e) => {
tracing::warn!(key, error = %e, "ToolContext::get_state: lock poisoned, returning default");
default
}
}
}
pub fn set_state(&self, key: &str, value: serde_json::Value) -> Result<(), ToolError> {
match write_lock(&self.state.0) {
Ok(mut guard) => {
guard.insert(key.to_owned(), value);
Ok(())
}
Err(e) => {
let msg = format!("ToolContext::set_state: lock poisoned for key '{key}': {e}");
tracing::warn!("{msg}");
Err(ToolError::new(msg))
}
}
}
pub fn set_ext<T: Send + Sync + 'static>(&self, value: T) -> Result<(), ToolError> {
match write_lock(&self.extensions) {
Ok(mut exts) => {
exts.insert(TypeId::of::<T>(), Box::new(value));
Ok(())
}
Err(e) => {
let msg = format!(
"ToolContext::set_ext: lock poisoned for type '{}': {e}",
core::any::type_name::<T>()
);
tracing::warn!("{msg}");
Err(ToolError::new(msg))
}
}
}
#[must_use]
pub fn get_ext<T: Clone + Send + Sync + 'static>(&self) -> Option<T> {
match read_lock(&self.extensions) {
Ok(exts) => exts
.get(&TypeId::of::<T>())
.and_then(|v| v.downcast_ref::<T>())
.cloned(),
Err(e) => {
tracing::warn!(error = %e, "ToolContext::get_ext: lock poisoned, returning None");
None
}
}
}
}