[codex] add roles to realtime append text (#27936)

## Summary

Add an explicit `user` or `developer` role to
`thread/realtime/appendText` and propagate it through the realtime input
queue into `conversation.item.create`. Older JSON clients that omit the
field continue to default to `user`.

This lets app-provided context such as memory retain developer authority
without bypassing app-server through a renderer-owned data channel. The
app-server schemas, API documentation, and focused protocol and
websocket coverage are updated with the new contract.

The Codex Apps consumer is tracked in
[openai/openai#1025261](https://github.com/openai/openai/pull/1025261).
This commit is contained in:
Alex Gamble authored and GitHub committed 2026-06-12 15:05:37 -07:00
1 parent 9915d34684
commit 216dee1189
18 files changed
+176 -50

No files matched your search

@@ -17,6 +17,7 @@ use crate::error::ApiError;
use crate::provider::Provider;
use codex_client::backoff;
use codex_client::maybe_build_rustls_client_config_with_custom_ca;
use codex_protocol::protocol::ConversationTextRole;
use codex_protocol::protocol::RealtimeTranscriptDelta;
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
use futures::SinkExt;
@@ -227,8 +228,12 @@ impl RealtimeWebsocketConnection {
self.writer.send_audio_frame(frame).await
}
pub async fn send_conversation_item_create(&self, text: String) -> Result<(), ApiError> {
self.writer.send_conversation_item_create(text).await
pub async fn send_conversation_item_create(
&self,
text: String,
role: ConversationTextRole,
) -> Result<(), ApiError> {
self.writer.send_conversation_item_create(text, role).await
}
pub async fn send_conversation_function_call_output(
@@ -286,9 +291,17 @@ impl RealtimeWebsocketWriter {
.await
}
pub async fn send_conversation_item_create(&self, text: String) -> Result<(), ApiError> {
self.send_json(&conversation_item_create_message(self.event_parser, text))
.await
pub async fn send_conversation_item_create(
&self,
text: String,
role: ConversationTextRole,
) -> Result<(), ApiError> {
self.send_json(&conversation_item_create_message(
self.event_parser,
text,
role,
))
.await
}
pub async fn send_conversation_handoff_append(
@@ -1609,6 +1622,7 @@ mod tests {
.expect("text");
let third_json: Value = serde_json::from_str(&third).expect("json");
assert_eq!(third_json["type"], "conversation.item.create");
assert_eq!(third_json["item"]["role"], "developer");
assert_eq!(
third_json["item"]["content"][0]["type"],
Value::String("input_text".to_string())
@@ -1746,7 +1760,10 @@ mod tests {
.await
.expect("send audio");
connection
.send_conversation_item_create("hello agent".to_string())
.send_conversation_item_create(
"hello agent".to_string(),
ConversationTextRole::Developer,
)
.await
.expect("send item");
connection
@@ -1948,6 +1965,7 @@ mod tests {
.expect("text");
let second_json: Value = serde_json::from_str(&second).expect("json");
assert_eq!(second_json["type"], "conversation.item.create");
assert_eq!(second_json["item"]["role"], "developer");
assert_eq!(
second_json["item"]["type"],
Value::String("message".to_string())
@@ -2030,7 +2048,10 @@ mod tests {
);
connection
.send_conversation_item_create("delegate this".to_string())
.send_conversation_item_create(
"delegate this".to_string(),
ConversationTextRole::Developer,
)
.await
.expect("send text item");
connection