use serde::{Deserialize, Serialize};
use crate::blob::BlobId;
use crate::types::{ContentBlock, StopReason, Usage};
pub const SESSION_REALTIME_TRANSCRIPT_STATE_KEY: &str = "realtime_transcript_state";
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RealtimeTranscriptRole {
User,
Assistant,
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TranscriptLane {
#[default]
Display,
Spoken,
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RealtimeUserContentIdentity {
pub idempotency_key: String,
pub item_id: String,
pub previous_item_id: Option<String>,
pub content_index: u32,
pub blob_id: BlobId,
pub media_type: String,
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PendingRealtimeUserContentBlob {
pub idempotency_key: String,
pub item_id: String,
pub previous_item_id: Option<String>,
pub content_index: u32,
pub blob_id: BlobId,
pub media_type: String,
}
impl PendingRealtimeUserContentBlob {
#[must_use]
pub fn identity(&self) -> RealtimeUserContentIdentity {
RealtimeUserContentIdentity {
idempotency_key: self.idempotency_key.clone(),
item_id: self.item_id.clone(),
previous_item_id: self.previous_item_id.clone(),
content_index: self.content_index,
blob_id: self.blob_id.clone(),
media_type: self.media_type.clone(),
}
}
#[must_use]
pub fn canonical_event(&self) -> RealtimeTranscriptEvent {
RealtimeTranscriptEvent::UserContentFinal {
idempotency_key: self.idempotency_key.clone(),
item_id: self.item_id.clone(),
previous_item_id: self.previous_item_id.clone(),
content_index: self.content_index,
content: vec![ContentBlock::Image {
media_type: self.media_type.clone(),
data: crate::types::ImageData::Blob {
blob_id: self.blob_id.clone(),
},
}],
}
}
#[must_use]
pub fn matches_identity(&self, identity: &RealtimeUserContentIdentity) -> bool {
self.identity() == *identity
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct RealtimeUserContentTombstone {
pub idempotency_key: String,
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RealtimeTranscriptEvent {
ItemObserved {
item_id: String,
previous_item_id: Option<String>,
role: RealtimeTranscriptRole,
response_id: Option<String>,
},
ItemSkipped {
item_id: String,
previous_item_id: Option<String>,
},
UserTranscriptFinal {
item_id: String,
previous_item_id: Option<String>,
content_index: u32,
text: String,
},
#[cfg_attr(feature = "schema", schemars(skip))]
UserContentFinal {
idempotency_key: String,
item_id: String,
previous_item_id: Option<String>,
content_index: u32,
content: Vec<ContentBlock>,
},
AssistantTextDelta {
response_id: String,
delta_id: String,
item_id: String,
previous_item_id: Option<String>,
content_index: u32,
delta: String,
},
AssistantTranscriptDelta {
response_id: String,
delta_id: String,
item_id: String,
previous_item_id: Option<String>,
content_index: u32,
delta: String,
},
AssistantTranscriptTruncated {
response_id: String,
item_id: String,
content_index: u32,
text: String,
},
AssistantTranscriptFinalText {
response_id: String,
item_id: String,
content_index: u32,
text: String,
},
AssistantTurnCompleted {
response_id: String,
stop_reason: StopReason,
usage: Usage,
},
AssistantTurnInterrupted { response_id: String },
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AppendRealtimeTranscript {
pub item_id: String,
pub text: String,
pub role: RealtimeTranscriptRole,
pub lane: TranscriptLane,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RealtimeTranscriptMaterializedMessage {
User {
item_id: String,
text: String,
},
Assistant {
item_id: String,
response_id: String,
text: String,
stop_reason: StopReason,
usage: Usage,
lane: TranscriptLane,
},
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct RealtimeTranscriptApplyOutcome {
pub materialized_messages: Vec<RealtimeTranscriptMaterializedMessage>,
pub user_content: Option<RealtimeUserContentApplyOutcome>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RealtimeUserContentApplyOutcome {
Committed(RealtimeUserContentIdentity),
AlreadyCommitted(RealtimeUserContentIdentity),
RejectedInvalidIdentity {
idempotency_key: String,
},
RejectedUnmaterializedPredecessor {
idempotency_key: String,
previous_item_id: Option<String>,
},
RejectedConflict {
idempotency_key: String,
},
}
impl RealtimeTranscriptApplyOutcome {
#[must_use]
pub fn is_inert(&self) -> bool {
self.materialized_messages.is_empty() && self.user_content.is_none()
}
}