Retry realtime sideband attachment while an existing call activates (#51460)

## Why

Instant Connect can return a call ID before ICE activates the call, causing sideband attachment to briefly fail with HTTP `404` while the client connects media.

## What changed

Retry `404` responses from `connect_existing_call_sideband` up to three times, waiting 100, 300, and 800 milliseconds. Return the error once this activation retry budget is exhausted, and preserve the existing handling of other errors.

## Testing

Add WebSocket coverage for successful attachment after three `404` responses, failure after four `404` responses, and no retry on HTTP `410`. Verify that successful attachment does not overwrite the prepared session configuration.

GitOrigin-RevId: 5d18ff7eeeb9abd56388808828382f7350c7ae57
This commit is contained in:
bullman-oai
2026-10-06 19:13:04 +00:00
committed by copyberry
parent 414d165b47
commit 44984d2081
2 changed files with 108 additions and 9 deletions
@@ -42,6 +42,7 @@ use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Duration;
use tokio::sync::Mutex;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
@@ -908,15 +909,28 @@ impl RealtimeWebsocketClient {
default_headers: HeaderMap,
transcript_state: RealtimeTranscriptState,
) -> Result<RealtimeWebsocketConnection, ApiError> {
self.connect_sideband(
config,
call_id,
extra_headers,
default_headers,
RealtimeSessionInitialization::ExistingCall,
transcript_state,
)
.await
// Instant Connect returns a call ID before ICE activates the call. Its sideband
// can briefly return 404 while the client connects media in parallel.
let mut activation_delays = [100, 300, 800].into_iter();
loop {
let result = self
.connect_sideband(
config.clone(),
call_id,
extra_headers.clone(),
default_headers.clone(),
RealtimeSessionInitialization::ExistingCall,
transcript_state.clone(),
)
.await;
if matches!(&result, Err(ApiError::Api { status, .. }) if *status == StatusCode::NOT_FOUND)
&& let Some(delay_ms) = activation_delays.next()
{
sleep(Duration::from_millis(delay_ms)).await;
continue;
}
return result;
}
}
async fn connect_sideband(