Support configurable realtime BEM channel prefixes (#34816)

## What changed

- Route realtime V3 `bemTags` handoffs using `[ANALYSIS]`, `[COMMENTARY]`, and `[FINAL]` prefixes by default.
- Add `codexResponseHandoffChannelPrefixes` to realtime start requests so clients can replace the accepted prefixes for individual channels. Each channel may accept multiple prefixes, and omitted channels retain their defaults.
- Apply the configured prefixes to both complete and streamed handoff messages, without treating empty prefixes as matches.

## Testing

- Cover request deserialization, default and custom routing, partial streamed prefixes, empty-prefix handling, and end-to-end V3 handoffs.

GitOrigin-RevId: d0aef50aa71362a5beef2c6f6d28b8633da4e529
This commit is contained in:
jiayuhuang-openai authored and copyberry committed 2026-07-22 19:43:35 +00:00
1 parent 08ae0fc0ce
commit 4ebd976312
15 files changed
+325 -73

No files matched your search

@@ -9,6 +9,7 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use std::collections::BTreeMap;
use ts_rs::TS;
/// EXPERIMENTAL - thread realtime audio chunk.
@@ -87,6 +88,10 @@ pub struct ThreadRealtimeStartParams {
/// default to `thinking`. Realtime V1 and V2 ignore this setting.
#[ts(optional = nullable)]
pub codex_response_handoff_mode: Option<CodexResponseHandoffMode>,
/// Overrides BEM channel prefixes by `analysis`, `commentary`, or `final`.
/// Omitted channels retain their default uppercase bracketed prefixes.
#[ts(optional = nullable)]
pub codex_response_handoff_channel_prefixes: Option<BTreeMap<String, Vec<String>>>,
/// Overrides the configured realtime model for this session only.
#[ts(optional = nullable)]
pub model: Option<String>,
@@ -4462,3 +4462,28 @@ fn realtime_start_omitted_initial_items_remain_none() {
assert_eq!(params.initial_items, None);
}
#[test]
fn realtime_start_deserializes_client_handoff_channel_prefixes() {
let params = serde_json::from_value::<ThreadRealtimeStartParams>(json!({
"threadId": "thread_123",
"outputModality": "audio",
"codexResponseHandoffChannelPrefixes": {
"analysis": ["[THINKING]"],
"commentary": ["[PROGRESS]", "[UPDATE]"],
"final": ["[DONE]"]
}
}))
.expect("params should deserialize");
assert_eq!(
params.codex_response_handoff_channel_prefixes,
Some(BTreeMap::from([
("analysis".to_string(), vec!["[THINKING]".to_string()]),
(
"commentary".to_string(),
vec!["[PROGRESS]".to_string(), "[UPDATE]".to_string()],
),
("final".to_string(), vec!["[DONE]".to_string()]),
]))
);
}