mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add realtime silence tool (#18635)
## Summary Adds a second realtime v2 function tool, `remain_silent`, so the realtime model has an explicit non-speaking action when the collaboration mode or latest context says it should not answer aloud. This is stacked on #18597. ## Design - Advertise `remain_silent` alongside `background_agent` in realtime v2 conversational sessions. - Parse `remain_silent` function calls into a typed `RealtimeEvent::NoopRequested` event. - Have core answer that function call with an empty `function_call_output` and deliberately avoid `response.create`, so no follow-up realtime response is requested. - Keep the event hidden from app-server/TUI surfaces; it is operational plumbing, not user-visible conversation content.
This commit is contained in:
1 parent
a718b6fd47
commit
1029742cf7
10 files changed
+242
-39
No files matched your search
@@ -1,4 +1,4 @@
|
||||
use crate::endpoint::realtime_websocket::methods_common::conversation_handoff_append_message;
|
||||
use crate::endpoint::realtime_websocket::methods_common::conversation_function_call_output_message;
|
||||
use crate::endpoint::realtime_websocket::methods_common::conversation_item_create_message;
|
||||
use crate::endpoint::realtime_websocket::methods_common::normalized_session_mode;
|
||||
use crate::endpoint::realtime_websocket::methods_common::session_update_session;
|
||||
@@ -230,13 +230,13 @@ impl RealtimeWebsocketConnection {
|
||||
self.writer.send_conversation_item_create(text).await
|
||||
}
|
||||
|
||||
pub async fn send_conversation_handoff_append(
|
||||
pub async fn send_conversation_function_call_output(
|
||||
&self,
|
||||
handoff_id: String,
|
||||
call_id: String,
|
||||
output_text: String,
|
||||
) -> Result<(), ApiError> {
|
||||
self.writer
|
||||
.send_conversation_handoff_append(handoff_id, output_text)
|
||||
.send_conversation_function_call_output(call_id, output_text)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -290,14 +290,14 @@ impl RealtimeWebsocketWriter {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_conversation_handoff_append(
|
||||
pub async fn send_conversation_function_call_output(
|
||||
&self,
|
||||
handoff_id: String,
|
||||
call_id: String,
|
||||
output_text: String,
|
||||
) -> Result<(), ApiError> {
|
||||
self.send_json(&conversation_handoff_append_message(
|
||||
self.send_json(&conversation_function_call_output_message(
|
||||
self.event_parser,
|
||||
handoff_id,
|
||||
call_id,
|
||||
output_text,
|
||||
))
|
||||
.await
|
||||
@@ -471,6 +471,7 @@ impl RealtimeWebsocketEvents {
|
||||
| RealtimeEvent::ResponseCancelled(_)
|
||||
| RealtimeEvent::ResponseDone(_)
|
||||
| RealtimeEvent::ConversationItemDone { .. }
|
||||
| RealtimeEvent::NoopRequested(_)
|
||||
| RealtimeEvent::ConversationItemAdded(_)
|
||||
| RealtimeEvent::Error(_) => {}
|
||||
}
|
||||
@@ -825,6 +826,7 @@ mod tests {
|
||||
use crate::endpoint::realtime_websocket::protocol::RealtimeTranscriptEntry;
|
||||
use codex_protocol::protocol::RealtimeHandoffRequested;
|
||||
use codex_protocol::protocol::RealtimeInputAudioSpeechStarted;
|
||||
use codex_protocol::protocol::RealtimeNoopRequested;
|
||||
use codex_protocol::protocol::RealtimeResponseCancelled;
|
||||
use codex_protocol::protocol::RealtimeResponseCreated;
|
||||
use codex_protocol::protocol::RealtimeResponseDone;
|
||||
@@ -1090,6 +1092,29 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_realtime_v2_noop_tool_call_event() {
|
||||
let payload = json!({
|
||||
"type": "conversation.item.done",
|
||||
"item": {
|
||||
"id": "item_silent",
|
||||
"type": "function_call",
|
||||
"name": "remain_silent",
|
||||
"call_id": "call_silent",
|
||||
"arguments": "{}"
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(
|
||||
parse_realtime_event(payload.as_str(), RealtimeEventParser::RealtimeV2),
|
||||
Some(RealtimeEvent::NoopRequested(RealtimeNoopRequested {
|
||||
call_id: "call_silent".to_string(),
|
||||
item_id: "item_silent".to_string(),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_realtime_v2_input_audio_transcription_delta_event() {
|
||||
let payload = json!({
|
||||
@@ -1689,7 +1714,7 @@ mod tests {
|
||||
.await
|
||||
.expect("send item");
|
||||
connection
|
||||
.send_conversation_handoff_append(
|
||||
.send_conversation_function_call_output(
|
||||
"handoff_1".to_string(),
|
||||
"hello from background agent".to_string(),
|
||||
)
|
||||
@@ -1850,6 +1875,18 @@ mod tests {
|
||||
first_json["session"]["tools"][0]["parameters"]["required"],
|
||||
json!(["prompt"])
|
||||
);
|
||||
assert_eq!(
|
||||
first_json["session"]["tools"][1]["type"],
|
||||
Value::String("function".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
first_json["session"]["tools"][1]["name"],
|
||||
Value::String("remain_silent".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
first_json["session"]["tools"][1]["parameters"]["properties"],
|
||||
json!({})
|
||||
);
|
||||
assert_eq!(
|
||||
first_json["session"]["tool_choice"],
|
||||
Value::String("auto".to_string())
|
||||
@@ -1961,7 +1998,10 @@ mod tests {
|
||||
.await
|
||||
.expect("send text item");
|
||||
connection
|
||||
.send_conversation_handoff_append("call_1".to_string(), "delegated result".to_string())
|
||||
.send_conversation_function_call_output(
|
||||
"call_1".to_string(),
|
||||
"delegated result".to_string(),
|
||||
)
|
||||
.await
|
||||
.expect("send handoff output");
|
||||
|
||||
|
||||
Reference in new issue
Block a user