use std::fmt;
use std::sync::Arc;
use asupersync::Cx;
use fastmcp_core::McpRequestCancellation;
use fastmcp_protocol::http_headers::ParameterHeaderBinding;
use fastmcp_protocol::{CoreRequest, FinalTool, RequestId};
use super::{
ManagedCatalogError, ManagedOAuthSession, RejectedToolHeaders, ReviewedToolHeaders,
ToolHeaderRepairContract, ToolHeaderRepairError, ToolHeaderRepairLimits,
ToolHeaderRepairOutcome,
};
use crate::http_auth::rpc::interaction::{
ManagedInteraction, ManagedInteractionError, ManagedInteractionLimits,
};
#[derive(Clone, Copy, Debug)]
pub struct ToolHeaderInteractionLimits {
repair: ToolHeaderRepairLimits,
maximum_continuations: usize,
maximum_input_responses: usize,
}
impl Default for ToolHeaderInteractionLimits {
fn default() -> Self {
Self {
repair: ToolHeaderRepairLimits::default(),
maximum_continuations: 8,
maximum_input_responses: 256,
}
}
}
impl ToolHeaderInteractionLimits {
pub fn new(
repair: ToolHeaderRepairLimits,
maximum_continuations: usize,
maximum_input_responses: usize,
) -> Result<Self, ManagedInteractionError> {
ManagedInteractionLimits::new(repair.core, maximum_continuations, maximum_input_responses)?;
Ok(Self {
repair,
maximum_continuations,
maximum_input_responses,
})
}
}
#[derive(Debug)]
pub enum ToolHeaderInteractionError {
Repair(ToolHeaderRepairError),
Interaction(ManagedInteractionError),
}
impl fmt::Display for ToolHeaderInteractionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Repair(error) => fmt::Display::fmt(error, f),
Self::Interaction(error) => fmt::Display::fmt(error, f),
}
}
}
impl std::error::Error for ToolHeaderInteractionError {}
impl From<ToolHeaderRepairError> for ToolHeaderInteractionError {
fn from(error: ToolHeaderRepairError) -> Self {
Self::Repair(error)
}
}
impl From<ManagedInteractionError> for ToolHeaderInteractionError {
fn from(error: ManagedInteractionError) -> Self {
Self::Interaction(error)
}
}
#[must_use = "drive the interaction or explicitly decide whether to repair the rejection"]
pub enum ToolHeaderInteractionOutcome {
Interaction(Box<ManagedInteraction>),
Rejected(Box<RejectedToolHeaderInteraction>),
}
pub struct RejectedToolHeaderInteraction {
rejected: RejectedToolHeaders,
limits: ToolHeaderInteractionLimits,
}
impl fmt::Debug for RejectedToolHeaderInteraction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RejectedToolHeaderInteraction")
.finish_non_exhaustive()
}
}
impl ManagedOAuthSession {
#[allow(clippy::too_many_arguments)]
pub async fn start_tool_interaction_with_header_repair(
&self,
cx: &Cx,
request: CoreRequest,
request_id: RequestId,
reviewed: Arc<ReviewedToolHeaders>,
endpoint: ToolHeaderRepairContract,
limits: ToolHeaderInteractionLimits,
) -> Result<ToolHeaderInteractionOutcome, ToolHeaderInteractionError> {
Box::pin(
self.start_tool_interaction_with_header_repair_and_cancellation(
cx,
&McpRequestCancellation::new(),
request,
request_id,
reviewed,
endpoint,
limits,
),
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn start_tool_interaction_with_header_repair_and_cancellation(
&self,
cx: &Cx,
cancellation: &McpRequestCancellation,
request: CoreRequest,
request_id: RequestId,
reviewed: Arc<ReviewedToolHeaders>,
endpoint: ToolHeaderRepairContract,
limits: ToolHeaderInteractionLimits,
) -> Result<ToolHeaderInteractionOutcome, ToolHeaderInteractionError> {
let outcome = Box::pin(self.request_tool_with_header_repair_and_cancellation(
cx,
cancellation,
request,
request_id,
&reviewed,
endpoint,
limits.repair,
))
.await?;
Ok(match outcome {
ToolHeaderRepairOutcome::Call(call) => ToolHeaderInteractionOutcome::Interaction(
Box::new(ManagedInteraction::from_initial_header_call(
cx,
self.clone(),
call,
reviewed,
limits.maximum_continuations,
limits.maximum_input_responses,
)?),
),
ToolHeaderRepairOutcome::Rejected(rejected) => {
ToolHeaderInteractionOutcome::Rejected(Box::new(RejectedToolHeaderInteraction {
rejected,
limits,
}))
}
})
}
}
impl RejectedToolHeaderInteraction {
pub async fn refresh_and_start<I, A, R>(
self,
cx: &Cx,
next_id: I,
approve: A,
review: R,
) -> Result<ManagedInteraction, ToolHeaderInteractionError>
where
I: FnMut() -> Result<RequestId, ManagedCatalogError>,
A: FnOnce(&FinalTool) -> bool,
R: FnMut(&ParameterHeaderBinding) -> bool,
{
Ok(Box::pin(self.rejected.refresh_and_start_interaction(
cx,
self.limits.maximum_continuations,
self.limits.maximum_input_responses,
next_id,
approve,
review,
))
.await?)
}
}
#[cfg(test)]
mod tests;