feat: revision-2025-03-26 without streamable http (#84)

1. Suppot revision 2025-03-26 data types
2. Support meta, progress tokne, and extensions in request/notification,
3. Remove `Message`, use `JsonRpcMessage` directly
This commit is contained in:
4t145 2025-04-07 09:47:39 +08:00 • committed by GitHub
commit 68375460df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1280 additions and 229 deletions

View file

@ -4,8 +4,8 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::{
RawContent, RawEmbeddedResource, RawImageContent, RawResource, RawResourceTemplate,
RawTextContent, Role,
RawAudioContent, RawContent, RawEmbeddedResource, RawImageContent, RawResource,
RawResourceTemplate, RawTextContent, Role,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
@ -162,6 +162,7 @@ macro_rules! annotate {
annotate!(RawContent);
annotate!(RawTextContent);
annotate!(RawImageContent);
annotate!(RawAudioContent);
annotate!(RawEmbeddedResource);
annotate!(RawResource);
annotate!(RawResourceTemplate);

View file

@ -76,6 +76,8 @@ pub struct ServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub logging: Option<JsonObject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completions: Option<JsonObject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompts: Option<PromptsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<ResourcesCapability>,
@ -192,14 +194,15 @@ builder! {
ServerCapabilities {
experimental: ExperimentalCapabilities,
logging: JsonObject,
completions: JsonObject,
prompts: PromptsCapability,
resources: ResourcesCapability,
tools: ToolsCapability
}
}
impl<const E: bool, const L: bool, const P: bool, const R: bool>
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, P, R, true>>
impl<const E: bool, const L: bool, const C: bool, const P: bool, const R: bool>
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, R, true>>
{
pub fn enable_tool_list_changed(mut self) -> Self {
if let Some(c) = self.tools.as_mut() {
@ -209,8 +212,8 @@ impl<const E: bool, const L: bool, const P: bool, const R: bool>
}
}
impl<const E: bool, const L: bool, const R: bool, const T: bool>
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, true, R, T>>
impl<const E: bool, const L: bool, const C: bool, const R: bool, const T: bool>
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, true, R, T>>
{
pub fn enable_prompts_list_changed(mut self) -> Self {
if let Some(c) = self.prompts.as_mut() {
@ -220,8 +223,8 @@ impl<const E: bool, const L: bool, const R: bool, const T: bool>
}
}
impl<const E: bool, const L: bool, const P: bool, const T: bool>
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, P, true, T>>
impl<const E: bool, const L: bool, const C: bool, const P: bool, const T: bool>
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, true, T>>
{
pub fn enable_resources_list_changed(mut self) -> Self {
if let Some(c) = self.resources.as_mut() {

View file

@ -37,12 +37,22 @@ impl EmbeddedResource {
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RawAudioContent {
pub data: String,
pub mime_type: String,
}
pub type AudioContent = Annotated<RawAudioContent>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum RawContent {
Text(RawTextContent),
Image(RawImageContent),
Resource(RawEmbeddedResource),
Audio(AudioContent),
}
pub type Content = Annotated<RawContent>;

View file

@ -0,0 +1,337 @@
//! A container for those extra data could be carried on request or notification.
//!
//! This file is copied and modified from crate [http](https://github.com/hyperium/http).
//!
//! - Original code license: <https://github.com/hyperium/http/blob/master/LICENSE-MIT>
//! - Original code: <https://github.com/hyperium/http/blob/master/src/extensions.rs>
use std::{
any::{Any, TypeId},
collections::HashMap,
fmt,
hash::{BuildHasherDefault, Hasher},
};
type AnyMap = HashMap<TypeId, Box<dyn AnyClone + Send + Sync>, BuildHasherDefault<IdHasher>>;
// With TypeIds as keys, there's no need to hash them. They are already hashes
// themselves, coming from the compiler. The IdHasher just holds the u64 of
// the TypeId, and then returns it, instead of doing any bit fiddling.
#[derive(Default)]
struct IdHasher(u64);
impl Hasher for IdHasher {
fn write(&mut self, _: &[u8]) {
unreachable!("TypeId calls write_u64");
}
#[inline]
fn write_u64(&mut self, id: u64) {
self.0 = id;
}
#[inline]
fn finish(&self) -> u64 {
self.0
}
}
/// A type map of protocol extensions.
///
/// `Extensions` can be used by `Request` `Notification` and `Response` to store
/// extra data derived from the underlying protocol.
#[derive(Clone, Default)]
pub struct Extensions {
// If extensions are never used, no need to carry around an empty HashMap.
// That's 3 words. Instead, this is only 1 word.
map: Option<Box<AnyMap>>,
}
impl Extensions {
/// Create an empty `Extensions`.
#[inline]
pub fn new() -> Extensions {
Extensions { map: None }
}
/// Insert a type into this `Extensions`.
///
/// If a extension of this type already existed, it will
/// be returned and replaced with the new one.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// assert!(ext.insert(5i32).is_none());
/// assert!(ext.insert(4u8).is_none());
/// assert_eq!(ext.insert(9i32), Some(5i32));
/// ```
pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
self.map
.get_or_insert_with(Box::default)
.insert(TypeId::of::<T>(), Box::new(val))
.and_then(|boxed| boxed.into_any().downcast().ok().map(|boxed| *boxed))
}
/// Get a reference to a type previously inserted on this `Extensions`.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// assert!(ext.get::<i32>().is_none());
/// ext.insert(5i32);
///
/// assert_eq!(ext.get::<i32>(), Some(&5i32));
/// ```
pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.map
.as_ref()
.and_then(|map| map.get(&TypeId::of::<T>()))
.and_then(|boxed| (**boxed).as_any().downcast_ref())
}
/// Get a mutable reference to a type previously inserted on this `Extensions`.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// ext.insert(String::from("Hello"));
/// ext.get_mut::<String>().unwrap().push_str(" World");
///
/// assert_eq!(ext.get::<String>().unwrap(), "Hello World");
/// ```
pub fn get_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
self.map
.as_mut()
.and_then(|map| map.get_mut(&TypeId::of::<T>()))
.and_then(|boxed| (**boxed).as_any_mut().downcast_mut())
}
/// Get a mutable reference to a type, inserting `value` if not already present on this
/// `Extensions`.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// *ext.get_or_insert(1i32) += 2;
///
/// assert_eq!(*ext.get::<i32>().unwrap(), 3);
/// ```
pub fn get_or_insert<T: Clone + Send + Sync + 'static>(&mut self, value: T) -> &mut T {
self.get_or_insert_with(|| value)
}
/// Get a mutable reference to a type, inserting the value created by `f` if not already present
/// on this `Extensions`.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// *ext.get_or_insert_with(|| 1i32) += 2;
///
/// assert_eq!(*ext.get::<i32>().unwrap(), 3);
/// ```
pub fn get_or_insert_with<T: Clone + Send + Sync + 'static, F: FnOnce() -> T>(
&mut self,
f: F,
) -> &mut T {
let out = self
.map
.get_or_insert_with(Box::default)
.entry(TypeId::of::<T>())
.or_insert_with(|| Box::new(f()));
(**out).as_any_mut().downcast_mut().unwrap()
}
/// Get a mutable reference to a type, inserting the type's default value if not already present
/// on this `Extensions`.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// *ext.get_or_insert_default::<i32>() += 2;
///
/// assert_eq!(*ext.get::<i32>().unwrap(), 2);
/// ```
pub fn get_or_insert_default<T: Default + Clone + Send + Sync + 'static>(&mut self) -> &mut T {
self.get_or_insert_with(T::default)
}
/// Remove a type from this `Extensions`.
///
/// If a extension of this type existed, it will be returned.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// ext.insert(5i32);
/// assert_eq!(ext.remove::<i32>(), Some(5i32));
/// assert!(ext.get::<i32>().is_none());
/// ```
pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
self.map
.as_mut()
.and_then(|map| map.remove(&TypeId::of::<T>()))
.and_then(|boxed| boxed.into_any().downcast().ok().map(|boxed| *boxed))
}
/// Clear the `Extensions` of all inserted extensions.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// ext.insert(5i32);
/// ext.clear();
///
/// assert!(ext.get::<i32>().is_none());
/// ```
#[inline]
pub fn clear(&mut self) {
if let Some(ref mut map) = self.map {
map.clear();
}
}
/// Check whether the extension set is empty or not.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// assert!(ext.is_empty());
/// ext.insert(5i32);
/// assert!(!ext.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.map.as_ref().is_none_or(|map| map.is_empty())
}
/// Get the number of extensions available.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext = Extensions::new();
/// assert_eq!(ext.len(), 0);
/// ext.insert(5i32);
/// assert_eq!(ext.len(), 1);
/// ```
#[inline]
pub fn len(&self) -> usize {
self.map.as_ref().map_or(0, |map| map.len())
}
/// Extends `self` with another `Extensions`.
///
/// If an instance of a specific type exists in both, the one in `self` is overwritten with the
/// one from `other`.
///
/// # Example
///
/// ```
/// # use rmcp::model::Extensions;
/// let mut ext_a = Extensions::new();
/// ext_a.insert(8u8);
/// ext_a.insert(16u16);
///
/// let mut ext_b = Extensions::new();
/// ext_b.insert(4u8);
/// ext_b.insert("hello");
///
/// ext_a.extend(ext_b);
/// assert_eq!(ext_a.len(), 3);
/// assert_eq!(ext_a.get::<u8>(), Some(&4u8));
/// assert_eq!(ext_a.get::<u16>(), Some(&16u16));
/// assert_eq!(ext_a.get::<&'static str>().copied(), Some("hello"));
/// ```
pub fn extend(&mut self, other: Self) {
if let Some(other) = other.map {
if let Some(map) = &mut self.map {
map.extend(*other);
} else {
self.map = Some(other);
}
}
}
}
impl fmt::Debug for Extensions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Extensions").finish()
}
}
trait AnyClone: Any {
fn clone_box(&self) -> Box<dyn AnyClone + Send + Sync>;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
impl<T: Clone + Send + Sync + 'static> AnyClone for T {
fn clone_box(&self) -> Box<dyn AnyClone + Send + Sync> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
impl Clone for Box<dyn AnyClone + Send + Sync> {
fn clone(&self) -> Self {
(**self).clone_box()
}
}
#[test]
fn test_extensions() {
#[derive(Clone, Debug, PartialEq)]
struct MyType(i32);
let mut extensions = Extensions::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
assert_eq!(extensions.get(), Some(&5i32));
assert_eq!(extensions.get_mut(), Some(&mut 5i32));
let ext2 = extensions.clone();
assert_eq!(extensions.remove::<i32>(), Some(5i32));
assert!(extensions.get::<i32>().is_none());
// clone still has it
assert_eq!(ext2.get(), Some(&5i32));
assert_eq!(ext2.get(), Some(&MyType(10)));
assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10)));
}

View file

@ -0,0 +1,150 @@
use std::ops::{Deref, DerefMut};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::{
ClientNotification, ClientRequest, Extensions, JsonObject, NumberOrString, ProgressToken,
ServerNotification, ServerRequest,
};
pub trait GetMeta {
fn get_meta_mut(&mut self) -> &mut Meta;
fn get_meta(&self) -> &Meta;
}
macro_rules! variant_extension {
(
$Enum: ident {
$($variant: ident)*
}
) => {
impl $Enum {
pub fn extensions(&self) -> &Extensions {
match self {
$(
$Enum::$variant(v) => &v.extensions,
)*
}
}
pub fn extensions_mut(&mut self) -> &mut Extensions {
match self {
$(
$Enum::$variant(v) => &mut v.extensions,
)*
}
}
}
impl GetMeta for $Enum {
fn get_meta_mut(&mut self) -> &mut Meta {
self.extensions_mut().get_or_insert_default()
}
fn get_meta(&self) -> &Meta {
self.extensions().get::<Meta>().unwrap_or(Meta::static_empty())
}
}
};
}
variant_extension! {
ClientRequest {
PingRequest
InitializeRequest
CompleteRequest
SetLevelRequest
GetPromptRequest
ListPromptsRequest
ListResourcesRequest
ListResourceTemplatesRequest
ReadResourceRequest
SubscribeRequest
UnsubscribeRequest
CallToolRequest
ListToolsRequest
}
}
variant_extension! {
ServerRequest {
PingRequest
CreateMessageRequest
ListRootsRequest
}
}
variant_extension! {
ClientNotification {
CancelledNotification
ProgressNotification
InitializedNotification
RootsListChangedNotification
}
}
variant_extension! {
ServerNotification {
CancelledNotification
ProgressNotification
LoggingMessageNotification
ResourceUpdatedNotification
ResourceListChangedNotification
ToolListChangedNotification
PromptListChangedNotification
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(transparent)]
pub struct Meta(pub JsonObject);
const PROGRESS_TOKEN_FIELD: &str = "progressToken";
impl Meta {
pub fn new() -> Self {
Self(JsonObject::new())
}
pub(crate) fn static_empty() -> &'static Self {
static EMPTY: std::sync::OnceLock<Meta> = std::sync::OnceLock::new();
EMPTY.get_or_init(Default::default)
}
pub fn get_progress_token(&self) -> Option<ProgressToken> {
self.0.get(PROGRESS_TOKEN_FIELD).and_then(|v| match v {
Value::String(s) => Some(ProgressToken(NumberOrString::String(s.to_string().into()))),
Value::Number(n) => n
.as_u64()
.map(|n| ProgressToken(NumberOrString::Number(n as u32))),
_ => None,
})
}
pub fn set_progress_token(&mut self, token: ProgressToken) {
match token.0 {
NumberOrString::String(ref s) => self.0.insert(
PROGRESS_TOKEN_FIELD.to_string(),
Value::String(s.to_string()),
),
NumberOrString::Number(n) => self
.0
.insert(PROGRESS_TOKEN_FIELD.to_string(), Value::Number(n.into())),
};
}
pub fn extend(&mut self, other: Meta) {
for (k, v) in other.0.into_iter() {
self.0.insert(k, v);
}
}
}
impl Deref for Meta {
type Target = JsonObject;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Meta {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

View file

@ -0,0 +1,267 @@
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use super::{
Extensions, Meta, Notification, NotificationNoParam, Request, RequestNoParam,
RequestOptionalParam,
};
#[derive(Serialize, Deserialize)]
struct WithMeta<'a, P> {
#[serde(skip_serializing_if = "Option::is_none")]
_meta: Option<Cow<'a, Meta>>,
#[serde(flatten)]
_rest: P,
}
#[derive(Serialize, Deserialize)]
struct Proxy<'a, M, P> {
method: M,
params: WithMeta<'a, P>,
}
#[derive(Serialize, Deserialize)]
struct ProxyOptionalParam<'a, M, P> {
method: M,
params: Option<WithMeta<'a, P>>,
}
#[derive(Serialize, Deserialize)]
struct ProxyNoParam<M> {
method: M,
}
impl<M, R> Serialize for Request<M, R>
where
M: Serialize,
R: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
Proxy::serialize(
&Proxy {
method: &self.method,
params: WithMeta {
_rest: &self.params,
_meta,
},
},
serializer,
)
}
}
impl<'de, M, R> Deserialize<'de> for Request<M, R>
where
M: Deserialize<'de>,
R: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let body = Proxy::deserialize(deserializer)?;
let _meta = body.params._meta.map(|m| m.into_owned());
let mut extensions = Extensions::new();
if let Some(meta) = _meta {
extensions.insert(meta);
}
Ok(Request {
extensions,
method: body.method,
params: body.params._rest,
})
}
}
impl<M, R> Serialize for RequestOptionalParam<M, R>
where
M: Serialize,
R: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
Proxy::serialize(
&Proxy {
method: &self.method,
params: WithMeta {
_rest: &self.params,
_meta,
},
},
serializer,
)
}
}
impl<'de, M, R> Deserialize<'de> for RequestOptionalParam<M, R>
where
M: Deserialize<'de>,
R: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let body = ProxyOptionalParam::<'_, _, Option<R>>::deserialize(deserializer)?;
let mut params = None;
let mut _meta = None;
if let Some(body_params) = body.params {
params = body_params._rest;
_meta = body_params._meta.map(|m| m.into_owned());
}
let mut extensions = Extensions::new();
if let Some(meta) = _meta {
extensions.insert(meta);
}
Ok(RequestOptionalParam {
extensions,
method: body.method,
params,
})
}
}
impl<M> Serialize for RequestNoParam<M>
where
M: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
ProxyNoParam::serialize(
&ProxyNoParam {
method: &self.method,
},
serializer,
)
}
}
impl<'de, M> Deserialize<'de> for RequestNoParam<M>
where
M: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let body = ProxyNoParam::<_>::deserialize(deserializer)?;
let extensions = Extensions::new();
Ok(RequestNoParam {
extensions,
method: body.method,
})
}
}
impl<M, R> Serialize for Notification<M, R>
where
M: Serialize,
R: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
Proxy::serialize(
&Proxy {
method: &self.method,
params: WithMeta {
_rest: &self.params,
_meta,
},
},
serializer,
)
}
}
impl<'de, M, R> Deserialize<'de> for Notification<M, R>
where
M: Deserialize<'de>,
R: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let body = Proxy::deserialize(deserializer)?;
let _meta = body.params._meta.map(|m| m.into_owned());
let mut extensions = Extensions::new();
if let Some(meta) = _meta {
extensions.insert(meta);
}
Ok(Notification {
extensions,
method: body.method,
params: body.params._rest,
})
}
}
impl<M> Serialize for NotificationNoParam<M>
where
M: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
ProxyNoParam::serialize(
&ProxyNoParam {
method: &self.method,
},
serializer,
)
}
}
impl<'de, M> Deserialize<'de> for NotificationNoParam<M>
where
M: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let body = ProxyNoParam::<_>::deserialize(deserializer)?;
let extensions = Extensions::new();
Ok(NotificationNoParam {
extensions,
method: body.method,
})
}
}
#[cfg(test)]
mod test {
use serde_json::json;
use crate::model::ListToolsRequest;
#[test]
fn test_deserialize_lost_tools_request() {
let _req: ListToolsRequest = serde_json::from_value(json!(
{
"method": "tools/list",
}
))
.unwrap();
}
}

View file

@ -18,6 +18,102 @@ pub struct Tool {
pub description: Option<Cow<'static, str>>,
/// A JSON Schema object defining the expected parameters for the tool
pub input_schema: Arc<JsonObject>,
/// Optional additional tool information.
pub annotations: Option<ToolAnnotations>,
}
/// Additional properties describing a Tool to clients.
///
/// NOTE: all properties in ToolAnnotations are **hints**.
/// They are not guaranteed to provide a faithful description of
/// tool behavior (including descriptive properties like `title`).
///
/// Clients should never make tool use decisions based on ToolAnnotations
/// received from untrusted servers.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ToolAnnotations {
/// A human-readable title for the tool.
pub title: Option<String>,
/// If true, the tool does not modify its environment.
///
/// Default: false
pub read_only_hint: Option<bool>,
/// If true, the tool may perform destructive updates to its environment.
/// If false, the tool performs only additive updates.
///
/// (This property is meaningful only when `readOnlyHint == false`)
///
/// Default: true
/// A human-readable description of the tool's purpose.
pub destructive_hint: Option<bool>,
/// If true, calling the tool repeatedly with the same arguments
/// will have no additional effect on the its environment.
///
/// (This property is meaningful only when `readOnlyHint == false`)
///
/// Default: false.
pub idempotent_hint: Option<bool>,
/// If true, this tool may interact with an "open world" of external
/// entities. If false, the tool's domain of interaction is closed.
/// For example, the world of a web search tool is open, whereas that
/// of a memory tool is not.
///
/// Default: true
pub open_world_hint: Option<bool>,
}
impl ToolAnnotations {
pub fn new() -> Self {
Self::default()
}
pub fn with_title<T>(title: T) -> Self
where
T: Into<String>,
{
ToolAnnotations {
title: Some(title.into()),
..Self::default()
}
}
pub fn read_only(self, read_only: bool) -> Self {
ToolAnnotations {
read_only_hint: Some(read_only),
..self
}
}
pub fn destructive(self, destructive: bool) -> Self {
ToolAnnotations {
destructive_hint: Some(destructive),
..self
}
}
pub fn idempotent(self, idempotent: bool) -> Self {
ToolAnnotations {
idempotent_hint: Some(idempotent),
..self
}
}
pub fn open_world(self, open_world: bool) -> Self {
ToolAnnotations {
open_world_hint: Some(open_world),
..self
}
}
/// If not set, defaults to true.
pub fn is_destructive(&self) -> bool {
self.destructive_hint.unwrap_or(true)
}
/// If not set, defaults to false.
pub fn is_idempotent(&self) -> bool {
self.idempotent_hint.unwrap_or(false)
}
}
impl Tool {
@ -32,6 +128,14 @@ impl Tool {
name: name.into(),
description: Some(description.into()),
input_schema: input_schema.into(),
annotations: None,
}
}
pub fn annotate(self, annotations: ToolAnnotations) -> Self {
Tool {
annotations: Some(annotations),
..self
}
}