systemprompt_models/a2a/
message.rs1use serde::{Deserialize, Serialize};
7use systemprompt_identifiers::{ContextId, MessageId, TaskId};
8
9#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
10#[serde(rename_all = "camelCase")]
11#[expect(
12 clippy::struct_field_names,
13 reason = "A2A wire schema names these fields message_id/task_id/context_id — public protocol \
14 vocabulary"
15)]
16pub struct Message {
17 pub role: MessageRole,
18 pub parts: Vec<Part>,
19 pub message_id: MessageId,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub task_id: Option<TaskId>,
22 pub context_id: ContextId,
23 pub metadata: Option<serde_json::Value>,
24 pub extensions: Option<Vec<String>>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub reference_task_ids: Option<Vec<TaskId>>,
27}
28
29#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
30pub enum MessageRole {
31 #[serde(rename = "ROLE_USER")]
32 User,
33 #[serde(rename = "ROLE_AGENT")]
34 Agent,
35}
36
37#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
38#[serde(untagged)]
39pub enum Part {
40 Text(TextPart),
41 File(FilePart),
42 Data(DataPart),
43}
44
45#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
46pub struct TextPart {
47 pub text: String,
48}
49
50#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
51pub struct DataPart {
52 pub data: serde_json::Map<String, serde_json::Value>,
53}
54
55#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
56pub struct FilePart {
57 pub file: FileContent,
58}
59
60#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
61#[serde(rename_all = "camelCase")]
62pub struct FileContent {
63 pub name: Option<String>,
64 pub mime_type: Option<String>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub bytes: Option<String>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub url: Option<String>,
69}
70
71impl Part {
72 pub fn as_text(&self) -> Option<&str> {
73 match self {
74 Self::Text(text_part) => Some(&text_part.text),
75 _ => None,
76 }
77 }
78
79 pub fn as_data(&self) -> Option<serde_json::Value> {
80 match self {
81 Self::Data(data_part) => Some(serde_json::Value::Object(data_part.data.clone())),
82 _ => None,
83 }
84 }
85
86 pub fn as_file(&self) -> Option<serde_json::Value> {
87 match self {
88 Self::File(file_part) => serde_json::to_value(&file_part.file).ok(),
89 _ => None,
90 }
91 }
92}