Files
codex/codex-rs/rollout-trace/src/model/conversation.rs
T
jif 8f2d6416ce Support plaintext agent messages (#27830)
## Why

Multi-agent v2 `send_message` deliveries already reach the receiving
model as typed `agent_message` items with encrypted content.
Child-completion notifications are generated by Codex itself, so their
content is plaintext and previously fell back to a serialized JSON
envelope inside an assistant message.

With plaintext `input_text` supported for `agent_message`, both delivery
paths can use the same model-visible type while preserving explicit
author and recipient metadata.

## What changed

- add plaintext `input_text` support to `AgentMessageInputContent` and
regenerate the affected app-server schemas
- preserve `InterAgentCommunication` as structured mailbox input instead
of converting it to assistant text
- record delivered communications as typed `agent_message` history items
- persist a dedicated rollout item so local delivery metadata such as
`trigger_turn` remains available without leaking into the Responses
request
- reconstruct typed agent messages on resume and preserve fork-turn
truncation behavior
- remove request-time assistant-content parsing
- preserve plaintext and encrypted inter-agent deliveries in stage-one
memory inputs
- normalize and link plaintext and encrypted agent messages in rollout
traces without treating inbound messages as child results
- cover the real MultiAgent V2 child-completion path end to end with
deterministic mailbox synchronization

## Verification

- `just test -p codex-core
plaintext_multi_agent_v2_completion_sends_agent_message`
- `just test -p codex-core input_queue_drains_mailbox_in_delivery_order
record_initial_history_reconstructs_typed_inter_agent_message
fork_turn_positions_use_inter_agent_delivery_metadata`
- `just test -p codex-memories-write
serializes_inter_agent_communications_for_memory`
- `just test -p codex-rollout-trace
agent_messages_preserve_routing_and_content
sub_agent_started_activity_creates_spawn_edge`
- `just test -p codex-rollout-trace
agent_result_edge_falls_back_to_child_thread_without_result_message`
- `just test -p codex-protocol -p codex-rollout -p
codex-app-server-protocol`
2026-06-12 13:50:04 -07:00

192 lines
6.9 KiB
Rust

use serde::Deserialize;
use serde::Serialize;
use crate::payload::RawPayloadId;
use super::AgentPath;
use super::AgentThreadId;
use super::CodeCellId;
use super::CodexTurnId;
use super::CompactionId;
use super::ConversationItemId;
use super::EdgeId;
use super::InferenceCallId;
use super::ModelVisibleCallId;
use super::ToolCallId;
use super::session::ExecutionWindow;
/// One logical transcript item or transcript boundary.
///
/// The reducer builds conversation items primarily from inference request and
/// response payloads. Runtime objects can be listed in `produced_by`, but they
/// must not rewrite what the item body says the model saw. Structural items,
/// such as compaction markers, live in the same ordered list so conversation
/// views can show where the live history changed.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConversationItem {
pub item_id: ConversationItemId,
pub thread_id: AgentThreadId,
/// Runtime activation that first introduced this item locally, when known.
pub codex_turn_id: Option<CodexTurnId>,
pub first_seen_at_unix_ms: i64,
pub role: ConversationRole,
/// Codex channel for assistant/tool content, when the item is channel-specific.
pub channel: Option<ConversationChannel>,
pub kind: ConversationItemKind,
/// Routing metadata carried by a Responses `agent_message` item.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_message: Option<AgentMessageMetadata>,
pub body: ConversationBody,
/// Protocol/model `call_id` for function/custom tool call and output items.
pub call_id: Option<ModelVisibleCallId>,
/// Runtime or control-plane objects that caused this conversation item to exist.
pub produced_by: Vec<ProducerRef>,
}
/// Sender and destination identities attached to a model-visible agent message.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AgentMessageMetadata {
/// Agent path that authored the message.
pub author: AgentPath,
/// Agent path that received the message.
pub recipient: AgentPath,
}
/// Model-visible role assigned to a conversation item.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConversationRole {
System,
Developer,
User,
Assistant,
Tool,
}
/// Codex channel for model-visible content.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConversationChannel {
Analysis,
Commentary,
Final,
/// Remote compaction summaries are reintroduced as assistant summary-channel content.
Summary,
}
/// Responses item category after normalization into the reduced transcript.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConversationItemKind {
Message,
Reasoning,
FunctionCall,
FunctionCallOutput,
CustomToolCall,
CustomToolCallOutput,
/// Structural marker inserted where live history was replaced by compaction.
CompactionMarker,
}
/// Ordered content parts for a reduced conversation item.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConversationBody {
/// Renderable model-visible parts. Raw payload refs are used when the bytes
/// are too large or too structured for the normal conversation path.
pub parts: Vec<ConversationPart>,
}
/// One model-visible part inside a conversation item.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum ConversationPart {
Text {
text: String,
},
/// A model-provided summary of content whose full form may also be present.
///
/// Reasoning summaries are not interchangeable with raw reasoning text:
/// both can be present in one payload, and replay/debug tooling needs to
/// preserve which representation the model actually returned.
Summary {
text: String,
},
/// Opaque model-visible content that is intentionally not decoded here.
///
/// Reasoning can be carried as `encrypted_content` with no readable text.
/// Keeping that blob inline makes it part of item identity, unlike a raw
/// payload reference whose ID changes every time the same item is replayed
/// in a later inference request.
Encoded {
label: String,
value: String,
},
/// Small JSON-ish body represented by a summary plus a raw ref.
Json {
summary: String,
raw_payload_id: RawPayloadId,
},
Code {
language: String,
source: String,
},
/// Large or uncommon payload that should be lazy-loaded from details UI.
PayloadRef {
label: String,
raw_payload_id: RawPayloadId,
},
}
/// Explanation for where a conversation item came from.
///
/// This is deliberately plural at the call site: a function output can be both
/// model-visible conversation and the product of a runtime tool call.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum ProducerRef {
UserInput,
Inference { inference_call_id: InferenceCallId },
Tool { tool_call_id: ToolCallId },
CodeCell { code_cell_id: CodeCellId },
InteractionEdge { edge_id: EdgeId },
Compaction { compaction_id: CompactionId },
Harness,
}
/// One outbound inference request and its response metadata.
///
/// Full upstream request/response bodies live behind raw payload refs. The
/// request/response item ID lists are the reduced, model-visible snapshot.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InferenceCall {
pub inference_call_id: InferenceCallId,
pub thread_id: AgentThreadId,
pub codex_turn_id: CodexTurnId,
pub execution: ExecutionWindow,
pub model: String,
pub provider_name: String,
/// Responses API response id, used by follow-up `previous_response_id` requests.
pub response_id: Option<String>,
/// Request id returned by HTTP/proxy/engine infrastructure.
pub upstream_request_id: Option<String>,
/// Complete ordered input snapshot sent with this request.
pub request_item_ids: Vec<ConversationItemId>,
/// Ordered output items produced by this response.
pub response_item_ids: Vec<ConversationItemId>,
/// Runtime tool calls whose model-visible call item came from this response.
pub tool_call_ids_started_by_response: Vec<ToolCallId>,
pub usage: Option<TokenUsage>,
pub raw_request_payload_id: RawPayloadId,
/// Full upstream response payload. `None` while running or after pre-stream failures.
pub raw_response_payload_id: Option<RawPayloadId>,
}
/// Token usage summary for one inference call.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenUsage {
pub input_tokens: u64,
pub cached_input_tokens: u64,
pub output_tokens: u64,
pub reasoning_output_tokens: u64,
}