Files
codex/codex-rs/codex-api/src/endpoint/realtime_websocket/methods_v1.rs
T
guinness-oai e922f46a0f [codex] Support assistant realtime append text (#28836)
## Why

Frontend realtime voice continuity needs to replay a tiny
previous-session overlap as actual conversation items, including
assistant text. The app-server `thread/realtime/appendText` API already
carries a role through to the Rust realtime websocket layer, but the
shared role enum only accepted `user` and `developer`.

## What Changed

- Added `assistant` to `ConversationTextRole` and regenerated the
app-server schema/type fixtures.
- Added `output_text` as a realtime conversation content type.
- Updated realtime websocket item creation so assistant appendText emits
`content: [{ type: "output_text", text }]`, while user and developer
continue to emit `input_text`.
- Updated app-server docs and tests to cover assistant appendText
alongside the existing developer role behavior.

## Validation

- `just write-app-server-schema`
- `just fmt` (first sandboxed attempt failed because `uv` could not
access `~/.cache/uv`; reran with filesystem access and passed)
- `just test -p codex-api` passed: 126/126
- `just test -p codex-app-server-protocol` passed: 239/239, including
generated JSON/TypeScript fixture checks
- `just test -p codex-app-server` was started locally but stopped per
request after unrelated local sandbox/Seatbelt failures (`sandbox-exec:
sandbox_apply: Operation not permitted`) and one missing local `codex`
binary failure; CI should be faster and more authoritative for the full
suite.
2026-06-17 20:57:13 -07:00

84 lines
3.0 KiB
Rust

use crate::endpoint::realtime_websocket::methods_common::REALTIME_AUDIO_SAMPLE_RATE;
use crate::endpoint::realtime_websocket::protocol::AudioFormatType;
use crate::endpoint::realtime_websocket::protocol::ConversationContentType;
use crate::endpoint::realtime_websocket::protocol::ConversationItemContent;
use crate::endpoint::realtime_websocket::protocol::ConversationItemPayload;
use crate::endpoint::realtime_websocket::protocol::ConversationItemType;
use crate::endpoint::realtime_websocket::protocol::ConversationMessageItem;
use crate::endpoint::realtime_websocket::protocol::RealtimeOutboundMessage;
use crate::endpoint::realtime_websocket::protocol::RealtimeVoice;
use crate::endpoint::realtime_websocket::protocol::SessionAudio;
use crate::endpoint::realtime_websocket::protocol::SessionAudioFormat;
use crate::endpoint::realtime_websocket::protocol::SessionAudioInput;
use crate::endpoint::realtime_websocket::protocol::SessionAudioOutput;
use crate::endpoint::realtime_websocket::protocol::SessionType;
use crate::endpoint::realtime_websocket::protocol::SessionUpdateSession;
use codex_protocol::protocol::ConversationTextRole;
pub(super) fn conversation_item_create_message(
text: String,
role: ConversationTextRole,
) -> RealtimeOutboundMessage {
let content_type = match role {
ConversationTextRole::Assistant => ConversationContentType::OutputText,
ConversationTextRole::User | ConversationTextRole::Developer => {
ConversationContentType::InputText
}
};
RealtimeOutboundMessage::ConversationItemCreate {
item: ConversationItemPayload::Message(ConversationMessageItem {
r#type: ConversationItemType::Message,
role,
content: vec![ConversationItemContent {
r#type: content_type,
text,
}],
}),
}
}
pub(super) fn conversation_handoff_append_message(
handoff_id: String,
output_text: String,
) -> RealtimeOutboundMessage {
RealtimeOutboundMessage::ConversationHandoffAppend {
handoff_id,
output_text,
}
}
pub(super) fn session_update_session(
instructions: String,
voice: RealtimeVoice,
) -> SessionUpdateSession {
SessionUpdateSession {
id: None,
r#type: SessionType::Quicksilver,
model: None,
instructions: Some(instructions),
output_modalities: None,
audio: SessionAudio {
input: SessionAudioInput {
format: SessionAudioFormat {
r#type: AudioFormatType::AudioPcm,
rate: REALTIME_AUDIO_SAMPLE_RATE,
},
noise_reduction: None,
transcription: None,
turn_detection: None,
},
output: Some(SessionAudioOutput {
format: None,
voice,
}),
},
tools: None,
tool_choice: None,
}
}
pub(super) fn websocket_intent() -> Option<&'static str> {
Some("quicksilver")
}