Skip to main content

systemprompt_models/a2a/
message.rs

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