mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
Disambiguate snapshot cwd placeholders
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
co-authored by
Codex
parent
85880b4495
commit
217eeaee9c
@@ -1,4 +1,5 @@
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::responses::ResponsesRequest;
|
||||
|
||||
@@ -42,6 +43,7 @@ pub fn format_request_input_snapshot(
|
||||
}
|
||||
|
||||
pub fn format_response_items_snapshot(items: &[Value], options: &ContextSnapshotOptions) -> String {
|
||||
let mut canonicalizer = SnapshotCanonicalizer::default();
|
||||
items
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -70,7 +72,11 @@ pub fn format_response_items_snapshot(items: &[Value], options: &ContextSnapshot
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
if let Some(text) = entry.get("text").and_then(Value::as_str) {
|
||||
return format_snapshot_text(text, options);
|
||||
return format_snapshot_text(
|
||||
text,
|
||||
options,
|
||||
&mut canonicalizer,
|
||||
);
|
||||
}
|
||||
let Some(content_type) =
|
||||
entry.get("type").and_then(Value::as_str)
|
||||
@@ -123,7 +129,7 @@ pub fn format_response_items_snapshot(items: &[Value], options: &ContextSnapshot
|
||||
let output = item
|
||||
.get("output")
|
||||
.and_then(Value::as_str)
|
||||
.map(|output| format_snapshot_text(output, options))
|
||||
.map(|output| format_snapshot_text(output, options, &mut canonicalizer))
|
||||
.unwrap_or_else(|| "<NON_STRING_OUTPUT>".to_string());
|
||||
format!("{idx:02}:function_call_output:{output}")
|
||||
}
|
||||
@@ -139,7 +145,9 @@ pub fn format_response_items_snapshot(items: &[Value], options: &ContextSnapshot
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" ")
|
||||
})
|
||||
.map(|command| format_snapshot_text(&command, options))
|
||||
.map(|command| {
|
||||
format_snapshot_text(&command, options, &mut canonicalizer)
|
||||
})
|
||||
.filter(|cmd| !cmd.is_empty())
|
||||
.unwrap_or_else(|| "<NO_COMMAND>".to_string());
|
||||
format!("{idx:02}:local_shell_call:{command}")
|
||||
@@ -151,7 +159,7 @@ pub fn format_response_items_snapshot(items: &[Value], options: &ContextSnapshot
|
||||
.and_then(|summary| summary.first())
|
||||
.and_then(|entry| entry.get("text"))
|
||||
.and_then(Value::as_str)
|
||||
.map(|text| format_snapshot_text(text, options))
|
||||
.map(|text| format_snapshot_text(text, options, &mut canonicalizer))
|
||||
.unwrap_or_else(|| "<NO_SUMMARY>".to_string());
|
||||
let has_encrypted_content = item
|
||||
.get("encrypted_content")
|
||||
@@ -211,17 +219,71 @@ pub fn format_labeled_items_snapshot(
|
||||
format!("Scenario: {scenario}\n\n{sections}")
|
||||
}
|
||||
|
||||
fn format_snapshot_text(text: &str, options: &ContextSnapshotOptions) -> String {
|
||||
#[derive(Default)]
|
||||
struct SnapshotCanonicalizer {
|
||||
cwd_placeholders: HashMap<String, usize>,
|
||||
}
|
||||
|
||||
impl SnapshotCanonicalizer {
|
||||
fn canonicalize_text(&mut self, text: &str) -> String {
|
||||
if text.starts_with("<permissions instructions>") {
|
||||
return "<PERMISSIONS_INSTRUCTIONS>".to_string();
|
||||
}
|
||||
if text.starts_with("# AGENTS.md instructions for ") {
|
||||
return "<AGENTS_MD>".to_string();
|
||||
}
|
||||
if text.starts_with("<environment_context>") {
|
||||
if let (Some(cwd_start), Some(cwd_end)) = (text.find("<cwd>"), text.find("</cwd>")) {
|
||||
let cwd = &text[cwd_start + "<cwd>".len()..cwd_end];
|
||||
return if cwd.ends_with("PRETURN_CONTEXT_DIFF_CWD") {
|
||||
"<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>".to_string()
|
||||
} else {
|
||||
let next_idx = self.cwd_placeholders.len() + 1;
|
||||
let idx = *self
|
||||
.cwd_placeholders
|
||||
.entry(cwd.to_string())
|
||||
.or_insert(next_idx);
|
||||
format!("<ENVIRONMENT_CONTEXT:cwd=<CWD#{idx}>>")
|
||||
};
|
||||
}
|
||||
return "<ENVIRONMENT_CONTEXT>".to_string();
|
||||
}
|
||||
if text.starts_with("<subagents>") {
|
||||
let subagent_count = text
|
||||
.lines()
|
||||
.filter(|line| line.trim_start().starts_with("- "))
|
||||
.count();
|
||||
return format!("<SUBAGENTS:count={subagent_count}>");
|
||||
}
|
||||
if text.starts_with("You are performing a CONTEXT CHECKPOINT COMPACTION.") {
|
||||
return "<SUMMARIZATION_PROMPT>".to_string();
|
||||
}
|
||||
if text.starts_with("Another language model started to solve this problem")
|
||||
&& let Some((_, summary)) = text.split_once('\n')
|
||||
{
|
||||
return format!("<COMPACTION_SUMMARY>\n{summary}");
|
||||
}
|
||||
text.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_snapshot_text(
|
||||
text: &str,
|
||||
options: &ContextSnapshotOptions,
|
||||
canonicalizer: &mut SnapshotCanonicalizer,
|
||||
) -> String {
|
||||
match options.render_mode {
|
||||
ContextSnapshotRenderMode::RedactedText => {
|
||||
normalize_snapshot_line_endings(&canonicalize_snapshot_text(text)).replace('\n', "\\n")
|
||||
normalize_snapshot_line_endings(&canonicalizer.canonicalize_text(text))
|
||||
.replace('\n', "\\n")
|
||||
}
|
||||
ContextSnapshotRenderMode::FullText => {
|
||||
normalize_snapshot_line_endings(text).replace('\n', "\\n")
|
||||
}
|
||||
ContextSnapshotRenderMode::KindWithTextPrefix { max_chars } => {
|
||||
let normalized = normalize_snapshot_line_endings(&canonicalize_snapshot_text(text))
|
||||
.replace('\n', "\\n");
|
||||
let normalized =
|
||||
normalize_snapshot_line_endings(&canonicalizer.canonicalize_text(text))
|
||||
.replace('\n', "\\n");
|
||||
if normalized.chars().count() <= max_chars {
|
||||
normalized
|
||||
} else {
|
||||
@@ -237,42 +299,6 @@ fn normalize_snapshot_line_endings(text: &str) -> String {
|
||||
text.replace("\r\n", "\n").replace('\r', "\n")
|
||||
}
|
||||
|
||||
fn canonicalize_snapshot_text(text: &str) -> String {
|
||||
if text.starts_with("<permissions instructions>") {
|
||||
return "<PERMISSIONS_INSTRUCTIONS>".to_string();
|
||||
}
|
||||
if text.starts_with("# AGENTS.md instructions for ") {
|
||||
return "<AGENTS_MD>".to_string();
|
||||
}
|
||||
if text.starts_with("<environment_context>") {
|
||||
if let (Some(cwd_start), Some(cwd_end)) = (text.find("<cwd>"), text.find("</cwd>")) {
|
||||
let cwd = &text[cwd_start + "<cwd>".len()..cwd_end];
|
||||
return if cwd.ends_with("PRETURN_CONTEXT_DIFF_CWD") {
|
||||
"<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>".to_string()
|
||||
} else {
|
||||
"<ENVIRONMENT_CONTEXT:cwd=<CWD>>".to_string()
|
||||
};
|
||||
}
|
||||
return "<ENVIRONMENT_CONTEXT>".to_string();
|
||||
}
|
||||
if text.starts_with("<subagents>") {
|
||||
let subagent_count = text
|
||||
.lines()
|
||||
.filter(|line| line.trim_start().starts_with("- "))
|
||||
.count();
|
||||
return format!("<SUBAGENTS:count={subagent_count}>");
|
||||
}
|
||||
if text.starts_with("You are performing a CONTEXT CHECKPOINT COMPACTION.") {
|
||||
return "<SUMMARIZATION_PROMPT>".to_string();
|
||||
}
|
||||
if text.starts_with("Another language model started to solve this problem")
|
||||
&& let Some((_, summary)) = text.split_once('\n')
|
||||
{
|
||||
return format!("<COMPACTION_SUMMARY>\n{summary}");
|
||||
}
|
||||
text.to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ContextSnapshotOptions;
|
||||
@@ -360,6 +386,50 @@ mod tests {
|
||||
assert_eq!(rendered, "00:message/developer:<SUBAGENTS:count=2>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redacted_text_mode_assigns_distinct_cwd_placeholders_within_one_snapshot() {
|
||||
let items = vec![
|
||||
json!({
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [{
|
||||
"type": "input_text",
|
||||
"text": "<environment_context>\n <cwd>/tmp/one</cwd>\n</environment_context>"
|
||||
}]
|
||||
}),
|
||||
json!({
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [{
|
||||
"type": "input_text",
|
||||
"text": "<environment_context>\n <cwd>/tmp/two</cwd>\n</environment_context>"
|
||||
}]
|
||||
}),
|
||||
json!({
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [{
|
||||
"type": "input_text",
|
||||
"text": "<environment_context>\n <cwd>/tmp/one</cwd>\n</environment_context>"
|
||||
}]
|
||||
}),
|
||||
];
|
||||
|
||||
let rendered = format_response_items_snapshot(
|
||||
&items,
|
||||
&ContextSnapshotOptions::default().render_mode(ContextSnapshotRenderMode::RedactedText),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
concat!(
|
||||
"00:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD#1>>\n",
|
||||
"01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD#2>>\n",
|
||||
"02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD#1>>"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kind_with_text_prefix_mode_normalizes_crlf_line_endings() {
|
||||
let items = vec![json!({
|
||||
|
||||
+4
-4
@@ -8,19 +8,19 @@ Scenario: Second turn changes cwd to a directory with different AGENTS.md; updat
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user[2]:
|
||||
[01] <AGENTS_MD>
|
||||
[02] <ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
[02] <ENVIRONMENT_CONTEXT:cwd=<CWD#1>>
|
||||
02:message/user:first turn in agents_one
|
||||
|
||||
## Second Request (agents_two cwd)
|
||||
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user[2]:
|
||||
[01] <AGENTS_MD>
|
||||
[02] <ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
[02] <ENVIRONMENT_CONTEXT:cwd=<CWD#1>>
|
||||
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
|
||||
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD#2>>
|
||||
04:message/user:first turn in agents_one
|
||||
05:message/assistant:turn one complete
|
||||
06:message/user[2]:
|
||||
[01] <AGENTS_MD>
|
||||
[02] <ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
[02] <ENVIRONMENT_CONTEXT:cwd=<CWD#2>>
|
||||
07:message/user:second turn in agents_two
|
||||
|
||||
Reference in New Issue
Block a user