use std::collections::HashMap;
use std::time::Instant;
use a2a_protocol_types::error::A2aResult;
use a2a_protocol_types::params::MessageSendParams;
use a2a_protocol_types::task::Task;
use tokio::sync::OwnedSemaphorePermit;
use tokio::task::JoinHandle;
use crate::call_context::CallContext;
use crate::error::ServerResult;
use crate::streaming::InMemoryQueueReader;
use super::helpers::build_call_context;
use super::{RequestHandler, SendMessageResult};
mod admission;
mod commit_guard;
mod continuation;
mod create;
mod decisions;
mod eviction;
mod execute;
mod idempotency;
mod respond;
mod terminal;
mod validation;
pub use decisions::MAX_TASK_HISTORY_MESSAGES;
#[derive(Clone, Copy)]
struct SendMode {
use_background: bool,
response_history_length: Option<u32>,
}
impl SendMode {
fn of(params: &MessageSendParams, streaming: bool) -> Self {
let configuration = params.configuration.as_ref();
let return_immediately = configuration
.and_then(|c| c.return_immediately)
.unwrap_or(false);
Self {
use_background: streaming || return_immediately,
response_history_length: configuration.and_then(|c| c.history_length),
}
}
}
struct Started {
task: Task,
reader: InMemoryQueueReader,
persistence_rx: Option<tokio::sync::mpsc::Receiver<A2aResult<crate::streaming::StreamEvent>>>,
executor_handle: JoinHandle<()>,
cancel: tokio_util::sync::CancellationToken,
gate: Option<std::sync::Arc<crate::streaming::event_queue::terminal_gate::TerminalGate>>,
}
enum Committed {
Started(Box<Started>),
Replay(Box<Task>),
}
impl RequestHandler {
pub async fn on_send_message(
&self,
params: MessageSendParams,
streaming: bool,
headers: Option<&HashMap<String, String>>,
) -> ServerResult<SendMessageResult> {
let method_name = if streaming {
"SendStreamingMessage"
} else {
"SendMessage"
};
let start = Instant::now();
trace_info!(method = method_name, streaming, "handling send message");
self.metrics.on_request(method_name);
let tenant = self
.resolve_tenant(method_name, headers, params.tenant.as_deref())
.await?;
let result = crate::store::tenant::TenantContext::scope(tenant, async {
self.send_message_inner(params, streaming, method_name, headers)
.await
})
.await;
let elapsed = start.elapsed();
match &result {
Ok(_) => {
self.metrics.on_response(method_name);
self.metrics.on_latency(method_name, elapsed);
}
Err(e) => {
self.metrics.on_error(method_name, e.metric_label());
self.metrics.on_latency(method_name, elapsed);
}
}
result
}
async fn send_message_inner(
&self,
params: MessageSendParams,
streaming: bool,
method_name: &str,
headers: Option<&HashMap<String, String>>,
) -> ServerResult<SendMessageResult> {
let call_ctx = build_call_context(method_name, headers, self.inbound_trace_policy);
let mut call = self.interceptors.begin(&call_ctx);
let result = async {
call.before().await?;
self.ensure_required_extensions(&call_ctx)?;
let (mode, committed) = self
.validate_and_commit(params, streaming, &call_ctx)
.await?;
let response = match committed {
Committed::Replay(task) => {
Box::pin(self.respond_replay(*task, streaming, mode.response_history_length))
.await
}
Committed::Started(started) => {
if mode.use_background {
Ok(self
.respond_in_background(
*started,
streaming,
mode.response_history_length,
)
.await)
} else {
self.respond_blocking(*started, mode.response_history_length)
.await
}
}
}?;
Ok(response)
}
.await;
call.finish(result).await
}
async fn validate_and_commit(
&self,
mut params: MessageSendParams,
streaming: bool,
call_ctx: &CallContext,
) -> ServerResult<(SendMode, Committed)> {
let tenant_slot = self.acquire_tenant_slot().await?;
if streaming {
self.ensure_streaming_supported()?;
}
self.validate_send_params(&mut params)?;
let mode = SendMode::of(¶ms, streaming);
self.commit_task(params, mode.use_background, tenant_slot, call_ctx)
.await
.map(|started| (mode, started))
}
#[allow(clippy::too_many_lines)]
async fn commit_task(
&self,
params: MessageSendParams,
use_background: bool,
tenant_slot: Option<OwnedSemaphorePermit>,
call_ctx: &CallContext,
) -> ServerResult<Committed> {
let context_id = self.resolve_context_id(¶ms.message).await?;
let context_lock = self.keyed_lock(&context_id).await;
let context_guard = context_lock.lock().await;
let stored_task = self.find_task_by_context(&context_id).await?;
let resolution = self
.resolve_task_id(¶ms.message, stored_task.as_ref())
.await?;
let task_id = resolution.id;
let stored_task = resolution.continues.or(stored_task);
let claimed_key = match Box::pin(self.claim_send_key(¶ms.message, &task_id)).await? {
idempotency::SendKey::Replay(task) => return Ok(Committed::Replay(task)),
idempotency::SendKey::Claimed(key) => Some(key),
idempotency::SendKey::Absent => None,
};
let mut guard = commit_guard::CommitGuard::new(
task_id.clone(),
self.event_queue_manager.clone(),
std::sync::Arc::clone(&self.cancellation_tokens),
std::sync::Arc::clone(&self.task_store),
claimed_key.clone(),
);
let guard_ref = &mut guard;
let started = Box::pin(async move {
self.reject_in_flight_send(&task_id).await?;
trace_debug!(
task_id = %task_id,
context_id = %context_id,
"creating task"
);
let task = create::build_initial_task(
&task_id,
&context_id,
stored_task.as_ref(),
¶ms.message,
);
let mut ctx = create::build_request_context(
params.message,
task_id.clone(),
context_id,
stored_task,
params.metadata,
call_ctx.clone(),
);
ctx.cancellation_token = self.in_flight.task_token();
let (writer, reader, persistence_rx) =
self.lease_event_queue(&task_id, use_background).await?;
guard_ref.leased();
let turn = self
.register_cancellation_token(&task_id, ctx.cancellation_token.clone())
.await;
guard_ref.registered(&turn);
self.persist_initial_task(&task).await?;
Box::pin(self.register_inline_push_config(params.configuration.as_ref(), &task_id))
.await?;
drop(context_guard);
let cancel = ctx.cancellation_token.clone();
let gate = writer.terminal_gate();
let executor_handle = self.spawn_executor(ctx, writer, tenant_slot, turn);
Ok(Started {
task,
reader,
persistence_rx,
executor_handle,
cancel,
gate,
})
})
.await;
guard.disarm();
match started {
Ok(started) => Ok(Committed::Started(Box::new(started))),
Err(err) => {
if let Some(key) = claimed_key {
self.release_send_key(&key).await;
}
Err(err)
}
}
}
}
#[cfg(test)]
mod idempotency_tests;
#[cfg(test)]
mod tests;