Skip to main content

arcp_core/messages/
control.rs

1//! Control plane messages (RFC §6.2 "Control" group).
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::ErrorCode;
6use crate::ids::MessageId;
7
8pub use crate::messages::{PingPayload, PongPayload};
9
10/// Payload for `ack` — generic acknowledgement.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct AckPayload {
13    /// Optional human-readable note.
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub note: Option<String>,
16}
17
18/// Payload for `nack` — negative acknowledgement; carries an error.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct NackPayload {
21    /// Canonical error code.
22    pub code: ErrorCode,
23    /// Human-readable explanation.
24    pub message: String,
25    /// Optional structured details.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub details: Option<serde_json::Value>,
28}
29
30/// Cancellation target discriminator (RFC §10.4).
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum CancelTargetKind {
34    /// Cancel a job.
35    Job,
36    /// Cancel a stream.
37    Stream,
38    /// Cancel an entire session.
39    Session,
40}
41
42/// Payload for `cancel` (RFC §10.4).
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct CancelPayload {
45    /// Kind of target.
46    pub target: CancelTargetKind,
47    /// Identifier of the target.
48    pub target_id: String,
49    /// Free-form reason.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub reason: Option<String>,
52    /// Cooperative-cancel deadline in milliseconds. Default per A4.7 = 5000.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub deadline_ms: Option<u64>,
55}
56
57/// Payload for `cancel.accepted`.
58#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
59pub struct CancelAcceptedPayload {
60    /// Echo of the cancel target id.
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub target_id: Option<String>,
63}
64
65/// Payload for `cancel.refused`.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct CancelRefusedPayload {
68    /// Echo of the cancel target id.
69    pub target_id: String,
70    /// Reason for refusal.
71    pub reason: String,
72}
73
74/// Payload for `interrupt` (RFC §10.5).
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub struct InterruptPayload {
77    /// Kind of target.
78    pub target: CancelTargetKind,
79    /// Identifier of the target.
80    pub target_id: String,
81    /// Operator-supplied prompt for the human follow-up.
82    pub prompt: String,
83}
84
85/// Payload for `resume` (RFC §19).
86#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
87pub struct ResumePayload {
88    /// Replay starting strictly after this message id.
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub after_message_id: Option<MessageId>,
91    /// Checkpoint id to restore from (v0.2).
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub checkpoint_id: Option<String>,
94    /// If true, re-open active streams as part of the resume.
95    #[serde(default, skip_serializing_if = "is_false")]
96    pub include_open_streams: bool,
97}
98
99#[allow(clippy::trivially_copy_pass_by_ref)]
100const fn is_false(b: &bool) -> bool {
101    !*b
102}
103
104/// Payload for `backpressure` (RFC §11.2).
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106pub struct BackpressurePayload {
107    /// Desired chunk-rate in chunks per second (PLAN.md §A4.8 choice).
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub desired_rate_per_second: Option<u32>,
110    /// Approximate buffer headroom in bytes.
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub buffer_remaining_bytes: Option<u64>,
113    /// Free-form reason.
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub reason: Option<String>,
116}