use std::sync::{Arc, Mutex, PoisonError, Weak};
use arc_swap::ArcSwap;
use crate::application::registry::store::{Published, RegistryStore};
use crate::application::registry::{Fanout, Subscriber, SubscriberSet};
use crate::application::server::builder::{PromptHandler, ResourceHandler, ToolHandler};
use crate::application::server::watch::Watchers;
use crate::application::session::DynFrameSink;
use crate::domain::registry::{
Primitive, PrimitiveId, PrimitiveKind, Prompt, Registered, Resource, ResourceTemplate, Tool,
};
use crate::domain::{Feature, FeatureProfile};
#[derive(Clone)]
pub(crate) struct ToolEntry {
pub(crate) name: PrimitiveId,
pub(crate) handler: ToolHandler,
}
impl core::fmt::Debug for ToolEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ToolEntry")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
#[derive(Clone, Default)]
pub(crate) struct Handlers {
pub(crate) tools: Vec<ToolEntry>,
pub(crate) resources: Vec<(PrimitiveId, ResourceHandler)>,
pub(crate) prompts: Vec<(PrimitiveId, PromptHandler)>,
}
pub(crate) struct Live {
registry: RegistryStore,
handlers: ArcSwap<Handlers>,
subscribers: SubscriberSet,
watchers: Watchers,
writer: Mutex<()>,
}
impl Live {
pub(crate) fn new(registry: RegistryStore, handlers: Handlers) -> Self {
Self {
registry,
handlers: ArcSwap::from_pointee(handlers),
subscribers: SubscriberSet::new(),
watchers: Watchers::default(),
writer: Mutex::new(()),
}
}
pub(crate) const fn registry(&self) -> &RegistryStore {
&self.registry
}
pub(crate) fn tool_count(&self) -> usize {
self.handlers.load().tools.len()
}
pub(crate) fn tool(&self, name: &str) -> Option<ToolHandler> {
self.handlers
.load()
.tools
.iter()
.find(|entry| entry.name.as_str() == name)
.map(|entry| Arc::clone(&entry.handler))
}
pub(crate) fn reader(&self, uri: &str) -> Option<ResourceHandler> {
self.handlers
.load()
.resources
.iter()
.find(|(registered, _)| registered.as_str() == uri)
.map(|(_, handler)| Arc::clone(handler))
}
pub(crate) fn getter(&self, name: &str) -> Option<PromptHandler> {
self.handlers
.load()
.prompts
.iter()
.find(|(registered, _)| registered.as_str() == name)
.map(|(_, handler)| Arc::clone(handler))
}
fn swap_handlers(&self, edit: impl FnOnce(&mut Handlers)) {
let mut next = Handlers::clone(&self.handlers.load());
edit(&mut next);
self.handlers.store(Arc::new(next));
}
pub(crate) fn add(
&self,
registered: Registered,
edit: impl FnOnce(&mut Handlers),
) -> Published {
let _writer = self.writer.lock().unwrap_or_else(PoisonError::into_inner);
self.swap_handlers(edit);
self.registry.register(registered)
}
pub(crate) fn remove(
&self,
kind: PrimitiveKind,
id: &PrimitiveId,
edit: impl FnOnce(&mut Handlers),
) -> Published {
let _writer = self.writer.lock().unwrap_or_else(PoisonError::into_inner);
let published = self.registry.unregister(kind, id);
self.swap_handlers(edit);
published
}
async fn announce(&self, published: &Published) -> Fanout {
self.subscribers
.announce(published.snapshot(), published.changed())
.await
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ListKind {
Tools,
Resources,
Prompts,
}
impl ListKind {
pub const ALL: [Self; 3] = [Self::Tools, Self::Resources, Self::Prompts];
#[must_use]
pub const fn method(self) -> &'static str {
match self {
Self::Tools => "notifications/tools/list_changed",
Self::Resources => "notifications/resources/list_changed",
Self::Prompts => "notifications/prompts/list_changed",
}
}
#[must_use]
pub const fn feature(self) -> Feature {
match self {
Self::Tools => Feature::ToolsListChanged,
Self::Resources => Feature::ResourcesListChanged,
Self::Prompts => Feature::PromptsListChanged,
}
}
fn subscriber(
self,
frame: &str,
profile: FeatureProfile,
sink: Arc<dyn DynFrameSink>,
) -> Subscriber {
let primary = match self {
Self::Tools => PrimitiveKind::Tool,
Self::Resources => PrimitiveKind::Resource,
Self::Prompts => PrimitiveKind::Prompt,
};
let subscriber =
Subscriber::new(primary, self.feature(), self.method(), frame, profile, sink);
if self == Self::Resources {
subscriber.also_for(PrimitiveKind::ResourceTemplate)
} else {
subscriber
}
}
fn frame(self, meta: Option<&serde_json::Value>) -> String {
let mut frame = serde_json::json!({
"jsonrpc": "2.0",
"method": self.method(),
});
if let Some(meta) = meta
&& let Some(object) = frame.as_object_mut()
{
object.insert("params".to_owned(), serde_json::json!({ "_meta": meta }));
}
frame.to_string()
}
}
pub struct ListSubscription {
live: Weak<Live>,
sink: Arc<dyn DynFrameSink>,
}
impl core::fmt::Debug for ListSubscription {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ListSubscription").finish_non_exhaustive()
}
}
impl Drop for ListSubscription {
fn drop(&mut self) {
if let Some(live) = self.live.upgrade() {
live.subscribers.remove_by_sink(&self.sink);
live.watchers.forget(&self.sink);
}
}
}
#[derive(Clone)]
pub struct ServerHandle {
live: Arc<Live>,
}
impl core::fmt::Debug for ServerHandle {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ServerHandle")
.field("tool_count", &self.live.tool_count())
.finish_non_exhaustive()
}
}
impl ServerHandle {
pub(crate) const fn new(live: Arc<Live>) -> Self {
Self { live }
}
pub async fn add_tool(&self, tool: Tool, handler: ToolHandler) -> Fanout {
self.add_restricted_tool(Registered::from(Primitive::from(tool)), handler)
.await
}
pub async fn add_restricted_tool(
&self,
registered: Registered,
handler: ToolHandler,
) -> Fanout {
let name = match registered.primitive() {
Primitive::Tool(tool) => Some(tool.name.clone()),
_ => None,
};
let published = self.live.add(registered, |table| {
if let Some(name) = name {
table.tools.retain(|entry| entry.name != name);
table.tools.push(ToolEntry { name, handler });
}
});
self.live.announce(&published).await
}
pub async fn remove_tool(&self, name: &str) -> Fanout {
let id = PrimitiveId::from(name);
let published = self.live.remove(PrimitiveKind::Tool, &id, |table| {
table.tools.retain(|entry| entry.name != id);
});
self.live.announce(&published).await
}
pub async fn add_resource(&self, resource: Resource, reader: ResourceHandler) -> Fanout {
let uri = resource.uri.clone();
let published = self
.live
.add(Registered::from(Primitive::from(resource)), |table| {
table.resources.retain(|(registered, _)| *registered != uri);
table.resources.push((uri, reader));
});
self.live.announce(&published).await
}
pub async fn remove_resource(&self, uri: &str) -> Fanout {
let id = PrimitiveId::from(uri);
let published = self.live.remove(PrimitiveKind::Resource, &id, |table| {
table.resources.retain(|(registered, _)| *registered != id);
});
self.live.announce(&published).await
}
pub async fn add_resource_template(&self, template: ResourceTemplate) -> Fanout {
let published = self
.live
.add(Registered::from(Primitive::from(template)), |_| {});
self.live.announce(&published).await
}
pub async fn remove_resource_template(&self, uri_template: &str) -> Fanout {
let id = PrimitiveId::from(uri_template);
let published = self
.live
.remove(PrimitiveKind::ResourceTemplate, &id, |_| {});
self.live.announce(&published).await
}
pub async fn add_prompt(&self, prompt: Prompt, getter: PromptHandler) -> Fanout {
let name = prompt.name.clone();
let published = self
.live
.add(Registered::from(Primitive::from(prompt)), |table| {
table.prompts.retain(|(registered, _)| *registered != name);
table.prompts.push((name, getter));
});
self.live.announce(&published).await
}
pub async fn remove_prompt(&self, name: &str) -> Fanout {
let id = PrimitiveId::from(name);
let published = self.live.remove(PrimitiveKind::Prompt, &id, |table| {
table.prompts.retain(|(registered, _)| *registered != id);
});
self.live.announce(&published).await
}
#[must_use]
pub fn subscribe(
&self,
sink: &Arc<dyn DynFrameSink>,
profile: FeatureProfile,
kinds: &[ListKind],
meta: Option<&serde_json::Value>,
) -> ListSubscription {
for kind in kinds {
self.live.subscribers.add(kind.subscriber(
&kind.frame(meta),
profile,
Arc::clone(sink),
));
}
ListSubscription {
live: Arc::downgrade(&self.live),
sink: Arc::clone(sink),
}
}
pub async fn notify_resource_updated(&self, uri: &str) -> Fanout {
self.live.watchers.notify(uri).await
}
pub fn watch_resource(
&self,
sink: &Arc<dyn DynFrameSink>,
uri: &str,
meta: Option<&serde_json::Value>,
) {
self.live.watchers.watch(sink, uri, meta);
}
pub fn unwatch_resource(&self, sink: &Arc<dyn DynFrameSink>, uri: &str) {
self.live.watchers.unwatch(sink, uri);
}
#[must_use]
pub fn watcher_count(&self) -> usize {
self.live.watchers.len()
}
#[must_use]
pub fn is_listed(&self, kind: PrimitiveKind, id: &str) -> bool {
self.live
.registry
.snapshot()
.contains(kind, &PrimitiveId::from(id))
}
#[must_use]
pub fn subscriber_count(&self) -> usize {
self.live.subscribers.len()
}
}
#[cfg(test)]
mod tests;