use std::fmt;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex, PoisonError};
use std::time::Duration;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64;
use futures_util::{SinkExt, StreamExt};
use serde_json::{Value, json};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::{Notify, mpsc, oneshot};
use tokio::task::{JoinHandle, JoinSet};
use tokio_tungstenite::accept_hdr_async;
use tokio_tungstenite::tungstenite::handshake::server::{ErrorResponse, Request, Response};
use tokio_tungstenite::tungstenite::http::StatusCode;
use tokio_tungstenite::tungstenite::protocol::{CloseFrame, frame::coding::CloseCode};
use tokio_tungstenite::tungstenite::{Bytes as WsBytes, Message, Utf8Bytes};
use tokio_util::sync::CancellationToken;
pub const FRAME_BYTES: usize = 160;
pub const F_SILENCE: [u8; FRAME_BYTES] = [0xFF; FRAME_BYTES];
pub const F_SILENCE_BASE64: &str = "/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w==";
pub const F_RAMP_BASE64: &str = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2enw==";
pub const FIXTURE_BEARER: &str = "fixture-bearer";
pub const OBSERVATION_BOUND: Duration = Duration::from_secs(10);
#[must_use]
pub fn tone_frame(index: usize) -> [u8; FRAME_BYTES] {
let mut frame = [0u8; FRAME_BYTES];
let base = index.wrapping_mul(FRAME_BYTES);
for (offset, byte) in frame.iter_mut().enumerate() {
*byte = u8::try_from(base.wrapping_add(offset) % 256).unwrap_or_default();
}
frame
}
#[must_use]
pub fn tone_bytes(frames: usize) -> Vec<u8> {
(0..frames).flat_map(tone_frame).collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UpgradeOutcome {
Accepted,
Refused(u16),
}
#[derive(Debug, Clone)]
pub struct Upgrade {
pub target: String,
pub authorization: Option<String>,
pub header_names: Vec<String>,
pub outcome: UpgradeOutcome,
}
#[derive(Debug, Clone)]
pub enum ClientEvent {
SessionUpdate(Value),
Append {
audio: Vec<u8>,
},
Cancel,
Outside {
event_type: String,
},
Unreadable {
reason: String,
},
}
#[derive(Debug, Clone, Default)]
pub struct Record {
pub upgrades: Vec<Upgrade>,
pub client_events: Vec<ClientEvent>,
pub appended_audio: Vec<u8>,
pub pings: usize,
pub deltas_sent: usize,
pub deltas_suppressed: usize,
pub sessions_ended: usize,
}
impl Record {
#[must_use]
pub fn appends(&self) -> usize {
self.client_events
.iter()
.filter(|event| matches!(event, ClientEvent::Append { .. }))
.count()
}
#[must_use]
pub fn cancels(&self) -> usize {
self.client_events
.iter()
.filter(|event| matches!(event, ClientEvent::Cancel))
.count()
}
#[must_use]
pub fn session_updates(&self) -> Vec<&Value> {
self.client_events
.iter()
.filter_map(|event| match event {
ClientEvent::SessionUpdate(update) => Some(update),
_ => None,
})
.collect()
}
#[must_use]
pub fn events_outside_the_client_subset(&self) -> Vec<String> {
self.client_events
.iter()
.filter_map(|event| match event {
ClientEvent::Outside { event_type } => Some(event_type.clone()),
ClientEvent::Unreadable { reason } => Some(format!("unreadable: {reason}")),
_ => None,
})
.collect()
}
#[must_use]
pub fn accepted(&self) -> usize {
self.outcomes(UpgradeOutcome::Accepted)
}
#[must_use]
pub fn refused(&self) -> usize {
self.upgrades
.iter()
.filter(|upgrade| matches!(upgrade.outcome, UpgradeOutcome::Refused(_)))
.count()
}
fn outcomes(&self, outcome: UpgradeOutcome) -> usize {
self.upgrades
.iter()
.filter(|upgrade| upgrade.outcome == outcome)
.count()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Withhold {
#[default]
Nothing,
SessionCreated,
SessionUpdated,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StallPoint {
Upgrade,
Session,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CancelPolicy {
#[default]
Truncate,
KeepStreaming,
}
#[derive(Debug, Clone)]
pub enum Malformed {
NotJson,
NoType,
Binary,
DeltaNotBase64 {
response: String,
},
DeltaMissing {
response: String,
},
AudioDoneWithoutResponseId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Emission {
Sent,
SuppressedByCancel,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PeerError {
#[error("the stand-in peer could not bind a loopback listener: {0}")]
Bind(String),
#[error("the stand-in peer has no connected session")]
NoSession,
#[error("the stand-in peer's session ended before the directive was performed")]
SessionEnded,
#[error("the stand-in peer did not observe {what} within {OBSERVATION_BOUND:?}")]
NotObserved {
what: String,
},
}
#[derive(Debug, Clone)]
pub struct PeerConfig {
bearer: String,
withhold: Withhold,
stall: Option<StallPoint>,
cancel: CancelPolicy,
}
impl Default for PeerConfig {
fn default() -> Self {
Self::new()
}
}
impl PeerConfig {
#[must_use]
pub fn new() -> Self {
Self {
bearer: FIXTURE_BEARER.to_owned(),
withhold: Withhold::Nothing,
stall: None,
cancel: CancelPolicy::Truncate,
}
}
#[must_use]
pub fn expecting_bearer(mut self, bearer: &str) -> Self {
bearer.clone_into(&mut self.bearer);
self
}
#[must_use]
pub fn withholding(mut self, withhold: Withhold) -> Self {
self.withhold = withhold;
self
}
#[must_use]
pub fn stalling_at(mut self, stall: StallPoint) -> Self {
self.stall = Some(stall);
self
}
#[must_use]
pub fn on_cancel(mut self, cancel: CancelPolicy) -> Self {
self.cancel = cancel;
self
}
pub async fn start(self) -> Result<RealtimePeer, PeerError> {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.map_err(|error| PeerError::Bind(error.to_string()))?;
let addr = listener
.local_addr()
.map_err(|error| PeerError::Bind(error.to_string()))?;
let shared = Arc::new(Shared::default());
let shutdown = CancellationToken::new();
let accepting = tokio::spawn(accept(
listener,
self,
Arc::clone(&shared),
shutdown.clone(),
));
Ok(RealtimePeer {
url: format!("ws://{addr}/v1/realtime"),
addr,
shared,
shutdown,
accepting: Some(accepting),
})
}
}
#[derive(Debug)]
pub struct RealtimePeer {
url: String,
addr: SocketAddr,
shared: Arc<Shared>,
shutdown: CancellationToken,
accepting: Option<JoinHandle<()>>,
}
impl Drop for RealtimePeer {
fn drop(&mut self) {
self.shutdown.cancel();
}
}
impl RealtimePeer {
#[must_use]
pub fn url(&self) -> &str {
&self.url
}
#[must_use]
pub fn addr(&self) -> SocketAddr {
self.addr
}
#[must_use]
pub fn record(&self) -> Record {
self.shared.snapshot()
}
pub async fn observe<F>(&self, what: &str, condition: F) -> Result<Record, PeerError>
where
F: Fn(&Record) -> bool,
{
tokio::time::timeout(OBSERVATION_BOUND, async {
loop {
let changed = self.shared.changed.notified();
{
let record = self.shared.lock();
if condition(&record) {
return record.clone();
}
}
changed.await;
}
})
.await
.map_err(|_elapsed| PeerError::NotObserved {
what: what.to_owned(),
})
}
pub async fn await_upgrade(&self) -> Result<Record, PeerError> {
self.observe("an upgrade", |record| !record.upgrades.is_empty())
.await
}
pub async fn await_session_update(&self) -> Result<Record, PeerError> {
self.observe("a session.update", |record| {
!record.session_updates().is_empty()
})
.await
}
pub async fn await_appends(&self, count: usize) -> Result<Record, PeerError> {
self.observe(&format!("{count} appends"), move |record| {
record.appends() >= count
})
.await
}
pub async fn await_cancel(&self) -> Result<Record, PeerError> {
self.observe("a response.cancel", |record| record.cancels() > 0)
.await
}
pub async fn send_delta(&self, response: &str, audio: &[u8]) -> Result<Emission, PeerError> {
self.direct(Action::Delta {
response: response.to_owned(),
audio: audio.to_vec(),
})
.await
}
pub async fn speak_tone(&self, response: &str, frames: usize) -> Result<usize, PeerError> {
let mut sent = 0;
for frame in 0..frames {
if self.send_delta(response, &tone_frame(frame)).await? == Emission::SuppressedByCancel
{
break;
}
sent += 1;
}
Ok(sent)
}
pub async fn send_audio_done(&self, response: &str) -> Result<Emission, PeerError> {
self.direct(Action::Scripted(Scripted::AudioDone {
response: Some(response.to_owned()),
}))
.await
}
pub async fn send_response_done(
&self,
response: &str,
status: &str,
) -> Result<Emission, PeerError> {
self.direct(Action::Scripted(Scripted::ResponseDone {
response: response.to_owned(),
status: status.to_owned(),
}))
.await
}
pub async fn send_speech_started(&self) -> Result<Emission, PeerError> {
self.direct(Action::Scripted(Scripted::SpeechStarted)).await
}
pub async fn send_error(&self, code: &str, message: &str) -> Result<Emission, PeerError> {
self.direct(Action::Scripted(Scripted::Error {
code: code.to_owned(),
message: message.to_owned(),
}))
.await
}
pub async fn send_unknown(&self, event_type: &str) -> Result<Emission, PeerError> {
self.direct(Action::Scripted(Scripted::Unknown {
event_type: event_type.to_owned(),
}))
.await
}
pub async fn send_malformed(&self, malformed: Malformed) -> Result<Emission, PeerError> {
self.direct(Action::Malformed(malformed)).await
}
pub async fn send_oversize(&self, bytes: usize) -> Result<Emission, PeerError> {
self.direct(Action::Scripted(Scripted::Oversize { bytes }))
.await
}
pub async fn close_normally(&self) -> Result<Emission, PeerError> {
self.direct(Action::Close { code: 1000 }).await
}
pub async fn reset(&self) -> Result<Emission, PeerError> {
self.direct(Action::Reset).await
}
pub async fn shutdown(mut self) {
self.shutdown.cancel();
if let Some(accepting) = self.accepting.take() {
let _joined = accepting.await;
}
}
async fn direct(&self, action: Action) -> Result<Emission, PeerError> {
let session = self.shared.session().ok_or(PeerError::NoSession)?;
let (done, performed) = oneshot::channel();
session
.send(Directive { action, done })
.await
.map_err(|_closed| PeerError::SessionEnded)?;
performed.await.map_err(|_closed| PeerError::SessionEnded)
}
}
#[derive(Debug, Default)]
struct Shared {
record: Mutex<Record>,
changed: Notify,
session: Mutex<Option<(u64, mpsc::Sender<Directive>)>>,
generations: Mutex<u64>,
}
impl Shared {
fn lock(&self) -> std::sync::MutexGuard<'_, Record> {
self.record.lock().unwrap_or_else(PoisonError::into_inner)
}
fn update<F: FnOnce(&mut Record)>(&self, edit: F) {
edit(&mut self.lock());
self.changed.notify_waiters();
}
fn snapshot(&self) -> Record {
self.lock().clone()
}
fn register(&self, sender: mpsc::Sender<Directive>) -> u64 {
let mut generations = self
.generations
.lock()
.unwrap_or_else(PoisonError::into_inner);
*generations += 1;
let generation = *generations;
*self.session.lock().unwrap_or_else(PoisonError::into_inner) = Some((generation, sender));
generation
}
fn unregister(&self, generation: u64) {
let mut session = self.session.lock().unwrap_or_else(PoisonError::into_inner);
if session
.as_ref()
.is_some_and(|(open, _)| *open == generation)
{
*session = None;
}
}
fn session(&self) -> Option<mpsc::Sender<Directive>> {
self.session
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
.map(|(_, sender)| sender.clone())
}
}
#[derive(Debug)]
struct Directive {
action: Action,
done: oneshot::Sender<Emission>,
}
#[derive(Debug)]
enum Action {
Delta { response: String, audio: Vec<u8> },
Scripted(Scripted),
Malformed(Malformed),
Close { code: u16 },
Reset,
}
#[derive(Debug)]
enum Scripted {
AudioDone { response: Option<String> },
ResponseDone { response: String, status: String },
SpeechStarted,
Error { code: String, message: String },
Unknown { event_type: String },
Oversize { bytes: usize },
}
type Socket = tokio_tungstenite::WebSocketStream<TcpStream>;
#[derive(Debug, Default, PartialEq, Eq)]
enum Cancelled {
#[default]
No,
Pending,
Response(String),
}
#[derive(Debug, Default)]
struct Session {
in_flight: Option<String>,
cancelled: Cancelled,
events: u32,
}
impl Session {
fn next_event_id(&mut self) -> String {
self.events += 1;
format!("event_{:03}", self.events)
}
fn is_cancelled(&self, response: &str) -> bool {
match &self.cancelled {
Cancelled::No => false,
Cancelled::Pending => true,
Cancelled::Response(cancelled) => cancelled == response,
}
}
}
async fn accept(
listener: TcpListener,
config: PeerConfig,
shared: Arc<Shared>,
shutdown: CancellationToken,
) {
let mut sessions = JoinSet::new();
loop {
tokio::select! {
() = shutdown.cancelled() => break,
accepted = listener.accept() => {
let Ok((stream, _from)) = accepted else { break };
sessions.spawn(serve(
stream,
config.clone(),
Arc::clone(&shared),
shutdown.child_token(),
));
}
Some(_finished) = sessions.join_next(), if !sessions.is_empty() => {}
}
}
sessions.shutdown().await;
}
#[allow(clippy::result_large_err)] async fn serve(
stream: TcpStream,
config: PeerConfig,
shared: Arc<Shared>,
shutdown: CancellationToken,
) {
let expected = format!("Bearer {}", config.bearer);
let inspecting = Arc::clone(&shared);
let upgraded = accept_hdr_async(stream, move |request: &Request, response: Response| {
inspect_upgrade(request, response, &expected, &inspecting)
})
.await;
let Ok(mut socket) = upgraded else {
return;
};
if config.stall == Some(StallPoint::Upgrade) {
shutdown.cancelled().await;
return;
}
let (directives, mut inbox) = mpsc::channel::<Directive>(16);
let _own = directives.clone();
let generation = shared.register(directives);
let mut session = Session::default();
if config.withhold != Withhold::SessionCreated {
let created = json!({
"type": "session.created",
"event_id": session.next_event_id(),
"session": {"id": "sess_fixture", "object": "realtime.session", "type": "realtime"},
});
if write(&mut socket, created).await == Next::End {
finish(&shared, generation);
return;
}
}
loop {
let step = tokio::select! {
biased;
() = shutdown.cancelled() => Step::Stop,
frame = socket.next() => Step::Inbound(frame),
directive = inbox.recv() => Step::Directed(directive),
};
let next = match step {
Step::Stop | Step::Directed(None) => Next::End,
Step::Inbound(frame) => {
inbound(frame, &mut socket, &config, &shared, &mut session).await
}
Step::Directed(Some(directive)) => {
directed(directive, &mut socket, &config, &shared, &mut session).await
}
};
match next {
Next::Serve => {}
Next::End => break,
Next::Stall => {
shutdown.cancelled().await;
break;
}
}
}
finish(&shared, generation);
}
enum Step {
Stop,
Inbound(Option<Result<Message, tokio_tungstenite::tungstenite::Error>>),
Directed(Option<Directive>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Next {
Serve,
End,
Stall,
}
fn finish(shared: &Arc<Shared>, generation: u64) {
shared.unregister(generation);
shared.update(|record| record.sessions_ended += 1);
}
#[allow(clippy::result_large_err)]
fn inspect_upgrade(
request: &Request,
response: Response,
expected: &str,
shared: &Arc<Shared>,
) -> Result<Response, ErrorResponse> {
let authorization = request
.headers()
.get("authorization")
.and_then(|value| value.to_str().ok())
.map(str::to_owned);
let authorised = authorization.as_deref() == Some(expected);
let upgrade = Upgrade {
target: request.uri().to_string(),
authorization,
header_names: request
.headers()
.keys()
.map(|name| name.as_str().to_owned())
.collect(),
outcome: if authorised {
UpgradeOutcome::Accepted
} else {
UpgradeOutcome::Refused(401)
},
};
shared.update(move |record| record.upgrades.push(upgrade));
if authorised {
Ok(response)
} else {
let mut refusal = ErrorResponse::new(Some(
"invalid_request_error: the bearer token is missing or does not match".to_owned(),
));
*refusal.status_mut() = StatusCode::UNAUTHORIZED;
Err(refusal)
}
}
async fn inbound(
frame: Option<Result<Message, tokio_tungstenite::tungstenite::Error>>,
socket: &mut Socket,
config: &PeerConfig,
shared: &Arc<Shared>,
session: &mut Session,
) -> Next {
match frame {
Some(Ok(Message::Text(text))) => {
let event = read_client_event(&text);
match &event {
ClientEvent::Cancel => {
session.cancelled = session
.in_flight
.clone()
.map_or(Cancelled::Pending, Cancelled::Response);
}
ClientEvent::Append { audio } => {
let audio = audio.clone();
shared.update(|record| record.appended_audio.extend_from_slice(&audio));
}
_ => {}
}
let reply_wanted = matches!(event, ClientEvent::SessionUpdate(_))
&& config.withhold != Withhold::SessionUpdated;
let stall_now = config.stall == Some(StallPoint::Session)
&& matches!(event, ClientEvent::Append { .. });
shared.update(move |record| record.client_events.push(event));
if stall_now {
return Next::Stall;
}
if reply_wanted {
let updated = json!({
"type": "session.updated",
"event_id": session.next_event_id(),
"session": {"id": "sess_fixture", "object": "realtime.session", "type": "realtime"},
});
return write(socket, updated).await;
}
Next::Serve
}
Some(Ok(Message::Binary(bytes))) => {
shared.update(move |record| {
record.client_events.push(ClientEvent::Unreadable {
reason: format!("a binary frame of {} bytes", bytes.len()),
});
});
Next::Serve
}
Some(Ok(Message::Ping(_))) => {
shared.update(|record| record.pings += 1);
let _flushed = socket.flush().await;
Next::Serve
}
Some(Ok(Message::Pong(_) | Message::Frame(_))) => Next::Serve,
Some(Ok(Message::Close(_)) | Err(_)) | None => Next::End,
}
}
fn read_client_event(text: &str) -> ClientEvent {
let Ok(event) = serde_json::from_str::<Value>(text) else {
return ClientEvent::Unreadable {
reason: "a text frame that is not JSON".to_owned(),
};
};
let Some(event_type) = event.get("type").and_then(Value::as_str) else {
return ClientEvent::Unreadable {
reason: "a JSON frame with no string `type`".to_owned(),
};
};
match event_type {
"session.update" => ClientEvent::SessionUpdate(event.clone()),
"response.cancel" => ClientEvent::Cancel,
"input_audio_buffer.append" => match event
.get("audio")
.and_then(Value::as_str)
.map(|audio| BASE64.decode(audio))
{
Some(Ok(audio)) => ClientEvent::Append { audio },
Some(Err(error)) => ClientEvent::Unreadable {
reason: format!("an append whose audio is not RFC 4648 §4 base64: {error}"),
},
None => ClientEvent::Unreadable {
reason: "an append with no string `audio` member".to_owned(),
},
},
other => ClientEvent::Outside {
event_type: other.to_owned(),
},
}
}
async fn directed(
directive: Directive,
socket: &mut Socket,
config: &PeerConfig,
shared: &Arc<Shared>,
session: &mut Session,
) -> Next {
let Directive { action, done } = directive;
match action {
Action::Delta { response, audio } => {
if config.cancel == CancelPolicy::Truncate && session.is_cancelled(&response) {
shared.update(|record| record.deltas_suppressed += 1);
let _answered = done.send(Emission::SuppressedByCancel);
return Next::Serve;
}
session.in_flight = Some(response.clone());
let delta = json!({
"type": "response.output_audio.delta",
"event_id": session.next_event_id(),
"response_id": response,
"item_id": "item_fixture",
"output_index": 0,
"content_index": 0,
"delta": BASE64.encode(&audio),
});
let flow = write(socket, delta).await;
if flow == Next::Serve {
shared.update(|record| record.deltas_sent += 1);
}
answer(flow, done)
}
Action::Scripted(scripted) => {
let event = scripted_event(scripted, session);
answer(write(socket, event).await, done)
}
Action::Malformed(malformed) => malformed_frame(malformed, socket, session, done).await,
Action::Close { code } => {
let close = Message::Close(Some(CloseFrame {
code: CloseCode::from(code),
reason: Utf8Bytes::from_static("session ended"),
}));
let sent = socket.send(close).await.is_ok();
let _flushed = socket.flush().await;
let _answered = done.send(Emission::Sent);
if sent { Next::Serve } else { Next::End }
}
Action::Reset => {
#[allow(deprecated)]
let _lingered = socket.get_ref().set_linger(Some(Duration::ZERO));
let _answered = done.send(Emission::Sent);
Next::End
}
}
}
fn scripted_event(scripted: Scripted, session: &mut Session) -> Value {
match scripted {
Scripted::AudioDone { response } => {
let mut event = json!({
"type": "response.output_audio.done",
"event_id": session.next_event_id(),
"item_id": "item_fixture",
"output_index": 0,
"content_index": 0,
});
if let (Some(response), Some(members)) = (response, event.as_object_mut()) {
members.insert("response_id".to_owned(), Value::String(response));
}
event
}
Scripted::ResponseDone { response, status } => {
session.in_flight = None;
session.cancelled = Cancelled::No;
json!({
"type": "response.done",
"event_id": session.next_event_id(),
"response": {"id": response, "object": "realtime.response", "status": status},
})
}
Scripted::SpeechStarted => json!({
"type": "input_audio_buffer.speech_started",
"event_id": session.next_event_id(),
"audio_start_ms": 460,
"item_id": "item_fixture",
}),
Scripted::Error { code, message } => json!({
"type": "error",
"event_id": session.next_event_id(),
"error": {
"type": "invalid_request_error",
"code": code,
"message": message,
"param": Value::Null,
},
}),
Scripted::Unknown { event_type } => {
json!({"type": event_type, "event_id": session.next_event_id()})
}
Scripted::Oversize { bytes } => {
json!({
"type": "response.output_audio.delta",
"event_id": session.next_event_id(),
"response_id": "resp_oversize",
"delta": "A".repeat(bytes.next_multiple_of(4)),
})
}
}
}
async fn malformed_frame(
malformed: Malformed,
socket: &mut Socket,
session: &mut Session,
done: oneshot::Sender<Emission>,
) -> Next {
let flow = match malformed {
Malformed::NotJson => socket
.send(Message::Text(Utf8Bytes::from_static("not json{")))
.await
.map_or(Next::End, |()| Next::Serve),
Malformed::NoType => {
let event = json!({"event_id": session.next_event_id(), "session": {}});
write(socket, event).await
}
Malformed::Binary => socket
.send(Message::Binary(WsBytes::from_static(b"\x00\x01binary")))
.await
.map_or(Next::End, |()| Next::Serve),
Malformed::DeltaNotBase64 { response } => {
let event = json!({
"type": "response.output_audio.delta",
"event_id": session.next_event_id(),
"response_id": response,
"delta": "not base64!!",
});
write(socket, event).await
}
Malformed::DeltaMissing { response } => {
let event = json!({
"type": "response.output_audio.delta",
"event_id": session.next_event_id(),
"response_id": response,
"item_id": "item_fixture",
});
write(socket, event).await
}
Malformed::AudioDoneWithoutResponseId => {
let event = json!({
"type": "response.output_audio.done",
"event_id": session.next_event_id(),
"item_id": "item_fixture",
});
write(socket, event).await
}
};
answer(flow, done)
}
fn answer(flow: Next, done: oneshot::Sender<Emission>) -> Next {
if flow == Next::Serve {
let _answered = done.send(Emission::Sent);
}
flow
}
async fn write(socket: &mut Socket, event: Value) -> Next {
if socket
.send(Message::Text(event.to_string().into()))
.await
.is_err()
{
return Next::End;
}
match socket.flush().await {
Ok(()) => Next::Serve,
Err(_) => Next::End,
}
}
impl fmt::Display for Emission {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Sent => "sent",
Self::SuppressedByCancel => "suppressed by cancel",
})
}
}