Skip to main content

zerox1_client/
types.rs

1//! Canonical wire types for the zerox1-node REST and WebSocket APIs.
2
3use serde::{Deserialize, Serialize};
4
5// ── Identity types ─────────────────────────────────────────────────────────
6
7/// A 32-byte Ed25519 agent identity, hex-encoded on the wire.
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub struct AgentId(pub [u8; 32]);
10
11impl AgentId {
12    /// Parse from a 64-char hex string.
13    pub fn from_hex(s: &str) -> anyhow::Result<Self> {
14        let bytes = hex::decode(s)?;
15        let arr: [u8; 32] = bytes
16            .try_into()
17            .map_err(|_| anyhow::anyhow!("AgentId must be 32 bytes"))?;
18        Ok(Self(arr))
19    }
20
21    /// Encode to lowercase hex.
22    pub fn to_hex(&self) -> String {
23        hex::encode(self.0)
24    }
25}
26
27impl std::fmt::Display for AgentId {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{}", self.to_hex())
30    }
31}
32
33/// A 16-byte conversation identifier, hex-encoded on the wire.
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub struct ConversationId(pub [u8; 16]);
36
37impl ConversationId {
38    /// Generate a fresh random conversation ID.
39    pub fn new_random() -> Self {
40        Self(*uuid::Uuid::new_v4().as_bytes())
41    }
42
43    /// Parse from a 32-char hex string.
44    pub fn from_hex(s: &str) -> anyhow::Result<Self> {
45        let bytes = hex::decode(s)?;
46        let arr: [u8; 16] = bytes
47            .try_into()
48            .map_err(|_| anyhow::anyhow!("ConversationId must be 16 bytes"))?;
49        Ok(Self(arr))
50    }
51
52    /// Encode to lowercase hex.
53    pub fn to_hex(&self) -> String {
54        hex::encode(self.0)
55    }
56}
57
58impl std::fmt::Display for ConversationId {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(f, "{}", self.to_hex())
61    }
62}
63
64// ── Node API types ─────────────────────────────────────────────────────────
65
66/// Inbound envelope received from `GET /ws/inbox`.
67///
68/// `sender` is Ed25519-authenticated at the protocol layer — it cannot be
69/// spoofed regardless of what the payload claims.
70#[derive(Debug, Clone, Deserialize)]
71pub struct InboundEnvelope {
72    /// Message type: `"PROPOSE"`, `"ACCEPT"`, `"REJECT"`, `"DELIVER"`, `"FEEDBACK"`, …
73    pub msg_type: String,
74    /// Authenticated sender agent_id (hex 32 bytes).
75    pub sender: String,
76    /// Recipient agent_id — typically the local node (hex 32 bytes).
77    #[serde(default)]
78    pub recipient: Option<String>,
79    /// Conversation identifier (hex 16 bytes).
80    pub conversation_id: String,
81    /// Beacon slot the envelope was committed in.
82    pub slot: u64,
83    /// Per-sender replay-prevention nonce.
84    pub nonce: u64,
85    /// Payload bytes as base64.
86    pub payload_b64: String,
87    /// Decoded FEEDBACK fields (only present for FEEDBACK messages).
88    #[serde(default)]
89    pub feedback: Option<serde_json::Value>,
90}
91
92/// Request body for `POST /envelopes/send`.
93#[derive(Debug, Serialize)]
94pub struct SendEnvelopeRequest {
95    pub msg_type: String,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub recipient: Option<String>,
98    pub conversation_id: String,
99    pub payload_b64: String,
100}
101
102/// Response from `POST /envelopes/send`.
103#[derive(Debug, Deserialize)]
104pub struct SendEnvelopeResponse {
105    pub nonce: u64,
106    pub payload_hash: String,
107}
108
109/// Request body for `POST /hosted/send` (hosted-agent mode).
110#[derive(Debug, Serialize)]
111pub struct HostedSendRequest {
112    pub msg_type: String,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub recipient: Option<String>,
115    pub conversation_id: String,
116    /// Payload bytes as hex (hosted API requirement).
117    pub payload_hex: String,
118}
119
120// ── Aggregator types ───────────────────────────────────────────────────────
121
122/// Activity event broadcast on the aggregator's `GET /ws/activity` stream.
123#[derive(Debug, Clone, Deserialize)]
124pub struct ActivityEvent {
125    pub id: i64,
126    pub ts: i64,
127    /// `"JOIN"` | `"FEEDBACK"` | `"DISPUTE"` | `"VERDICT"` | `"REJECT"` | `"DELIVER"`
128    pub event_type: String,
129    /// Primary agent_id (hex).
130    pub agent_id: String,
131    /// Secondary agent_id for bilateral events (hex).
132    #[serde(default)]
133    pub target_id: Option<String>,
134    #[serde(default)]
135    pub score: Option<i64>,
136    #[serde(default)]
137    pub name: Option<String>,
138    #[serde(default)]
139    pub conversation_id: Option<String>,
140}
141
142/// Identity response from `GET /identity`.
143#[derive(Debug, Deserialize)]
144pub struct IdentityResponse {
145    pub agent_id: String,
146}