tests: drop context snapshot text replacement rules

This commit is contained in:
Charles Cunningham
2026-02-11 19:40:54 -08:00
parent dc7ec6e201
commit f7c1979abe
3 changed files with 79 additions and 215 deletions

View File

@@ -13,30 +13,12 @@ pub enum ContextSnapshotRenderMode {
#[derive(Debug, Clone)]
pub struct ContextSnapshotOptions {
render_mode: ContextSnapshotRenderMode,
normalize_environment_context: bool,
text_exact_replacements: Vec<(String, String)>,
text_prefix_replacements: Vec<(String, String)>,
text_contains_replacements: Vec<(String, String)>,
cwd_contains_replacements: Vec<(String, String)>,
tool_name_replacements: Vec<(String, String)>,
}
impl Default for ContextSnapshotOptions {
fn default() -> Self {
Self {
render_mode: ContextSnapshotRenderMode::RedactedText,
normalize_environment_context: true,
text_exact_replacements: Vec::new(),
text_prefix_replacements: vec![(
"# AGENTS.md instructions for ".to_string(),
"<AGENTS_MD>".to_string(),
)],
text_contains_replacements: vec![(
"<permissions instructions>".to_string(),
"<PERMISSIONS_INSTRUCTIONS>".to_string(),
)],
cwd_contains_replacements: Vec::new(),
tool_name_replacements: Vec::new(),
}
}
}
@@ -46,61 +28,6 @@ impl ContextSnapshotOptions {
self.render_mode = render_mode;
self
}
pub fn normalize_environment_context(mut self, enabled: bool) -> Self {
self.normalize_environment_context = enabled;
self
}
pub fn replace_exact_text(
mut self,
text: impl Into<String>,
replacement: impl Into<String>,
) -> Self {
self.text_exact_replacements
.push((text.into(), replacement.into()));
self
}
pub fn replace_text_with_prefix(
mut self,
prefix: impl Into<String>,
replacement: impl Into<String>,
) -> Self {
self.text_prefix_replacements
.push((prefix.into(), replacement.into()));
self
}
pub fn replace_text_containing(
mut self,
needle: impl Into<String>,
replacement: impl Into<String>,
) -> Self {
self.text_contains_replacements
.push((needle.into(), replacement.into()));
self
}
pub fn replace_cwd_when_contains(
mut self,
cwd_substring: impl Into<String>,
replacement: impl Into<String>,
) -> Self {
self.cwd_contains_replacements
.push((cwd_substring.into(), replacement.into()));
self
}
pub fn replace_tool_name(
mut self,
original_name: impl Into<String>,
replacement: impl Into<String>,
) -> Self {
self.tool_name_replacements
.push((original_name.into(), replacement.into()));
self
}
}
pub fn request_input_shape(request: &ResponsesRequest, options: &ContextSnapshotOptions) -> String {
@@ -146,32 +73,13 @@ pub fn response_items_shape(items: &[Value], options: &ContextSnapshotOptions) -
}
"function_call" => {
let name = item.get("name").and_then(Value::as_str).unwrap_or("unknown");
let normalized_name = options
.tool_name_replacements
.iter()
.find_map(|(original_name, replacement)| {
(original_name == name).then_some(replacement.as_str())
})
.unwrap_or(name);
format!("{idx:02}:function_call/{normalized_name}")
format!("{idx:02}:function_call/{name}")
}
"function_call_output" => {
let output = item
.get("output")
.and_then(Value::as_str)
.map(|output| match options.render_mode {
ContextSnapshotRenderMode::RedactedText => {
if output.starts_with("unsupported call: ")
|| output.starts_with("unsupported custom tool call: ")
{
"<TOOL_ERROR_OUTPUT>".to_string()
} else {
normalize_shape_text(output, options)
}
}
ContextSnapshotRenderMode::FullText => output.replace('\n', "\\n"),
ContextSnapshotRenderMode::KindOnly => unreachable!(),
})
.map(|output| output.replace('\n', "\\n"))
.unwrap_or_else(|| "<NON_STRING_OUTPUT>".to_string());
format!("{idx:02}:function_call_output:{output}")
}
@@ -249,52 +157,10 @@ pub fn sectioned_item_shapes(
}
fn normalize_shape_text(text: &str, options: &ContextSnapshotOptions) -> String {
if options.render_mode == ContextSnapshotRenderMode::FullText {
return text.replace('\n', "\\n");
match options.render_mode {
ContextSnapshotRenderMode::RedactedText | ContextSnapshotRenderMode::FullText => {
text.replace('\n', "\\n")
}
ContextSnapshotRenderMode::KindOnly => unreachable!(),
}
if let Some((_, replacement)) = options
.text_exact_replacements
.iter()
.find(|(target, _)| target == text)
{
return replacement.clone();
}
if let Some((_, replacement)) = options
.text_prefix_replacements
.iter()
.find(|(prefix, _)| text.starts_with(prefix.as_str()))
{
return replacement.clone();
}
if options.normalize_environment_context && text.starts_with("<environment_context>") {
let cwd = text.lines().find_map(|line| {
let trimmed = line.trim();
let cwd = trimmed.strip_prefix("<cwd>")?.strip_suffix("</cwd>")?;
if let Some((_, replacement)) = options
.cwd_contains_replacements
.iter()
.find(|(needle, _)| cwd.contains(needle.as_str()))
{
return Some(replacement.clone());
}
Some("<CWD>".to_string())
});
return match cwd {
Some(cwd) => format!("<ENVIRONMENT_CONTEXT:cwd={cwd}>"),
None => "<ENVIRONMENT_CONTEXT:cwd=<NONE>>".to_string(),
};
}
if let Some((_, replacement)) = options
.text_contains_replacements
.iter()
.find(|(needle, _)| text.contains(needle.as_str()))
{
return replacement.clone();
}
text.replace('\n', "\\n")
}

View File

@@ -22,6 +22,7 @@ use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::user_input::UserInput;
use core_test_support::context_snapshot;
use core_test_support::context_snapshot::ContextSnapshotOptions;
use core_test_support::context_snapshot::ContextSnapshotRenderMode;
use core_test_support::responses::ev_local_shell_call;
use core_test_support::responses::ev_reasoning_item;
use core_test_support::responses::mount_models_once;
@@ -192,21 +193,7 @@ async fn assert_compaction_uses_turn_lifecycle_id(codex: &std::sync::Arc<codex_c
);
}
fn context_snapshot_options() -> ContextSnapshotOptions {
ContextSnapshotOptions::default()
.replace_exact_text(SUMMARIZATION_PROMPT, "<SUMMARIZATION_PROMPT>")
.replace_cwd_when_contains(
PRETURN_CONTEXT_DIFF_CWD_MARKER,
PRETURN_CONTEXT_DIFF_CWD_MARKER,
)
.replace_tool_name(DUMMY_FUNCTION_NAME, "<TOOL_CALL>")
}
fn summary_snapshot_text(summary: &str) -> String {
summary_with_prefix(summary).replace('\n', "\\n")
}
fn request_input_shape(request: &core_test_support::responses::ResponsesRequest) -> String {
context_snapshot::request_input_shape(request, &context_snapshot_options())
ContextSnapshotOptions::default().render_mode(ContextSnapshotRenderMode::KindOnly)
}
fn sectioned_request_shapes(
@@ -216,6 +203,13 @@ fn sectioned_request_shapes(
context_snapshot::sectioned_request_shapes(scenario, sections, &context_snapshot_options())
}
fn request_contains_text(
request: &core_test_support::responses::ResponsesRequest,
text: &str,
) -> bool {
body_contains_text(&request.body_json().to_string(), text)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn summarize_context_three_requests_and_instructions() {
skip_if_no_network!();
@@ -3055,8 +3049,6 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
let requests = request_log.requests();
assert_eq!(requests.len(), 4, "expected user, user, compact, follow-up");
let compact_shape = request_input_shape(&requests[2]);
let follow_up_shape = request_input_shape(&requests[3]);
insta::assert_snapshot!(
"pre_turn_compaction_including_incoming_shapes",
sectioned_request_shapes(
@@ -3068,15 +3060,15 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
)
);
assert!(
compact_shape.contains("<SUMMARIZATION_PROMPT>"),
request_contains_text(&requests[2], SUMMARIZATION_PROMPT),
"expected compact request to include summarization prompt"
);
assert!(
compact_shape.contains(PRETURN_CONTEXT_DIFF_CWD_MARKER),
request_contains_text(&requests[2], PRETURN_CONTEXT_DIFF_CWD_MARKER),
"expected compact request to include pre-turn context diff"
);
assert!(
!compact_shape.contains("USER_THREE"),
!request_contains_text(&requests[2], "USER_THREE"),
"current behavior excludes incoming user message from pre-turn compaction input"
);
let follow_up_has_incoming_image = requests[3].inputs_of_type("message").iter().any(|item| {
@@ -3101,7 +3093,7 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
"expected post-compaction follow-up request to keep incoming user image content"
);
assert!(
follow_up_shape.contains(&summary_snapshot_text("PRE_TURN_SUMMARY")),
request_contains_text(&requests[3], &summary_with_prefix("PRE_TURN_SUMMARY")),
"expected post-compaction request to include summary text"
);
}
@@ -3178,7 +3170,6 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() {
"expected first turn and at least one compaction request"
);
let include_attempt_shape = request_input_shape(&requests[1]);
insta::assert_snapshot!(
"pre_turn_compaction_context_window_exceeded_shapes",
sectioned_request_shapes(
@@ -3191,7 +3182,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() {
);
assert!(
!include_attempt_shape.contains("USER_TWO"),
!request_contains_text(&requests[1], "USER_TWO"),
"current behavior excludes incoming user message from pre-turn compaction input"
);
assert!(
@@ -3251,8 +3242,6 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() {
let requests = request_log.requests();
assert_eq!(requests.len(), 3, "expected user, compact, follow-up");
let compact_shape = request_input_shape(&requests[1]);
let follow_up_shape = request_input_shape(&requests[2]);
insta::assert_snapshot!(
"mid_turn_compaction_shapes",
sectioned_request_shapes(
@@ -3264,15 +3253,17 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() {
)
);
assert!(
compact_shape.contains("function_call_output"),
!requests[1]
.inputs_of_type("function_call_output")
.is_empty(),
"mid-turn compaction request should include function call output"
);
assert!(
compact_shape.contains("<SUMMARIZATION_PROMPT>"),
request_contains_text(&requests[1], SUMMARIZATION_PROMPT),
"mid-turn compaction request should include summarization prompt"
);
assert!(
follow_up_shape.contains(&summary_snapshot_text("MID_TURN_SUMMARY")),
request_contains_text(&requests[2], &summary_with_prefix("MID_TURN_SUMMARY")),
"post-mid-turn compaction request should include summary text"
);
}
@@ -3326,8 +3317,6 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages()
"expected manual /compact request and follow-up turn request"
);
let compact_shape = request_input_shape(&requests[0]);
let follow_up_shape = request_input_shape(&requests[1]);
insta::assert_snapshot!(
"manual_compact_without_prev_user_shapes",
sectioned_request_shapes(
@@ -3339,11 +3328,11 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages()
)
);
assert!(
compact_shape.contains("<SUMMARIZATION_PROMPT>"),
request_contains_text(&requests[0], SUMMARIZATION_PROMPT),
"manual /compact request should include summarization prompt"
);
assert!(
follow_up_shape.contains("AFTER_MANUAL_EMPTY_COMPACT"),
request_contains_text(&requests[1], "AFTER_MANUAL_EMPTY_COMPACT"),
"follow-up request should include the submitted user message"
);
}
@@ -3410,8 +3399,6 @@ async fn snapshot_request_shape_manual_compact_with_previous_user_messages() {
let requests = request_log.requests();
assert_eq!(requests.len(), 3, "expected user, compact, follow-up");
let compact_shape = request_input_shape(&requests[1]);
let follow_up_shape = request_input_shape(&requests[2]);
insta::assert_snapshot!(
"manual_compact_with_history_shapes",
sectioned_request_shapes(
@@ -3423,19 +3410,19 @@ async fn snapshot_request_shape_manual_compact_with_previous_user_messages() {
)
);
assert!(
compact_shape.contains("USER_ONE"),
request_contains_text(&requests[1], "USER_ONE"),
"manual compact request should include existing user history"
);
assert!(
compact_shape.contains("<SUMMARIZATION_PROMPT>"),
request_contains_text(&requests[1], SUMMARIZATION_PROMPT),
"manual compact request should include summarization prompt"
);
assert!(
follow_up_shape.contains(&summary_snapshot_text("MANUAL_SUMMARY")),
request_contains_text(&requests[2], &summary_with_prefix("MANUAL_SUMMARY")),
"post-compact request should include compact summary text"
);
assert!(
follow_up_shape.contains("USER_TWO"),
request_contains_text(&requests[2], "USER_TWO"),
"post-compact request should include the latest user message"
);
}

View File

@@ -18,6 +18,7 @@ use codex_protocol::models::ResponseItem;
use codex_protocol::user_input::UserInput;
use core_test_support::context_snapshot;
use core_test_support::context_snapshot::ContextSnapshotOptions;
use core_test_support::context_snapshot::ContextSnapshotRenderMode;
use core_test_support::responses;
use core_test_support::responses::mount_sse_once;
use core_test_support::responses::sse;
@@ -65,20 +66,7 @@ fn user_message_item(text: &str) -> ResponseItem {
}
fn context_snapshot_options() -> ContextSnapshotOptions {
ContextSnapshotOptions::default()
.replace_cwd_when_contains(
PRETURN_CONTEXT_DIFF_CWD_MARKER,
PRETURN_CONTEXT_DIFF_CWD_MARKER,
)
.replace_tool_name(DUMMY_FUNCTION_NAME, "<TOOL_CALL>")
}
fn summary_snapshot_text(summary: &str) -> String {
summary_with_prefix(summary).replace('\n', "\\n")
}
fn request_input_shape(request: &responses::ResponsesRequest) -> String {
context_snapshot::request_input_shape(request, &context_snapshot_options())
ContextSnapshotOptions::default().render_mode(ContextSnapshotRenderMode::KindOnly)
}
fn sectioned_request_shapes(
@@ -88,6 +76,20 @@ fn sectioned_request_shapes(
context_snapshot::sectioned_request_shapes(scenario, sections, &context_snapshot_options())
}
fn json_fragment(text: &str) -> String {
serde_json::to_string(text)
.expect("serialize text to JSON")
.trim_matches('"')
.to_string()
}
fn request_contains_text(request: &responses::ResponsesRequest, text: &str) -> bool {
request
.body_json()
.to_string()
.contains(&json_fragment(text))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn remote_compact_replaces_history_for_followups() -> Result<()> {
skip_if_no_network!(Ok(()));
@@ -1452,8 +1454,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us
);
let compact_request = compact_mock.single_request();
let compact_shape = request_input_shape(&compact_request);
let follow_up_shape = request_input_shape(&requests[2]);
insta::assert_snapshot!(
"remote_pre_turn_compaction_including_incoming_shapes",
sectioned_request_shapes(
@@ -1465,19 +1465,26 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us
)
);
assert!(
compact_shape.contains(PRETURN_CONTEXT_DIFF_CWD_MARKER),
request_contains_text(&compact_request, PRETURN_CONTEXT_DIFF_CWD_MARKER),
"expected remote compact request to include pre-turn context diff"
);
assert!(
!compact_shape.contains("USER_THREE"),
!request_contains_text(&compact_request, "USER_THREE"),
"current behavior excludes incoming user message from remote pre-turn compaction input"
);
assert!(
follow_up_shape.contains(&summary_snapshot_text("REMOTE_PRE_TURN_SUMMARY")),
request_contains_text(
&requests[2],
&summary_with_prefix("REMOTE_PRE_TURN_SUMMARY")
),
"post-compaction request should include remote summary text"
);
assert_eq!(
follow_up_shape.matches("USER_THREE").count(),
requests[2]
.message_input_texts("user")
.iter()
.filter(|text| text.as_str() == "USER_THREE")
.count(),
1,
"post-compaction request should contain incoming user exactly once from runtime append"
);
@@ -1565,7 +1572,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_failure_stops_without
);
let include_attempt_request = first_compact_mock.single_request();
let include_attempt_shape = request_input_shape(&include_attempt_request);
insta::assert_snapshot!(
"remote_pre_turn_compaction_failure_shapes",
sectioned_request_shapes(
@@ -1577,7 +1583,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_failure_stops_without
)
);
assert!(
!include_attempt_shape.contains("USER_TWO"),
!request_contains_text(&include_attempt_request, "USER_TWO"),
"current behavior excludes incoming user message from remote pre-turn compaction input"
);
assert!(
@@ -1673,7 +1679,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed
);
let include_attempt_request = compact_mock.single_request();
let include_attempt_shape = request_input_shape(&include_attempt_request);
insta::assert_snapshot!(
"remote_pre_turn_compaction_context_window_exceeded_shapes",
sectioned_request_shapes(
@@ -1685,7 +1690,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed
)
);
assert!(
!include_attempt_shape.contains("USER_TWO"),
!request_contains_text(&include_attempt_request, "USER_TWO"),
"current behavior excludes incoming user message from remote pre-turn compaction input"
);
assert!(
@@ -1755,8 +1760,6 @@ async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Res
);
let compact_request = compact_mock.single_request();
let compact_shape = request_input_shape(&compact_request);
let follow_up_shape = request_input_shape(&requests[1]);
insta::assert_snapshot!(
"remote_mid_turn_compaction_shapes",
sectioned_request_shapes(
@@ -1768,11 +1771,16 @@ async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Res
)
);
assert!(
compact_shape.contains("function_call_output"),
!compact_request
.inputs_of_type("function_call_output")
.is_empty(),
"remote mid-turn compaction request should include function call output"
);
assert!(
follow_up_shape.contains(&summary_snapshot_text("REMOTE_MID_TURN_SUMMARY")),
request_contains_text(
&requests[1],
&summary_with_prefix("REMOTE_MID_TURN_SUMMARY")
),
"post-mid-turn request should include remote compaction summary text"
);
@@ -1825,7 +1833,6 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess
);
let compact_request = compact_mock.single_request();
let follow_up_request = responses_mock.single_request();
let follow_up_shape = request_input_shape(&follow_up_request);
insta::assert_snapshot!(
"remote_manual_compact_without_prev_user_shapes",
sectioned_request_shapes(
@@ -1837,11 +1844,14 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess
)
);
assert!(
!follow_up_shape.contains(&summary_snapshot_text("REMOTE_MANUAL_EMPTY_SUMMARY")),
!request_contains_text(
&follow_up_request,
&summary_with_prefix("REMOTE_MANUAL_EMPTY_SUMMARY"),
),
"post-compact request should not include compact summary when remote compaction is skipped"
);
assert!(
follow_up_shape.contains("USER_ONE"),
request_contains_text(&follow_up_request, "USER_ONE"),
"post-compact request should include the submitted user message"
);
@@ -1913,8 +1923,6 @@ async fn snapshot_request_shape_remote_manual_compact_with_previous_user_message
assert_eq!(requests.len(), 2, "expected user and post-compact requests");
let compact_request = compact_mock.single_request();
let compact_shape = request_input_shape(&compact_request);
let follow_up_shape = request_input_shape(&requests[1]);
insta::assert_snapshot!(
"remote_manual_compact_with_history_shapes",
sectioned_request_shapes(
@@ -1926,15 +1934,18 @@ async fn snapshot_request_shape_remote_manual_compact_with_previous_user_message
)
);
assert!(
compact_shape.contains("USER_ONE"),
request_contains_text(&compact_request, "USER_ONE"),
"remote compaction request should include existing user history"
);
assert!(
follow_up_shape.contains("USER_TWO"),
request_contains_text(&requests[1], "USER_TWO"),
"post-compact request should include latest user message"
);
assert!(
follow_up_shape.contains(&summary_snapshot_text("REMOTE_MANUAL_WITH_HISTORY_SUMMARY")),
request_contains_text(
&requests[1],
&summary_with_prefix("REMOTE_MANUAL_WITH_HISTORY_SUMMARY"),
),
"post-compact request should include remote compact summary text"
);