Preserve Guardian transcript boundaries in sampling input (#38555)

## What changed

- Represent Guardian sampling input as an ordered list of text entries.
- Send each transcript entry and approval-request delimiter as a separate input
  content item while preserving the existing transcript byte limit.

## Testing

- Update transcript and sampler tests to verify entry boundaries, multi-item
  request serialization, and bounded transcript behavior.

GitOrigin-RevId: 433e2e6ca18ce9288025715f5956295bfe852e98
This commit is contained in:
felixxia-oai authored and copyberry committed 2026-08-14 11:02:01 +00:00
1 parent a44f266a4a
commit db5b4a4cfb
6 files changed
+101 -63

No files matched your search

+26 -9
View File
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::collections::VecDeque;
use codex_extension_api::ResponseItem;
use codex_protocol::models::ContentItem;
@@ -36,8 +37,12 @@ impl Default for TranscriptConfig {
}
impl TranscriptConfig {
pub(crate) fn build<'a>(&self, items: impl IntoIterator<Item = &'a ResponseItem>) -> String {
let mut transcript = String::new();
pub(crate) fn build<'a>(
&self,
items: impl IntoIterator<Item = &'a ResponseItem>,
) -> Vec<String> {
let mut transcript = VecDeque::new();
let mut transcript_bytes = 0;
let mut tool_names_by_call_id = HashMap::new();
let mut entry_number = 0;
@@ -173,18 +178,30 @@ impl TranscriptConfig {
continue;
}
entry_number += 1;
transcript.push_str(&format!("[{entry_number}] {role}: {text}\n"));
let entry = format!("[{entry_number}] {role}: {text}\n");
transcript_bytes += entry.len();
transcript.push_back(entry);
if transcript.len() > MAX_TRANSCRIPT_BYTES {
let mut first_retained_byte = transcript.len() - MAX_TRANSCRIPT_BYTES;
while !transcript.is_char_boundary(first_retained_byte) {
first_retained_byte += 1;
while transcript_bytes > MAX_TRANSCRIPT_BYTES {
let Some(first_entry) = transcript.front_mut() else {
break;
};
let bytes_to_remove = transcript_bytes - MAX_TRANSCRIPT_BYTES;
if first_entry.len() <= bytes_to_remove {
transcript_bytes -= first_entry.len();
transcript.pop_front();
} else {
let mut first_retained_byte = bytes_to_remove;
while !first_entry.is_char_boundary(first_retained_byte) {
first_retained_byte += 1;
}
first_entry.drain(..first_retained_byte);
transcript_bytes -= first_retained_byte;
}
transcript.drain(..first_retained_byte);
}
}
transcript
transcript.into()
}
}