use std::fmt;
use std::sync::Arc;
use asupersync::Cx;
use fastmcp_core::McpRequestCancellation;
use fastmcp_protocol::{CoreRequest, FinalInputResponses, InputRequiredResult, RequestId};
use super::{ManagedToolClient, ManagedToolError, ToolContract, await_validity, check_tool_call};
use crate::http_auth::rpc::{
ManagedCoreError,
interaction::{
ManagedInteraction, ManagedInteractionError, ManagedInteractionEvent,
ManagedInteractionLimits,
},
};
pub mod drive;
pub mod recovery;
#[derive(Debug)]
pub enum ManagedToolInteractionError {
Tool(ManagedToolError),
Interaction(ManagedInteractionError),
}
impl fmt::Display for ManagedToolInteractionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tool(error) => fmt::Display::fmt(error, f),
Self::Interaction(error) => fmt::Display::fmt(error, f),
}
}
}
impl std::error::Error for ManagedToolInteractionError {}
impl From<ManagedToolError> for ManagedToolInteractionError {
fn from(error: ManagedToolError) -> Self {
Self::Tool(error)
}
}
impl From<ManagedInteractionError> for ManagedToolInteractionError {
fn from(error: ManagedInteractionError) -> Self {
Self::Interaction(error)
}
}
impl ManagedToolClient {
pub async fn start_interaction(
&self,
cx: &Cx,
request: CoreRequest,
request_id: RequestId,
limits: ManagedInteractionLimits,
) -> Result<ManagedToolInteraction, ManagedToolInteractionError> {
Box::pin(self.start_interaction_with_cancellation(
cx,
&McpRequestCancellation::new(),
request,
request_id,
limits,
))
.await
}
pub async fn start_interaction_with_cancellation(
&self,
cx: &Cx,
cancellation: &McpRequestCancellation,
request: CoreRequest,
request_id: RequestId,
limits: ManagedInteractionLimits,
) -> Result<ManagedToolInteraction, ManagedToolInteractionError> {
check_tool_call(cx, cancellation, &self.contract)?;
self.contract.validate_request(&request)?;
check_tool_call(cx, cancellation, &self.contract)?;
let operation = Box::pin(await_validity(
cx,
cancellation,
&self.contract,
self.session.start_core_interaction_configured(
cx,
cancellation,
request,
request_id,
limits,
self.header_review.clone(),
),
))
.await??;
check_tool_call(cx, cancellation, &self.contract)?;
Ok(ManagedToolInteraction {
operation: Some(operation),
contract: self.contract.clone(),
cancellation: cancellation.clone(),
finished: false,
})
}
}
pub struct ManagedToolInteraction {
operation: Option<ManagedInteraction>,
contract: Arc<ToolContract>,
cancellation: McpRequestCancellation,
finished: bool,
}
impl fmt::Debug for ManagedToolInteraction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ManagedToolInteraction")
.field("awaiting_input", &self.pending_input().is_some())
.field("finished", &self.finished)
.field("closed", &self.operation.is_none())
.finish_non_exhaustive()
}
}
impl ManagedToolInteraction {
pub(in crate::http_auth::tool) fn from_repaired_call(
cx: &Cx,
operation: ManagedInteraction,
contract: Arc<ToolContract>,
cancellation: McpRequestCancellation,
) -> Result<Self, ManagedToolError> {
check_tool_call(cx, &cancellation, &contract)?;
Ok(Self {
operation: Some(operation),
contract,
cancellation,
finished: false,
})
}
pub fn pending_input(&self) -> Option<&InputRequiredResult> {
if self.contract.check().is_err() || self.cancellation.is_cancel_requested() {
return None;
}
self.operation
.as_ref()
.and_then(ManagedInteraction::pending_input)
}
pub fn close(&mut self) {
self.operation = None;
}
pub async fn next_event(
&mut self,
cx: &Cx,
) -> Result<Option<ManagedInteractionEvent>, ManagedToolInteractionError> {
if self.finished {
return Ok(None);
}
let mut operation = self.take_checked(cx)?;
let next = Box::pin(await_validity(
cx,
&self.cancellation,
&self.contract,
operation.next_event(cx),
))
.await?;
check_tool_call(cx, &self.cancellation, &self.contract)?;
let event = match next {
Ok(Some(event)) => event,
Ok(None) => {
return Err(ManagedToolError::Core(ManagedCoreError::MissingTerminal).into());
}
Err(error) => {
if matches!(&error, ManagedInteractionError::InputPending)
&& operation.pending_input().is_some()
{
self.operation = Some(operation);
}
return Err(error.into());
}
};
let complete = admit_event(&self.contract, &event)?;
check_tool_call(cx, &self.cancellation, &self.contract)?;
if complete {
self.finished = true;
} else {
self.operation = Some(operation);
}
Ok(Some(event))
}
pub async fn resume(
&mut self,
cx: &Cx,
request_id: RequestId,
responses: Option<FinalInputResponses>,
) -> Result<(), ManagedToolInteractionError> {
Box::pin(self.resume_selected(cx, request_id, responses, false)).await
}
pub async fn resume_partial(
&mut self,
cx: &Cx,
request_id: RequestId,
responses: FinalInputResponses,
) -> Result<(), ManagedToolInteractionError> {
Box::pin(self.resume_selected(cx, request_id, Some(responses), true)).await
}
async fn resume_selected(
&mut self,
cx: &Cx,
request_id: RequestId,
responses: Option<FinalInputResponses>,
partial: bool,
) -> Result<(), ManagedToolInteractionError> {
let mut operation = self.take_checked(cx)?;
if operation.pending_input().is_none() {
self.operation = Some(operation);
return Err(ManagedInteractionError::NotAwaitingInput.into());
}
let outcome = Box::pin(await_validity(
cx,
&self.cancellation,
&self.contract,
async {
if partial {
let responses =
responses.ok_or(ManagedInteractionError::InvalidInputResponses)?;
operation.resume_partial(cx, request_id, responses).await
} else {
operation.resume(cx, request_id, responses).await
}
},
))
.await?;
check_tool_call(cx, &self.cancellation, &self.contract)?;
if outcome.is_ok() || operation.pending_input().is_some() {
self.operation = Some(operation);
}
outcome.map_err(ManagedToolInteractionError::from)
}
fn take_checked(&mut self, cx: &Cx) -> Result<ManagedInteraction, ManagedToolInteractionError> {
let operation = self.operation.take().ok_or(ManagedToolError::Closed)?;
check_tool_call(cx, &self.cancellation, &self.contract)?;
Ok(operation)
}
}
fn admit_event(
contract: &ToolContract,
event: &ManagedInteractionEvent,
) -> Result<bool, ManagedToolError> {
contract.check()?;
match event {
ManagedInteractionEvent::Complete(result) => {
contract.validate_result(result)?;
Ok(true)
}
ManagedInteractionEvent::Notification(_) | ManagedInteractionEvent::InputRequired(_) => {
Ok(false)
}
}
}
#[cfg(test)]
mod tests;