Skip to main content

arcp_core/messages/
streaming.rs

1//! Stream messages (RFC §11).
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::ErrorCode;
6
7/// Stream kind discriminator (RFC §11.1).
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum StreamKind {
11    /// Plain text.
12    Text,
13    /// Opaque bytes (base64 in v0.1).
14    Binary,
15    /// Structured JSON events.
16    Event,
17    /// Structured log lines.
18    Log,
19    /// Telemetry samples.
20    Metric,
21    /// Model reasoning / chain-of-thought.
22    Thought,
23}
24
25/// Payload for `stream.open`.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct StreamOpenPayload {
28    /// Stream kind.
29    pub kind: StreamKind,
30    /// Content type (e.g. `text/plain`).
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub content_type: Option<String>,
33    /// Encoding (e.g. `utf-8`, `base64`).
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub encoding: Option<String>,
36}
37
38/// Payload for `stream.chunk`.
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct StreamChunkPayload {
41    /// Per-stream sequence number for ordering.
42    pub sequence: u64,
43    /// Inline data.
44    ///
45    /// For `kind: text` this is a string; for `kind: binary` it is base64.
46    /// For structured kinds (`event`, `log`, `metric`, `thought`) the value
47    /// is a JSON object whose schema is determined by the stream kind.
48    pub data: serde_json::Value,
49    /// Optional content type override (per-chunk, rare).
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub content_type: Option<String>,
52    /// Optional integrity hash for binary chunks.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub sha256: Option<String>,
55    /// True if this is a redacted thought chunk (RFC §11.4).
56    #[serde(default, skip_serializing_if = "is_false")]
57    pub redacted: bool,
58    /// Optional role marker for thought chunks.
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub role: Option<String>,
61}
62
63#[allow(clippy::trivially_copy_pass_by_ref)]
64const fn is_false(b: &bool) -> bool {
65    !*b
66}
67
68/// Payload for `stream.close`.
69#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
70pub struct StreamClosePayload {
71    /// Optional final sequence number.
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub final_sequence: Option<u64>,
74}
75
76/// Payload for `stream.error`.
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct StreamErrorPayload {
79    /// Canonical error code.
80    pub code: ErrorCode,
81    /// Human-readable message.
82    pub message: String,
83}