mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Unify realtime v1/v2 session config (#14606)
## Summary - unify realtime websocket settings under `[realtime]` (`version` and `type`) - remove `realtime_conversation_v2` and select parser/session mode from config ## Testing - not run (per request) --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
9dba7337f2
commit
3aabce9e0a
@@ -51,7 +51,7 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
|
||||
vec![],
|
||||
vec![
|
||||
json!({
|
||||
"type": "conversation.output_audio.delta",
|
||||
"type": "response.output_audio.delta",
|
||||
"delta": "AQID",
|
||||
"sample_rate": 24_000,
|
||||
"channels": 1,
|
||||
@@ -403,6 +403,10 @@ sandbox_mode = "read-only"
|
||||
model_provider = "mock_provider"
|
||||
experimental_realtime_ws_base_url = "{realtime_server_uri}"
|
||||
|
||||
[realtime]
|
||||
version = "v2"
|
||||
type = "conversational"
|
||||
|
||||
[features]
|
||||
{realtime_feature_key} = {realtime_enabled}
|
||||
|
||||
|
||||
@@ -426,9 +426,6 @@
|
||||
"realtime_conversation": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"realtime_conversation_v2": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"remote_models": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -1342,6 +1339,18 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"RealtimeToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"$ref": "#/definitions/RealtimeWsMode"
|
||||
},
|
||||
"version": {
|
||||
"$ref": "#/definitions/RealtimeWsVersion"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"RealtimeWsMode": {
|
||||
"enum": [
|
||||
"conversational",
|
||||
@@ -1349,6 +1358,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RealtimeWsVersion": {
|
||||
"enum": [
|
||||
"v1",
|
||||
"v2"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ReasoningEffort": {
|
||||
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
|
||||
"enum": [
|
||||
@@ -1827,14 +1843,6 @@
|
||||
"description": "Experimental / do not use. Overrides only the realtime conversation websocket transport base URL (the `Op::RealtimeConversation` `/v1/realtime` connection) without changing normal provider HTTP requests.",
|
||||
"type": "string"
|
||||
},
|
||||
"experimental_realtime_ws_mode": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RealtimeWsMode"
|
||||
}
|
||||
],
|
||||
"description": "Experimental / do not use. Selects the realtime websocket intent mode. `conversational` is speech-to-speech while `transcription` is transcript-only."
|
||||
},
|
||||
"experimental_realtime_ws_model": {
|
||||
"description": "Experimental / do not use. Selects the realtime websocket model/snapshot used for the `Op::RealtimeConversation` connection.",
|
||||
"type": "string"
|
||||
@@ -1959,9 +1967,6 @@
|
||||
"realtime_conversation": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"realtime_conversation_v2": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"remote_models": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -2309,6 +2314,15 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"realtime": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RealtimeToml"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Experimental / do not use. Realtime websocket session selection. `version` controls v1/v2 and `type` controls conversational/transcription."
|
||||
},
|
||||
"review_model": {
|
||||
"description": "Review model override used by the `/review` feature.",
|
||||
"type": "string"
|
||||
|
||||
@@ -4130,7 +4130,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
experimental_realtime_start_instructions: None,
|
||||
experimental_realtime_ws_base_url: None,
|
||||
experimental_realtime_ws_model: None,
|
||||
experimental_realtime_ws_mode: RealtimeWsMode::Conversational,
|
||||
realtime: RealtimeConfig::default(),
|
||||
experimental_realtime_ws_backend_prompt: None,
|
||||
experimental_realtime_ws_startup_context: None,
|
||||
base_instructions: None,
|
||||
@@ -4268,7 +4268,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
experimental_realtime_start_instructions: None,
|
||||
experimental_realtime_ws_base_url: None,
|
||||
experimental_realtime_ws_model: None,
|
||||
experimental_realtime_ws_mode: RealtimeWsMode::Conversational,
|
||||
realtime: RealtimeConfig::default(),
|
||||
experimental_realtime_ws_backend_prompt: None,
|
||||
experimental_realtime_ws_startup_context: None,
|
||||
base_instructions: None,
|
||||
@@ -4404,7 +4404,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
experimental_realtime_start_instructions: None,
|
||||
experimental_realtime_ws_base_url: None,
|
||||
experimental_realtime_ws_model: None,
|
||||
experimental_realtime_ws_mode: RealtimeWsMode::Conversational,
|
||||
realtime: RealtimeConfig::default(),
|
||||
experimental_realtime_ws_backend_prompt: None,
|
||||
experimental_realtime_ws_startup_context: None,
|
||||
base_instructions: None,
|
||||
@@ -4526,7 +4526,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
experimental_realtime_start_instructions: None,
|
||||
experimental_realtime_ws_base_url: None,
|
||||
experimental_realtime_ws_model: None,
|
||||
experimental_realtime_ws_mode: RealtimeWsMode::Conversational,
|
||||
realtime: RealtimeConfig::default(),
|
||||
experimental_realtime_ws_backend_prompt: None,
|
||||
experimental_realtime_ws_startup_context: None,
|
||||
base_instructions: None,
|
||||
@@ -5575,17 +5575,22 @@ experimental_realtime_ws_model = "realtime-test-model"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn experimental_realtime_ws_mode_loads_from_config_toml() -> std::io::Result<()> {
|
||||
fn realtime_loads_from_config_toml() -> std::io::Result<()> {
|
||||
let cfg: ConfigToml = toml::from_str(
|
||||
r#"
|
||||
experimental_realtime_ws_mode = "transcription"
|
||||
[realtime]
|
||||
version = "v2"
|
||||
type = "transcription"
|
||||
"#,
|
||||
)
|
||||
.expect("TOML deserialization should succeed");
|
||||
|
||||
assert_eq!(
|
||||
cfg.experimental_realtime_ws_mode,
|
||||
Some(RealtimeWsMode::Transcription)
|
||||
cfg.realtime,
|
||||
Some(RealtimeToml {
|
||||
version: Some(RealtimeWsVersion::V2),
|
||||
session_type: Some(RealtimeWsMode::Transcription),
|
||||
})
|
||||
);
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
@@ -5596,8 +5601,11 @@ experimental_realtime_ws_mode = "transcription"
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
config.experimental_realtime_ws_mode,
|
||||
RealtimeWsMode::Transcription
|
||||
config.realtime,
|
||||
RealtimeConfig {
|
||||
version: RealtimeWsVersion::V2,
|
||||
session_type: RealtimeWsMode::Transcription,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -466,9 +466,9 @@ pub struct Config {
|
||||
/// Experimental / do not use. Selects the realtime websocket model/snapshot
|
||||
/// used for the `Op::RealtimeConversation` connection.
|
||||
pub experimental_realtime_ws_model: Option<String>,
|
||||
/// Experimental / do not use. Selects the realtime websocket intent mode.
|
||||
/// `conversational` is speech-to-speech while `transcription` is transcript-only.
|
||||
pub experimental_realtime_ws_mode: RealtimeWsMode,
|
||||
/// Experimental / do not use. Realtime websocket session selection.
|
||||
/// `version` controls v1/v2 and `type` controls conversational/transcription.
|
||||
pub realtime: RealtimeConfig,
|
||||
/// Experimental / do not use. Overrides only the realtime conversation
|
||||
/// websocket transport instructions (the `Op::RealtimeConversation`
|
||||
/// `/ws` session.update instructions) without changing normal prompts.
|
||||
@@ -1244,9 +1244,10 @@ pub struct ConfigToml {
|
||||
/// Experimental / do not use. Selects the realtime websocket model/snapshot
|
||||
/// used for the `Op::RealtimeConversation` connection.
|
||||
pub experimental_realtime_ws_model: Option<String>,
|
||||
/// Experimental / do not use. Selects the realtime websocket intent mode.
|
||||
/// `conversational` is speech-to-speech while `transcription` is transcript-only.
|
||||
pub experimental_realtime_ws_mode: Option<RealtimeWsMode>,
|
||||
/// Experimental / do not use. Realtime websocket session selection.
|
||||
/// `version` controls v1/v2 and `type` controls conversational/transcription.
|
||||
#[serde(default)]
|
||||
pub realtime: Option<RealtimeToml>,
|
||||
/// Experimental / do not use. Overrides only the realtime conversation
|
||||
/// websocket transport instructions (the `Op::RealtimeConversation`
|
||||
/// `/ws` session.update instructions) without changing normal prompts.
|
||||
@@ -1400,6 +1401,30 @@ pub enum RealtimeWsMode {
|
||||
Transcription,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RealtimeWsVersion {
|
||||
#[default]
|
||||
V1,
|
||||
V2,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct RealtimeConfig {
|
||||
pub version: RealtimeWsVersion,
|
||||
#[serde(rename = "type")]
|
||||
pub session_type: RealtimeWsMode,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct RealtimeToml {
|
||||
pub version: Option<RealtimeWsVersion>,
|
||||
#[serde(rename = "type")]
|
||||
pub session_type: Option<RealtimeWsMode>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct RealtimeAudioToml {
|
||||
@@ -2482,7 +2507,12 @@ impl Config {
|
||||
}),
|
||||
experimental_realtime_ws_base_url: cfg.experimental_realtime_ws_base_url,
|
||||
experimental_realtime_ws_model: cfg.experimental_realtime_ws_model,
|
||||
experimental_realtime_ws_mode: cfg.experimental_realtime_ws_mode.unwrap_or_default(),
|
||||
realtime: cfg
|
||||
.realtime
|
||||
.map_or_else(RealtimeConfig::default, |realtime| RealtimeConfig {
|
||||
version: realtime.version.unwrap_or_default(),
|
||||
session_type: realtime.session_type.unwrap_or_default(),
|
||||
}),
|
||||
experimental_realtime_ws_backend_prompt: cfg.experimental_realtime_ws_backend_prompt,
|
||||
experimental_realtime_ws_startup_context: cfg.experimental_realtime_ws_startup_context,
|
||||
experimental_realtime_start_instructions: cfg.experimental_realtime_start_instructions,
|
||||
|
||||
@@ -180,8 +180,6 @@ pub enum Feature {
|
||||
VoiceTranscription,
|
||||
/// Enable experimental realtime voice conversation mode in the TUI.
|
||||
RealtimeConversation,
|
||||
/// Route realtime conversations through the v2 event parser.
|
||||
RealtimeConversationV2,
|
||||
/// Prevent idle system sleep while a turn is actively running.
|
||||
PreventIdleSleep,
|
||||
/// Use the Responses API WebSocket transport for OpenAI by default.
|
||||
@@ -825,12 +823,6 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RealtimeConversationV2,
|
||||
key: "realtime_conversation_v2",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::PreventIdleSleep,
|
||||
key: "prevent_idle_sleep",
|
||||
|
||||
@@ -2,10 +2,11 @@ use crate::CodexAuth;
|
||||
use crate::api_bridge::map_api_error;
|
||||
use crate::auth::read_openai_api_key_from_env;
|
||||
use crate::codex::Session;
|
||||
use crate::config::RealtimeWsMode;
|
||||
use crate::config::RealtimeWsVersion;
|
||||
use crate::default_client::default_headers;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::features::Feature;
|
||||
use crate::realtime_context::build_realtime_startup_context;
|
||||
use async_channel::Receiver;
|
||||
use async_channel::Sender;
|
||||
@@ -294,14 +295,13 @@ pub(crate) async fn handle_start(
|
||||
format!("{prompt}\n\n{startup_context}")
|
||||
};
|
||||
let model = config.experimental_realtime_ws_model.clone();
|
||||
let event_parser = if config.features.enabled(Feature::RealtimeConversationV2) {
|
||||
RealtimeEventParser::RealtimeV2
|
||||
} else {
|
||||
RealtimeEventParser::V1
|
||||
let event_parser = match config.realtime.version {
|
||||
RealtimeWsVersion::V1 => RealtimeEventParser::V1,
|
||||
RealtimeWsVersion::V2 => RealtimeEventParser::RealtimeV2,
|
||||
};
|
||||
let session_mode = match config.experimental_realtime_ws_mode {
|
||||
crate::config::RealtimeWsMode::Conversational => RealtimeSessionMode::Conversational,
|
||||
crate::config::RealtimeWsMode::Transcription => RealtimeSessionMode::Transcription,
|
||||
let session_mode = match config.realtime.session_type {
|
||||
RealtimeWsMode::Conversational => RealtimeSessionMode::Conversational,
|
||||
RealtimeWsMode::Transcription => RealtimeSessionMode::Transcription,
|
||||
};
|
||||
let requested_session_id = params
|
||||
.session_id
|
||||
|
||||
Reference in New Issue
Block a user