Hide synthetic timer prompts in chat history

This commit is contained in:
Eric Traut
2026-04-08 21:46:40 -07:00
parent 1475f41b01
commit fbee3bf34f
2 changed files with 58 additions and 4 deletions

View File

@@ -1131,7 +1131,24 @@ fn parse_timer_fired_user_message(message: &str) -> Option<TimerFiredUserMessage
})
}
fn parse_synthetic_user_message_display(message: &str) -> Option<String> {
let trimmed = message.trim();
if !trimmed.starts_with("<codex_tui_synthetic_user_message>")
|| !trimmed.ends_with("</codex_tui_synthetic_user_message>")
{
return None;
}
let display = extract_xml_tag(trimmed, "display")?;
let display = xml_unescape(display.trim());
(!display.is_empty()).then_some(display)
}
fn extract_timer_fired_tag(message: &str, tag: &str) -> Option<String> {
extract_xml_tag(message, tag)
}
fn extract_xml_tag(message: &str, tag: &str) -> Option<String> {
let open = format!("<{tag}>");
let close = format!("</{tag}>");
let after_open = message.split_once(&open)?.1;
@@ -1139,6 +1156,12 @@ fn extract_timer_fired_tag(message: &str, tag: &str) -> Option<String> {
Some(value.to_string())
}
fn xml_unescape(text: &str) -> String {
text.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&amp;", "&")
}
struct PendingSteer {
user_message: UserMessage,
compare_key: PendingSteerCompareKey,
@@ -5892,6 +5915,8 @@ impl ChatWidget {
// Show replayable user content in conversation history.
if render_in_history && !text.is_empty() {
let history_text =
parse_synthetic_user_message_display(&text).unwrap_or_else(|| text.clone());
let local_image_paths = local_images
.into_iter()
.map(|img| img.path)
@@ -5904,7 +5929,7 @@ impl ChatWidget {
remote_image_urls.clone(),
));
self.add_to_history(history_cell::new_user_prompt(
text,
history_text,
text_elements,
local_image_paths,
remote_image_urls,
@@ -7257,8 +7282,10 @@ impl ChatWidget {
|| !event.text_elements.is_empty()
|| !remote_image_urls.is_empty()
{
let message = parse_synthetic_user_message_display(&event.message)
.unwrap_or_else(|| event.message);
self.add_to_history(history_cell::new_user_prompt(
event.message,
message,
event.text_elements,
event.local_images,
remote_image_urls,

View File

@@ -1,5 +1,8 @@
pub(crate) fn build_timer_list_prompt() -> String {
"List the thread timers that are currently scheduled. Call the TimerList tool directly, then summarize the pending timers briefly for the user. If there are no pending timers, say that none are scheduled.".to_string()
wrap_synthetic_user_message(
"/loop",
"List the thread timers that are currently scheduled. Call the TimerList tool directly, then summarize the pending timers briefly for the user. If there are no pending timers, say that none are scheduled.",
)
}
pub(crate) fn build_loop_timer_prompt(spec: &str) -> Result<String, String> {
@@ -9,7 +12,7 @@ pub(crate) fn build_loop_timer_prompt(spec: &str) -> Result<String, String> {
}
let now = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S");
let timezone = chrono::Local::now().offset().to_string();
Ok(format!(
let prompt = format!(
r#"Create a Codex thread timer from this `/loop` request. Call the TimerCreate tool directly; do not only describe the timer.
Current local datetime: {now}
@@ -30,9 +33,31 @@ Interpretation rules:
- For recurring calendar timing, use a schedule trigger with rrule set to an RFC 5545 RRULE string and dtstart set when the user supplies a start datetime; otherwise omit dtstart.
- For schedule triggers, use floating local wall-clock datetimes without timezone suffixes.
- After TimerCreate succeeds, briefly confirm the schedule and the timer prompt."#
);
Ok(wrap_synthetic_user_message(
&format!("/loop {spec}"),
&prompt,
))
}
fn wrap_synthetic_user_message(display: &str, prompt: &str) -> String {
format!(
r#"<codex_tui_synthetic_user_message>
<display>{}</display>
<prompt>
{prompt}
</prompt>
</codex_tui_synthetic_user_message>"#,
xml_escape(display)
)
}
fn xml_escape(text: &str) -> String {
text.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
#[cfg(test)]
mod tests {
use super::*;
@@ -45,6 +70,7 @@ mod tests {
assert!(prompt.contains("Call the TimerCreate tool directly"));
assert!(prompt.contains("every 5 minutes run tests"));
assert!(prompt.contains("Current local datetime:"));
assert!(prompt.contains("<display>/loop every 5 minutes run tests</display>"));
}
#[test]
@@ -52,5 +78,6 @@ mod tests {
let prompt = build_timer_list_prompt();
assert!(prompt.contains("Call the TimerList tool directly"));
assert!(prompt.contains("<display>/loop</display>"));
}
}