mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Forward client voice session IDs in realtime requests (#53784)
## What changed Add optional `voiceSessionId` to realtime start parameters and forward it as the `voice-session-id` header for WebSocket, WebRTC, and existing-call transports. Keep this client-generated voice session identifier distinct from `realtimeSessionId`, which uses `x-session-id`. ## Testing Extend serialization tests and add coverage for distinct session ID headers, WebRTC request forwarding, and existing-call handshake forwarding. GitOrigin-RevId: cb558dbb9d438f2d34e281199a7e81fe5d33b879
This commit is contained in:
BIN
Binary file not shown.
@@ -4083,6 +4083,7 @@ mod tests {
|
||||
realtime_end_instructions: Some("Resume normal text responses.".to_string()),
|
||||
prompt: Some(Some("You are on a call".to_string())),
|
||||
realtime_session_id: Some("sess_456".to_string()),
|
||||
voice_session_id: Some("voice_789".to_string()),
|
||||
transport: None,
|
||||
version: Some(RealtimeConversationVersion::V3),
|
||||
voice: Some(RealtimeVoice::Marin),
|
||||
@@ -4122,6 +4123,7 @@ mod tests {
|
||||
"realtimeEndInstructions": "Resume normal text responses.",
|
||||
"prompt": "You are on a call",
|
||||
"realtimeSessionId": "sess_456",
|
||||
"voiceSessionId": "voice_789",
|
||||
"transport": null,
|
||||
"version": "v3",
|
||||
"voice": "marin"
|
||||
@@ -4154,6 +4156,7 @@ mod tests {
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -4179,6 +4182,7 @@ mod tests {
|
||||
"realtimeStartInstructions": null,
|
||||
"realtimeEndInstructions": null,
|
||||
"realtimeSessionId": null,
|
||||
"voiceSessionId": null,
|
||||
"transport": null,
|
||||
"version": null,
|
||||
"voice": null
|
||||
@@ -4207,6 +4211,7 @@ mod tests {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(None),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -4233,6 +4238,7 @@ mod tests {
|
||||
"realtimeEndInstructions": null,
|
||||
"prompt": null,
|
||||
"realtimeSessionId": null,
|
||||
"voiceSessionId": null,
|
||||
"transport": null,
|
||||
"version": null,
|
||||
"voice": null
|
||||
|
||||
@@ -257,6 +257,10 @@ pub struct ThreadRealtimeStartParams {
|
||||
pub prompt: Option<Option<String>>,
|
||||
#[ts(optional = nullable)]
|
||||
pub realtime_session_id: Option<String>,
|
||||
/// Client-generated identifier for the product voice session. This is distinct from
|
||||
/// `realtimeSessionId`.
|
||||
#[ts(optional = nullable)]
|
||||
pub voice_session_id: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub transport: Option<ThreadRealtimeStartTransport>,
|
||||
/// Overrides the configured realtime protocol version for this session only.
|
||||
|
||||
@@ -1283,6 +1283,7 @@ impl TurnRequestProcessor {
|
||||
realtime_end_instructions: params.realtime_end_instructions,
|
||||
prompt: params.prompt,
|
||||
realtime_session_id: params.realtime_session_id,
|
||||
voice_session_id: params.voice_session_id,
|
||||
transport: params.transport.map(|transport| match transport {
|
||||
ThreadRealtimeStartTransport::Websocket => {
|
||||
ConversationStartTransport::Websocket
|
||||
|
||||
@@ -149,6 +149,7 @@ async fn realtime_conversation_start_requires_experimental_api_capability() -> R
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("hello".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -291,6 +292,7 @@ async fn realtime_webrtc_start_requires_experimental_api_capability() -> Result<
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("hello".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ThreadRealtimeStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
|
||||
@@ -411,6 +411,7 @@ impl RealtimeE2eHarness {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ThreadRealtimeStartTransport::Webrtc {
|
||||
sdp: offer_sdp.to_string(),
|
||||
}),
|
||||
@@ -473,6 +474,7 @@ impl RealtimeE2eHarness {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -511,6 +513,7 @@ impl RealtimeE2eHarness {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: Some(RealtimeConversationVersion::V3),
|
||||
voice: None,
|
||||
@@ -729,6 +732,7 @@ async fn realtime_conversation_streams_timeline_items() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -927,6 +931,7 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: Some(RealtimeVoice::Cedar),
|
||||
@@ -1507,6 +1512,7 @@ async fn realtime_start_can_skip_startup_context() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -1605,6 +1611,7 @@ async fn realtime_text_output_modality_requests_text_output_and_final_transcript
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -1822,6 +1829,7 @@ async fn realtime_conversation_stop_emits_closed_notification(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: Some(RealtimeConversationVersion::V1),
|
||||
voice: None,
|
||||
@@ -1980,6 +1988,7 @@ async fn realtime_mode_uses_client_instructions_on_entry_and_exit() -> Result<()
|
||||
realtime_end_instructions: Some(end_instructions.to_string()),
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2266,6 +2275,7 @@ async fn realtime_webrtc_start_emits_sdp_notification() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ThreadRealtimeStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -2458,6 +2468,7 @@ async fn existing_call_attaches_without_reinitializing_the_client_session(
|
||||
expected_handshake_uri: &str,
|
||||
) -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
let voice_session_id = "voice-session-client-owned";
|
||||
|
||||
let mut harness = RealtimeE2eHarness::new(
|
||||
RealtimeTestVersion::V1,
|
||||
@@ -2485,6 +2496,7 @@ async fn existing_call_attaches_without_reinitializing_the_client_session(
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: realtime_session_id.map(str::to_string),
|
||||
voice_session_id: Some(voice_session_id.to_string()),
|
||||
transport: Some(ThreadRealtimeStartTransport::ExistingCall {
|
||||
call_id: "rtc_existing".to_string(),
|
||||
}),
|
||||
@@ -2522,6 +2534,14 @@ async fn existing_call_attaches_without_reinitializing_the_client_session(
|
||||
harness.realtime_server.single_handshake().uri(),
|
||||
expected_handshake_uri
|
||||
);
|
||||
assert_eq!(
|
||||
harness
|
||||
.realtime_server
|
||||
.single_handshake()
|
||||
.header("voice-session-id")
|
||||
.as_deref(),
|
||||
Some(voice_session_id)
|
||||
);
|
||||
assert!(
|
||||
harness.realtime_server.single_connection().is_empty(),
|
||||
"attaching to an existing call must not overwrite the client session"
|
||||
@@ -2574,6 +2594,7 @@ async fn existing_call_rejects_client_owned_session_configuration(option: &str)
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ThreadRealtimeStartTransport::ExistingCall {
|
||||
call_id: "rtc_existing".to_string(),
|
||||
}),
|
||||
@@ -4094,6 +4115,7 @@ async fn realtime_webrtc_start_surfaces_backend_error() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ThreadRealtimeStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
|
||||
@@ -1444,6 +1444,7 @@ async fn prepare_realtime_start(
|
||||
let realtime_api_key = realtime_api_key(auth.as_ref(), &provider)?;
|
||||
realtime_request_headers(
|
||||
requested_realtime_session_id.as_deref(),
|
||||
params.voice_session_id.as_deref(),
|
||||
Some(realtime_api_key.as_str()),
|
||||
event_parser,
|
||||
originator.as_str(),
|
||||
@@ -1453,6 +1454,7 @@ async fn prepare_realtime_start(
|
||||
| ConversationStartTransport::ExistingCall { .. } => {
|
||||
realtime_request_headers(
|
||||
requested_realtime_session_id.as_deref(),
|
||||
params.voice_session_id.as_deref(),
|
||||
/*api_key*/ None,
|
||||
event_parser,
|
||||
originator.as_str(),
|
||||
@@ -1951,6 +1953,7 @@ fn realtime_api_key(auth: Option<&CodexAuth>, provider: &ModelProviderInfo) -> C
|
||||
|
||||
fn realtime_request_headers(
|
||||
realtime_session_id: Option<&str>,
|
||||
voice_session_id: Option<&str>,
|
||||
api_key: Option<&str>,
|
||||
event_parser: RealtimeEventParser,
|
||||
originator: &str,
|
||||
@@ -1973,6 +1976,12 @@ fn realtime_request_headers(
|
||||
headers.insert("x-session-id", realtime_session_id);
|
||||
}
|
||||
|
||||
if let Some(voice_session_id) = voice_session_id
|
||||
&& let Ok(voice_session_id) = HeaderValue::from_str(voice_session_id)
|
||||
{
|
||||
headers.insert("voice-session-id", voice_session_id);
|
||||
}
|
||||
|
||||
if let Some(api_key) = api_key {
|
||||
let auth_value = HeaderValue::from_str(&format!("Bearer {api_key}")).map_err(|err| {
|
||||
CodexErr::InvalidRequest(format!("invalid realtime api key header: {err}"))
|
||||
|
||||
@@ -419,6 +419,7 @@ fn streamed_v3_handoff_omits_the_final_message_prefix() {
|
||||
fn uses_quicksilver_alpha_header_for_realtime_v1() {
|
||||
let headers = realtime_request_headers(
|
||||
Some("session_1"),
|
||||
/*voice_session_id*/ None,
|
||||
Some("sk-test"),
|
||||
RealtimeEventParser::V1,
|
||||
"codex_work_desktop",
|
||||
@@ -438,6 +439,7 @@ fn uses_quicksilver_alpha_header_for_realtime_v1() {
|
||||
fn omits_quicksilver_alpha_header_for_realtime_v2() {
|
||||
let headers = realtime_request_headers(
|
||||
Some("session_1"),
|
||||
/*voice_session_id*/ None,
|
||||
Some("sk-test"),
|
||||
RealtimeEventParser::RealtimeV2,
|
||||
"codex_work_desktop",
|
||||
@@ -452,6 +454,7 @@ fn omits_quicksilver_alpha_header_for_realtime_v2() {
|
||||
fn uses_frameless_alpha_header_for_realtime_v3() {
|
||||
let headers = realtime_request_headers(
|
||||
Some("session_1"),
|
||||
/*voice_session_id*/ None,
|
||||
Some("sk-test"),
|
||||
RealtimeEventParser::FramelessBidi,
|
||||
"codex_work_desktop",
|
||||
@@ -476,6 +479,7 @@ fn realtime_headers_include_only_non_default_originator() {
|
||||
] {
|
||||
let headers = realtime_request_headers(
|
||||
Some("session_1"),
|
||||
/*voice_session_id*/ None,
|
||||
Some("sk-test"),
|
||||
RealtimeEventParser::RealtimeV2,
|
||||
originator,
|
||||
@@ -491,3 +495,29 @@ fn realtime_headers_include_only_non_default_originator() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn realtime_headers_keep_realtime_and_voice_session_ids_distinct() {
|
||||
let headers = realtime_request_headers(
|
||||
Some("realtime_session_1"),
|
||||
Some("voice_session_1"),
|
||||
/*api_key*/ None,
|
||||
RealtimeEventParser::RealtimeV2,
|
||||
"codex_work_desktop",
|
||||
)
|
||||
.expect("headers")
|
||||
.expect("headers");
|
||||
|
||||
assert_eq!(
|
||||
headers
|
||||
.get("x-session-id")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("realtime_session_1")
|
||||
);
|
||||
assert_eq!(
|
||||
headers
|
||||
.get("voice-session-id")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("voice_session_1")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ async fn start_realtime_conversation(codex: &codex_core::CodexThread) -> Result<
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
|
||||
@@ -145,6 +145,7 @@ fn params(session: &str, url: &str) -> ConversationStartParams {
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: Some(session.to_owned()),
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::ExistingCall {
|
||||
call_id: format!("rtc_{session}"),
|
||||
sideband_base_url: Some(url.to_owned()),
|
||||
|
||||
@@ -332,6 +332,7 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -514,6 +515,7 @@ async fn conversation_records_history_without_an_event_observer(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("fixture".to_string())),
|
||||
realtime_session_id: Some("voice-1".to_string()),
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -625,6 +627,7 @@ async fn conversation_start_defaults_to_v2_and_gpt_realtime_1_5() -> Result<()>
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -720,6 +723,7 @@ async fn conversation_websocket_transports_send_codex_headers_without_creating_a
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(transport.clone()),
|
||||
version: Some(RealtimeConversationVersion::V3),
|
||||
voice: None,
|
||||
@@ -870,6 +874,7 @@ async fn conversation_webrtc_frameless_chatgpt_sends_codex_headers_to_backend(
|
||||
let codex = &conversation.thread;
|
||||
assert_eq!(codex.config_snapshot().await.thread_source, thread_source);
|
||||
let requested_realtime_session_id = Uuid::new_v4().to_string();
|
||||
let voice_session_id = "voice-session-test".to_string();
|
||||
|
||||
codex
|
||||
.submit(Op::RealtimeConversationStart(ConversationStartParams {
|
||||
@@ -893,6 +898,7 @@ async fn conversation_webrtc_frameless_chatgpt_sends_codex_headers_to_backend(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: Some(requested_realtime_session_id.clone()),
|
||||
voice_session_id: Some(voice_session_id.clone()),
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -935,6 +941,10 @@ async fn conversation_webrtc_frameless_chatgpt_sends_codex_headers_to_backend(
|
||||
.headers
|
||||
.get("x-session-id")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
request
|
||||
.headers
|
||||
.get("voice-session-id")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
request
|
||||
.headers
|
||||
.get("authorization")
|
||||
@@ -947,6 +957,7 @@ async fn conversation_webrtc_frameless_chatgpt_sends_codex_headers_to_backend(
|
||||
Some(expected_session_id.as_str()),
|
||||
Some(expected_thread_id.as_str()),
|
||||
Some(requested_realtime_session_id.as_str()),
|
||||
Some(voice_session_id.as_str()),
|
||||
Some("Bearer Access Token"),
|
||||
)
|
||||
);
|
||||
@@ -1052,6 +1063,7 @@ async fn conversation_webrtc_start_posts_generated_session() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -1421,6 +1433,7 @@ async fn conversation_webrtc_live_reconnects_sideband_after_unclean_disconnect(
|
||||
Some(Some("backend prompt".to_string()))
|
||||
},
|
||||
realtime_session_id: attaches_existing_call.then(|| "sess_client_owned".to_string()),
|
||||
voice_session_id: None,
|
||||
transport: Some(transport),
|
||||
version: Some(RealtimeConversationVersion::V3),
|
||||
voice: None,
|
||||
@@ -1573,6 +1586,7 @@ async fn conversation_webrtc_start_uses_avas_query() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -1679,6 +1693,7 @@ async fn conversation_webrtc_default_v1_ignores_configured_v2_voice() -> Result<
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -1747,6 +1762,7 @@ async fn conversation_webrtc_default_v1_rejects_explicit_v2_voice() -> Result<()
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -1825,6 +1841,7 @@ async fn conversation_webrtc_start_uses_configured_call_base_url_for_avas() -> R
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -1940,6 +1957,7 @@ async fn conversation_webrtc_close_while_sideband_connecting_drops_pending_join(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -2048,6 +2066,7 @@ async fn conversation_webrtc_sideband_connect_failure_closes_with_error() -> Res
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
@@ -2156,6 +2175,7 @@ async fn conversation_start_uses_openai_env_key_fallback_with_chatgpt_auth() ->
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2258,6 +2278,7 @@ async fn assert_transport_close_tail_flush(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2392,6 +2413,7 @@ async fn conversation_start_preflight_failure_emits_realtime_error_only() -> Res
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2451,6 +2473,7 @@ async fn conversation_start_connect_failure_emits_realtime_error_only() -> Resul
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2564,6 +2587,7 @@ async fn conversation_second_start_replaces_runtime() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("old".to_string())),
|
||||
realtime_session_id: Some("conv_old".to_string()),
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2602,6 +2626,7 @@ async fn conversation_second_start_replaces_runtime() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("new".to_string())),
|
||||
realtime_session_id: Some("conv_new".to_string()),
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2712,6 +2737,7 @@ async fn conversation_uses_experimental_realtime_ws_base_url_override() -> Resul
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2792,6 +2818,7 @@ async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2883,6 +2910,7 @@ async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Resu
|
||||
realtime_end_instructions: None,
|
||||
prompt,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -2967,6 +2995,7 @@ async fn conversation_uses_explicit_start_voice() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: Some(RealtimeVoice::Breeze),
|
||||
@@ -3041,6 +3070,7 @@ async fn conversation_uses_configured_realtime_voice() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3100,6 +3130,7 @@ async fn conversation_rejects_voice_for_wrong_realtime_version() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: Some(RealtimeVoice::Cove),
|
||||
@@ -3162,6 +3193,7 @@ async fn conversation_uses_experimental_realtime_ws_backend_prompt_override() ->
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("prompt from op".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3248,6 +3280,7 @@ async fn conversation_uses_experimental_realtime_ws_startup_context_override() -
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("prompt from op".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3327,6 +3360,7 @@ async fn conversation_disables_realtime_startup_context_with_empty_override() ->
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("prompt from op".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3399,6 +3433,7 @@ async fn conversation_start_injects_startup_context_from_thread_history() -> Res
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3533,6 +3568,7 @@ async fn conversation_startup_context_current_thread_selects_many_turns_by_budge
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3653,6 +3689,7 @@ async fn conversation_startup_context_falls_back_to_workspace_map() -> Result<()
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3725,6 +3762,7 @@ async fn conversation_startup_context_is_truncated_and_sent_once_per_start() ->
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3830,6 +3868,7 @@ async fn conversation_user_text_turn_is_not_sent_to_realtime(ephemeral: bool) ->
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -3971,6 +4010,7 @@ async fn realtime_v2_noop_tool_call_returns_empty_function_output_without_respon
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -4089,6 +4129,7 @@ async fn conversation_mirrors_assistant_message_text_to_realtime_handoff(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -4223,6 +4264,7 @@ async fn conversation_relays_only_public_reasoning_as_quiet_status(enabled: bool
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -4415,6 +4457,7 @@ async fn conversation_routes_assistant_messages_for_v3_handoff(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -4609,6 +4652,7 @@ async fn conversation_handoff_persists_across_item_done_until_turn_complete(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -4775,6 +4819,7 @@ async fn inbound_handoff_request_starts_turn_and_promotes_its_artifact() -> Resu
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -4940,6 +4985,7 @@ async fn inbound_handoff_request_uses_active_transcript() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -5055,6 +5101,7 @@ async fn inbound_handoff_request_sends_transcript_delta_after_each_handoff() ->
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -5194,6 +5241,7 @@ async fn conversation_close_routes_only_remaining_transcript_tail_once() -> Resu
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -5300,6 +5348,7 @@ async fn inbound_conversation_item_does_not_start_turn_and_still_forwards_audio(
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -5435,6 +5484,7 @@ async fn delegated_turn_user_role_echo_does_not_redelegate_and_still_forwards_au
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -5600,6 +5650,7 @@ async fn inbound_handoff_request_does_not_block_realtime_event_forwarding() -> R
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -5785,6 +5836,7 @@ async fn inbound_handoff_request_updates_realtime_state_during_active_turn(
|
||||
realtime_end_instructions: Some("Return to normal text updates.".to_string()),
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
@@ -6026,6 +6078,7 @@ async fn inbound_handoff_request_starts_turn_and_does_not_block_realtime_audio()
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
|
||||
@@ -227,6 +227,7 @@ fn start_params(version: RealtimeConversationVersion) -> ConversationStartParams
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: Some(version),
|
||||
voice: None,
|
||||
|
||||
@@ -107,6 +107,7 @@ async fn misalignment_retires_late_voice_handoff_before_it_starts_a_turn() -> Re
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
|
||||
@@ -87,6 +87,7 @@ async fn existing_call_uses_selected_endpoint_and_runtime_auth(
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::ExistingCall {
|
||||
call_id: "rtc_existing".to_string(),
|
||||
sideband_base_url: match selection {
|
||||
|
||||
@@ -150,6 +150,7 @@ async fn webrtc_sideband_honors_configured_system_proxy() -> Result<()> {
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ConversationStartTransport::Webrtc {
|
||||
sdp: "v=offer\r\n".to_string(),
|
||||
}),
|
||||
|
||||
@@ -436,6 +436,7 @@ async fn host_drain_closes_realtime_after_handoff_error() -> anyhow::Result<()>
|
||||
realtime_end_instructions: None,
|
||||
prompt: Some(Some("backend prompt".to_string())),
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: None,
|
||||
version: None,
|
||||
voice: None,
|
||||
|
||||
@@ -29,6 +29,7 @@ fn realtime_start_debug_redacts_text_and_transport() {
|
||||
realtime_end_instructions: Some(secret.to_owned()),
|
||||
prompt: Some(Some(secret.to_owned())),
|
||||
realtime_session_id: Some(secret.to_owned()),
|
||||
voice_session_id: Some(secret.to_owned()),
|
||||
transport: None,
|
||||
version: Some(RealtimeConversationVersion::V3),
|
||||
voice: None,
|
||||
|
||||
@@ -209,6 +209,9 @@ pub struct ConversationStartParams {
|
||||
pub realtime_end_instructions: Option<String>,
|
||||
pub prompt: Option<Option<String>>,
|
||||
pub realtime_session_id: Option<String>,
|
||||
/// Client-generated identifier for the product voice session. This is distinct from the
|
||||
/// Realtime API session identifier.
|
||||
pub voice_session_id: Option<String>,
|
||||
pub transport: Option<ConversationStartTransport>,
|
||||
/// Overrides the configured realtime protocol version for this session only.
|
||||
pub version: Option<RealtimeConversationVersion>,
|
||||
|
||||
@@ -47,6 +47,7 @@ impl AppServerSession {
|
||||
realtime_end_instructions: None,
|
||||
prompt: None,
|
||||
realtime_session_id: None,
|
||||
voice_session_id: None,
|
||||
transport: Some(ThreadRealtimeStartTransport::Webrtc { sdp: offer_sdp }),
|
||||
version: Some(RealtimeConversationVersion::V3),
|
||||
voice,
|
||||
|
||||
Reference in New Issue
Block a user