Skip to main content

meerkat_core/
realtime_transcript.rs

1//! Typed realtime transcript append seam.
2//!
3//! Provider adapters translate provider-native realtime events into these
4//! identity-bearing events. The session layer owns idempotency, causal ordering,
5//! and the decision to materialize canonical transcript messages.
6
7use serde::{Deserialize, Serialize};
8
9use crate::types::{StopReason, Usage};
10
11/// Durable session metadata key for realtime transcript append state.
12pub const SESSION_REALTIME_TRANSCRIPT_STATE_KEY: &str = "realtime_transcript_state";
13
14/// Provider-neutral role for a realtime transcript item.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum RealtimeTranscriptRole {
18    User,
19    Assistant,
20}
21
22/// A typed, identity-bearing realtime transcript event consumed by the session.
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24#[serde(tag = "type", rename_all = "snake_case")]
25pub enum RealtimeTranscriptEvent {
26    /// Observe a provider item and its causal predecessor without committing
27    /// content yet.
28    ItemObserved {
29        item_id: String,
30        previous_item_id: Option<String>,
31        role: RealtimeTranscriptRole,
32        response_id: Option<String>,
33    },
34    /// Observe a provider item that participates in provider causal ordering
35    /// but must not materialize transcript content.
36    ItemSkipped {
37        item_id: String,
38        previous_item_id: Option<String>,
39    },
40    /// Provider finalized the transcript for a user input item.
41    UserTranscriptFinal {
42        item_id: String,
43        previous_item_id: Option<String>,
44        content_index: u32,
45        text: String,
46    },
47    /// Provider emitted an assistant text delta for an output item.
48    AssistantTextDelta {
49        response_id: String,
50        delta_id: String,
51        item_id: String,
52        previous_item_id: Option<String>,
53        content_index: u32,
54        delta: String,
55    },
56    /// Provider reported the assistant output item was truncated to the heard
57    /// transcript prefix.
58    AssistantTranscriptTruncated {
59        response_id: String,
60        item_id: String,
61        content_index: u32,
62        text: String,
63    },
64    /// Provider turn reached a terminal boundary. The session decides which
65    /// staged assistant items, if any, are now canonical.
66    AssistantTurnCompleted {
67        response_id: String,
68        stop_reason: StopReason,
69        usage: Usage,
70    },
71    /// Provider turn was interrupted before terminal materialization.
72    AssistantTurnInterrupted { response_id: String },
73}
74
75/// Canonical message materialized by applying a realtime transcript event.
76#[derive(Debug, Clone, PartialEq)]
77pub enum RealtimeTranscriptMaterializedMessage {
78    User {
79        item_id: String,
80        text: String,
81    },
82    Assistant {
83        item_id: String,
84        response_id: String,
85        text: String,
86        stop_reason: StopReason,
87        usage: Usage,
88    },
89}
90
91/// Result of applying a realtime transcript event.
92#[derive(Debug, Clone, Default, PartialEq)]
93pub struct RealtimeTranscriptApplyOutcome {
94    pub materialized_messages: Vec<RealtimeTranscriptMaterializedMessage>,
95}
96
97impl RealtimeTranscriptApplyOutcome {
98    #[must_use]
99    pub fn is_inert(&self) -> bool {
100        self.materialized_messages.is_empty()
101    }
102}