mirror of
https://github.com/openai/codex.git
synced 2026-11-11 22:33:20 +00:00
## 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.
84 lines
3.0 KiB
Rust
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")
|
|
}
|