ralph_workflow/json_parser/streaming_state.rs
1//! Unified streaming state tracking module.
2//!
3//! This module provides a single source of truth for streaming state across
4//! all parsers (`Claude`, `Codex`, `Gemini`, `OpenCode`). It implements the streaming
5//! contract:
6//!
7//! # Streaming Contract
8//!
9//! 1. **Delta contract**: Each streaming event contains only newly generated text
10//! 2. **Message lifecycle**: `MessageStart` → (`ContentBlockStart` + deltas)* → `MessageStop`
11//! 3. **Deduplication rule**: Content displayed during streaming is never re-displayed
12//! 4. **State reset**: Streaming state resets on `MessageStart`/Init events
13//!
14//! # Message Lifecycle
15//!
16//! The streaming message lifecycle follows this sequence:
17//!
18//! 1. **`MessageStart`** (or equivalent init event):
19//! - Resets all streaming state to `Idle`
20//! - Clears accumulated content
21//! - Resets content block state
22//!
23//! 2. **`ContentBlockStart`** (optional, for each content block):
24//! - If already in a block with `started_output=true`, finalizes the previous block
25//! - Initializes tracking for the new block index
26//! - Clears any accumulated content for this block index
27//!
28//! 3. **Text/Thinking deltas** (zero or more per block):
29//! - First delta for each `(content_type, index)` shows prefix
30//! - Subsequent deltas update in-place (with prefix, using carriage return)
31//! - Sets `started_output=true` for the current block
32//!
33//! 4. **`MessageStop`**:
34//! - Finalizes the current content block
35//! - Marks message as displayed to prevent duplicate final output
36//! - Returns whether content was streamed (for emitting completion newline)
37//!
38//! # Content Block Transitions
39//!
40//! When transitioning between content blocks (e.g., block 0 → block 1):
41//!
42//! ```ignore
43//! // Streaming "Hello" in block 0
44//! session.on_text_delta(0, "Hello"); // started_output = true
45//!
46//! // Transition to block 1
47//! session.on_content_block_start(1); // Finalizes block 0, started_output was true
48//!
49//! // Stream "World" in block 1
50//! session.on_text_delta(1, "World"); // New block, shows prefix again
51//! ```
52//!
53//! The `ContentBlockState::InBlock { index, started_output }` tracks:
54//! - `index`: Which block is currently active
55//! - `started_output`: Whether any content was output for this block
56//!
57//! This state enables proper finalization of previous blocks when new ones start.
58//!
59//! # Delta Contract
60//!
61//! This module enforces a strict **delta contract** - all streaming events must
62//! contain only the newly generated text (deltas), not the full accumulated content.
63//!
64//! Treating snapshots as deltas causes exponential duplication bugs. The session
65//! validates that incoming content is genuinely delta-sized and rejects likely
66//! snapshot-as-delta violations.
67//!
68//! # Example
69//!
70//! ```ignore
71//! use crate::json_parser::streaming_state::{StreamingSession, StreamingState};
72//!
73//! let mut session = StreamingSession::new();
74//!
75//! // Message starts - reset state
76//! session.on_message_start();
77//!
78//! // Content block starts
79//! session.on_content_block_start(0);
80//!
81//! // Text deltas arrive - accumulate and display
82//! let should_show_prefix = session.on_text_delta(0, "Hello");
83//! assert!(should_show_prefix); // First chunk shows prefix
84//!
85//! let should_show_prefix = session.on_text_delta(0, " World");
86//! assert!(!should_show_prefix); // Subsequent chunks don't show prefix
87//!
88//! // Check if content was already streamed (for deduplication)
89//! assert!(session.has_any_streamed_content());
90//!
91//! // Message stops - finalize
92//! session.on_message_stop();
93//! ```
94
95// Re-export ContentType from types module for use in sub-modules
96use crate::json_parser::types::ContentType;
97
98// Contract types: constants, thresholds, and state enums
99include!("streaming_state/contract.rs");
100
101// Session implementation: StreamingSession struct and all methods
102include!("streaming_state/session.rs");
103
104// Tests: all unit tests for streaming state functionality
105include!("streaming_state/tests.rs");