Skip to main content

systemprompt_models/a2a/
message.rs

1//! A2A message and part wire types.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use 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}