mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Isolate realtime conversation tests from Responses prewarm connections (#49275)
## Why Responses prewarm can reconnect and consume scripted WebSocket replies intended for realtime conversations when both share a test server. ## What changed - Give realtime conversations a separate scripted server in tests for runtime replacement, prompts, and voice selection. - Disable Responses WebSocket prewarm in the audio/text round-trip test while preserving coverage of the default realtime endpoint. - Update connection counts and request assertions to address only realtime connections. GitOrigin-RevId: 8e3b5ac86ee80777cf94063db468c80203966999
This commit is contained in:
@@ -272,41 +272,45 @@ async fn seed_recent_thread(
|
||||
async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_websocket_server(vec![
|
||||
let api_server = start_mock_server().await;
|
||||
let server = start_websocket_server(vec![vec![
|
||||
vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_1", "instructions": "backend prompt" }
|
||||
})],
|
||||
vec![],
|
||||
vec![
|
||||
vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_1", "instructions": "backend prompt" }
|
||||
})],
|
||||
vec![],
|
||||
vec![
|
||||
json!({
|
||||
"type": "conversation.output_audio.delta",
|
||||
"delta": "AQID",
|
||||
"sample_rate": 24000,
|
||||
"channels": 1
|
||||
}),
|
||||
json!({
|
||||
"type": "conversation.item.added",
|
||||
"item": {
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "text", "text": "hi"}]
|
||||
}
|
||||
}),
|
||||
],
|
||||
json!({
|
||||
"type": "conversation.output_audio.delta",
|
||||
"delta": "AQID",
|
||||
"sample_rate": 24000,
|
||||
"channels": 1
|
||||
}),
|
||||
json!({
|
||||
"type": "conversation.item.added",
|
||||
"item": {
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "text", "text": "hi"}]
|
||||
}
|
||||
}),
|
||||
],
|
||||
])
|
||||
]])
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex();
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
assert!(
|
||||
server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
let mut builder = test_codex().with_config({
|
||||
let provider_base_url = format!("{}/v1", server.uri());
|
||||
move |config| {
|
||||
// Exercise the default Realtime endpoint without Responses prewarm
|
||||
// connections competing for this server's scripted replies.
|
||||
config.model_provider.base_url = Some(provider_base_url);
|
||||
config.model_provider.supports_websockets = false;
|
||||
config.experimental_realtime_ws_base_url = None;
|
||||
config.experimental_realtime_ws_model = Some("realtime-test-model".to_string());
|
||||
config.realtime.version = RealtimeWsVersion::V1;
|
||||
}
|
||||
});
|
||||
let test = builder.build_with_auto_env(&api_server).await?;
|
||||
|
||||
test.codex
|
||||
.submit(Op::RealtimeConversationStart(ConversationStartParams {
|
||||
@@ -384,8 +388,8 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
|
||||
assert_eq!(audio_out.data, "AQID");
|
||||
|
||||
let connections = server.connections();
|
||||
assert_eq!(connections.len(), 2);
|
||||
let connection = &connections[1];
|
||||
assert_eq!(connections.len(), 1);
|
||||
let connection = &connections[0];
|
||||
assert_eq!(connection.len(), 3);
|
||||
assert_eq!(
|
||||
connection[0].body_json()["type"].as_str(),
|
||||
@@ -399,7 +403,7 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
|
||||
.expect("initial session update instructions");
|
||||
assert!(initial_instructions.starts_with("backend prompt"));
|
||||
assert_eq!(
|
||||
server.handshakes()[1]
|
||||
server.handshakes()[0]
|
||||
.header("x-session-id")
|
||||
.expect("session.update x-session-id header"),
|
||||
started
|
||||
@@ -408,11 +412,11 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
|
||||
.expect("started session id should be present")
|
||||
);
|
||||
assert_eq!(
|
||||
server.handshakes()[1].header("authorization").as_deref(),
|
||||
server.handshakes()[0].header("authorization").as_deref(),
|
||||
Some("Bearer dummy")
|
||||
);
|
||||
assert_eq!(
|
||||
server.handshakes()[1].uri(),
|
||||
server.handshakes()[0].uri(),
|
||||
"/v1/realtime?intent=quicksilver&model=realtime-test-model"
|
||||
);
|
||||
let mut request_types = [
|
||||
@@ -2497,8 +2501,9 @@ async fn conversation_text_before_start_emits_error() -> Result<()> {
|
||||
async fn conversation_second_start_replaces_runtime() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
// Responses prewarm can reconnect; give realtime its own scripted connections.
|
||||
let startup_server = start_websocket_server(vec![vec![]]).await;
|
||||
let server = start_websocket_server(vec![
|
||||
vec![],
|
||||
vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_old", "instructions": "old" }
|
||||
@@ -2517,10 +2522,15 @@ async fn conversation_second_start_replaces_runtime() -> Result<()> {
|
||||
],
|
||||
])
|
||||
.await;
|
||||
let mut builder = test_codex();
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
let mut builder = test_codex().with_config({
|
||||
let realtime_base_url = server.uri().to_string();
|
||||
move |config| {
|
||||
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
|
||||
}
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&startup_server).await?;
|
||||
assert!(
|
||||
server
|
||||
startup_server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
@@ -2621,28 +2631,29 @@ async fn conversation_second_start_replaces_runtime() -> Result<()> {
|
||||
.await;
|
||||
|
||||
let connections = server.connections();
|
||||
assert_eq!(connections.len(), 3);
|
||||
assert_eq!(connections[1].len(), 1);
|
||||
assert_eq!(connections.len(), 2);
|
||||
assert_eq!(connections[0].len(), 1);
|
||||
let old_instructions =
|
||||
websocket_request_instructions(&connections[1][0]).expect("old session instructions");
|
||||
websocket_request_instructions(&connections[0][0]).expect("old session instructions");
|
||||
assert!(old_instructions.starts_with("old"));
|
||||
assert_eq!(
|
||||
server.handshakes()[1].header("x-session-id").as_deref(),
|
||||
server.handshakes()[0].header("x-session-id").as_deref(),
|
||||
Some("conv_old")
|
||||
);
|
||||
assert_eq!(connections[2].len(), 2);
|
||||
assert_eq!(connections[1].len(), 2);
|
||||
let new_instructions =
|
||||
websocket_request_instructions(&connections[2][0]).expect("new session instructions");
|
||||
websocket_request_instructions(&connections[1][0]).expect("new session instructions");
|
||||
assert!(new_instructions.starts_with("new"));
|
||||
assert_eq!(
|
||||
server.handshakes()[2].header("x-session-id").as_deref(),
|
||||
server.handshakes()[1].header("x-session-id").as_deref(),
|
||||
Some("conv_new")
|
||||
);
|
||||
assert_eq!(
|
||||
connections[2][1].body_json()["type"].as_str(),
|
||||
connections[1][1].body_json()["type"].as_str(),
|
||||
Some("input_audio_buffer.append")
|
||||
);
|
||||
|
||||
startup_server.shutdown().await;
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2729,22 +2740,25 @@ async fn conversation_uses_experimental_realtime_ws_base_url_override() -> Resul
|
||||
async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_websocket_server(vec![
|
||||
vec![],
|
||||
vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_default", "instructions": "default" }
|
||||
})]],
|
||||
])
|
||||
// Responses prewarm can reconnect; give realtime its own scripted connections.
|
||||
let startup_server = start_websocket_server(vec![vec![]]).await;
|
||||
let server = start_websocket_server(vec![vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_default", "instructions": "default" }
|
||||
})]]])
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.experimental_realtime_ws_startup_context =
|
||||
Some("controlled startup context".to_string());
|
||||
let mut builder = test_codex().with_config({
|
||||
let realtime_base_url = server.uri().to_string();
|
||||
move |config| {
|
||||
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
|
||||
config.experimental_realtime_ws_startup_context =
|
||||
Some("controlled startup context".to_string());
|
||||
}
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
let test = builder.build_with_websocket_server(&startup_server).await?;
|
||||
assert!(
|
||||
server
|
||||
startup_server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
@@ -2788,9 +2802,9 @@ async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
|
||||
assert_eq!(session_updated, "sess_default");
|
||||
|
||||
let connections = server.connections();
|
||||
assert_eq!(connections.len(), 2);
|
||||
assert_eq!(connections.len(), 1);
|
||||
let instructions =
|
||||
websocket_request_instructions(&connections[1][0]).expect("default session instructions");
|
||||
websocket_request_instructions(&connections[0][0]).expect("default session instructions");
|
||||
assert_eq!(
|
||||
instructions,
|
||||
format!(
|
||||
@@ -2799,6 +2813,7 @@ async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
|
||||
)
|
||||
);
|
||||
|
||||
startup_server.shutdown().await;
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2807,8 +2822,9 @@ async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
|
||||
async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
// Responses prewarm can reconnect; give realtime its own scripted connections.
|
||||
let startup_server = start_websocket_server(vec![vec![]]).await;
|
||||
let server = start_websocket_server(vec![
|
||||
vec![],
|
||||
vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_null", "instructions": "" }
|
||||
@@ -2820,12 +2836,16 @@ async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Resu
|
||||
])
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.experimental_realtime_ws_startup_context = Some(String::new());
|
||||
let mut builder = test_codex().with_config({
|
||||
let realtime_base_url = server.uri().to_string();
|
||||
move |config| {
|
||||
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
|
||||
config.experimental_realtime_ws_startup_context = Some(String::new());
|
||||
}
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
let test = builder.build_with_websocket_server(&startup_server).await?;
|
||||
assert!(
|
||||
server
|
||||
startup_server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
@@ -2881,14 +2901,15 @@ async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Resu
|
||||
}
|
||||
|
||||
let connections = server.connections();
|
||||
assert_eq!(connections.len(), 3);
|
||||
assert_eq!(connections.len(), 2);
|
||||
let null_instructions =
|
||||
websocket_request_instructions(&connections[1][0]).expect("null prompt instructions");
|
||||
websocket_request_instructions(&connections[0][0]).expect("null prompt instructions");
|
||||
let empty_instructions =
|
||||
websocket_request_instructions(&connections[2][0]).expect("empty prompt instructions");
|
||||
websocket_request_instructions(&connections[1][0]).expect("empty prompt instructions");
|
||||
assert_eq!(null_instructions, "");
|
||||
assert_eq!(empty_instructions, "");
|
||||
|
||||
startup_server.shutdown().await;
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2897,17 +2918,22 @@ async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Resu
|
||||
async fn conversation_uses_explicit_start_voice() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_websocket_server(vec![
|
||||
vec![],
|
||||
vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_voice", "instructions": "backend prompt" }
|
||||
})]],
|
||||
])
|
||||
// Responses prewarm can reconnect; give realtime its own scripted connections.
|
||||
let startup_server = start_websocket_server(vec![vec![]]).await;
|
||||
let server = start_websocket_server(vec![vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_voice", "instructions": "backend prompt" }
|
||||
})]]])
|
||||
.await;
|
||||
let test = test_codex().build_with_websocket_server(&server).await?;
|
||||
let mut builder = test_codex().with_config({
|
||||
let realtime_base_url = server.uri().to_string();
|
||||
move |config| {
|
||||
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
|
||||
}
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&startup_server).await?;
|
||||
assert!(
|
||||
server
|
||||
startup_server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
@@ -2952,10 +2978,11 @@ async fn conversation_uses_explicit_start_voice() -> Result<()> {
|
||||
|
||||
let connections = server.connections();
|
||||
assert_eq!(
|
||||
connections[1][0].body_json()["session"]["audio"]["output"]["voice"],
|
||||
connections[0][0].body_json()["session"]["audio"]["output"]["voice"],
|
||||
"breeze"
|
||||
);
|
||||
|
||||
startup_server.shutdown().await;
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2964,20 +2991,23 @@ async fn conversation_uses_explicit_start_voice() -> Result<()> {
|
||||
async fn conversation_uses_configured_realtime_voice() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_websocket_server(vec![
|
||||
vec![],
|
||||
vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_config_voice", "instructions": "backend prompt" }
|
||||
})]],
|
||||
])
|
||||
// Responses prewarm can reconnect; give realtime its own scripted connections.
|
||||
let startup_server = start_websocket_server(vec![vec![]]).await;
|
||||
let server = start_websocket_server(vec![vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_config_voice", "instructions": "backend prompt" }
|
||||
})]]])
|
||||
.await;
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.realtime.voice = Some(RealtimeVoice::Cove);
|
||||
let mut builder = test_codex().with_config({
|
||||
let realtime_base_url = server.uri().to_string();
|
||||
move |config| {
|
||||
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
|
||||
config.realtime.voice = Some(RealtimeVoice::Cove);
|
||||
}
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
let test = builder.build_with_websocket_server(&startup_server).await?;
|
||||
assert!(
|
||||
server
|
||||
startup_server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
@@ -3022,10 +3052,11 @@ async fn conversation_uses_configured_realtime_voice() -> Result<()> {
|
||||
|
||||
let connections = server.connections();
|
||||
assert_eq!(
|
||||
connections[1][0].body_json()["session"]["audio"]["output"]["voice"],
|
||||
connections[0][0].body_json()["session"]["audio"]["output"]["voice"],
|
||||
"cove"
|
||||
);
|
||||
|
||||
startup_server.shutdown().await;
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -3080,21 +3111,24 @@ async fn conversation_rejects_voice_for_wrong_realtime_version() -> Result<()> {
|
||||
async fn conversation_uses_experimental_realtime_ws_backend_prompt_override() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_websocket_server(vec![
|
||||
vec![],
|
||||
vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_override", "instructions": "prompt from config" }
|
||||
})]],
|
||||
])
|
||||
// Responses prewarm can reconnect; give realtime its own scripted connections.
|
||||
let startup_server = start_websocket_server(vec![vec![]]).await;
|
||||
let server = start_websocket_server(vec![vec![vec![json!({
|
||||
"type": "session.updated",
|
||||
"session": { "id": "sess_override", "instructions": "prompt from config" }
|
||||
})]]])
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.experimental_realtime_ws_backend_prompt = Some("prompt from config".to_string());
|
||||
let mut builder = test_codex().with_config({
|
||||
let realtime_base_url = server.uri().to_string();
|
||||
move |config| {
|
||||
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
|
||||
config.experimental_realtime_ws_backend_prompt = Some("prompt from config".to_string());
|
||||
}
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
let test = builder.build_with_websocket_server(&startup_server).await?;
|
||||
assert!(
|
||||
server
|
||||
startup_server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
@@ -3138,11 +3172,12 @@ async fn conversation_uses_experimental_realtime_ws_backend_prompt_override() ->
|
||||
assert_eq!(session_updated, "sess_override");
|
||||
|
||||
let connections = server.connections();
|
||||
assert_eq!(connections.len(), 2);
|
||||
let overridden_instructions = websocket_request_instructions(&connections[1][0])
|
||||
assert_eq!(connections.len(), 1);
|
||||
let overridden_instructions = websocket_request_instructions(&connections[0][0])
|
||||
.expect("overridden session instructions");
|
||||
assert!(overridden_instructions.starts_with("prompt from config"));
|
||||
|
||||
startup_server.shutdown().await;
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user