Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
0208094
tighten
aibrahim-oai Nov 15, 2025
e91a3b8
tighten
aibrahim-oai Nov 15, 2025
fbe5fcf
tighten_panic_double_truncation
aibrahim-oai Nov 15, 2025
dbb25e9
tighten_panic_double_truncation
aibrahim-oai Nov 15, 2025
d264c11
Merge branch 'main' of https://github.com/openai/codex into tighten_p…
aibrahim-oai Nov 16, 2025
63596d1
truncate
aibrahim-oai Nov 16, 2025
b811a9b
change function names
aibrahim-oai Nov 17, 2025
d599cf2
cleanup
aibrahim-oai Nov 17, 2025
16369ee
progress
aibrahim-oai Nov 17, 2025
5805ab0
tests
aibrahim-oai Nov 17, 2025
7812ef5
tests
aibrahim-oai Nov 17, 2025
c9bc844
tests
aibrahim-oai Nov 17, 2025
f1522ba
tests
aibrahim-oai Nov 17, 2025
da16813
lint
aibrahim-oai Nov 17, 2025
d1d0644
lint
aibrahim-oai Nov 17, 2025
7f9637d
remove line
aibrahim-oai Nov 17, 2025
2835118
remove line
aibrahim-oai Nov 17, 2025
64bb960
router
aibrahim-oai Nov 17, 2025
bbfa97e
router
aibrahim-oai Nov 17, 2025
4b58b60
router
aibrahim-oai Nov 17, 2025
5db71f6
router
aibrahim-oai Nov 17, 2025
d3c94a3
tests
aibrahim-oai Nov 18, 2025
fcc981f
tests
aibrahim-oai Nov 18, 2025
a8cdae9
avoid approx with tests
aibrahim-oai Nov 18, 2025
0312d3b
avoid approx with tests
aibrahim-oai Nov 18, 2025
121e943
tests
aibrahim-oai Nov 18, 2025
ff3fae6
lint
aibrahim-oai Nov 18, 2025
b270394
names
aibrahim-oai Nov 18, 2025
ba01537
names
aibrahim-oai Nov 18, 2025
7c3f260
test
aibrahim-oai Nov 18, 2025
95d68bf
comment
aibrahim-oai Nov 18, 2025
779bd97
comment
aibrahim-oai Nov 18, 2025
a42e62e
comment
aibrahim-oai Nov 18, 2025
f6e6128
comment
aibrahim-oai Nov 18, 2025
3835ee0
progress
aibrahim-oai Nov 18, 2025
6660471
progress
aibrahim-oai Nov 18, 2025
3a51044
progress
aibrahim-oai Nov 18, 2025
21677c5
progress
aibrahim-oai Nov 18, 2025
97ed9f2
tokio tests
aibrahim-oai Nov 18, 2025
459a82e
Merge branch 'main' into truncate-by-tokens
aibrahim-oai Nov 18, 2025
73c79e7
test
aibrahim-oai Nov 18, 2025
163acbe
tests
aibrahim-oai Nov 18, 2025
e798801
tests
aibrahim-oai Nov 18, 2025
9c92aad
tests
aibrahim-oai Nov 18, 2025
a6abc6a
tests
aibrahim-oai Nov 18, 2025
ebb5d98
tests
aibrahim-oai Nov 18, 2025
a87aba9
tests
aibrahim-oai Nov 18, 2025
1070fe1
tests
aibrahim-oai Nov 18, 2025
6e910a0
source
aibrahim-oai Nov 18, 2025
602956e
bytes
aibrahim-oai Nov 18, 2025
8c49888
source
aibrahim-oai Nov 18, 2025
0a6de89
test
aibrahim-oai Nov 18, 2025
c9bd3e2
tests
aibrahim-oai Nov 18, 2025
5dca008
bytes
aibrahim-oai Nov 18, 2025
53554e3
Merge branch 'main' into truncate-by-tokens
aibrahim-oai Nov 18, 2025
9f337f3
just use bytes
aibrahim-oai Nov 18, 2025
903514b
clean
aibrahim-oai Nov 18, 2025
9572b62
clean
aibrahim-oai Nov 18, 2025
cac5b3e
const
aibrahim-oai Nov 18, 2025
ddeadc5
const
aibrahim-oai Nov 18, 2025
8ae4de4
helpers
aibrahim-oai Nov 18, 2025
5dbf7d3
Merge branch 'main' into truncate-by-tokens
aibrahim-oai Nov 18, 2025
b244de2
tests
aibrahim-oai Nov 18, 2025
63df57d
tests
aibrahim-oai Nov 18, 2025
91741d6
tests
aibrahim-oai Nov 18, 2025
3027a59
tests
aibrahim-oai Nov 18, 2025
e5c77dd
test
aibrahim-oai Nov 18, 2025
f7a5f69
name
aibrahim-oai Nov 18, 2025
c380dae
name
aibrahim-oai Nov 18, 2025
5944058
fix
aibrahim-oai Nov 18, 2025
e6af809
fix
aibrahim-oai Nov 18, 2025
7c3afa4
fix
aibrahim-oai Nov 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::parse_command::parse_command;
use crate::parse_turn_item;
use crate::response_processing::process_items;
use crate::terminal;
use crate::truncate::TruncationPolicy;
use crate::user_notification::UserNotifier;
use crate::util::error_or_panic;
use async_channel::Receiver;
Expand Down Expand Up @@ -275,6 +276,7 @@ pub(crate) struct TurnContext {
pub(crate) final_output_json_schema: Option<Value>,
pub(crate) codex_linux_sandbox_exe: Option<PathBuf>,
pub(crate) tool_call_gate: Arc<ReadinessFlag>,
pub(crate) truncation_policy: TruncationPolicy,
}

impl TurnContext {
Expand Down Expand Up @@ -401,7 +403,7 @@ impl Session {
);

let client = ModelClient::new(
Arc::new(per_turn_config),
Arc::new(per_turn_config.clone()),
auth_manager,
otel_event_manager,
provider,
Expand Down Expand Up @@ -431,6 +433,7 @@ impl Session {
final_output_json_schema: None,
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
tool_call_gate: Arc::new(ReadinessFlag::new()),
truncation_policy: TruncationPolicy::new(&per_turn_config),
}
}

Expand Down Expand Up @@ -678,7 +681,8 @@ impl Session {
let reconstructed_history =
self.reconstruct_history_from_rollout(&turn_context, &rollout_items);
if !reconstructed_history.is_empty() {
self.record_into_history(&reconstructed_history).await;
self.record_into_history(&reconstructed_history, &turn_context)
.await;
}

// If persisting, persist all rollout items as-is (recorder filters)
Expand Down Expand Up @@ -935,7 +939,7 @@ impl Session {
turn_context: &TurnContext,
items: &[ResponseItem],
) {
self.record_into_history(items).await;
self.record_into_history(items, turn_context).await;
self.persist_rollout_response_items(items).await;
self.send_raw_response_items(turn_context, items).await;
}
Expand All @@ -949,7 +953,10 @@ impl Session {
for item in rollout_items {
match item {
RolloutItem::ResponseItem(response_item) => {
history.record_items(std::iter::once(response_item));
history.record_items(
std::iter::once(response_item),
turn_context.truncation_policy,
);
}
RolloutItem::Compacted(compacted) => {
let snapshot = history.get_history();
Expand All @@ -973,9 +980,13 @@ impl Session {
}

/// Append ResponseItems to the in-memory conversation history only.
pub(crate) async fn record_into_history(&self, items: &[ResponseItem]) {
pub(crate) async fn record_into_history(
&self,
items: &[ResponseItem],
turn_context: &TurnContext,
) {
let mut state = self.state.lock().await;
state.record_items(items.iter());
state.record_items(items.iter(), turn_context.truncation_policy);
}

pub(crate) async fn replace_history(&self, items: Vec<ResponseItem>) {
Expand Down Expand Up @@ -1755,6 +1766,7 @@ async fn spawn_review_thread(
final_output_json_schema: None,
codex_linux_sandbox_exe: parent_turn_context.codex_linux_sandbox_exe.clone(),
tool_call_gate: Arc::new(ReadinessFlag::new()),
truncation_policy: TruncationPolicy::new(&per_turn_config),
};

// Seed the child task with the review prompt as the initial user message.
Expand Down Expand Up @@ -2886,7 +2898,7 @@ mod tests {
for item in &initial_context {
rollout_items.push(RolloutItem::ResponseItem(item.clone()));
}
live_history.record_items(initial_context.iter());
live_history.record_items(initial_context.iter(), turn_context.truncation_policy);

let user1 = ResponseItem::Message {
id: None,
Expand All @@ -2895,7 +2907,7 @@ mod tests {
text: "first user".to_string(),
}],
};
live_history.record_items(std::iter::once(&user1));
live_history.record_items(std::iter::once(&user1), turn_context.truncation_policy);
rollout_items.push(RolloutItem::ResponseItem(user1.clone()));

let assistant1 = ResponseItem::Message {
Expand All @@ -2905,7 +2917,7 @@ mod tests {
text: "assistant reply one".to_string(),
}],
};
live_history.record_items(std::iter::once(&assistant1));
live_history.record_items(std::iter::once(&assistant1), turn_context.truncation_policy);
rollout_items.push(RolloutItem::ResponseItem(assistant1.clone()));

let summary1 = "summary one";
Expand All @@ -2929,7 +2941,7 @@ mod tests {
text: "second user".to_string(),
}],
};
live_history.record_items(std::iter::once(&user2));
live_history.record_items(std::iter::once(&user2), turn_context.truncation_policy);
rollout_items.push(RolloutItem::ResponseItem(user2.clone()));

let assistant2 = ResponseItem::Message {
Expand All @@ -2939,7 +2951,7 @@ mod tests {
text: "assistant reply two".to_string(),
}],
};
live_history.record_items(std::iter::once(&assistant2));
live_history.record_items(std::iter::once(&assistant2), turn_context.truncation_policy);
rollout_items.push(RolloutItem::ResponseItem(assistant2.clone()));

let summary2 = "summary two";
Expand All @@ -2963,7 +2975,7 @@ mod tests {
text: "third user".to_string(),
}],
};
live_history.record_items(std::iter::once(&user3));
live_history.record_items(std::iter::once(&user3), turn_context.truncation_policy);
rollout_items.push(RolloutItem::ResponseItem(user3.clone()));

let assistant3 = ResponseItem::Message {
Expand All @@ -2973,7 +2985,7 @@ mod tests {
text: "assistant reply three".to_string(),
}],
};
live_history.record_items(std::iter::once(&assistant3));
live_history.record_items(std::iter::once(&assistant3), turn_context.truncation_policy);
rollout_items.push(RolloutItem::ResponseItem(assistant3.clone()));

(rollout_items, live_history.get_history())
Expand Down
38 changes: 23 additions & 15 deletions codex-rs/core/src/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use crate::protocol::EventMsg;
use crate::protocol::TaskStartedEvent;
use crate::protocol::TurnContextItem;
use crate::protocol::WarningEvent;
use crate::truncate::truncate_middle;
use crate::truncate::TruncationPolicy;
use crate::truncate::approx_token_count;
use crate::truncate::truncate_text;
use crate::util::backoff;
use codex_protocol::items::TurnItem;
use codex_protocol::models::ContentItem;
Expand Down Expand Up @@ -59,7 +61,10 @@ async fn run_compact_task_inner(
let initial_input_for_turn: ResponseInputItem = ResponseInputItem::from(input);

let mut history = sess.clone_history().await;
history.record_items(&[initial_input_for_turn.into()]);
history.record_items(
&[initial_input_for_turn.into()],
turn_context.truncation_policy,
);

let mut truncated_count = 0usize;

Expand Down Expand Up @@ -230,28 +235,29 @@ pub(crate) fn build_compacted_history(
initial_context,
user_messages,
summary_text,
COMPACT_USER_MESSAGE_MAX_TOKENS * 4,
COMPACT_USER_MESSAGE_MAX_TOKENS,
)
}

fn build_compacted_history_with_limit(
mut history: Vec<ResponseItem>,
user_messages: &[String],
summary_text: &str,
max_bytes: usize,
max_tokens: usize,
) -> Vec<ResponseItem> {
let mut selected_messages: Vec<String> = Vec::new();
if max_bytes > 0 {
let mut remaining = max_bytes;
if max_tokens > 0 {
let mut remaining = max_tokens;
for message in user_messages.iter().rev() {
if remaining == 0 {
break;
}
if message.len() <= remaining {
let tokens = approx_token_count(message);
if tokens <= remaining {
selected_messages.push(message.clone());
remaining = remaining.saturating_sub(message.len());
remaining = remaining.saturating_sub(tokens);
} else {
let (truncated, _) = truncate_middle(message, remaining);
let truncated = truncate_text(message, TruncationPolicy::Tokens(remaining));
selected_messages.push(truncated);
break;
}
Expand Down Expand Up @@ -300,7 +306,8 @@ async fn drain_to_completed(
};
match event {
Ok(ResponseEvent::OutputItemDone(item)) => {
sess.record_into_history(std::slice::from_ref(&item)).await;
sess.record_into_history(std::slice::from_ref(&item), turn_context)
.await;
}
Ok(ResponseEvent::RateLimits(snapshot)) => {
sess.update_rate_limits(turn_context, snapshot).await;
Expand All @@ -318,6 +325,7 @@ async fn drain_to_completed(

#[cfg(test)]
mod tests {

use super::*;
use pretty_assertions::assert_eq;

Expand Down Expand Up @@ -409,16 +417,16 @@ mod tests {
}

#[test]
fn build_compacted_history_truncates_overlong_user_messages() {
fn build_token_limited_compacted_history_truncates_overlong_user_messages() {
// Use a small truncation limit so the test remains fast while still validating
// that oversized user content is truncated.
let max_bytes = 128;
let big = "X".repeat(max_bytes + 50);
let max_tokens = 16;
let big = "word ".repeat(200);
let history = super::build_compacted_history_with_limit(
Vec::new(),
std::slice::from_ref(&big),
"SUMMARY",
max_bytes,
max_tokens,
);
assert_eq!(history.len(), 2);

Expand Down Expand Up @@ -451,7 +459,7 @@ mod tests {
}

#[test]
fn build_compacted_history_appends_summary_message() {
fn build_token_limited_compacted_history_appends_summary_message() {
let initial_context: Vec<ResponseItem> = Vec::new();
let user_messages = vec!["first user message".to_string()];
let summary_text = "summary text";
Expand Down
5 changes: 4 additions & 1 deletion codex-rs/core/src/compact_remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ async fn run_remote_compact_task_inner(
let mut history = sess.clone_history().await;
if !input.is_empty() {
let initial_input_for_turn: ResponseInputItem = ResponseInputItem::from(input);
history.record_items(&[initial_input_for_turn.into()]);
history.record_items(
&[initial_input_for_turn.into()],
turn_context.truncation_policy,
);
}

let prompt = Prompt {
Expand Down
11 changes: 11 additions & 0 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ pub struct Config {
/// Additional filenames to try when looking for project-level docs.
pub project_doc_fallback_filenames: Vec<String>,

/// Token budget applied when storing tool/function outputs in the context manager.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a user-friendlier name?

pub tool_output_token_limit: Option<usize>,

/// Directory containing all Codex state (defaults to `~/.codex` but can be
/// overridden by the `CODEX_HOME` environment variable).
pub codex_home: PathBuf,
Expand Down Expand Up @@ -636,6 +639,9 @@ pub struct ConfigToml {
/// Ordered list of fallback filenames to look for when AGENTS.md is missing.
pub project_doc_fallback_filenames: Option<Vec<String>>,

/// Token budget applied when storing tool/function outputs in the context manager.
pub tool_output_token_limit: Option<usize>,

/// Profile to use from the `profiles` map.
pub profile: Option<String>,

Expand Down Expand Up @@ -1209,6 +1215,7 @@ impl Config {
}
})
.collect(),
tool_output_token_limit: cfg.tool_output_token_limit,
codex_home,
history,
file_opener: cfg.file_opener.unwrap_or(UriBasedFileOpener::VsCode),
Expand Down Expand Up @@ -2961,6 +2968,7 @@ model_verbosity = "high"
model_providers: fixture.model_provider_map.clone(),
project_doc_max_bytes: PROJECT_DOC_MAX_BYTES,
project_doc_fallback_filenames: Vec::new(),
tool_output_token_limit: None,
codex_home: fixture.codex_home(),
history: History::default(),
file_opener: UriBasedFileOpener::VsCode,
Expand Down Expand Up @@ -3032,6 +3040,7 @@ model_verbosity = "high"
model_providers: fixture.model_provider_map.clone(),
project_doc_max_bytes: PROJECT_DOC_MAX_BYTES,
project_doc_fallback_filenames: Vec::new(),
tool_output_token_limit: None,
codex_home: fixture.codex_home(),
history: History::default(),
file_opener: UriBasedFileOpener::VsCode,
Expand Down Expand Up @@ -3118,6 +3127,7 @@ model_verbosity = "high"
model_providers: fixture.model_provider_map.clone(),
project_doc_max_bytes: PROJECT_DOC_MAX_BYTES,
project_doc_fallback_filenames: Vec::new(),
tool_output_token_limit: None,
codex_home: fixture.codex_home(),
history: History::default(),
file_opener: UriBasedFileOpener::VsCode,
Expand Down Expand Up @@ -3190,6 +3200,7 @@ model_verbosity = "high"
model_providers: fixture.model_provider_map.clone(),
project_doc_max_bytes: PROJECT_DOC_MAX_BYTES,
project_doc_fallback_filenames: Vec::new(),
tool_output_token_limit: None,
codex_home: fixture.codex_home(),
history: History::default(),
file_opener: UriBasedFileOpener::VsCode,
Expand Down
Loading
Loading