use std::collections::HashMap;
use base64::{Engine as _, engine::general_purpose};
use serde_json::{Map, Value, json};
use super::error::A2aError;
use super::task_store::TaskStoreEntry;
pub fn wire_part_to_adk(part: &a2a_protocol_types::Part) -> Result<adk_core::Part, A2aError> {
match &part.content {
a2a_protocol_types::PartContent::Text(text) => {
Ok(adk_core::Part::Text { text: text.clone() })
}
a2a_protocol_types::PartContent::Raw(base64_str) => {
let data = general_purpose::STANDARD.decode(base64_str).map_err(|e| {
A2aError::InvalidParams { message: format!("base64 decode error: {e}") }
})?;
Ok(adk_core::Part::InlineData {
mime_type: part.media_type.clone().unwrap_or_default(),
data,
})
}
a2a_protocol_types::PartContent::Url(uri) => Ok(adk_core::Part::FileData {
mime_type: part.media_type.clone().unwrap_or_default(),
file_uri: uri.clone(),
}),
a2a_protocol_types::PartContent::Data(data) => wire_data_to_adk(data),
_ => {
Err(A2aError::InvalidParams { message: "unsupported PartContent variant".to_string() })
}
}
}
fn wire_data_to_adk(data: &Value) -> Result<adk_core::Part, A2aError> {
if let Some(call) = data.get("function_call") {
let name = call
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| A2aError::InvalidParams {
message: "function_call missing 'name'".to_string(),
})?
.to_string();
let args = call.get("args").cloned().unwrap_or(Value::Object(Map::new()));
let id = call.get("id").and_then(|v| v.as_str()).map(String::from);
Ok(adk_core::Part::FunctionCall { name, args, id, thought_signature: None })
} else if let Some(resp) = data.get("function_response") {
let name = resp
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| A2aError::InvalidParams {
message: "function_response missing 'name'".to_string(),
})?
.to_string();
let response = resp.get("response").cloned().unwrap_or(Value::Object(Map::new()));
let id = resp.get("id").and_then(|v| v.as_str()).map(String::from);
Ok(adk_core::Part::FunctionResponse {
function_response: adk_core::FunctionResponseData::new(name, response),
id,
})
} else if let Some(stc) = data.get("server_tool_call") {
Ok(adk_core::Part::ServerToolCall { server_tool_call: stc.clone() })
} else if let Some(str_val) = data.get("server_tool_response") {
Ok(adk_core::Part::ServerToolResponse { server_tool_response: str_val.clone() })
} else {
Err(A2aError::InvalidParams {
message: "data part does not contain a recognized key (function_call, function_response, server_tool_call, server_tool_response)".to_string(),
})
}
}
pub fn adk_part_to_wire(part: &adk_core::Part) -> Result<a2a_protocol_types::Part, A2aError> {
match part {
adk_core::Part::Text { text } => Ok(a2a_protocol_types::Part::text(text.clone())),
adk_core::Part::InlineData { mime_type, data } => {
let encoded = general_purpose::STANDARD.encode(data);
Ok(a2a_protocol_types::Part::raw(encoded).with_media_type(mime_type.clone()))
}
adk_core::Part::FileData { mime_type, file_uri } => {
Ok(a2a_protocol_types::Part::url(file_uri.clone()).with_media_type(mime_type.clone()))
}
adk_core::Part::FunctionCall { name, args, id, .. } => {
let mut call_data = Map::new();
call_data.insert("name".to_string(), Value::String(name.clone()));
call_data.insert("args".to_string(), args.clone());
if let Some(call_id) = id {
call_data.insert("id".to_string(), Value::String(call_id.clone()));
}
let data = json!({ "function_call": Value::Object(call_data) });
Ok(a2a_protocol_types::Part::data(data))
}
adk_core::Part::FunctionResponse { function_response, id } => {
let mut resp_data = Map::new();
resp_data.insert("name".to_string(), Value::String(function_response.name.clone()));
resp_data.insert("response".to_string(), function_response.response.clone());
if let Some(resp_id) = id {
resp_data.insert("id".to_string(), Value::String(resp_id.clone()));
}
let data = json!({ "function_response": Value::Object(resp_data) });
Ok(a2a_protocol_types::Part::data(data))
}
adk_core::Part::Thinking { thinking, .. } => {
Ok(a2a_protocol_types::Part::text(thinking.clone()))
}
adk_core::Part::ServerToolCall { server_tool_call } => {
let data = json!({ "server_tool_call": server_tool_call.clone() });
Ok(a2a_protocol_types::Part::data(data))
}
adk_core::Part::ServerToolResponse { server_tool_response } => {
let data = json!({ "server_tool_response": server_tool_response.clone() });
Ok(a2a_protocol_types::Part::data(data))
}
}
}
pub fn wire_message_to_adk(
msg: &a2a_protocol_types::Message,
) -> Result<crate::a2a::Message, A2aError> {
let role = match msg.role {
a2a_protocol_types::MessageRole::User => crate::a2a::Role::User,
a2a_protocol_types::MessageRole::Agent => crate::a2a::Role::Agent,
_ => crate::a2a::Role::User, };
let parts: Vec<crate::a2a::Part> =
msg.parts.iter().map(wire_part_to_adk_internal).collect::<Result<Vec<_>, _>>()?;
let mut metadata: Option<Map<String, Value>> =
msg.metadata.as_ref().and_then(|v| v.as_object().cloned());
if let Some(ref extensions) = msg.extensions {
let meta = metadata.get_or_insert_with(Map::new);
meta.insert(
"_a2a_extensions".to_string(),
serde_json::to_value(extensions).unwrap_or(Value::Null),
);
}
if let Some(ref ref_ids) = msg.reference_task_ids {
let meta = metadata.get_or_insert_with(Map::new);
let ids: Vec<String> = ref_ids.iter().map(|id| id.0.clone()).collect();
meta.insert(
"_a2a_reference_task_ids".to_string(),
serde_json::to_value(ids).unwrap_or(Value::Null),
);
}
Ok(crate::a2a::Message {
role,
parts,
metadata,
message_id: msg.id.0.clone(),
task_id: msg.task_id.as_ref().map(|id| id.0.clone()),
context_id: msg.context_id.as_ref().map(|id| id.0.clone()),
})
}
fn wire_part_to_adk_internal(
part: &a2a_protocol_types::Part,
) -> Result<crate::a2a::Part, A2aError> {
match &part.content {
a2a_protocol_types::PartContent::Text(text) => {
Ok(crate::a2a::Part::Text { text: text.clone(), metadata: None })
}
a2a_protocol_types::PartContent::Raw(base64_str) => Ok(crate::a2a::Part::File {
file: crate::a2a::FileContent {
name: part.filename.clone(),
mime_type: part.media_type.clone(),
bytes: Some(base64_str.clone()),
uri: None,
},
metadata: None,
}),
a2a_protocol_types::PartContent::Url(uri) => Ok(crate::a2a::Part::File {
file: crate::a2a::FileContent {
name: part.filename.clone(),
mime_type: part.media_type.clone(),
bytes: None,
uri: Some(uri.clone()),
},
metadata: None,
}),
a2a_protocol_types::PartContent::Data(data) => {
let obj = data.as_object().cloned().unwrap_or_default();
Ok(crate::a2a::Part::Data { data: obj, metadata: None })
}
_ => {
Err(A2aError::InvalidParams { message: "unsupported PartContent variant".to_string() })
}
}
}
pub fn adk_message_to_wire(
msg: &crate::a2a::Message,
) -> Result<a2a_protocol_types::Message, A2aError> {
let role = match msg.role {
crate::a2a::Role::User => a2a_protocol_types::MessageRole::User,
crate::a2a::Role::Agent => a2a_protocol_types::MessageRole::Agent,
};
let parts: Vec<a2a_protocol_types::Part> =
msg.parts.iter().map(adk_internal_part_to_wire).collect::<Result<Vec<_>, _>>()?;
let mut extensions: Option<Vec<String>> = None;
let mut reference_task_ids: Option<Vec<a2a_protocol_types::TaskId>> = None;
let mut wire_metadata: Option<Value> = None;
if let Some(ref meta) = msg.metadata {
let mut clean_meta = meta.clone();
if let Some(ext_val) = clean_meta.remove("_a2a_extensions") {
if let Ok(exts) = serde_json::from_value::<Vec<String>>(ext_val) {
extensions = Some(exts);
}
}
if let Some(ref_val) = clean_meta.remove("_a2a_reference_task_ids") {
if let Ok(ids) = serde_json::from_value::<Vec<String>>(ref_val) {
reference_task_ids =
Some(ids.into_iter().map(a2a_protocol_types::TaskId::new).collect());
}
}
if !clean_meta.is_empty() {
wire_metadata = Some(Value::Object(clean_meta));
}
}
Ok(a2a_protocol_types::Message {
id: a2a_protocol_types::MessageId::new(&msg.message_id),
role,
parts,
task_id: msg.task_id.as_ref().map(a2a_protocol_types::TaskId::new),
context_id: msg.context_id.as_ref().map(a2a_protocol_types::ContextId::new),
reference_task_ids,
extensions,
metadata: wire_metadata,
})
}
fn adk_internal_part_to_wire(
part: &crate::a2a::Part,
) -> Result<a2a_protocol_types::Part, A2aError> {
match part {
crate::a2a::Part::Text { text, .. } => Ok(a2a_protocol_types::Part::text(text.clone())),
crate::a2a::Part::File { file, .. } => {
if let Some(ref bytes) = file.bytes {
let mut p = a2a_protocol_types::Part::raw(bytes.clone());
if let Some(ref name) = file.name {
p = p.with_filename(name.clone());
}
if let Some(ref mt) = file.mime_type {
p = p.with_media_type(mt.clone());
}
Ok(p)
} else if let Some(ref uri) = file.uri {
let mut p = a2a_protocol_types::Part::url(uri.clone());
if let Some(ref name) = file.name {
p = p.with_filename(name.clone());
}
if let Some(ref mt) = file.mime_type {
p = p.with_media_type(mt.clone());
}
Ok(p)
} else {
Err(A2aError::InvalidParams {
message: "file part has neither bytes nor uri".to_string(),
})
}
}
crate::a2a::Part::Data { data, .. } => {
Ok(a2a_protocol_types::Part::data(Value::Object(data.clone())))
}
}
}
pub fn wire_task_to_internal(task: &a2a_protocol_types::Task) -> Result<TaskStoreEntry, A2aError> {
let now = chrono::Utc::now();
let metadata: HashMap<String, Value> = task
.metadata
.as_ref()
.and_then(|v| v.as_object())
.map(|obj| obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
Ok(TaskStoreEntry {
id: task.id.0.clone(),
context_id: task.context_id.0.clone(),
status: task.status.clone(),
artifacts: task.artifacts.clone().unwrap_or_default(),
history: task.history.clone().unwrap_or_default(),
metadata,
push_configs: Vec::new(),
created_at: now,
updated_at: now,
})
}
pub fn internal_task_to_wire(entry: &TaskStoreEntry) -> Result<a2a_protocol_types::Task, A2aError> {
let metadata = if entry.metadata.is_empty() {
None
} else {
let obj: Map<String, Value> =
entry.metadata.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
Some(Value::Object(obj))
};
let history = if entry.history.is_empty() { None } else { Some(entry.history.clone()) };
let artifacts = if entry.artifacts.is_empty() { None } else { Some(entry.artifacts.clone()) };
Ok(a2a_protocol_types::Task {
id: a2a_protocol_types::TaskId::new(&entry.id),
context_id: a2a_protocol_types::ContextId::new(&entry.context_id),
status: entry.status.clone(),
history,
artifacts,
metadata,
})
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn text_part_wire_to_adk() {
let wire = a2a_protocol_types::Part::text("hello");
let adk = wire_part_to_adk(&wire).unwrap();
assert!(matches!(adk, adk_core::Part::Text { ref text } if text == "hello"));
}
#[test]
fn raw_part_wire_to_adk() {
let encoded = general_purpose::STANDARD.encode(b"binary data");
let wire = a2a_protocol_types::Part::raw(&encoded).with_media_type("image/png");
let adk = wire_part_to_adk(&wire).unwrap();
match adk {
adk_core::Part::InlineData { mime_type, data } => {
assert_eq!(mime_type, "image/png");
assert_eq!(data, b"binary data");
}
_ => panic!("expected InlineData"),
}
}
#[test]
fn url_part_wire_to_adk() {
let wire = a2a_protocol_types::Part::url("https://example.com/f.pdf")
.with_media_type("application/pdf");
let adk = wire_part_to_adk(&wire).unwrap();
match adk {
adk_core::Part::FileData { mime_type, file_uri } => {
assert_eq!(mime_type, "application/pdf");
assert_eq!(file_uri, "https://example.com/f.pdf");
}
_ => panic!("expected FileData"),
}
}
#[test]
fn data_function_call_wire_to_adk() {
let data = json!({
"function_call": {
"name": "get_weather",
"args": {"city": "Seattle"},
"id": "call_1"
}
});
let wire = a2a_protocol_types::Part::data(data);
let adk = wire_part_to_adk(&wire).unwrap();
match adk {
adk_core::Part::FunctionCall { name, args, id, .. } => {
assert_eq!(name, "get_weather");
assert_eq!(args["city"], "Seattle");
assert_eq!(id.as_deref(), Some("call_1"));
}
_ => panic!("expected FunctionCall"),
}
}
#[test]
fn data_function_response_wire_to_adk() {
let data = json!({
"function_response": {
"name": "get_weather",
"response": {"temp": 72},
"id": "call_1"
}
});
let wire = a2a_protocol_types::Part::data(data);
let adk = wire_part_to_adk(&wire).unwrap();
match adk {
adk_core::Part::FunctionResponse { function_response, id } => {
assert_eq!(function_response.name, "get_weather");
assert_eq!(function_response.response["temp"], 72);
assert_eq!(id.as_deref(), Some("call_1"));
}
_ => panic!("expected FunctionResponse"),
}
}
#[test]
fn data_server_tool_call_wire_to_adk() {
let data = json!({ "server_tool_call": {"tool": "search", "query": "rust"} });
let wire = a2a_protocol_types::Part::data(data);
let adk = wire_part_to_adk(&wire).unwrap();
match adk {
adk_core::Part::ServerToolCall { server_tool_call } => {
assert_eq!(server_tool_call["tool"], "search");
}
_ => panic!("expected ServerToolCall"),
}
}
#[test]
fn data_server_tool_response_wire_to_adk() {
let data = json!({ "server_tool_response": {"result": "found"} });
let wire = a2a_protocol_types::Part::data(data);
let adk = wire_part_to_adk(&wire).unwrap();
match adk {
adk_core::Part::ServerToolResponse { server_tool_response } => {
assert_eq!(server_tool_response["result"], "found");
}
_ => panic!("expected ServerToolResponse"),
}
}
#[test]
fn data_unknown_key_returns_error() {
let data = json!({ "unknown_key": "value" });
let wire = a2a_protocol_types::Part::data(data);
assert!(wire_part_to_adk(&wire).is_err());
}
#[test]
fn raw_invalid_base64_returns_error() {
let wire = a2a_protocol_types::Part::raw("not-valid-base64!!!");
assert!(wire_part_to_adk(&wire).is_err());
}
#[test]
fn text_adk_to_wire() {
let adk = adk_core::Part::Text { text: "hello".to_string() };
let wire = adk_part_to_wire(&adk).unwrap();
assert!(
matches!(wire.content, a2a_protocol_types::PartContent::Text(ref t) if t == "hello")
);
}
#[test]
fn inline_data_adk_to_wire() {
let adk = adk_core::Part::InlineData {
mime_type: "image/png".to_string(),
data: b"binary".to_vec(),
};
let wire = adk_part_to_wire(&adk).unwrap();
match wire.content {
a2a_protocol_types::PartContent::Raw(ref encoded) => {
let decoded = general_purpose::STANDARD.decode(encoded).unwrap();
assert_eq!(decoded, b"binary");
}
_ => panic!("expected Raw"),
}
assert_eq!(wire.media_type.as_deref(), Some("image/png"));
}
#[test]
fn file_data_adk_to_wire() {
let adk = adk_core::Part::FileData {
mime_type: "application/pdf".to_string(),
file_uri: "https://example.com/f.pdf".to_string(),
};
let wire = adk_part_to_wire(&adk).unwrap();
assert!(
matches!(wire.content, a2a_protocol_types::PartContent::Url(ref u) if u == "https://example.com/f.pdf")
);
assert_eq!(wire.media_type.as_deref(), Some("application/pdf"));
}
#[test]
fn function_call_adk_to_wire() {
let adk = adk_core::Part::FunctionCall {
name: "test_fn".to_string(),
args: json!({"key": "val"}),
id: Some("c1".to_string()),
thought_signature: None,
};
let wire = adk_part_to_wire(&adk).unwrap();
match wire.content {
a2a_protocol_types::PartContent::Data(ref data) => {
let call = &data["function_call"];
assert_eq!(call["name"], "test_fn");
assert_eq!(call["args"]["key"], "val");
assert_eq!(call["id"], "c1");
}
_ => panic!("expected Data"),
}
}
#[test]
fn function_response_adk_to_wire() {
let adk = adk_core::Part::FunctionResponse {
function_response: adk_core::FunctionResponseData::new(
"test_fn",
json!({"result": 42}),
),
id: Some("r1".to_string()),
};
let wire = adk_part_to_wire(&adk).unwrap();
match wire.content {
a2a_protocol_types::PartContent::Data(ref data) => {
let resp = &data["function_response"];
assert_eq!(resp["name"], "test_fn");
assert_eq!(resp["response"]["result"], 42);
assert_eq!(resp["id"], "r1");
}
_ => panic!("expected Data"),
}
}
#[test]
fn thinking_adk_to_wire_becomes_text() {
let adk = adk_core::Part::Thinking {
thinking: "let me think...".to_string(),
signature: Some("sig123".to_string()),
};
let wire = adk_part_to_wire(&adk).unwrap();
assert!(
matches!(wire.content, a2a_protocol_types::PartContent::Text(ref t) if t == "let me think...")
);
}
#[test]
fn server_tool_call_adk_to_wire() {
let adk = adk_core::Part::ServerToolCall { server_tool_call: json!({"tool": "search"}) };
let wire = adk_part_to_wire(&adk).unwrap();
match wire.content {
a2a_protocol_types::PartContent::Data(ref data) => {
assert_eq!(data["server_tool_call"]["tool"], "search");
}
_ => panic!("expected Data"),
}
}
#[test]
fn server_tool_response_adk_to_wire() {
let adk =
adk_core::Part::ServerToolResponse { server_tool_response: json!({"result": "ok"}) };
let wire = adk_part_to_wire(&adk).unwrap();
match wire.content {
a2a_protocol_types::PartContent::Data(ref data) => {
assert_eq!(data["server_tool_response"]["result"], "ok");
}
_ => panic!("expected Data"),
}
}
#[test]
fn text_roundtrip() {
let original = adk_core::Part::Text { text: "round trip".to_string() };
let wire = adk_part_to_wire(&original).unwrap();
let back = wire_part_to_adk(&wire).unwrap();
assert!(matches!(back, adk_core::Part::Text { ref text } if text == "round trip"));
}
#[test]
fn inline_data_roundtrip() {
let original = adk_core::Part::InlineData {
mime_type: "image/jpeg".to_string(),
data: vec![0xFF, 0xD8, 0xFF, 0xE0],
};
let wire = adk_part_to_wire(&original).unwrap();
let back = wire_part_to_adk(&wire).unwrap();
match back {
adk_core::Part::InlineData { mime_type, data } => {
assert_eq!(mime_type, "image/jpeg");
assert_eq!(data, vec![0xFF, 0xD8, 0xFF, 0xE0]);
}
_ => panic!("expected InlineData"),
}
}
#[test]
fn file_data_roundtrip() {
let original = adk_core::Part::FileData {
mime_type: "text/plain".to_string(),
file_uri: "gs://bucket/file.txt".to_string(),
};
let wire = adk_part_to_wire(&original).unwrap();
let back = wire_part_to_adk(&wire).unwrap();
match back {
adk_core::Part::FileData { mime_type, file_uri } => {
assert_eq!(mime_type, "text/plain");
assert_eq!(file_uri, "gs://bucket/file.txt");
}
_ => panic!("expected FileData"),
}
}
#[test]
fn function_call_roundtrip() {
let original = adk_core::Part::FunctionCall {
name: "my_func".to_string(),
args: json!({"a": 1}),
id: Some("id1".to_string()),
thought_signature: None,
};
let wire = adk_part_to_wire(&original).unwrap();
let back = wire_part_to_adk(&wire).unwrap();
match back {
adk_core::Part::FunctionCall { name, args, id, .. } => {
assert_eq!(name, "my_func");
assert_eq!(args, json!({"a": 1}));
assert_eq!(id.as_deref(), Some("id1"));
}
_ => panic!("expected FunctionCall"),
}
}
#[test]
fn message_wire_to_adk_basic() {
let wire = a2a_protocol_types::Message {
id: a2a_protocol_types::MessageId::new("msg-1"),
role: a2a_protocol_types::MessageRole::User,
parts: vec![a2a_protocol_types::Part::text("hello")],
task_id: Some(a2a_protocol_types::TaskId::new("task-1")),
context_id: Some(a2a_protocol_types::ContextId::new("ctx-1")),
reference_task_ids: None,
extensions: None,
metadata: None,
};
let adk = wire_message_to_adk(&wire).unwrap();
assert_eq!(adk.message_id, "msg-1");
assert!(matches!(adk.role, crate::a2a::Role::User));
assert_eq!(adk.parts.len(), 1);
assert_eq!(adk.task_id.as_deref(), Some("task-1"));
assert_eq!(adk.context_id.as_deref(), Some("ctx-1"));
}
#[test]
fn message_preserves_extensions_in_metadata() {
let wire = a2a_protocol_types::Message {
id: a2a_protocol_types::MessageId::new("msg-2"),
role: a2a_protocol_types::MessageRole::Agent,
parts: vec![a2a_protocol_types::Part::text("hi")],
task_id: None,
context_id: None,
reference_task_ids: Some(vec![a2a_protocol_types::TaskId::new("ref-1")]),
extensions: Some(vec!["urn:ext:custom".to_string()]),
metadata: None,
};
let adk = wire_message_to_adk(&wire).unwrap();
let meta = adk.metadata.as_ref().unwrap();
assert!(meta.contains_key("_a2a_extensions"));
assert!(meta.contains_key("_a2a_reference_task_ids"));
}
#[test]
fn message_adk_to_wire_basic() {
let adk = crate::a2a::Message {
role: crate::a2a::Role::Agent,
parts: vec![crate::a2a::Part::text("response".to_string())],
metadata: None,
message_id: "msg-3".to_string(),
task_id: Some("task-2".to_string()),
context_id: Some("ctx-2".to_string()),
};
let wire = adk_message_to_wire(&adk).unwrap();
assert_eq!(wire.id.0, "msg-3");
assert_eq!(wire.role, a2a_protocol_types::MessageRole::Agent);
assert_eq!(wire.parts.len(), 1);
assert_eq!(wire.task_id.as_ref().unwrap().0, "task-2");
assert_eq!(wire.context_id.as_ref().unwrap().0, "ctx-2");
}
#[test]
fn message_roundtrip_preserves_extensions() {
let wire_original = a2a_protocol_types::Message {
id: a2a_protocol_types::MessageId::new("msg-rt"),
role: a2a_protocol_types::MessageRole::User,
parts: vec![a2a_protocol_types::Part::text("test")],
task_id: None,
context_id: None,
reference_task_ids: Some(vec![a2a_protocol_types::TaskId::new("ref-1")]),
extensions: Some(vec!["urn:ext:a".to_string()]),
metadata: None,
};
let adk = wire_message_to_adk(&wire_original).unwrap();
let wire_back = adk_message_to_wire(&adk).unwrap();
assert_eq!(wire_back.extensions, wire_original.extensions);
assert_eq!(
wire_back.reference_task_ids.as_ref().map(|v| v.len()),
wire_original.reference_task_ids.as_ref().map(|v| v.len())
);
}
#[test]
fn task_wire_to_internal() {
let wire = a2a_protocol_types::Task {
id: a2a_protocol_types::TaskId::new("task-1"),
context_id: a2a_protocol_types::ContextId::new("ctx-1"),
status: a2a_protocol_types::TaskStatus::new(a2a_protocol_types::TaskState::Working),
history: Some(vec![a2a_protocol_types::Message {
id: a2a_protocol_types::MessageId::new("m1"),
role: a2a_protocol_types::MessageRole::User,
parts: vec![a2a_protocol_types::Part::text("hi")],
task_id: None,
context_id: None,
reference_task_ids: None,
extensions: None,
metadata: None,
}]),
artifacts: None,
metadata: Some(json!({"key": "val"})),
};
let entry = wire_task_to_internal(&wire).unwrap();
assert_eq!(entry.id, "task-1");
assert_eq!(entry.context_id, "ctx-1");
assert_eq!(entry.status.state, a2a_protocol_types::TaskState::Working);
assert_eq!(entry.history.len(), 1);
assert!(entry.artifacts.is_empty());
assert_eq!(entry.metadata.get("key").unwrap(), &json!("val"));
}
#[test]
fn task_internal_to_wire() {
let now = chrono::Utc::now();
let mut metadata = HashMap::new();
metadata.insert("foo".to_string(), json!("bar"));
let entry = TaskStoreEntry {
id: "task-2".to_string(),
context_id: "ctx-2".to_string(),
status: a2a_protocol_types::TaskStatus::new(a2a_protocol_types::TaskState::Completed),
artifacts: vec![a2a_protocol_types::Artifact::new(
a2a_protocol_types::ArtifactId::new("art-1"),
vec![a2a_protocol_types::Part::text("result")],
)],
history: Vec::new(),
metadata,
push_configs: Vec::new(),
created_at: now,
updated_at: now,
};
let wire = internal_task_to_wire(&entry).unwrap();
assert_eq!(wire.id.0, "task-2");
assert_eq!(wire.context_id.0, "ctx-2");
assert_eq!(wire.status.state, a2a_protocol_types::TaskState::Completed);
assert_eq!(wire.artifacts.as_ref().unwrap().len(), 1);
assert!(wire.history.is_none());
assert_eq!(wire.metadata.as_ref().unwrap()["foo"], "bar");
}
#[test]
fn task_roundtrip() {
let wire_original = a2a_protocol_types::Task {
id: a2a_protocol_types::TaskId::new("task-rt"),
context_id: a2a_protocol_types::ContextId::new("ctx-rt"),
status: a2a_protocol_types::TaskStatus::new(a2a_protocol_types::TaskState::Submitted),
history: None,
artifacts: None,
metadata: None,
};
let entry = wire_task_to_internal(&wire_original).unwrap();
let wire_back = internal_task_to_wire(&entry).unwrap();
assert_eq!(wire_back.id, wire_original.id);
assert_eq!(wire_back.context_id, wire_original.context_id);
assert_eq!(wire_back.status.state, wire_original.status.state);
}
}