mirror of
https://github.com/openai/codex.git
synced 2026-10-21 07:42:01 +00:00
Expose voice conversations in experimental features (#44331)
## What changed - Mark `realtime_conversation` as experimental, keeping it disabled by default, and label it “Voice conversations” in `/experimental`. - Add an announcement directing users to enable the feature, restart Codex, and run `/voice`. - Hide server-discovered voice entries and the announcement when the client lacks native voice runtime support. ## Testing Add tests and snapshots for voice discovery and announcement visibility with and without runtime support, and update the experimental features popup snapshot. GitOrigin-RevId: 709655f3733cb88191eb78b40ac1f67e27c9816e
This commit is contained in:
committed by
copyberry
parent
130d6e4fba
commit
e722303e38
@@ -1680,7 +1680,11 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
FeatureSpec {
|
||||
id: Feature::RealtimeConversation,
|
||||
key: "realtime_conversation",
|
||||
stage: Stage::Removed,
|
||||
stage: Stage::Experimental {
|
||||
name: "Voice conversations",
|
||||
menu_description: "Talk with Codex using /voice.",
|
||||
announcement: "NEW: Voice conversations can now be enabled from /experimental. Restart Codex after enabling, then use /voice.",
|
||||
},
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Experimental controls with popup-owned discovery and configured enablement.
|
||||
//! Failed saves retain intent for explicit retry; cancellation remains available.
|
||||
//! Server voice discovery is gated by the client package's native runtime.
|
||||
|
||||
use crate::experimental_features::FeatureWriteResult;
|
||||
use codex_app_server_protocol::ExperimentalFeature;
|
||||
@@ -53,6 +54,7 @@ pub(crate) struct ExperimentalFeatureItem {
|
||||
|
||||
pub(crate) struct ExperimentalFeaturesView {
|
||||
features: Vec<ExperimentalFeatureItem>,
|
||||
voice_supported: bool,
|
||||
initial_enabled: Vec<bool>,
|
||||
unconfirmed: Vec<String>,
|
||||
catalog_rx: Option<oneshot::Receiver<Result<Vec<ExperimentalFeature>, String>>>,
|
||||
@@ -82,6 +84,7 @@ impl ExperimentalFeaturesView {
|
||||
}
|
||||
.to_string(),
|
||||
thread_id,
|
||||
voice_supported: codex_realtime_webrtc::RealtimeWebrtcSession::is_supported(),
|
||||
write_rx: None,
|
||||
catalog_rx,
|
||||
unconfirmed: Vec::new(),
|
||||
@@ -312,7 +315,10 @@ impl BottomPaneView for ExperimentalFeaturesView {
|
||||
Ok(features) => {
|
||||
let mut count = 0;
|
||||
for feature in features {
|
||||
if feature.stage != ExperimentalFeatureStage::Beta {
|
||||
if feature.stage != ExperimentalFeatureStage::Beta
|
||||
|| (feature.name == Feature::RealtimeConversation.key()
|
||||
&& !self.voice_supported)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
self.initial_enabled.push(feature.enabled);
|
||||
|
||||
@@ -207,3 +207,32 @@ fn snapshot_view(name: &str, view: &ExperimentalFeaturesView) {
|
||||
view.render(area, &mut buffer);
|
||||
insta::assert_snapshot!(name, buffer_text(&buffer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voice_discovery_requires_the_client_runtime() {
|
||||
for supported in [false, true] {
|
||||
let (app_tx, _app_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let (catalog_tx, catalog_rx) = oneshot::channel();
|
||||
let mut view = ExperimentalFeaturesView::new(
|
||||
Vec::new(),
|
||||
ThreadId::new(),
|
||||
Some(catalog_rx),
|
||||
AppEventSender::new(app_tx),
|
||||
crate::keymap::RuntimeKeymap::defaults().list,
|
||||
);
|
||||
view.voice_supported = supported;
|
||||
catalog_tx
|
||||
.send(Ok(vec![server_feature("realtime_conversation")]))
|
||||
.unwrap();
|
||||
assert!(view.pre_draw_tick(Instant::now()));
|
||||
assert_eq!(view.features.len(), usize::from(supported));
|
||||
snapshot_view(
|
||||
if supported {
|
||||
"voice_runtime_available"
|
||||
} else {
|
||||
"voice_runtime_unavailable"
|
||||
},
|
||||
&view,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/experimental_features_view_tests.rs
|
||||
assertion_line: 208
|
||||
expression: buffer_text(&buffer)
|
||||
---
|
||||
|
||||
Experimental features
|
||||
Checked features are configured on. Some experimental features take effect
|
||||
only in new tasks or after restarting the Codex server.
|
||||
|
||||
› [x] Server realtime_conversation Description supplied by the server.
|
||||
|
||||
Press space to select or enter to save
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/experimental_features_view_tests.rs
|
||||
assertion_line: 208
|
||||
expression: buffer_text(&buffer)
|
||||
---
|
||||
|
||||
Experimental features
|
||||
Checked features are configured on. Some experimental features take effect
|
||||
only in new tasks or after restarting the Codex server.
|
||||
No server experiments available.
|
||||
|
||||
No experimental features available for now
|
||||
|
||||
Press space to select or enter to save
|
||||
+8
-6
@@ -1,16 +1,18 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/popups_and_settings.rs
|
||||
assertion_line: 3083
|
||||
expression: popup
|
||||
---
|
||||
Experimental features
|
||||
Checked features are configured on. Some experimental features take effect
|
||||
only in new tasks or after restarting the Codex server.
|
||||
|
||||
› [ ] JavaScript REPL Enable a persistent Node-backed JavaScript REPL for
|
||||
interactive website debugging and other inline
|
||||
JavaScript execution capabilities.
|
||||
[x] Shell tool Allow the model to run shell commands.
|
||||
[ ] Worktrees Create isolated Git worktrees and group sessions by
|
||||
repository.
|
||||
› [ ] JavaScript REPL Enable a persistent Node-backed JavaScript REPL for
|
||||
interactive website debugging and other inline
|
||||
JavaScript execution capabilities.
|
||||
[x] Shell tool Allow the model to run shell commands.
|
||||
[ ] Worktrees Create isolated Git worktrees and group sessions by
|
||||
repository.
|
||||
[ ] Voice conversations Talk with Codex using /voice.
|
||||
|
||||
Press space to select or enter to save
|
||||
|
||||
@@ -3039,6 +3039,7 @@ async fn experimental_features_popup_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
let worktrees = Feature::Worktrees.stage();
|
||||
let voice = Feature::RealtimeConversation.stage();
|
||||
let features = vec![
|
||||
ExperimentalFeatureItem {
|
||||
key: Feature::JsRepl.key().to_string(),
|
||||
@@ -3061,6 +3062,13 @@ async fn experimental_features_popup_snapshot() {
|
||||
description: worktrees.experimental_menu_description().unwrap().to_string(),
|
||||
enabled: false,
|
||||
},
|
||||
ExperimentalFeatureItem {
|
||||
key: Feature::RealtimeConversation.key().to_string(),
|
||||
writable: true,
|
||||
name: voice.experimental_menu_name().unwrap().to_string(),
|
||||
description: voice.experimental_menu_description().unwrap().to_string(),
|
||||
enabled: false,
|
||||
},
|
||||
];
|
||||
let view = ExperimentalFeaturesView::new(
|
||||
features,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: tui/src/tooltips.rs
|
||||
assertion_line: 382
|
||||
expression: "available.into_iter().filter(|tip| *tip ==\nvoice_tip).collect::<Vec<_>>().join(\"\\n\")"
|
||||
---
|
||||
NEW: Voice conversations can now be enabled from /experimental. Restart Codex after enabling, then use /voice.
|
||||
@@ -1,4 +1,5 @@
|
||||
use codex_features::FEATURES;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::account::PlanType;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::Rng;
|
||||
@@ -36,14 +37,17 @@ lazy_static! {
|
||||
static ref ALL_TOOLTIPS: Vec<&'static str> = {
|
||||
let mut tips = Vec::new();
|
||||
tips.extend(TOOLTIPS.iter().copied());
|
||||
tips.extend(experimental_tooltips());
|
||||
tips.extend(experimental_tooltips(
|
||||
codex_realtime_webrtc::RealtimeWebrtcSession::is_supported,
|
||||
));
|
||||
tips
|
||||
};
|
||||
}
|
||||
|
||||
fn experimental_tooltips() -> Vec<&'static str> {
|
||||
fn experimental_tooltips(voice_supported: impl Fn() -> bool) -> Vec<&'static str> {
|
||||
FEATURES
|
||||
.iter()
|
||||
.filter(|spec| spec.id != Feature::RealtimeConversation || voice_supported())
|
||||
.filter_map(|spec| spec.stage.experimental_announcement())
|
||||
.collect()
|
||||
}
|
||||
@@ -358,6 +362,33 @@ mod tests {
|
||||
use rand::SeedableRng;
|
||||
use rand::rngs::StdRng;
|
||||
|
||||
#[test]
|
||||
fn experimental_voice_tooltip_requires_runtime_support() {
|
||||
let unavailable = experimental_tooltips(|| false);
|
||||
let available = experimental_tooltips(|| true);
|
||||
let voice_tip = FEATURES
|
||||
.iter()
|
||||
.find(|spec| spec.id == Feature::RealtimeConversation)
|
||||
.and_then(|spec| spec.stage.experimental_announcement())
|
||||
.expect("voice has an experimental announcement");
|
||||
assert_eq!(
|
||||
unavailable,
|
||||
available
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|tip| *tip != voice_tip)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
"experimental_voice_tooltip",
|
||||
available
|
||||
.into_iter()
|
||||
.filter(|tip| *tip == voice_tip)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_tooltip_returns_some_tip_when_available() {
|
||||
let mut rng = StdRng::seed_from_u64(42);
|
||||
|
||||
Reference in New Issue
Block a user