Add per-session realtime model and version overrides (#24999)

## Why

Clients need to select a realtime session configuration for an
individual start without rewriting persisted configuration or restarting
the app-server process.

## What Changed

- Add optional `model` and `version` fields to `thread/realtime/start`
- Forward those optional values through the realtime start operation and
apply them only for that session
- Preserve existing configured/default behavior when the new fields are
omitted
- Update generated protocol schema and app-server documentation

## Validation

- Added/updated protocol serialization coverage for the new optional
request fields
- Added focused core coverage for a session override taking precedence
over configured realtime selection
- Added focused app-server coverage that a request override reaches the
realtime WebSocket handshake
This commit is contained in:
guinness-oai
2026-06-09 17:54:32 -07:00
committed by GitHub
Unverified
parent 5ac640ac49
commit 4a3eac2144
11 changed files with 145 additions and 13 deletions
+11 -10
View File
@@ -625,12 +625,14 @@ async fn prepare_realtime_start(
if let Some(realtime_ws_base_url) = &config.experimental_realtime_ws_base_url {
api_provider.base_url = realtime_ws_base_url.clone();
}
let version = config.realtime.version;
let version = params.version.unwrap_or(config.realtime.version);
let session_config = build_realtime_session_config(
sess,
params.model,
params.prompt,
params.realtime_session_id,
params.output_modality,
version,
params.voice,
)
.await?;
@@ -664,9 +666,11 @@ async fn prepare_realtime_start(
pub(crate) async fn build_realtime_session_config(
sess: &Arc<Session>,
model: Option<String>,
prompt: Option<Option<String>>,
realtime_session_id: Option<String>,
output_modality: RealtimeOutputModality,
version: RealtimeWsVersion,
voice: Option<RealtimeVoice>,
) -> CodexResult<RealtimeSessionConfig> {
let config = sess.get_config().await;
@@ -689,18 +693,15 @@ pub(crate) async fn build_realtime_session_config(
(false, false) => format!("{prompt}\n\n{startup_context}"),
};
let model = Some(
config
.experimental_realtime_ws_model
.clone()
model
.or_else(|| config.experimental_realtime_ws_model.clone())
.unwrap_or_else(|| DEFAULT_REALTIME_MODEL.to_string()),
);
let event_parser = match config.realtime.version {
let event_parser = match version {
RealtimeWsVersion::V1 => RealtimeEventParser::V1,
RealtimeWsVersion::V2 => RealtimeEventParser::RealtimeV2,
};
if config.realtime.version == RealtimeWsVersion::V1
&& matches!(output_modality, RealtimeOutputModality::Text)
{
if version == RealtimeWsVersion::V1 && matches!(output_modality, RealtimeOutputModality::Text) {
return Err(CodexErr::InvalidRequest(
"text realtime output modality requires realtime v2".to_string(),
));
@@ -711,8 +712,8 @@ pub(crate) async fn build_realtime_session_config(
};
let voice = voice
.or(config.realtime.voice)
.unwrap_or_else(|| default_realtime_voice(config.realtime.version));
validate_realtime_voice(config.realtime.version, voice)?;
.unwrap_or_else(|| default_realtime_voice(version));
validate_realtime_voice(version, voice)?;
Ok(RealtimeSessionConfig {
instructions: prompt,
model,