mirror of
https://github.com/openai/codex.git
synced 2026-04-29 17:06:51 +00:00
feat: structured multi-agent output (#15515)
Send input now sends messages as assistant message and with this format: ``` author: /root/worker_a recipient: /root/worker_a/tester other_recipients: [] Content: bla bla bla. Actual content. Only text for now ```
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
use crate::agent::AgentStatus;
|
||||
use crate::agent::inter_agent_instruction::InterAgentDelivery;
|
||||
use crate::agent::inter_agent_instruction::InterAgentInstruction;
|
||||
use crate::codex::Codex;
|
||||
use crate::codex::SteerInputError;
|
||||
use crate::config::ConstraintResult;
|
||||
@@ -122,29 +124,90 @@ impl CodexThread {
|
||||
|
||||
/// Records a user-role session-prefix message without creating a new user turn boundary.
|
||||
pub(crate) async fn inject_user_message_without_turn(&self, message: String) {
|
||||
let pending_item = ResponseInputItem::Message {
|
||||
let message = ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText { text: message }],
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
};
|
||||
let pending_items = vec![pending_item];
|
||||
let Err(items_without_active_turn) = self
|
||||
let pending_item = match pending_message_input_item(&message) {
|
||||
Ok(pending_item) => pending_item,
|
||||
Err(err) => {
|
||||
debug_assert!(false, "session-prefix message append should succeed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if self
|
||||
.codex
|
||||
.session
|
||||
.inject_response_items(pending_items)
|
||||
.inject_response_items(vec![pending_item])
|
||||
.await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
.is_err()
|
||||
{
|
||||
let turn_context = self.codex.session.new_default_turn().await;
|
||||
self.codex
|
||||
.session
|
||||
.record_conversation_items(turn_context.as_ref(), &[message])
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
let turn_context = self.codex.session.new_default_turn().await;
|
||||
let items: Vec<ResponseItem> = items_without_active_turn
|
||||
.into_iter()
|
||||
.map(ResponseItem::from)
|
||||
.collect();
|
||||
/// Append a prebuilt message to the thread history without treating it as a user turn.
|
||||
///
|
||||
/// If the thread already has an active turn, the message is queued as pending input for that
|
||||
/// turn. Otherwise it is queued at session scope and a regular turn is started so the agent
|
||||
/// can consume that pending input through the normal turn pipeline.
|
||||
pub(crate) async fn append_message(&self, message: ResponseItem) -> CodexResult<String> {
|
||||
let submission_id = uuid::Uuid::new_v4().to_string();
|
||||
let pending_item = pending_message_input_item(&message)?;
|
||||
if let Err(items) = self
|
||||
.codex
|
||||
.session
|
||||
.inject_response_items(vec![pending_item])
|
||||
.await
|
||||
{
|
||||
self.codex
|
||||
.session
|
||||
.queue_response_items_for_next_turn(items)
|
||||
.await;
|
||||
self.codex
|
||||
.session
|
||||
.ensure_task_for_queued_response_items()
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(submission_id)
|
||||
}
|
||||
|
||||
pub(crate) async fn deliver_inter_agent_instruction(
|
||||
&self,
|
||||
instruction: InterAgentInstruction,
|
||||
delivery: InterAgentDelivery,
|
||||
) -> CodexResult<String> {
|
||||
let message = instruction.to_response_item();
|
||||
match delivery {
|
||||
InterAgentDelivery::CurrentTurn => self.append_message(message).await,
|
||||
InterAgentDelivery::NextTurn => self.queue_message_for_next_turn(message).await,
|
||||
}
|
||||
}
|
||||
|
||||
/// Queue a prebuilt message so the next turn records it before any submitted user input.
|
||||
pub(crate) async fn queue_message_for_next_turn(
|
||||
&self,
|
||||
message: ResponseItem,
|
||||
) -> CodexResult<String> {
|
||||
let submission_id = uuid::Uuid::new_v4().to_string();
|
||||
let pending_item = pending_message_input_item(&message)?;
|
||||
self.codex
|
||||
.session
|
||||
.record_conversation_items(turn_context.as_ref(), &items)
|
||||
.queue_response_items_for_next_turn(vec![pending_item])
|
||||
.await;
|
||||
self.codex
|
||||
.session
|
||||
.ensure_task_for_queued_response_items()
|
||||
.await;
|
||||
Ok(submission_id)
|
||||
}
|
||||
|
||||
pub fn rollout_path(&self) -> Option<PathBuf> {
|
||||
@@ -198,3 +261,15 @@ impl CodexThread {
|
||||
Ok(*guard)
|
||||
}
|
||||
}
|
||||
|
||||
fn pending_message_input_item(message: &ResponseItem) -> CodexResult<ResponseInputItem> {
|
||||
match message {
|
||||
ResponseItem::Message { role, content, .. } => Ok(ResponseInputItem::Message {
|
||||
role: role.clone(),
|
||||
content: content.clone(),
|
||||
}),
|
||||
_ => Err(CodexErr::InvalidRequest(
|
||||
"append_message only supports ResponseItem::Message".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user