mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
Simplify injected message envelopes
This commit is contained in:
@@ -32,6 +32,7 @@ use super::EventMsg;
|
||||
use super::INITIAL_SUBMIT_ID;
|
||||
use super::Session;
|
||||
use crate::messages::MessageInvocationContext;
|
||||
use crate::messages::MessageInvocationKind;
|
||||
use crate::messages::MessagePayload;
|
||||
use crate::messages::db_message_to_thread_message;
|
||||
use crate::messages::injected_message_event;
|
||||
@@ -381,11 +382,10 @@ impl Session {
|
||||
};
|
||||
let delivery = message.delivery;
|
||||
let message_context = MessageInvocationContext {
|
||||
kind: MessageInvocationKind::External,
|
||||
source: message.source.clone(),
|
||||
content: message.content.clone(),
|
||||
instructions: message.instructions.clone(),
|
||||
meta: message.meta.clone(),
|
||||
queued_at: message.queued_at,
|
||||
content: message.content,
|
||||
instructions: None,
|
||||
};
|
||||
let input_item = PendingInputItem::GeneratedMessage(GeneratedMessageInput {
|
||||
item: message_prompt_input_item(&message_context),
|
||||
|
||||
@@ -5067,7 +5067,8 @@ async fn generated_message_records_raw_item_and_emits_injected_event() {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "<codex_message><content>hidden wrapper</content></codex_message>".to_string(),
|
||||
text: "<external_message><content>hidden wrapper</content></external_message>"
|
||||
.to_string(),
|
||||
}],
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
|
||||
@@ -8,8 +8,10 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub const CODEX_MESSAGE_OPEN_TAG: &str = "<codex_message>";
|
||||
pub const CODEX_MESSAGE_CLOSE_TAG: &str = "</codex_message>";
|
||||
const EXTERNAL_MESSAGE_OPEN_TAG: &str = "<external_message>";
|
||||
const EXTERNAL_MESSAGE_CLOSE_TAG: &str = "</external_message>";
|
||||
const TIMER_MESSAGE_OPEN_TAG: &str = "<timer_message>";
|
||||
const TIMER_MESSAGE_CLOSE_TAG: &str = "</timer_message>";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MessagePayload {
|
||||
@@ -32,13 +34,18 @@ pub struct ThreadMessage {
|
||||
pub queued_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum MessageInvocationKind {
|
||||
External,
|
||||
Timer { timer_id: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct MessageInvocationContext {
|
||||
pub(crate) kind: MessageInvocationKind,
|
||||
pub(crate) source: String,
|
||||
pub(crate) content: String,
|
||||
pub(crate) instructions: Option<String>,
|
||||
pub(crate) meta: BTreeMap<String, String>,
|
||||
pub(crate) queued_at: i64,
|
||||
}
|
||||
|
||||
pub fn validate_meta_key(key: &str) -> Result<(), String> {
|
||||
@@ -81,28 +88,24 @@ pub(crate) fn injected_message_event(message: &MessageInvocationContext) -> Inje
|
||||
|
||||
pub(crate) fn render_message_prompt(message: &MessageInvocationContext) -> String {
|
||||
let mut rendered = String::new();
|
||||
rendered.push_str(CODEX_MESSAGE_OPEN_TAG);
|
||||
rendered.push('\n');
|
||||
push_tag(&mut rendered, "source", &message.source);
|
||||
push_tag(&mut rendered, "queued_at", &message.queued_at.to_string());
|
||||
push_block_tag(&mut rendered, "content", &message.content);
|
||||
if let Some(instructions) = message.instructions.as_deref() {
|
||||
push_block_tag(&mut rendered, "instructions", instructions);
|
||||
}
|
||||
if message.meta.is_empty() {
|
||||
rendered.push_str("<meta />\n");
|
||||
} else {
|
||||
rendered.push_str("<meta>\n");
|
||||
for (key, value) in &message.meta {
|
||||
rendered.push_str(" <entry id=\"");
|
||||
rendered.push_str(&xml_escape(key));
|
||||
rendered.push_str("\">");
|
||||
rendered.push_str(&xml_escape(value));
|
||||
rendered.push_str("</entry>\n");
|
||||
match &message.kind {
|
||||
MessageInvocationKind::External => {
|
||||
rendered.push_str(EXTERNAL_MESSAGE_OPEN_TAG);
|
||||
rendered.push('\n');
|
||||
push_block_tag(&mut rendered, "content", &message.content);
|
||||
rendered.push_str(EXTERNAL_MESSAGE_CLOSE_TAG);
|
||||
}
|
||||
MessageInvocationKind::Timer { timer_id } => {
|
||||
rendered.push_str(TIMER_MESSAGE_OPEN_TAG);
|
||||
rendered.push('\n');
|
||||
push_tag(&mut rendered, "timer_id", timer_id);
|
||||
push_block_tag(&mut rendered, "content", &message.content);
|
||||
if let Some(instructions) = message.instructions.as_deref() {
|
||||
push_block_tag(&mut rendered, "instructions", instructions);
|
||||
}
|
||||
rendered.push_str(TIMER_MESSAGE_CLOSE_TAG);
|
||||
}
|
||||
rendered.push_str("</meta>\n");
|
||||
}
|
||||
rendered.push_str(CODEX_MESSAGE_CLOSE_TAG);
|
||||
rendered
|
||||
}
|
||||
|
||||
@@ -162,18 +165,34 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn renders_message_prompt_with_escaped_metadata() {
|
||||
fn renders_external_message_prompt_with_only_content() {
|
||||
let message = MessageInvocationContext {
|
||||
source: "timer timer-1".to_string(),
|
||||
kind: MessageInvocationKind::External,
|
||||
source: "external".to_string(),
|
||||
content: "run <tests>".to_string(),
|
||||
instructions: Some("stay \"brief\"".to_string()),
|
||||
meta: BTreeMap::from([("ticket".to_string(), "ABC&123".to_string())]),
|
||||
queued_at: 100,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
render_message_prompt(&message),
|
||||
"<codex_message>\n<source>timer timer-1</source>\n<queued_at>100</queued_at>\n<content>\nrun <tests>\n</content>\n<instructions>\nstay "brief"\n</instructions>\n<meta>\n <entry id=\"ticket\">ABC&123</entry>\n</meta>\n</codex_message>"
|
||||
"<external_message>\n<content>\nrun <tests>\n</content>\n</external_message>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_timer_message_prompt_with_timer_id_and_no_metadata() {
|
||||
let message = MessageInvocationContext {
|
||||
kind: MessageInvocationKind::Timer {
|
||||
timer_id: "timer-1".to_string(),
|
||||
},
|
||||
source: "timer timer-1".to_string(),
|
||||
content: "run <tests>".to_string(),
|
||||
instructions: Some("stay \"brief\"".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
render_message_prompt(&message),
|
||||
"<timer_message>\n<timer_id>timer-1</timer_id>\n<content>\nrun <tests>\n</content>\n<instructions>\nstay "brief"\n</instructions>\n</timer_message>"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! restore timers after a harness restart.
|
||||
|
||||
use crate::messages::MessageInvocationContext;
|
||||
use crate::messages::MessageInvocationKind;
|
||||
use crate::messages::MessagePayload;
|
||||
use crate::messages::validate_meta;
|
||||
use crate::timer_trigger::TimerTrigger;
|
||||
@@ -488,11 +489,12 @@ pub(crate) fn timer_message_invocation_context(
|
||||
timer: &TimerInvocationContext,
|
||||
) -> MessageInvocationContext {
|
||||
MessageInvocationContext {
|
||||
kind: MessageInvocationKind::Timer {
|
||||
timer_id: timer.current_timer_id.clone(),
|
||||
},
|
||||
source: format!("timer {}", timer.current_timer_id),
|
||||
content: timer_message_content(timer),
|
||||
instructions: timer_message_instructions(timer),
|
||||
meta: timer.meta.clone(),
|
||||
queued_at: timer.queued_at,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -947,7 +949,7 @@ mod tests {
|
||||
ResponseInputItem::Message {
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "<codex_message>\n<source>timer timer-1</source>\n<queued_at>100</queued_at>\n<content>\nTimer fired: run tests\n</content>\n<instructions>\nrun tests\n\nThis timer should keep running on its schedule after this invocation.\nDo not call delete_timer just because you completed this invocation.\nCall delete_timer with {"id":"timer-1"} only if the user's timer message included an explicit stop condition, such as "until", "stop when", or "while", and that condition is now satisfied.\nDo not expose scheduler internals unless they matter to the user.\n</instructions>\n<meta />\n</codex_message>".to_string(),
|
||||
text: "<timer_message>\n<timer_id>timer-1</timer_id>\n<content>\nTimer fired: run tests\n</content>\n<instructions>\nrun tests\n\nThis timer should keep running on its schedule after this invocation.\nDo not call delete_timer just because you completed this invocation.\nCall delete_timer with {"id":"timer-1"} only if the user's timer message included an explicit stop condition, such as "until", "stop when", or "while", and that condition is now satisfied.\nDo not expose scheduler internals unless they matter to the user.\n</instructions>\n</timer_message>".to_string(),
|
||||
}],
|
||||
}
|
||||
);
|
||||
@@ -969,7 +971,7 @@ mod tests {
|
||||
ResponseInputItem::Message {
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "<codex_message>\n<source>timer timer-1</source>\n<queued_at>101</queued_at>\n<content>\nTimer fired: run tests once\n</content>\n<instructions>\nrun tests once\n\nuser-specific instruction\n\nThis one-shot timer has already been removed from the schedule, so you do not need to call delete_timer.\nDo not expose scheduler internals unless they matter to the user.\n</instructions>\n<meta>\n <entry id=\"ticket\">ABC_123</entry>\n</meta>\n</codex_message>".to_string(),
|
||||
text: "<timer_message>\n<timer_id>timer-1</timer_id>\n<content>\nTimer fired: run tests once\n</content>\n<instructions>\nrun tests once\n\nuser-specific instruction\n\nThis one-shot timer has already been removed from the schedule, so you do not need to call delete_timer.\nDo not expose scheduler internals unless they matter to the user.\n</instructions>\n</timer_message>".to_string(),
|
||||
}],
|
||||
}
|
||||
);
|
||||
|
||||
@@ -117,14 +117,13 @@ async fn assert_after_turn_timer_starts_and_emits_fired_event() -> Result<()> {
|
||||
let user_messages = mock.single_request().message_input_texts("user");
|
||||
let timer_messages = user_messages
|
||||
.iter()
|
||||
.filter(|message| message.contains("<codex_message>"))
|
||||
.filter(|message| message.contains("<timer_message>"))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(timer_messages.len(), 1);
|
||||
let timer_message = timer_messages[0];
|
||||
assert!(timer_message.contains(&format!("<source>timer {}</source>", created.id)));
|
||||
assert!(timer_message.contains(&format!("<timer_id>{}</timer_id>", created.id)));
|
||||
assert!(timer_message.contains("<content>\nTimer fired: run timer\n</content>"));
|
||||
assert!(timer_message.contains("<instructions>\nrun timer\n\nThis one-shot timer"));
|
||||
assert!(timer_message.contains("<meta />"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user