use super::*;
pub struct TaskContext {
task_id: String,
live: Option<Arc<LiveTask>>,
}
impl std::fmt::Debug for TaskContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TaskContext")
.field("task_id", &self.task_id)
.field("live", &self.live.is_some())
.finish()
}
}
impl PartialEq for TaskContext {
fn eq(&self, other: &Self) -> bool {
self.task_id == other.task_id
}
}
impl Eq for TaskContext {}
pub(crate) struct LiveTask {
pub(crate) store: Arc<dyn crate::async_task::TaskStore>,
pub(crate) error_policy: crate::router::TaskErrorPolicy,
pub(crate) input_ready: tokio::sync::Notify,
pub(crate) cancelled: crate::context::CancellationToken,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum TaskOutcome {
Completed(CallToolResult),
Failed(crate::error::JsonRpcError),
Cancelled {
message: Option<String>,
},
}
impl TaskContext {
pub(crate) fn new(task_id: String) -> Self {
Self {
task_id,
live: None,
}
}
pub(crate) fn with_live(task_id: String, live: Arc<LiveTask>) -> Self {
Self {
task_id,
live: Some(live),
}
}
pub fn task_id(&self) -> &str {
&self.task_id
}
pub fn is_live(&self) -> bool {
self.live.is_some()
}
pub async fn require_input(&self, requests: InputRequests) -> Result<InputResponses> {
self.park_input(requests).await?.wait().await
}
pub async fn require_input_with_message(
&self,
requests: InputRequests,
message: impl Into<String>,
) -> Result<InputResponses> {
self.park_input_with_message(requests, message)
.await?
.wait()
.await
}
pub async fn park_input(&self, requests: InputRequests) -> Result<PendingInput> {
self.park_input_inner(requests, None).await
}
pub async fn park_input_with_message(
&self,
requests: InputRequests,
message: impl Into<String>,
) -> Result<PendingInput> {
self.park_input_inner(requests, Some(message.into())).await
}
async fn park_input_inner(
&self,
requests: InputRequests,
message: Option<String>,
) -> Result<PendingInput> {
let live = self.live.as_ref().ok_or_else(|| {
crate::error::Error::Tool(crate::error::ToolError::new(
"require_input needs a live task handler; a replay handler returns RequestOutcome::InputRequired instead",
))
})?;
if requests.is_empty() {
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::ParkInput,
&self.task_id,
"require_input needs at least one request, or the task would wait for something that can never arrive",
),
));
}
let asked: Vec<String> = requests.keys().cloned().collect();
if live.cancelled.is_cancelled() {
return Err(crate::error::Error::TaskCancelled);
}
let accepted = live
.store
.require_input(&self.task_id, requests, message.as_deref())
.await
.map_err(|error| {
crate::error::Error::JsonRpc(live.error_policy.map_store_error(
crate::router::TaskOperation::ParkInput,
&self.task_id,
error,
))
})?;
if !accepted {
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::ParkInput,
&self.task_id,
"the task is already terminal, so it cannot ask for input",
),
));
}
Ok(PendingInput {
live: live.clone(),
task_id: self.task_id.clone(),
asked,
})
}
pub async fn working(&self, message: impl Into<String>) -> Result<()> {
let live = self.live.as_ref().ok_or_else(|| {
crate::error::Error::Tool(crate::error::ToolError::new(
"working needs a live task handler",
))
})?;
let updated = live
.store
.set_status(&self.task_id, TaskStatus::Working, Some(&message.into()))
.await
.map_err(|error| {
crate::error::Error::JsonRpc(live.error_policy.map_store_error(
crate::router::TaskOperation::Execute,
&self.task_id,
error,
))
})?;
if !updated {
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::Execute,
&self.task_id,
"the task is already terminal, so its status cannot be updated",
),
));
}
Ok(())
}
pub fn is_cancelled(&self) -> bool {
self.live
.as_ref()
.is_some_and(|live| live.cancelled.is_cancelled())
}
pub async fn cancelled(&self) {
match self.live.as_ref() {
Some(live) => live.cancelled.cancelled().await,
None => std::future::pending().await,
}
}
}
impl Clone for TaskContext {
fn clone(&self) -> Self {
Self {
task_id: self.task_id.clone(),
live: self.live.clone(),
}
}
}
#[must_use = "the task is parked in `input_required` until this is awaited; \
dropping it leaves the task parked with nothing waiting to \
resume it"]
pub struct PendingInput {
pub(super) live: Arc<LiveTask>,
pub(super) task_id: String,
pub(super) asked: Vec<String>,
}
impl std::fmt::Debug for PendingInput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PendingInput")
.field("task_id", &self.task_id)
.field("asked", &self.asked)
.finish_non_exhaustive()
}
}
impl PendingInput {
pub fn task_id(&self) -> &str {
&self.task_id
}
pub fn asked(&self) -> &[String] {
&self.asked
}
pub async fn wait(self) -> Result<InputResponses> {
let live = &self.live;
if live.cancelled.is_cancelled() {
return Err(crate::error::Error::TaskCancelled);
}
loop {
let woken = live.input_ready.notified();
let outstanding = live
.store
.outstanding_input_requests(&self.task_id)
.await
.map_err(|error| {
crate::error::Error::JsonRpc(live.error_policy.map_store_error(
crate::router::TaskOperation::Execute,
&self.task_id,
error,
))
})?;
let Some(outstanding) = outstanding else {
if live.cancelled.is_cancelled() {
return Err(crate::error::Error::TaskCancelled);
}
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::Execute,
&self.task_id,
"the task disappeared while waiting for input",
),
));
};
if !outstanding.keys().any(|key| self.asked.contains(key)) {
break;
}
tokio::select! {
_ = woken => {}
_ = live.cancelled.cancelled() => {
return Err(crate::error::Error::TaskCancelled);
}
}
}
let all = live
.store
.input_responses(&self.task_id)
.await
.map_err(|error| {
crate::error::Error::JsonRpc(live.error_policy.map_store_error(
crate::router::TaskOperation::Execute,
&self.task_id,
error,
))
})?;
let Some(all) = all else {
if live.cancelled.is_cancelled() {
return Err(crate::error::Error::TaskCancelled);
}
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::Execute,
&self.task_id,
"the task disappeared before its input responses could be read",
),
));
};
Ok(all
.into_iter()
.filter(|(key, _)| self.asked.contains(key))
.collect())
}
}
#[derive(Debug, Clone, Default)]
pub struct TaskPreparation {
pub(crate) meta: Option<Map<String, Value>>,
pub(crate) extensions: Extensions,
}
impl TaskPreparation {
pub fn new() -> Self {
Self::default()
}
pub fn with_meta(mut self, meta: Map<String, Value>) -> Self {
self.meta = Some(meta);
self
}
pub fn with_extension<T: Send + Sync + 'static>(mut self, value: T) -> Self {
self.extensions.insert(value);
self
}
}
pub(crate) trait TaskPreparer: Send + Sync {
fn prepare(
&self,
context: TaskContext,
arguments: Value,
) -> BoxFuture<'_, Result<TaskPreparation>>;
}
impl<F, Fut> TaskPreparer for F
where
F: Fn(TaskContext, Value) -> Fut + Send + Sync,
Fut: Future<Output = Result<TaskPreparation>> + Send + 'static,
{
fn prepare(
&self,
context: TaskContext,
arguments: Value,
) -> BoxFuture<'_, Result<TaskPreparation>> {
Box::pin((self)(context, arguments))
}
}
pub(super) struct TypedTaskPreparer<I, F> {
pub(super) prepare: F,
pub(super) _phantom: std::marker::PhantomData<I>,
}
impl<I, F, Fut> TaskPreparer for TypedTaskPreparer<I, F>
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(TaskContext, I) -> Fut + Send + Sync,
Fut: Future<Output = Result<TaskPreparation>> + Send + 'static,
{
fn prepare(
&self,
context: TaskContext,
arguments: Value,
) -> BoxFuture<'_, Result<TaskPreparation>> {
let input = serde_json::from_value(arguments)
.map_err(|error| Error::invalid_params(format!("Invalid input: {error}")));
match input {
Ok(input) => Box::pin((self.prepare)(context, input)),
Err(error) => Box::pin(async move { Err(error) }),
}
}
}