use std::collections::HashMap;
use std::time::Instant;
use a2a_protocol_types::params::TaskQueryParams;
use a2a_protocol_types::task::{Task, TaskId};
use crate::error::{ServerError, ServerResult};
use super::super::helpers::build_call_context;
use super::super::RequestHandler;
impl RequestHandler {
pub async fn on_get_task(
&self,
params: TaskQueryParams,
headers: Option<&HashMap<String, String>>,
) -> ServerResult<Task> {
let start = Instant::now();
trace_info!(method = "GetTask", task_id = %params.id, "handling get task");
self.metrics.on_request("GetTask");
let tenant = params.tenant.clone().unwrap_or_default();
let result: ServerResult<_> = crate::store::tenant::TenantContext::scope(tenant, async {
let call_ctx = build_call_context("GetTask", headers);
self.interceptors.run_before(&call_ctx).await?;
let task_id = TaskId::new(¶ms.id);
let mut task = self
.task_store
.get(&task_id)
.await?
.ok_or_else(|| ServerError::TaskNotFound(task_id))?;
if let Some(history_length) = params.history_length {
task.history = match (task.history, history_length) {
(Some(msgs), n) if n > 0 => {
let n = n as usize;
if msgs.len() > n {
Some(msgs[msgs.len() - n..].to_vec())
} else {
Some(msgs)
}
}
_ => None,
};
}
self.interceptors.run_after(&call_ctx).await?;
Ok(task)
})
.await;
let elapsed = start.elapsed();
match &result {
Ok(_) => {
self.metrics.on_response("GetTask");
self.metrics.on_latency("GetTask", elapsed);
}
Err(e) => {
self.metrics.on_error("GetTask", &e.to_string());
self.metrics.on_latency("GetTask", elapsed);
}
}
result
}
}
#[cfg(test)]
mod tests {
use a2a_protocol_types::params::TaskQueryParams;
use a2a_protocol_types::task::{ContextId, Task, TaskId, TaskState, TaskStatus};
use crate::agent_executor;
use crate::builder::RequestHandlerBuilder;
use crate::error::ServerError;
struct DummyExecutor;
agent_executor!(DummyExecutor, |_ctx, _queue| async { Ok(()) });
fn make_completed_task(id: &str) -> Task {
Task {
id: TaskId::new(id),
context_id: ContextId::new("ctx-1"),
status: TaskStatus::new(TaskState::Completed),
history: None,
artifacts: None,
metadata: None,
}
}
#[tokio::test]
async fn get_task_not_found_returns_error() {
let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
let params = TaskQueryParams {
tenant: None,
id: "nonexistent-task".to_owned(),
history_length: None,
};
let result = handler.on_get_task(params, None).await;
assert!(
matches!(result, Err(ServerError::TaskNotFound(_))),
"expected TaskNotFound for missing task, got: {result:?}"
);
}
#[tokio::test]
async fn get_task_found_returns_task() {
let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
let task = make_completed_task("t-get-1");
handler.task_store.save(task).await.unwrap();
let params = TaskQueryParams {
tenant: None,
id: "t-get-1".to_owned(),
history_length: None,
};
let result = handler.on_get_task(params, None).await;
assert!(
result.is_ok(),
"expected Ok for existing task, got: {result:?}"
);
assert_eq!(result.unwrap().id, TaskId::new("t-get-1"));
}
#[tokio::test]
async fn get_task_error_path_records_metrics() {
let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
let params = TaskQueryParams {
tenant: None,
id: "nonexistent-metrics".to_owned(),
history_length: None,
};
let result = handler.on_get_task(params, None).await;
assert!(
matches!(result, Err(ServerError::TaskNotFound(_))),
"expected TaskNotFound for error metrics path, got: {result:?}"
);
}
fn make_task_with_history(id: &str, num_messages: usize) -> Task {
use a2a_protocol_types::message::{Message, MessageId, MessageRole, Part};
let history: Vec<Message> = (0..num_messages)
.map(|i| Message {
id: MessageId::new(format!("msg-{i}")),
role: MessageRole::User,
parts: vec![Part::text(format!("message {i}"))],
context_id: None,
task_id: None,
reference_task_ids: None,
extensions: None,
metadata: None,
})
.collect();
Task {
id: TaskId::new(id),
context_id: ContextId::new("ctx-hist"),
status: TaskStatus::new(TaskState::Completed),
history: if history.is_empty() {
None
} else {
Some(history)
},
artifacts: None,
metadata: None,
}
}
#[tokio::test]
async fn get_task_history_length_zero_returns_no_history() {
let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
handler
.task_store
.save(make_task_with_history("t-hl-0", 5))
.await
.unwrap();
let params = TaskQueryParams {
tenant: None,
id: "t-hl-0".to_owned(),
history_length: Some(0),
};
let task = handler.on_get_task(params, None).await.unwrap();
assert!(
task.history.is_none(),
"historyLength=0 should return no history, got: {:?}",
task.history
);
}
#[tokio::test]
async fn get_task_history_length_truncates_to_most_recent() {
let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
handler
.task_store
.save(make_task_with_history("t-hl-2", 5))
.await
.unwrap();
let params = TaskQueryParams {
tenant: None,
id: "t-hl-2".to_owned(),
history_length: Some(2),
};
let task = handler.on_get_task(params, None).await.unwrap();
let history = task.history.expect("should have history");
assert_eq!(history.len(), 2, "historyLength=2 should return 2 messages");
assert!(
history[0]
.parts
.iter()
.any(|p| p.text_content() == Some("message 3")),
"first message should be 'message 3', got: {:?}",
history[0].parts
);
assert!(
history[1]
.parts
.iter()
.any(|p| p.text_content() == Some("message 4")),
"second message should be 'message 4', got: {:?}",
history[1].parts
);
}
#[tokio::test]
async fn get_task_history_length_larger_than_history_returns_all() {
let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
handler
.task_store
.save(make_task_with_history("t-hl-big", 3))
.await
.unwrap();
let params = TaskQueryParams {
tenant: None,
id: "t-hl-big".to_owned(),
history_length: Some(100),
};
let task = handler.on_get_task(params, None).await.unwrap();
let history = task.history.expect("should have history");
assert_eq!(
history.len(),
3,
"historyLength > actual should return all messages"
);
}
#[tokio::test]
async fn get_task_no_history_length_returns_full_history() {
let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
handler
.task_store
.save(make_task_with_history("t-hl-none", 5))
.await
.unwrap();
let params = TaskQueryParams {
tenant: None,
id: "t-hl-none".to_owned(),
history_length: None,
};
let task = handler.on_get_task(params, None).await.unwrap();
let history = task.history.expect("should have history");
assert_eq!(
history.len(),
5,
"no historyLength should return all messages"
);
}
}