Skip to main content

agentkit_capabilities/
lib.rs

1//! Capability abstractions shared by tools, MCP servers, and agentkit hosts.
2//!
3//! This crate defines the [`Invocable`] trait and its supporting types, which
4//! let you expose arbitrary functionality (tools, resources, prompts) through a
5//! uniform interface that the agentkit loop can discover and call during a
6//! session.
7//!
8//! # Overview
9//!
10//! The core abstraction is [`Invocable`]: anything the model can call.  Each
11//! invocable carries an [`InvocableSpec`] (name, description, JSON-schema for
12//! its input) and an async `invoke` method that receives an
13//! [`InvocableRequest`] and returns an [`InvocableResult`].
14//!
15//! Beyond direct invocation the crate also provides:
16//!
17//! * [`ResourceProvider`] -- lists and reads named data blobs (files, database
18//!   rows, API responses) that the model can reference.
19//! * [`PromptProvider`] -- lists and renders parameterised prompt templates.
20//! * [`CapabilityProvider`] -- a bundle that groups invocables, resources, and
21//!   prompts from a single source (e.g. an MCP server).
22//!
23//! All provider traits share a common [`CapabilityContext`] that carries the
24//! current session and turn identifiers, plus an open-ended metadata map.
25//!
26//! # Example
27//!
28//! ```rust
29//! use agentkit_capabilities::{
30//!     CapabilityContext, CapabilityError, CapabilityName, Invocable,
31//!     InvocableOutput, InvocableRequest, InvocableResult, InvocableSpec,
32//! };
33//! use async_trait::async_trait;
34//! use serde_json::json;
35//!
36//! /// A simple capability that echoes its input back to the model.
37//! struct Echo {
38//!     spec: InvocableSpec,
39//! }
40//!
41//! impl Echo {
42//!     fn new() -> Self {
43//!         Self {
44//!             spec: InvocableSpec::new(
45//!                 CapabilityName::new("echo"),
46//!                 "Return the input unchanged",
47//!                 json!({
48//!                     "type": "object",
49//!                     "properties": {
50//!                         "message": { "type": "string" }
51//!                     }
52//!                 }),
53//!             ),
54//!         }
55//!     }
56//! }
57//!
58//! #[async_trait]
59//! impl Invocable for Echo {
60//!     fn spec(&self) -> &InvocableSpec {
61//!         &self.spec
62//!     }
63//!
64//!     async fn invoke(
65//!         &self,
66//!         request: InvocableRequest,
67//!         _ctx: &mut CapabilityContext<'_>,
68//!     ) -> Result<InvocableResult, CapabilityError> {
69//!         Ok(InvocableResult::structured(request.input.clone()))
70//!     }
71//! }
72//!
73//! let echo = Echo::new();
74//! assert_eq!(echo.spec().name.as_str(), "echo");
75//! ```
76
77use std::fmt;
78use std::sync::Arc;
79
80use agentkit_core::{DataRef, Item, MetadataMap, SessionId, TurnId};
81use async_trait::async_trait;
82use serde::{Deserialize, Serialize};
83use serde_json::Value;
84use thiserror::Error;
85
86macro_rules! capability_id {
87    ($name:ident, $doc:expr) => {
88        #[doc = $doc]
89        ///
90        /// This is a newtype wrapper around [`String`] that provides
91        /// type-safe identity within the capability system. It implements
92        /// [`Display`](fmt::Display), serialisation, ordering, and hashing.
93        ///
94        /// # Example
95        ///
96        /// ```rust
97        #[doc = concat!("use agentkit_capabilities::", stringify!($name), ";")]
98        ///
99        #[doc = concat!("let id = ", stringify!($name), "::new(\"my-id\");")]
100        #[doc = concat!("assert_eq!(id.as_str(), \"my-id\");")]
101        /// ```
102        #[derive(
103            Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
104        )]
105        pub struct $name(pub String);
106
107        impl $name {
108            /// Creates a new identifier from any value that can be converted
109            /// into a [`String`].
110            pub fn new(value: impl Into<String>) -> Self {
111                Self(value.into())
112            }
113
114            /// Returns the underlying string slice.
115            pub fn as_str(&self) -> &str {
116                &self.0
117            }
118        }
119
120        impl fmt::Display for $name {
121            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122                self.0.fmt(f)
123            }
124        }
125    };
126}
127
128capability_id!(
129    CapabilityName,
130    "Unique name for an [`Invocable`] capability."
131);
132capability_id!(ResourceId, "Unique identifier for a resource.");
133capability_id!(PromptId, "Unique identifier for a prompt template.");
134
135/// Describes an [`Invocable`] capability so it can be advertised to the model.
136///
137/// The spec is presented to the model alongside other available tools so that
138/// it can decide when to call the capability.  The `input_schema` field should
139/// be a valid JSON Schema object describing the expected input shape.
140///
141/// # Example
142///
143/// ```rust
144/// use agentkit_capabilities::{CapabilityName, InvocableSpec};
145/// use serde_json::json;
146///
147/// let spec = InvocableSpec::new(
148///     CapabilityName::new("search"),
149///     "Search the codebase for a pattern",
150///     json!({
151///         "type": "object",
152///         "properties": {
153///             "query": { "type": "string" }
154///         },
155///         "required": ["query"]
156///     }),
157/// );
158///
159/// assert_eq!(spec.name.as_str(), "search");
160/// ```
161#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
162pub struct InvocableSpec {
163    /// The capability name that the model uses to reference this invocable.
164    pub name: CapabilityName,
165    /// A human-readable description shown to the model so it can decide when
166    /// to invoke this capability.
167    pub description: String,
168    /// A JSON Schema describing the expected shape of
169    /// [`InvocableRequest::input`].
170    pub input_schema: Value,
171    /// Arbitrary key-value metadata attached to the spec.
172    pub metadata: MetadataMap,
173}
174
175impl InvocableSpec {
176    /// Builds an invocable spec with empty metadata.
177    pub fn new(
178        name: impl Into<CapabilityName>,
179        description: impl Into<String>,
180        input_schema: Value,
181    ) -> Self {
182        Self {
183            name: name.into(),
184            description: description.into(),
185            input_schema,
186            metadata: MetadataMap::new(),
187        }
188    }
189
190    /// Replaces the spec metadata.
191    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
192        self.metadata = metadata;
193        self
194    }
195}
196
197/// A request to execute an [`Invocable`] capability.
198///
199/// Created by the agentkit loop when the model emits a tool-call that targets
200/// a registered invocable. The `input` field contains the arguments the model
201/// provided, validated against the capability's [`InvocableSpec::input_schema`].
202#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
203pub struct InvocableRequest {
204    /// The JSON input arguments provided by the model.
205    pub input: Value,
206    /// The session in which this invocation occurs, if available.
207    pub session_id: Option<SessionId>,
208    /// The turn within the session, if available.
209    pub turn_id: Option<TurnId>,
210    /// Arbitrary key-value metadata attached to this request.
211    pub metadata: MetadataMap,
212}
213
214impl InvocableRequest {
215    /// Builds an invocable request with no session or turn ids.
216    pub fn new(input: Value) -> Self {
217        Self {
218            input,
219            session_id: None,
220            turn_id: None,
221            metadata: MetadataMap::new(),
222        }
223    }
224
225    /// Sets the session id.
226    pub fn with_session(mut self, session_id: impl Into<SessionId>) -> Self {
227        self.session_id = Some(session_id.into());
228        self
229    }
230
231    /// Sets the turn id.
232    pub fn with_turn(mut self, turn_id: impl Into<TurnId>) -> Self {
233        self.turn_id = Some(turn_id.into());
234        self
235    }
236
237    /// Replaces the request metadata.
238    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
239        self.metadata = metadata;
240        self
241    }
242}
243
244/// The result of executing an [`Invocable`] capability.
245///
246/// Returned by [`Invocable::invoke`] on success.  The `output` field carries
247/// the actual content while `metadata` can hold timing information, cache
248/// headers, or any other sideband data the caller might need.
249#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
250pub struct InvocableResult {
251    /// The content produced by the invocable.
252    pub output: InvocableOutput,
253    /// Arbitrary key-value metadata about the execution (e.g. latency, cache
254    /// status).
255    pub metadata: MetadataMap,
256}
257
258impl InvocableResult {
259    /// Builds an invocable result with empty metadata.
260    pub fn new(output: InvocableOutput) -> Self {
261        Self {
262            output,
263            metadata: MetadataMap::new(),
264        }
265    }
266
267    /// Builds a plain-text result.
268    pub fn text(text: impl Into<String>) -> Self {
269        Self::new(InvocableOutput::Text(text.into()))
270    }
271
272    /// Builds a structured result.
273    pub fn structured(value: Value) -> Self {
274        Self::new(InvocableOutput::Structured(value))
275    }
276
277    /// Builds an items result.
278    pub fn items(items: Vec<Item>) -> Self {
279        Self::new(InvocableOutput::Items(items))
280    }
281
282    /// Builds a data-reference result.
283    pub fn data(data: DataRef) -> Self {
284        Self::new(InvocableOutput::Data(data))
285    }
286
287    /// Replaces the result metadata.
288    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
289        self.metadata = metadata;
290        self
291    }
292}
293
294/// The output payload of an [`Invocable`] execution.
295///
296/// Capabilities may return plain text, structured JSON, a sequence of
297/// conversation [`Item`]s, or a raw data reference depending on the nature of
298/// the work they perform.
299#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
300pub enum InvocableOutput {
301    /// A plain-text response.
302    Text(String),
303    /// A structured JSON value.
304    Structured(Value),
305    /// One or more conversation items (messages, tool results, etc.).
306    Items(Vec<Item>),
307    /// A reference to binary or text data (inline bytes, a URI, etc.).
308    Data(DataRef),
309}
310
311/// Describes a resource that a [`ResourceProvider`] can serve.
312///
313/// Resource descriptors are returned by
314/// [`ResourceProvider::list_resources`] so that the host or model can
315/// discover what data is available and request it by [`ResourceId`].
316#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
317pub struct ResourceDescriptor {
318    /// Unique identifier used to request this resource.
319    pub id: ResourceId,
320    /// A short, human-readable name for the resource.
321    pub name: String,
322    /// An optional longer description of the resource contents.
323    pub description: Option<String>,
324    /// The MIME type of the resource data (e.g. `"text/plain"`,
325    /// `"application/json"`).
326    pub mime_type: Option<String>,
327    /// Arbitrary key-value metadata attached to the descriptor.
328    pub metadata: MetadataMap,
329}
330
331impl ResourceDescriptor {
332    /// Builds a resource descriptor with no description or mime type.
333    pub fn new(id: impl Into<ResourceId>, name: impl Into<String>) -> Self {
334        Self {
335            id: id.into(),
336            name: name.into(),
337            description: None,
338            mime_type: None,
339            metadata: MetadataMap::new(),
340        }
341    }
342
343    /// Sets the description.
344    pub fn with_description(mut self, description: impl Into<String>) -> Self {
345        self.description = Some(description.into());
346        self
347    }
348
349    /// Sets the mime type.
350    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
351        self.mime_type = Some(mime_type.into());
352        self
353    }
354
355    /// Replaces the descriptor metadata.
356    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
357        self.metadata = metadata;
358        self
359    }
360}
361
362/// The payload returned when a resource is read.
363///
364/// Obtained by calling [`ResourceProvider::read_resource`] with a
365/// [`ResourceId`].
366#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
367pub struct ResourceContents {
368    /// The resource data, which may be inline text, inline bytes, a URI, or
369    /// an artifact handle.
370    pub data: DataRef,
371    /// Arbitrary key-value metadata about the read (e.g. ETag, last-modified).
372    pub metadata: MetadataMap,
373}
374
375impl ResourceContents {
376    /// Builds resource contents with empty metadata.
377    pub fn new(data: DataRef) -> Self {
378        Self {
379            data,
380            metadata: MetadataMap::new(),
381        }
382    }
383
384    /// Replaces the contents metadata.
385    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
386        self.metadata = metadata;
387        self
388    }
389}
390
391/// Describes a prompt template that a [`PromptProvider`] can render.
392///
393/// Prompt descriptors are returned by [`PromptProvider::list_prompts`] so the
394/// host can discover available templates and present them to the user or model.
395#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
396pub struct PromptDescriptor {
397    /// Unique identifier used to request this prompt.
398    pub id: PromptId,
399    /// A short, human-readable name for the prompt.
400    pub name: String,
401    /// An optional longer description of when or why to use the prompt.
402    pub description: Option<String>,
403    /// A JSON Schema describing the arguments the prompt template accepts.
404    pub input_schema: Value,
405    /// Arbitrary key-value metadata attached to the descriptor.
406    pub metadata: MetadataMap,
407}
408
409impl PromptDescriptor {
410    /// Builds a prompt descriptor with no description and empty metadata.
411    pub fn new(id: impl Into<PromptId>, name: impl Into<String>, input_schema: Value) -> Self {
412        Self {
413            id: id.into(),
414            name: name.into(),
415            description: None,
416            input_schema,
417            metadata: MetadataMap::new(),
418        }
419    }
420
421    /// Sets the description.
422    pub fn with_description(mut self, description: impl Into<String>) -> Self {
423        self.description = Some(description.into());
424        self
425    }
426
427    /// Replaces the descriptor metadata.
428    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
429        self.metadata = metadata;
430        self
431    }
432}
433
434/// The rendered output of a prompt template.
435///
436/// Returned by [`PromptProvider::get_prompt`] after applying the provided
437/// arguments to the template. The resulting items are typically prepended to
438/// the conversation transcript before the next model turn.
439#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
440pub struct PromptContents {
441    /// The conversation items produced by rendering the prompt.
442    pub items: Vec<Item>,
443    /// Arbitrary key-value metadata about the rendering.
444    pub metadata: MetadataMap,
445}
446
447impl PromptContents {
448    /// Builds prompt contents with empty metadata.
449    pub fn new(items: Vec<Item>) -> Self {
450        Self {
451            items,
452            metadata: MetadataMap::new(),
453        }
454    }
455
456    /// Replaces the contents metadata.
457    pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
458        self.metadata = metadata;
459        self
460    }
461}
462
463/// Shared execution context passed to all capability invocations.
464///
465/// The context carries the current session and turn identifiers so that
466/// capabilities can correlate their work with the broader conversation.
467/// A mutable reference is passed to every [`Invocable::invoke`],
468/// [`ResourceProvider::read_resource`], and [`PromptProvider::get_prompt`]
469/// call.
470///
471/// # Example
472///
473/// ```rust
474/// use agentkit_capabilities::CapabilityContext;
475/// use agentkit_core::{MetadataMap, SessionId, TurnId};
476///
477/// let session = SessionId::new("sess-1");
478/// let turn = TurnId::new("turn-1");
479/// let meta = MetadataMap::new();
480///
481/// let mut ctx = CapabilityContext {
482///     session_id: Some(&session),
483///     turn_id: Some(&turn),
484///     metadata: &meta,
485/// };
486///
487/// assert_eq!(ctx.session_id.unwrap().0, "sess-1");
488/// ```
489#[derive(Clone, Debug, PartialEq, Eq)]
490pub struct CapabilityContext<'a> {
491    /// The active session identifier, if one has been established.
492    pub session_id: Option<&'a SessionId>,
493    /// The current turn identifier within the session, if available.
494    pub turn_id: Option<&'a TurnId>,
495    /// Ambient metadata shared across all capabilities for this invocation.
496    pub metadata: &'a MetadataMap,
497}
498
499/// A capability that the model can invoke during a conversation turn.
500///
501/// Implement this trait to expose custom functionality -- database queries,
502/// API calls, file operations, code execution -- to the agentic loop. Each
503/// implementor provides a [`spec`](Invocable::spec) describing the capability
504/// and an async [`invoke`](Invocable::invoke) method that performs the work.
505///
506/// The agentkit loop discovers invocables through a [`CapabilityProvider`]
507/// and presents them to the model alongside regular tools.
508///
509/// # Example
510///
511/// ```rust
512/// use agentkit_capabilities::{
513///     CapabilityContext, CapabilityError, CapabilityName, Invocable,
514///     InvocableOutput, InvocableRequest, InvocableResult, InvocableSpec,
515/// };
516/// use async_trait::async_trait;
517/// use serde_json::json;
518///
519/// struct CurrentTime {
520///     spec: InvocableSpec,
521/// }
522///
523/// impl CurrentTime {
524///     fn new() -> Self {
525///         Self {
526///             spec: InvocableSpec::new(
527///                 CapabilityName::new("current_time"),
528///                 "Return the current UTC time",
529///                 json!({ "type": "object" }),
530///             ),
531///         }
532///     }
533/// }
534///
535/// #[async_trait]
536/// impl Invocable for CurrentTime {
537///     fn spec(&self) -> &InvocableSpec {
538///         &self.spec
539///     }
540///
541///     async fn invoke(
542///         &self,
543///         _request: InvocableRequest,
544///         _ctx: &mut CapabilityContext<'_>,
545///     ) -> Result<InvocableResult, CapabilityError> {
546///         Ok(InvocableResult::text("2026-03-22T12:00:00Z"))
547///     }
548/// }
549/// ```
550#[async_trait]
551pub trait Invocable: Send + Sync {
552    /// Returns the specification that describes this capability to the model.
553    fn spec(&self) -> &InvocableSpec;
554
555    /// Executes the capability with the given request and context.
556    ///
557    /// # Arguments
558    ///
559    /// * `request` - The invocation request containing the model-provided input
560    ///   and session identifiers.
561    /// * `ctx` - The shared capability context for this turn.
562    ///
563    /// # Errors
564    ///
565    /// Returns [`CapabilityError`] if the capability is unavailable, the input
566    /// is invalid, or execution fails.
567    async fn invoke(
568        &self,
569        request: InvocableRequest,
570        ctx: &mut CapabilityContext<'_>,
571    ) -> Result<InvocableResult, CapabilityError>;
572}
573
574/// A provider of named data resources that can be listed and read.
575///
576/// Implement this trait to expose data sources -- files on disk, database
577/// rows, API responses -- to the model. The agentkit MCP bridge uses this
578/// trait to surface MCP-server resources into the agentic loop.
579///
580/// # Example
581///
582/// ```rust
583/// use agentkit_capabilities::{
584///     CapabilityContext, CapabilityError, ResourceContents,
585///     ResourceDescriptor, ResourceId, ResourceProvider,
586/// };
587/// use agentkit_core::{DataRef, MetadataMap};
588/// use async_trait::async_trait;
589///
590/// struct StaticFile;
591///
592/// #[async_trait]
593/// impl ResourceProvider for StaticFile {
594///     async fn list_resources(&self) -> Result<Vec<ResourceDescriptor>, CapabilityError> {
595///         Ok(vec![ResourceDescriptor {
596///             id: ResourceId::new("readme"),
597///             name: "README.md".into(),
598///             description: Some("Project readme".into()),
599///             mime_type: Some("text/markdown".into()),
600///             metadata: MetadataMap::new(),
601///         }])
602///     }
603///
604///     async fn read_resource(
605///         &self,
606///         id: &ResourceId,
607///         _ctx: &mut CapabilityContext<'_>,
608///     ) -> Result<ResourceContents, CapabilityError> {
609///         if id.as_str() == "readme" {
610///             Ok(ResourceContents {
611///                 data: DataRef::InlineText("# Hello".into()),
612///                 metadata: MetadataMap::new(),
613///             })
614///         } else {
615///             Err(CapabilityError::Unavailable(format!(
616///                 "unknown resource: {id}"
617///             )))
618///         }
619///     }
620/// }
621/// ```
622#[async_trait]
623pub trait ResourceProvider: Send + Sync {
624    /// Lists all resources currently available from this provider.
625    ///
626    /// # Errors
627    ///
628    /// Returns [`CapabilityError`] if the provider cannot enumerate its
629    /// resources (e.g. a network timeout).
630    async fn list_resources(&self) -> Result<Vec<ResourceDescriptor>, CapabilityError>;
631
632    /// Reads the contents of the resource identified by `id`.
633    ///
634    /// # Arguments
635    ///
636    /// * `id` - The unique resource identifier, as returned in a
637    ///   [`ResourceDescriptor`].
638    /// * `ctx` - The shared capability context for this turn.
639    ///
640    /// # Errors
641    ///
642    /// Returns [`CapabilityError::Unavailable`] if the resource does not exist
643    /// or [`CapabilityError::ExecutionFailed`] if reading fails.
644    async fn read_resource(
645        &self,
646        id: &ResourceId,
647        ctx: &mut CapabilityContext<'_>,
648    ) -> Result<ResourceContents, CapabilityError>;
649}
650
651/// A provider of parameterised prompt templates.
652///
653/// Implement this trait to offer reusable prompt templates that the host can
654/// render with user-supplied arguments and inject into the conversation
655/// transcript. The agentkit MCP bridge uses this trait to surface MCP-server
656/// prompts into the agentic loop.
657#[async_trait]
658pub trait PromptProvider: Send + Sync {
659    /// Lists all prompt templates currently available from this provider.
660    ///
661    /// # Errors
662    ///
663    /// Returns [`CapabilityError`] if the provider cannot enumerate its
664    /// prompts.
665    async fn list_prompts(&self) -> Result<Vec<PromptDescriptor>, CapabilityError>;
666
667    /// Renders a prompt template with the given arguments.
668    ///
669    /// # Arguments
670    ///
671    /// * `id` - The unique prompt identifier, as returned in a
672    ///   [`PromptDescriptor`].
673    /// * `args` - A JSON value containing the template arguments, validated
674    ///   against the prompt's [`PromptDescriptor::input_schema`].
675    /// * `ctx` - The shared capability context for this turn.
676    ///
677    /// # Errors
678    ///
679    /// Returns [`CapabilityError::Unavailable`] if the prompt does not exist,
680    /// [`CapabilityError::InvalidInput`] if the arguments are malformed, or
681    /// [`CapabilityError::ExecutionFailed`] if rendering fails.
682    async fn get_prompt(
683        &self,
684        id: &PromptId,
685        args: Value,
686        ctx: &mut CapabilityContext<'_>,
687    ) -> Result<PromptContents, CapabilityError>;
688}
689
690/// A bundle of capabilities from a single source.
691///
692/// [`CapabilityProvider`] groups [`Invocable`]s, [`ResourceProvider`]s, and
693/// [`PromptProvider`]s that originate from the same backend -- for example,
694/// a single MCP server or a built-in tool collection.  The agentkit loop
695/// collects providers and merges their contents into the unified tool list
696/// presented to the model.
697///
698/// # Example
699///
700/// ```rust
701/// use std::sync::Arc;
702/// use agentkit_capabilities::{
703///     CapabilityProvider, Invocable, PromptProvider, ResourceProvider,
704/// };
705///
706/// struct EmptyProvider;
707///
708/// impl CapabilityProvider for EmptyProvider {
709///     fn invocables(&self) -> Vec<Arc<dyn Invocable>> {
710///         vec![]
711///     }
712///
713///     fn resources(&self) -> Vec<Arc<dyn ResourceProvider>> {
714///         vec![]
715///     }
716///
717///     fn prompts(&self) -> Vec<Arc<dyn PromptProvider>> {
718///         vec![]
719///     }
720/// }
721/// ```
722pub trait CapabilityProvider: Send + Sync {
723    /// Returns all invocable capabilities offered by this provider.
724    fn invocables(&self) -> Vec<Arc<dyn Invocable>>;
725
726    /// Returns all resource providers offered by this provider.
727    fn resources(&self) -> Vec<Arc<dyn ResourceProvider>>;
728
729    /// Returns all prompt providers offered by this provider.
730    fn prompts(&self) -> Vec<Arc<dyn PromptProvider>>;
731}
732
733/// Errors that can occur when interacting with capabilities.
734///
735/// This enum is used as the error type across all capability traits
736/// ([`Invocable`], [`ResourceProvider`], [`PromptProvider`]).
737#[derive(Debug, Error)]
738pub enum CapabilityError {
739    /// The requested capability, resource, or prompt is not available.
740    ///
741    /// Returned when the identifier does not match any registered item or
742    /// when the provider is temporarily offline.
743    #[error("capability unavailable: {0}")]
744    Unavailable(String),
745
746    /// The input provided to the capability is invalid.
747    ///
748    /// Returned when the model-supplied arguments fail schema validation or
749    /// contain values outside the expected domain.
750    #[error("invalid capability input: {0}")]
751    InvalidInput(String),
752
753    /// The capability encountered an error during execution.
754    ///
755    /// Returned for runtime failures such as network timeouts, I/O errors,
756    /// or unexpected backend responses.
757    #[error("capability execution failed: {0}")]
758    ExecutionFailed(String),
759}