Negotiate realtime voice calls in the TUI (#61172)

## What changed

- Create WebRTC calls using the TUI's local provider and credentials, then attach the app server through `existingCall` with the returned call ID.
- Preserve the effective voice preference and share realtime prompt preparation and thread headers between call creation and the backend observer.
- Run negotiation asynchronously, cancel pending startup when voice stops, and ignore results from stale startup attempts.

## Testing

Update voice request and handoff tests for client-created calls. Add coverage ensuring that canceling and restarting voice attaches only the new attempt, including when an earlier result is already queued.

GitOrigin-RevId: b704976f4918dddd24a17fbaa868a7c62e81885b
This commit is contained in:
Zahan Malkani
2026-11-12 19:43:12 +00:00
committed by copyberry
parent ae50f973f5
commit 72532ecae5
29 changed files with 611 additions and 182 deletions
+1
View File
@@ -18,6 +18,7 @@ pub(crate) mod telemetry;
/// Path of the Responses endpoint, relative to a provider's base URL.
pub const RESPONSES_PATH: &str = "/responses";
pub use crate::requests::headers::build_realtime_thread_headers;
pub use crate::requests::headers::build_session_headers;
pub use codex_client::RequestTelemetry;
pub use codex_client::ReqwestTransport;
@@ -1,7 +1,27 @@
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::ThreadSource;
use codex_utils_string::to_ascii_json_string;
use http::HeaderMap;
use http::HeaderValue;
/// Correlate both the media call and its observer with the same thread and session tree.
pub fn build_realtime_thread_headers(
session_id: String,
thread_id: String,
thread_source: Option<ThreadSource>,
) -> HeaderMap {
let mut headers = build_session_headers(Some(session_id), Some(thread_id));
// Voice calls can span zero or many backing turns. Only the saved thread source applies.
if let Some(source) = thread_source
&& source.as_str().len() <= 256
&& let Ok(metadata) = to_ascii_json_string(&serde_json::json!({ "thread_source": source }))
&& let Ok(value) = HeaderValue::from_str(&metadata)
{
headers.insert("x-codex-turn-metadata", value);
}
headers
}
pub fn build_session_headers(session_id: Option<String>, thread_id: Option<String>) -> HeaderMap {
let mut headers = HeaderMap::new();
if let Some(id) = session_id {