mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +00:00
Preserve reasoning token accounting across resume and fork (#57839)
## Why Resuming or forking a thread loses the session flag indicating that reported token usage already includes retained reasoning. Counting that reasoning again can trigger automatic compaction too early. ## What changed Persist `last_usage_includes_reasoning` with `TokenUsageInfo` and use it when estimating context usage. Record the flag with completed sampling and compaction usage, and mark local estimates as including reasoning. Older records without the field retain the existing accounting behavior. ## Testing Add coverage for saved inclusive, exclusive, and legacy token counts. Extend the encrypted-reasoning compaction test to verify the compaction threshold for live, forked, and cold-resumed threads. GitOrigin-RevId: 9f22262a693111ec0406dce11804eb4844ad8983
This commit is contained in:
@@ -65,6 +65,7 @@ async fn cancelled_turn_skips_launch_but_preserves_existing_work(trigger: Compac
|
||||
total_tokens: 70_000,
|
||||
..Default::default()
|
||||
}),
|
||||
/*includes_reasoning*/ false,
|
||||
)
|
||||
.await
|
||||
.expect("record usage");
|
||||
|
||||
@@ -78,6 +78,7 @@ async fn guardian_checkpoint_preserves_live_context_without_storage(
|
||||
..Default::default()
|
||||
},
|
||||
Some(32_000),
|
||||
/*includes_reasoning*/ true,
|
||||
);
|
||||
}
|
||||
let expected = session.clone_history().await;
|
||||
|
||||
@@ -417,7 +417,7 @@ use codex_utils_stream_parser::ProposedPlanSegment;
|
||||
/// Whether reported usage updates the installed context baseline or only spending.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum TokenUsageContext {
|
||||
Sampling,
|
||||
Sampling { includes_reasoning: bool },
|
||||
SpendingOnly,
|
||||
}
|
||||
|
||||
@@ -1569,7 +1569,7 @@ impl Session {
|
||||
|
||||
pub(crate) async fn get_total_token_usage(&self) -> i64 {
|
||||
let state = self.state.lock().await;
|
||||
state.get_total_token_usage(state.server_reasoning_included())
|
||||
state.get_total_token_usage()
|
||||
}
|
||||
|
||||
pub(crate) async fn auto_compact_window_snapshot(&self) -> AutoCompactWindowSnapshot {
|
||||
@@ -5244,13 +5244,14 @@ impl Session {
|
||||
&self,
|
||||
turn_context: &TurnContext,
|
||||
token_usage: Option<&TokenUsage>,
|
||||
includes_reasoning: bool,
|
||||
) -> CodexResult<()> {
|
||||
let result = self
|
||||
.record_token_usage_info(
|
||||
turn_context,
|
||||
&turn_context.initial_settings,
|
||||
token_usage,
|
||||
TokenUsageContext::Sampling,
|
||||
TokenUsageContext::Sampling { includes_reasoning },
|
||||
)
|
||||
.await;
|
||||
self.send_token_count_event(turn_context).await;
|
||||
@@ -5312,10 +5313,11 @@ impl Session {
|
||||
let token_info = {
|
||||
let mut state = self.state.lock().await;
|
||||
match context {
|
||||
TokenUsageContext::Sampling => {
|
||||
TokenUsageContext::Sampling { includes_reasoning } => {
|
||||
state.update_token_info_from_usage(
|
||||
token_usage,
|
||||
turn_context.model_context_window(),
|
||||
includes_reasoning,
|
||||
);
|
||||
if matches!(
|
||||
turn_context.config.model_auto_compact_token_limit_scope,
|
||||
@@ -5328,6 +5330,7 @@ impl Session {
|
||||
TokenUsageContext::SpendingOnly => {
|
||||
let mut info = state.token_info().unwrap_or(TokenUsageInfo {
|
||||
total_token_usage: TokenUsage::default(),
|
||||
last_usage_includes_reasoning: None,
|
||||
last_token_usage: TokenUsage::default(),
|
||||
model_context_window: turn_context.model_context_window(),
|
||||
});
|
||||
@@ -5336,6 +5339,7 @@ impl Session {
|
||||
|
||||
// Contributors receive the reported usage; stored context stays unchanged.
|
||||
info.last_token_usage = token_usage.clone();
|
||||
info.last_usage_includes_reasoning = None;
|
||||
info.model_context_window = turn_context
|
||||
.model_context_window()
|
||||
.or(info.model_context_window);
|
||||
@@ -5384,6 +5388,7 @@ impl Session {
|
||||
let mut state = self.state.lock().await;
|
||||
let mut info = state.token_info().unwrap_or(TokenUsageInfo {
|
||||
total_token_usage: TokenUsage::default(),
|
||||
last_usage_includes_reasoning: None,
|
||||
last_token_usage: TokenUsage::default(),
|
||||
model_context_window: None,
|
||||
});
|
||||
@@ -5402,9 +5407,9 @@ impl Session {
|
||||
info.model_context_window = Some(model_context_window);
|
||||
}
|
||||
|
||||
state.set_token_info(Some(info));
|
||||
// The local estimate already includes every retained reasoning item.
|
||||
state.set_server_reasoning_included(/*included*/ true);
|
||||
info.last_usage_includes_reasoning = Some(true);
|
||||
state.set_token_info(Some(info));
|
||||
}
|
||||
self.set_auto_compact_window_estimated_prefill_for_scope(
|
||||
turn_context,
|
||||
@@ -5443,11 +5448,6 @@ impl Session {
|
||||
state.record_mcp_dependency_prompted(names);
|
||||
}
|
||||
|
||||
pub(crate) async fn set_server_reasoning_included(&self, included: bool) {
|
||||
let mut state = self.state.lock().await;
|
||||
state.set_server_reasoning_included(included);
|
||||
}
|
||||
|
||||
pub(crate) async fn send_token_count_event(&self, turn_context: &TurnContext) {
|
||||
let (info, rate_limits) = {
|
||||
let state = self.state.lock().await;
|
||||
|
||||
@@ -3420,6 +3420,7 @@ async fn record_initial_history_seeds_token_info_from_rollout() {
|
||||
total_tokens: 30,
|
||||
codex_rollout_budget_units: None,
|
||||
},
|
||||
last_usage_includes_reasoning: None,
|
||||
last_token_usage: TokenUsage {
|
||||
input_tokens: 3,
|
||||
cached_input_tokens: 0,
|
||||
@@ -3441,6 +3442,7 @@ async fn record_initial_history_seeds_token_info_from_rollout() {
|
||||
total_tokens: 375,
|
||||
codex_rollout_budget_units: None,
|
||||
},
|
||||
last_usage_includes_reasoning: None,
|
||||
last_token_usage: TokenUsage {
|
||||
input_tokens: 10,
|
||||
cached_input_tokens: 0,
|
||||
@@ -3590,6 +3592,7 @@ async fn recompute_token_usage_updates_model_context_window() {
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_token_info(Some(TokenUsageInfo {
|
||||
total_token_usage: TokenUsage::default(),
|
||||
last_usage_includes_reasoning: None,
|
||||
last_token_usage: TokenUsage::default(),
|
||||
model_context_window: Some(258_400),
|
||||
}));
|
||||
@@ -3606,7 +3609,7 @@ async fn recompute_token_usage_updates_model_context_window() {
|
||||
assert_eq!(actual.model_context_window, Some(128_000));
|
||||
}
|
||||
|
||||
#[test_case(crate::session::TokenUsageContext::Sampling; "sampling")]
|
||||
#[test_case(crate::session::TokenUsageContext::Sampling { includes_reasoning: false }; "sampling")]
|
||||
#[test_case(crate::session::TokenUsageContext::SpendingOnly; "spending_only")]
|
||||
#[tokio::test]
|
||||
async fn record_token_usage_info_notifies_extension_contributors(
|
||||
@@ -3693,7 +3696,9 @@ async fn record_token_usage_info_notifies_extension_contributors(
|
||||
&turn_context,
|
||||
&turn_context.initial_settings,
|
||||
Some(&first_usage),
|
||||
crate::session::TokenUsageContext::Sampling,
|
||||
crate::session::TokenUsageContext::Sampling {
|
||||
includes_reasoning: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("first usage should be recorded");
|
||||
@@ -3711,8 +3716,12 @@ async fn record_token_usage_info_notifies_extension_contributors(
|
||||
expected_total_usage.add_assign(&second_usage);
|
||||
let expected_session_info = TokenUsageInfo {
|
||||
total_token_usage: expected_total_usage.clone(),
|
||||
last_usage_includes_reasoning: Some(matches!(
|
||||
context,
|
||||
crate::session::TokenUsageContext::SpendingOnly
|
||||
)),
|
||||
last_token_usage: match context {
|
||||
crate::session::TokenUsageContext::Sampling => second_usage.clone(),
|
||||
crate::session::TokenUsageContext::Sampling { .. } => second_usage.clone(),
|
||||
crate::session::TokenUsageContext::SpendingOnly => first_usage.clone(),
|
||||
},
|
||||
model_context_window: turn_context.model_context_window(),
|
||||
@@ -3724,6 +3733,7 @@ async fn record_token_usage_info_notifies_extension_contributors(
|
||||
turn_level_id: turn_context.sub_id.clone(),
|
||||
token_usage: TokenUsageInfo {
|
||||
total_token_usage: first_usage.clone(),
|
||||
last_usage_includes_reasoning: Some(true),
|
||||
last_token_usage: first_usage,
|
||||
model_context_window: turn_context.model_context_window(),
|
||||
},
|
||||
@@ -3736,6 +3746,12 @@ async fn record_token_usage_info_notifies_extension_contributors(
|
||||
turn_level_id: turn_context.sub_id.clone(),
|
||||
token_usage: TokenUsageInfo {
|
||||
total_token_usage: expected_total_usage,
|
||||
last_usage_includes_reasoning: match context {
|
||||
crate::session::TokenUsageContext::Sampling { includes_reasoning } => {
|
||||
Some(includes_reasoning)
|
||||
}
|
||||
crate::session::TokenUsageContext::SpendingOnly => None,
|
||||
},
|
||||
last_token_usage: second_usage,
|
||||
model_context_window: turn_context.model_context_window(),
|
||||
},
|
||||
@@ -12972,6 +12988,7 @@ async fn set_total_token_usage(sess: &Session, total_token_usage: TokenUsage) {
|
||||
let mut state = sess.state.lock().await;
|
||||
state.set_token_info(Some(TokenUsageInfo {
|
||||
total_token_usage,
|
||||
last_usage_includes_reasoning: None,
|
||||
last_token_usage: TokenUsage::default(),
|
||||
model_context_window: None,
|
||||
}));
|
||||
|
||||
@@ -2760,8 +2760,7 @@ async fn try_run_sampling_request(
|
||||
let sampling_timing_guard = turn_context.turn_timing_state.begin_sampling();
|
||||
let mut response_completed = false;
|
||||
let mut request_token_usage = None;
|
||||
sess.set_server_reasoning_included(step_context.settings.model_info.use_responses_lite)
|
||||
.await;
|
||||
let mut includes_reasoning = step_context.settings.model_info.use_responses_lite;
|
||||
let stream = client_session
|
||||
.stream(
|
||||
&prompt,
|
||||
@@ -3215,7 +3214,7 @@ async fn try_run_sampling_request(
|
||||
.await;
|
||||
}
|
||||
ResponseEvent::ServerReasoningIncluded(included) => {
|
||||
sess.set_server_reasoning_included(included).await;
|
||||
includes_reasoning = included;
|
||||
}
|
||||
ResponseEvent::RateLimits(snapshot) => {
|
||||
// Update internal state with latest rate limits, but defer sending until
|
||||
@@ -3267,7 +3266,7 @@ async fn try_run_sampling_request(
|
||||
&turn_context,
|
||||
&step_context.settings,
|
||||
token_usage.as_ref(),
|
||||
super::TokenUsageContext::Sampling,
|
||||
super::TokenUsageContext::Sampling { includes_reasoning },
|
||||
)
|
||||
.await;
|
||||
should_emit_token_count = true;
|
||||
|
||||
Reference in New Issue
Block a user