mirror of
https://github.com/openai/codex.git
synced 2026-03-26 10:06:32 +03:00
Compare commits
7 Commits
dev/cc/rea
...
cc/referen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39d71da00b | ||
|
|
023ccef57d | ||
|
|
52de1b16ec | ||
|
|
5bffcae9e0 | ||
|
|
2d169322c1 | ||
|
|
e92d3a73b9 | ||
|
|
0bccddcd3f |
@@ -2157,14 +2157,13 @@ impl Session {
|
||||
InitialHistory::New => {
|
||||
// Defer initial context insertion until the first real turn starts so
|
||||
// turn/start overrides can be merged before we write model-visible context.
|
||||
self.set_previous_turn_settings(/*previous_turn_settings*/ None)
|
||||
.await;
|
||||
self.reset_reference_turn_context_state().await;
|
||||
}
|
||||
InitialHistory::Resumed(resumed_history) => {
|
||||
let rollout_items = resumed_history.history;
|
||||
let previous_turn_settings = self
|
||||
.apply_rollout_reconstruction(&turn_context, &rollout_items)
|
||||
self.apply_rollout_reconstruction(&turn_context, &rollout_items)
|
||||
.await;
|
||||
let previous_turn_settings = self.previous_turn_settings().await;
|
||||
|
||||
// If resuming, warn when the last recorded model differs from the current one.
|
||||
let curr: &str = turn_context.model_info.slug.as_str();
|
||||
@@ -2221,7 +2220,7 @@ impl Session {
|
||||
.await;
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
state.set_reference_context_item(Some(turn_context.to_turn_context_item()));
|
||||
state.note_model_visible_turn_context(turn_context.to_turn_context_item());
|
||||
}
|
||||
|
||||
// Forked threads should remain file-backed immediately after startup.
|
||||
@@ -2239,19 +2238,15 @@ impl Session {
|
||||
&self,
|
||||
turn_context: &TurnContext,
|
||||
rollout_items: &[RolloutItem],
|
||||
) -> Option<PreviousTurnSettings> {
|
||||
) {
|
||||
let reconstructed_rollout = self
|
||||
.reconstruct_history_from_rollout(turn_context, rollout_items)
|
||||
.await;
|
||||
let previous_turn_settings = reconstructed_rollout.previous_turn_settings.clone();
|
||||
self.replace_history(
|
||||
let mut state = self.state.lock().await;
|
||||
state.replace_history_with_reference_turn_context_state(
|
||||
reconstructed_rollout.history,
|
||||
reconstructed_rollout.reference_context_item,
|
||||
)
|
||||
.await;
|
||||
self.set_previous_turn_settings(previous_turn_settings.clone())
|
||||
.await;
|
||||
previous_turn_settings
|
||||
reconstructed_rollout.reference_turn_context_state,
|
||||
);
|
||||
}
|
||||
|
||||
fn last_token_info_from_rollout(rollout_items: &[RolloutItem]) -> Option<TokenUsageInfo> {
|
||||
@@ -2266,12 +2261,14 @@ impl Session {
|
||||
state.previous_turn_settings()
|
||||
}
|
||||
|
||||
pub(crate) async fn set_previous_turn_settings(
|
||||
&self,
|
||||
previous_turn_settings: Option<PreviousTurnSettings>,
|
||||
) {
|
||||
pub(crate) async fn note_model_visible_turn_context(&self, turn_context_item: TurnContextItem) {
|
||||
let mut state = self.state.lock().await;
|
||||
state.set_previous_turn_settings(previous_turn_settings);
|
||||
state.note_model_visible_turn_context(turn_context_item);
|
||||
}
|
||||
|
||||
async fn reset_reference_turn_context_state(&self) {
|
||||
let mut state = self.state.lock().await;
|
||||
state.reset_reference_turn_context_state();
|
||||
}
|
||||
|
||||
fn maybe_refresh_shell_snapshot_for_cwd(
|
||||
@@ -3667,10 +3664,11 @@ impl Session {
|
||||
self.persist_rollout_items(&[RolloutItem::TurnContext(turn_context_item.clone())])
|
||||
.await;
|
||||
|
||||
// Advance the in-memory diff baseline even when this turn emitted no model-visible
|
||||
// context items. This keeps later runtime diffing aligned with the current turn state.
|
||||
let mut state = self.state.lock().await;
|
||||
state.set_reference_context_item(Some(turn_context_item));
|
||||
// Advance the stored turn-context snapshot even when this turn emitted no model-visible
|
||||
// context items. Regular turns become both the `previous_turn_settings()` source and the
|
||||
// active model-visible reference baseline for subsequent diffing.
|
||||
self.note_model_visible_turn_context(turn_context_item)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn update_token_usage_info(
|
||||
@@ -5688,17 +5686,6 @@ pub(crate) async fn run_turn(
|
||||
sess.merge_connector_selection(explicitly_enabled_connectors.clone())
|
||||
.await;
|
||||
record_additional_contexts(&sess, &turn_context, additional_contexts).await;
|
||||
if !input.is_empty() {
|
||||
// Track the previous-turn baseline from the regular user-turn path only so
|
||||
// standalone tasks (compact/shell/review/undo) cannot suppress future
|
||||
// model/realtime injections.
|
||||
sess.set_previous_turn_settings(Some(PreviousTurnSettings {
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
}))
|
||||
.await;
|
||||
}
|
||||
|
||||
if !skill_items.is_empty() {
|
||||
sess.record_conversation_items(&turn_context, &skill_items)
|
||||
.await;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::context_manager::ReferenceTurnContextState;
|
||||
use crate::context_manager::is_user_turn_boundary;
|
||||
|
||||
// Return value of `Session::reconstruct_history_from_rollout`, bundling the rebuilt history with
|
||||
@@ -6,32 +7,14 @@ use crate::context_manager::is_user_turn_boundary;
|
||||
#[derive(Debug)]
|
||||
pub(super) struct RolloutReconstruction {
|
||||
pub(super) history: Vec<ResponseItem>,
|
||||
pub(super) previous_turn_settings: Option<PreviousTurnSettings>,
|
||||
pub(super) reference_context_item: Option<TurnContextItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
enum TurnReferenceContextItem {
|
||||
/// No `TurnContextItem` has been seen for this replay span yet.
|
||||
///
|
||||
/// This differs from `Cleared`: `NeverSet` means there is no evidence this turn ever
|
||||
/// established a baseline, while `Cleared` means a baseline existed and a later compaction
|
||||
/// invalidated it. Only the latter must emit an explicit clearing segment for resume/fork
|
||||
/// hydration.
|
||||
#[default]
|
||||
NeverSet,
|
||||
/// A previously established baseline was invalidated by later compaction.
|
||||
Cleared,
|
||||
/// The latest baseline established by this replay span.
|
||||
Latest(Box<TurnContextItem>),
|
||||
pub(super) reference_turn_context_state: ReferenceTurnContextState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ActiveReplaySegment<'a> {
|
||||
turn_id: Option<String>,
|
||||
counts_as_user_turn: bool,
|
||||
previous_turn_settings: Option<PreviousTurnSettings>,
|
||||
reference_context_item: TurnReferenceContextItem,
|
||||
reference_turn_context_state: ReferenceTurnContextState,
|
||||
base_replacement_history: Option<&'a [ResponseItem]>,
|
||||
}
|
||||
|
||||
@@ -43,8 +26,7 @@ fn turn_ids_are_compatible(active_turn_id: Option<&str>, item_turn_id: Option<&s
|
||||
fn finalize_active_segment<'a>(
|
||||
active_segment: ActiveReplaySegment<'a>,
|
||||
base_replacement_history: &mut Option<&'a [ResponseItem]>,
|
||||
previous_turn_settings: &mut Option<PreviousTurnSettings>,
|
||||
reference_context_item: &mut TurnReferenceContextItem,
|
||||
reference_turn_context_state: &mut ReferenceTurnContextState,
|
||||
pending_rollback_turns: &mut usize,
|
||||
) {
|
||||
// Thread rollback drops the newest surviving real user-message boundaries. In replay, that
|
||||
@@ -65,21 +47,47 @@ fn finalize_active_segment<'a>(
|
||||
*base_replacement_history = Some(segment_base_replacement_history);
|
||||
}
|
||||
|
||||
// `previous_turn_settings` come from the newest surviving user turn that established them.
|
||||
if previous_turn_settings.is_none() && active_segment.counts_as_user_turn {
|
||||
*previous_turn_settings = active_segment.previous_turn_settings;
|
||||
merge_surviving_segment_turn_context_state(
|
||||
reference_turn_context_state,
|
||||
active_segment.reference_turn_context_state,
|
||||
active_segment.counts_as_user_turn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Merge one surviving replay segment's turn-context bookkeeping into the aggregate
|
||||
/// `reference_turn_context_state` we are reconstructing for the newest surviving history tail.
|
||||
///
|
||||
/// `segment_turn_context_state` is the per-segment state collected while replaying one reverse
|
||||
/// segment. `reference_turn_context_state` is the cross-segment accumulator for the surviving
|
||||
/// transcript after rollback has skipped newer user turns.
|
||||
fn merge_surviving_segment_turn_context_state(
|
||||
reference_turn_context_state: &mut ReferenceTurnContextState,
|
||||
segment_turn_context_state: ReferenceTurnContextState,
|
||||
counts_as_user_turn: bool,
|
||||
) {
|
||||
// A compaction seen in this segment shadows any older stored turn context, but it must not
|
||||
// erase a newer stored turn context we already captured from a later surviving user turn.
|
||||
if segment_turn_context_state.compacted_since_model_saw_reference_turn_context()
|
||||
&& reference_turn_context_state
|
||||
.stored_turn_context_item()
|
||||
.is_none()
|
||||
{
|
||||
reference_turn_context_state.note_compaction();
|
||||
}
|
||||
|
||||
// `reference_context_item` comes from the newest surviving user turn baseline, or
|
||||
// from a surviving compaction that explicitly cleared that baseline.
|
||||
if matches!(reference_context_item, TurnReferenceContextItem::NeverSet)
|
||||
&& (active_segment.counts_as_user_turn
|
||||
|| matches!(
|
||||
active_segment.reference_context_item,
|
||||
TurnReferenceContextItem::Cleared
|
||||
))
|
||||
// Only real user turns should establish the stored turn context. Standalone task turns may
|
||||
// carry lifecycle events, but they must not become the source of `previous_turn_settings()`
|
||||
// or the reference baseline.
|
||||
//
|
||||
// This stores the newest surviving real turn context, while preserving any later compaction
|
||||
// shadow we already learned about from newer surviving segments.
|
||||
if counts_as_user_turn
|
||||
&& reference_turn_context_state
|
||||
.stored_turn_context_item()
|
||||
.is_none()
|
||||
&& let Some(turn_context_item) = segment_turn_context_state.stored_turn_context_item()
|
||||
{
|
||||
*reference_context_item = active_segment.reference_context_item;
|
||||
reference_turn_context_state.note_turn_context_during_reverse_replay(&turn_context_item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,12 +99,11 @@ impl Session {
|
||||
) -> RolloutReconstruction {
|
||||
// Replay metadata should already match the shape of the future lazy reverse loader, even
|
||||
// while history materialization still uses an eager bridge. Scan newest-to-oldest,
|
||||
// stopping once a surviving replacement-history checkpoint and the required resume metadata
|
||||
// are both known; then replay only the buffered surviving tail forward to preserve exact
|
||||
// history semantics.
|
||||
// stopping once a surviving replacement-history checkpoint and the latest surviving turn
|
||||
// context are both known; then replay only the buffered surviving tail forward to
|
||||
// preserve exact history semantics.
|
||||
let mut base_replacement_history: Option<&[ResponseItem]> = None;
|
||||
let mut previous_turn_settings = None;
|
||||
let mut reference_context_item = TurnReferenceContextItem::NeverSet;
|
||||
let mut reference_turn_context_state = ReferenceTurnContextState::default();
|
||||
// Rollback is "drop the newest N user turns". While scanning in reverse, that becomes
|
||||
// "skip the next N user-turn segments we finalize".
|
||||
let mut pending_rollback_turns = 0usize;
|
||||
@@ -114,12 +121,9 @@ impl Session {
|
||||
active_segment.get_or_insert_with(ActiveReplaySegment::default);
|
||||
// Looking backward, compaction clears any older baseline unless a newer
|
||||
// `TurnContextItem` in this same segment has already re-established it.
|
||||
if matches!(
|
||||
active_segment.reference_context_item,
|
||||
TurnReferenceContextItem::NeverSet
|
||||
) {
|
||||
active_segment.reference_context_item = TurnReferenceContextItem::Cleared;
|
||||
}
|
||||
active_segment
|
||||
.reference_turn_context_state
|
||||
.note_compaction_during_reverse_replay();
|
||||
if active_segment.base_replacement_history.is_none()
|
||||
&& let Some(replacement_history) = &compacted.replacement_history
|
||||
{
|
||||
@@ -171,17 +175,9 @@ impl Session {
|
||||
active_segment.turn_id.as_deref(),
|
||||
ctx.turn_id.as_deref(),
|
||||
) {
|
||||
active_segment.previous_turn_settings = Some(PreviousTurnSettings {
|
||||
model: ctx.model.clone(),
|
||||
realtime_active: ctx.realtime_active,
|
||||
});
|
||||
if matches!(
|
||||
active_segment.reference_context_item,
|
||||
TurnReferenceContextItem::NeverSet
|
||||
) {
|
||||
active_segment.reference_context_item =
|
||||
TurnReferenceContextItem::Latest(Box::new(ctx.clone()));
|
||||
}
|
||||
active_segment
|
||||
.reference_turn_context_state
|
||||
.note_turn_context_during_reverse_replay(ctx);
|
||||
}
|
||||
}
|
||||
RolloutItem::EventMsg(EventMsg::TurnStarted(event)) => {
|
||||
@@ -196,8 +192,7 @@ impl Session {
|
||||
finalize_active_segment(
|
||||
active_segment,
|
||||
&mut base_replacement_history,
|
||||
&mut previous_turn_settings,
|
||||
&mut reference_context_item,
|
||||
&mut reference_turn_context_state,
|
||||
&mut pending_rollback_turns,
|
||||
);
|
||||
}
|
||||
@@ -211,12 +206,13 @@ impl Session {
|
||||
}
|
||||
|
||||
if base_replacement_history.is_some()
|
||||
&& previous_turn_settings.is_some()
|
||||
&& !matches!(reference_context_item, TurnReferenceContextItem::NeverSet)
|
||||
&& pending_rollback_turns == 0
|
||||
&& reference_turn_context_state
|
||||
.stored_turn_context_item()
|
||||
.is_some()
|
||||
{
|
||||
// At this point we have both eager resume metadata values and the replacement-
|
||||
// history base for the surviving tail, so older rollout items cannot affect this
|
||||
// result.
|
||||
// At this point the replay-derived metadata and replacement-history base for the
|
||||
// surviving tail are both fixed, so older rollout items cannot affect this result.
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -225,8 +221,7 @@ impl Session {
|
||||
finalize_active_segment(
|
||||
active_segment,
|
||||
&mut base_replacement_history,
|
||||
&mut previous_turn_settings,
|
||||
&mut reference_context_item,
|
||||
&mut reference_turn_context_state,
|
||||
&mut pending_rollback_turns,
|
||||
);
|
||||
}
|
||||
@@ -280,22 +275,12 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
let reference_context_item = match reference_context_item {
|
||||
TurnReferenceContextItem::NeverSet | TurnReferenceContextItem::Cleared => None,
|
||||
TurnReferenceContextItem::Latest(turn_reference_context_item) => {
|
||||
Some(*turn_reference_context_item)
|
||||
}
|
||||
};
|
||||
let reference_context_item = if saw_legacy_compaction_without_replacement_history {
|
||||
None
|
||||
} else {
|
||||
reference_context_item
|
||||
};
|
||||
|
||||
if saw_legacy_compaction_without_replacement_history {
|
||||
reference_turn_context_state.note_compaction();
|
||||
}
|
||||
RolloutReconstruction {
|
||||
history: history.raw_items().to_vec(),
|
||||
previous_turn_settings,
|
||||
reference_context_item,
|
||||
reference_turn_context_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,18 +249,34 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com
|
||||
vec![turn_one_user, turn_one_assistant]
|
||||
);
|
||||
assert_eq!(
|
||||
reconstructed.previous_turn_settings,
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.previous_turn_settings(),
|
||||
Some(PreviousTurnSettings {
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(reconstructed.reference_context_item)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(Some(first_context_item))
|
||||
serde_json::to_value(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.reference_context_item(),
|
||||
)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(Some(first_context_item.clone()))
|
||||
.expect("serialize expected reference context item")
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.stored_turn_context_item(),
|
||||
)
|
||||
.expect("serialize surviving turn context item"),
|
||||
serde_json::to_value(Some(first_context_item))
|
||||
.expect("serialize expected surviving turn context item")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -331,20 +347,166 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_inc
|
||||
vec![turn_one_user, turn_one_assistant]
|
||||
);
|
||||
assert_eq!(
|
||||
reconstructed.previous_turn_settings,
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.previous_turn_settings(),
|
||||
Some(PreviousTurnSettings {
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(reconstructed.reference_context_item)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.reference_context_item(),
|
||||
)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(Some(first_context_item))
|
||||
.expect("serialize expected reference context item")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reconstruct_history_rollback_backfills_surviving_turn_context_from_older_turn() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
let first_context_item = turn_context.to_turn_context_item();
|
||||
let first_turn_id = first_context_item
|
||||
.turn_id
|
||||
.clone()
|
||||
.expect("turn context should have turn_id");
|
||||
let second_turn_id = "second-turn-without-context".to_string();
|
||||
let rolled_back_turn_id = "rolled-back-turn".to_string();
|
||||
let turn_one_user = user_message("turn 1 user");
|
||||
let turn_one_assistant = assistant_message("turn 1 assistant");
|
||||
let turn_two_user = user_message("turn 2 user");
|
||||
let turn_two_assistant = assistant_message("turn 2 assistant");
|
||||
|
||||
let rollout_items = vec![
|
||||
RolloutItem::EventMsg(EventMsg::TurnStarted(
|
||||
codex_protocol::protocol::TurnStartedEvent {
|
||||
turn_id: first_turn_id.clone(),
|
||||
model_context_window: Some(128_000),
|
||||
collaboration_mode_kind: ModeKind::Default,
|
||||
},
|
||||
)),
|
||||
RolloutItem::EventMsg(EventMsg::UserMessage(
|
||||
codex_protocol::protocol::UserMessageEvent {
|
||||
message: "turn 1 user".to_string(),
|
||||
images: None,
|
||||
local_images: Vec::new(),
|
||||
text_elements: Vec::new(),
|
||||
},
|
||||
)),
|
||||
RolloutItem::TurnContext(first_context_item.clone()),
|
||||
RolloutItem::ResponseItem(turn_one_user.clone()),
|
||||
RolloutItem::ResponseItem(turn_one_assistant.clone()),
|
||||
RolloutItem::EventMsg(EventMsg::TurnComplete(
|
||||
codex_protocol::protocol::TurnCompleteEvent {
|
||||
turn_id: first_turn_id,
|
||||
last_agent_message: None,
|
||||
},
|
||||
)),
|
||||
RolloutItem::EventMsg(EventMsg::TurnStarted(
|
||||
codex_protocol::protocol::TurnStartedEvent {
|
||||
turn_id: second_turn_id.clone(),
|
||||
model_context_window: Some(128_000),
|
||||
collaboration_mode_kind: ModeKind::Default,
|
||||
},
|
||||
)),
|
||||
RolloutItem::EventMsg(EventMsg::UserMessage(
|
||||
codex_protocol::protocol::UserMessageEvent {
|
||||
message: "turn 2 user".to_string(),
|
||||
images: None,
|
||||
local_images: Vec::new(),
|
||||
text_elements: Vec::new(),
|
||||
},
|
||||
)),
|
||||
RolloutItem::ResponseItem(turn_two_user.clone()),
|
||||
RolloutItem::ResponseItem(turn_two_assistant.clone()),
|
||||
RolloutItem::EventMsg(EventMsg::TurnComplete(
|
||||
codex_protocol::protocol::TurnCompleteEvent {
|
||||
turn_id: second_turn_id,
|
||||
last_agent_message: None,
|
||||
},
|
||||
)),
|
||||
RolloutItem::EventMsg(EventMsg::TurnStarted(
|
||||
codex_protocol::protocol::TurnStartedEvent {
|
||||
turn_id: rolled_back_turn_id.clone(),
|
||||
model_context_window: Some(128_000),
|
||||
collaboration_mode_kind: ModeKind::Default,
|
||||
},
|
||||
)),
|
||||
RolloutItem::EventMsg(EventMsg::UserMessage(
|
||||
codex_protocol::protocol::UserMessageEvent {
|
||||
message: "turn 3 user".to_string(),
|
||||
images: None,
|
||||
local_images: Vec::new(),
|
||||
text_elements: Vec::new(),
|
||||
},
|
||||
)),
|
||||
RolloutItem::TurnContext(TurnContextItem {
|
||||
turn_id: Some(rolled_back_turn_id.clone()),
|
||||
model: "rolled-back-model".to_string(),
|
||||
..first_context_item.clone()
|
||||
}),
|
||||
RolloutItem::ResponseItem(user_message("turn 3 user")),
|
||||
RolloutItem::ResponseItem(assistant_message("turn 3 assistant")),
|
||||
RolloutItem::EventMsg(EventMsg::TurnComplete(
|
||||
codex_protocol::protocol::TurnCompleteEvent {
|
||||
turn_id: rolled_back_turn_id,
|
||||
last_agent_message: None,
|
||||
},
|
||||
)),
|
||||
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(
|
||||
codex_protocol::protocol::ThreadRolledBackEvent { num_turns: 1 },
|
||||
)),
|
||||
];
|
||||
|
||||
let reconstructed = session
|
||||
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
reconstructed.history,
|
||||
vec![
|
||||
turn_one_user,
|
||||
turn_one_assistant,
|
||||
turn_two_user,
|
||||
turn_two_assistant
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.previous_turn_settings(),
|
||||
Some(PreviousTurnSettings {
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.reference_context_item(),
|
||||
)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(Some(first_context_item.clone()))
|
||||
.expect("serialize expected reference context item")
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.stored_turn_context_item(),
|
||||
)
|
||||
.expect("serialize surviving turn context item"),
|
||||
serde_json::to_value(Some(first_context_item))
|
||||
.expect("serialize expected surviving turn context item")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metadata() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
@@ -437,15 +599,21 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad
|
||||
vec![turn_one_user, turn_one_assistant]
|
||||
);
|
||||
assert_eq!(
|
||||
reconstructed.previous_turn_settings,
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.previous_turn_settings(),
|
||||
Some(PreviousTurnSettings {
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(reconstructed.reference_context_item)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.reference_context_item(),
|
||||
)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(Some(first_context_item))
|
||||
.expect("serialize expected reference context item")
|
||||
);
|
||||
@@ -525,15 +693,21 @@ async fn reconstruct_history_rollback_counts_inter_agent_assistant_turns() {
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
reconstructed.previous_turn_settings,
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.previous_turn_settings(),
|
||||
Some(PreviousTurnSettings {
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(reconstructed.reference_context_item)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.reference_context_item(),
|
||||
)
|
||||
.expect("serialize reconstructed reference context item"),
|
||||
serde_json::to_value(Some(first_context_item))
|
||||
.expect("serialize expected reference context item")
|
||||
);
|
||||
@@ -582,8 +756,18 @@ async fn reconstruct_history_rollback_clears_history_and_metadata_when_exceeding
|
||||
.await;
|
||||
|
||||
assert_eq!(reconstructed.history, Vec::new());
|
||||
assert_eq!(reconstructed.previous_turn_settings, None);
|
||||
assert!(reconstructed.reference_context_item.is_none());
|
||||
assert_eq!(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.previous_turn_settings(),
|
||||
None
|
||||
);
|
||||
assert!(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.reference_context_item()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -794,7 +978,12 @@ async fn reconstruct_history_legacy_compaction_without_replacement_history_does_
|
||||
user_message("legacy summary"),
|
||||
]
|
||||
);
|
||||
assert!(reconstructed.reference_context_item.is_none());
|
||||
assert!(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.reference_context_item()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -840,7 +1029,12 @@ async fn reconstruct_history_legacy_compaction_without_replacement_history_clear
|
||||
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
|
||||
.await;
|
||||
|
||||
assert!(reconstructed.reference_context_item.is_none());
|
||||
assert!(
|
||||
reconstructed
|
||||
.reference_turn_context_state
|
||||
.reference_context_item()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -134,6 +134,39 @@ fn user_message(text: &str) -> ResponseItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn turn_context_item_with_previous_turn_settings(
|
||||
turn_context: &TurnContext,
|
||||
previous_turn_settings: PreviousTurnSettings,
|
||||
) -> TurnContextItem {
|
||||
let mut turn_context_item = turn_context.to_turn_context_item();
|
||||
turn_context_item.model = previous_turn_settings.model;
|
||||
turn_context_item.realtime_active = previous_turn_settings.realtime_active;
|
||||
turn_context_item
|
||||
}
|
||||
|
||||
async fn seed_previous_turn_settings(
|
||||
session: &Session,
|
||||
turn_context: &TurnContext,
|
||||
previous_turn_settings: PreviousTurnSettings,
|
||||
) {
|
||||
session
|
||||
.note_model_visible_turn_context(turn_context_item_with_previous_turn_settings(
|
||||
turn_context,
|
||||
previous_turn_settings,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn seed_previous_turn_settings_without_reference(
|
||||
session: &Session,
|
||||
turn_context: &TurnContext,
|
||||
previous_turn_settings: PreviousTurnSettings,
|
||||
) {
|
||||
seed_previous_turn_settings(session, turn_context, previous_turn_settings).await;
|
||||
let mut state = session.state.lock().await;
|
||||
state.history.set_reference_context_item(None);
|
||||
}
|
||||
|
||||
fn assistant_message(text: &str) -> ResponseItem {
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
@@ -1266,7 +1299,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
async fn record_initial_history_forked_advances_previous_turn_settings_to_current_baseline() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
let previous_model = "forked-rollout-model";
|
||||
let previous_context_item = TurnContextItem {
|
||||
@@ -1325,7 +1358,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
assert_eq!(
|
||||
session.previous_turn_settings().await,
|
||||
Some(PreviousTurnSettings {
|
||||
model: previous_model.to_string(),
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
})
|
||||
);
|
||||
@@ -1356,14 +1389,11 @@ async fn thread_rollback_drops_last_turn_from_history() {
|
||||
.map(RolloutItem::ResponseItem)
|
||||
.collect();
|
||||
sess.persist_rollout_items(&rollout_items).await;
|
||||
sess.set_previous_turn_settings(Some(PreviousTurnSettings {
|
||||
model: "stale-model".to_string(),
|
||||
realtime_active: Some(tc.realtime_active),
|
||||
}))
|
||||
.await;
|
||||
{
|
||||
let mut state = sess.state.lock().await;
|
||||
state.set_reference_context_item(Some(tc.to_turn_context_item()));
|
||||
state
|
||||
.history
|
||||
.set_reference_context_item(Some(tc.to_turn_context_item()));
|
||||
}
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
@@ -1518,11 +1548,6 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context
|
||||
Some(first_context_item.clone()),
|
||||
)
|
||||
.await;
|
||||
sess.set_previous_turn_settings(Some(PreviousTurnSettings {
|
||||
model: "stale-model".to_string(),
|
||||
realtime_active: None,
|
||||
}))
|
||||
.await;
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
let rollback_event = wait_for_thread_rolled_back(&rx).await;
|
||||
@@ -3640,7 +3665,7 @@ async fn record_model_warning_appends_user_message() {
|
||||
#[tokio::test]
|
||||
async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_tasks() {
|
||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
||||
sess.set_previous_turn_settings(None).await;
|
||||
sess.reset_reference_turn_context_state().await;
|
||||
let input = vec![UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3824,9 +3849,7 @@ async fn build_settings_update_items_uses_previous_turn_settings_for_realtime_en
|
||||
.await;
|
||||
current_context.realtime_active = false;
|
||||
|
||||
session
|
||||
.set_previous_turn_settings(Some(previous_turn_settings))
|
||||
.await;
|
||||
seed_previous_turn_settings(&session, &previous_context, previous_turn_settings).await;
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&previous_context_item), ¤t_context)
|
||||
.await;
|
||||
@@ -3857,7 +3880,9 @@ async fn build_initial_context_uses_previous_realtime_state() {
|
||||
let previous_context_item = turn_context.to_turn_context_item();
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(Some(previous_context_item));
|
||||
state
|
||||
.history
|
||||
.set_reference_context_item(Some(previous_context_item));
|
||||
}
|
||||
let resumed_context = session.build_initial_context(&turn_context).await;
|
||||
let resumed_developer_texts = developer_input_texts(&resumed_context);
|
||||
@@ -4008,8 +4033,7 @@ async fn build_initial_context_uses_previous_turn_settings_for_realtime_end() {
|
||||
realtime_active: Some(true),
|
||||
};
|
||||
|
||||
session
|
||||
.set_previous_turn_settings(Some(previous_turn_settings))
|
||||
seed_previous_turn_settings_without_reference(&session, &turn_context, previous_turn_settings)
|
||||
.await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let developer_texts = developer_input_texts(&initial_context);
|
||||
@@ -4030,8 +4054,7 @@ async fn build_initial_context_restates_realtime_start_when_reference_context_is
|
||||
realtime_active: Some(true),
|
||||
};
|
||||
|
||||
session
|
||||
.set_previous_turn_settings(Some(previous_turn_settings))
|
||||
seed_previous_turn_settings_without_reference(&session, &turn_context, previous_turn_settings)
|
||||
.await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let developer_texts = developer_input_texts(&initial_context);
|
||||
@@ -4083,7 +4106,7 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
|
||||
.await;
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(None);
|
||||
state.history.set_reference_context_item(None);
|
||||
}
|
||||
session
|
||||
.replace_history(vec![compacted_summary.clone()], None)
|
||||
@@ -4100,7 +4123,7 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn record_context_updates_and_set_reference_context_item_persists_baseline_without_emitting_diffs()
|
||||
async fn record_context_updates_and_set_reference_context_item_emits_model_switch_for_stored_baseline()
|
||||
{
|
||||
let (session, previous_context) = make_session_and_context().await;
|
||||
let next_model = if previous_context.model_info.slug == "gpt-5.1" {
|
||||
@@ -4114,7 +4137,9 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
|
||||
let previous_context_item = previous_context.to_turn_context_item();
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(Some(previous_context_item.clone()));
|
||||
state
|
||||
.history
|
||||
.set_reference_context_item(Some(previous_context_item.clone()));
|
||||
}
|
||||
let config = session.get_config().await;
|
||||
let recorder = RolloutRecorder::new(
|
||||
@@ -4141,7 +4166,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&previous_context_item), &turn_context)
|
||||
.await;
|
||||
assert_eq!(update_items, Vec::new());
|
||||
assert!(!update_items.is_empty());
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
@@ -4149,7 +4174,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
|
||||
|
||||
assert_eq!(
|
||||
session.clone_history().await.raw_items().to_vec(),
|
||||
Vec::new()
|
||||
update_items
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(session.reference_context_item().await)
|
||||
@@ -4186,8 +4211,7 @@ async fn build_initial_context_prepends_model_switch_message() {
|
||||
realtime_active: None,
|
||||
};
|
||||
|
||||
session
|
||||
.set_previous_turn_settings(Some(previous_turn_settings))
|
||||
seed_previous_turn_settings_without_reference(&session, &turn_context, previous_turn_settings)
|
||||
.await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
|
||||
@@ -4247,15 +4271,18 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei
|
||||
.await;
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(None);
|
||||
state.history.set_reference_context_item(None);
|
||||
}
|
||||
|
||||
session
|
||||
.set_previous_turn_settings(Some(PreviousTurnSettings {
|
||||
seed_previous_turn_settings_without_reference(
|
||||
&session,
|
||||
&previous_context,
|
||||
PreviousTurnSettings {
|
||||
model: previous_context.model_info.slug.clone(),
|
||||
realtime_active: Some(previous_context.realtime_active),
|
||||
}))
|
||||
.await;
|
||||
},
|
||||
)
|
||||
.await;
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
@@ -4286,7 +4313,7 @@ async fn run_user_shell_command_does_not_set_reference_context_item() {
|
||||
let (session, _turn_context, rx) = make_session_and_context_with_rx().await;
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(None);
|
||||
state.history.set_reference_context_item(None);
|
||||
}
|
||||
|
||||
handlers::run_user_shell_command(&session, "sub-id".to_string(), "echo shell".to_string())
|
||||
|
||||
@@ -6,9 +6,15 @@ async fn process_compacted_history_with_test_session(
|
||||
previous_turn_settings: Option<&PreviousTurnSettings>,
|
||||
) -> (Vec<ResponseItem>, Vec<ResponseItem>) {
|
||||
let (session, turn_context) = crate::codex::make_session_and_context().await;
|
||||
session
|
||||
.set_previous_turn_settings(previous_turn_settings.cloned())
|
||||
.await;
|
||||
if let Some(previous_turn_settings) = previous_turn_settings {
|
||||
let mut previous_turn_context_item = turn_context.to_turn_context_item();
|
||||
previous_turn_context_item.model = previous_turn_settings.model.clone();
|
||||
previous_turn_context_item.realtime_active = previous_turn_settings.realtime_active;
|
||||
session
|
||||
.note_model_visible_turn_context(previous_turn_context_item)
|
||||
.await;
|
||||
session.replace_history(Vec::new(), None).await;
|
||||
}
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let refreshed = crate::compact_remote::process_compacted_history(
|
||||
&session,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::codex::PreviousTurnSettings;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::context_manager::normalize;
|
||||
use crate::event_mapping::has_non_contextual_dev_message_content;
|
||||
@@ -35,17 +36,94 @@ pub(crate) struct ContextManager {
|
||||
/// The oldest items are at the beginning of the vector.
|
||||
items: Vec<ResponseItem>,
|
||||
token_info: Option<TokenUsageInfo>,
|
||||
/// Reference context snapshot used for diffing and producing model-visible
|
||||
/// settings update items.
|
||||
reference_turn_context_state: ReferenceTurnContextState,
|
||||
}
|
||||
|
||||
/// Session-owned bookkeeping for the stored turn context that drives both
|
||||
/// `previous_turn_settings()` and the model-visible reference baseline.
|
||||
///
|
||||
/// The stored turn context survives compaction so future turns can still diff
|
||||
/// against the last known settings, while
|
||||
/// `compacted_since_model_saw_reference_turn_context` controls whether the model
|
||||
/// may still treat that stored item as the active reference baseline.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct ReferenceTurnContextState {
|
||||
/// The last stored turn context we reconstructed or recorded.
|
||||
///
|
||||
/// This is the baseline for the next regular model turn, and may already
|
||||
/// match the current turn after context updates are persisted.
|
||||
/// This is the single source of truth for both `previous_turn_settings()` and
|
||||
/// the reference baseline. Compaction can shadow it for model visibility
|
||||
/// without erasing it.
|
||||
turn_context_item: Option<TurnContextItem>,
|
||||
/// Whether compaction has crossed the current reference baseline without a later
|
||||
/// reinjection or real turn context re-establishing it.
|
||||
///
|
||||
/// When this is `None`, settings diffing treats the next turn as having no
|
||||
/// baseline and emits a full reinjection of context state. Rollback may
|
||||
/// also clear this when it trims a mixed initial-context developer bundle
|
||||
/// whose non-diff fragments no longer exist in the surviving history.
|
||||
reference_context_item: Option<TurnContextItem>,
|
||||
/// When this is true, `reference_context_item()` must return `None` even if
|
||||
/// `turn_context_item` still retains the last stored baseline for replay or
|
||||
/// rollback bookkeeping.
|
||||
compacted_since_model_saw_reference_turn_context: bool,
|
||||
}
|
||||
|
||||
impl ReferenceTurnContextState {
|
||||
pub(crate) fn reset(&mut self) {
|
||||
*self = Self::default();
|
||||
}
|
||||
|
||||
pub(crate) fn note_compaction(&mut self) {
|
||||
self.compacted_since_model_saw_reference_turn_context = true;
|
||||
}
|
||||
|
||||
pub(crate) fn note_compaction_during_reverse_replay(&mut self) {
|
||||
if self.turn_context_item.is_none() {
|
||||
self.compacted_since_model_saw_reference_turn_context = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn note_turn_context_during_reverse_replay(
|
||||
&mut self,
|
||||
turn_context_item: &TurnContextItem,
|
||||
) {
|
||||
if self.turn_context_item.is_none() {
|
||||
self.turn_context_item = Some(turn_context_item.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn note_model_visible_turn_context(&mut self, turn_context_item: TurnContextItem) {
|
||||
self.turn_context_item = Some(turn_context_item);
|
||||
self.compacted_since_model_saw_reference_turn_context = false;
|
||||
}
|
||||
|
||||
pub(crate) fn set_reference_context_item(&mut self, item: Option<TurnContextItem>) {
|
||||
if let Some(item) = item {
|
||||
self.note_model_visible_turn_context(item);
|
||||
} else {
|
||||
self.note_compaction();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn stored_turn_context_item(&self) -> Option<TurnContextItem> {
|
||||
self.turn_context_item.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn compacted_since_model_saw_reference_turn_context(&self) -> bool {
|
||||
self.compacted_since_model_saw_reference_turn_context
|
||||
}
|
||||
|
||||
pub(crate) fn previous_turn_settings(&self) -> Option<PreviousTurnSettings> {
|
||||
self.turn_context_item
|
||||
.as_ref()
|
||||
.map(|turn_context_item| PreviousTurnSettings {
|
||||
model: turn_context_item.model.clone(),
|
||||
realtime_active: turn_context_item.realtime_active,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn reference_context_item(&self) -> Option<TurnContextItem> {
|
||||
if self.compacted_since_model_saw_reference_turn_context {
|
||||
None
|
||||
} else {
|
||||
self.turn_context_item.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
@@ -63,7 +141,7 @@ impl ContextManager {
|
||||
token_info: TokenUsageInfo::new_or_append(
|
||||
&None, &None, /*model_context_window*/ None,
|
||||
),
|
||||
reference_context_item: None,
|
||||
reference_turn_context_state: ReferenceTurnContextState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +153,33 @@ impl ContextManager {
|
||||
self.token_info = info;
|
||||
}
|
||||
|
||||
pub(crate) fn set_reference_turn_context_state(
|
||||
&mut self,
|
||||
reference_turn_context_state: ReferenceTurnContextState,
|
||||
) {
|
||||
self.reference_turn_context_state = reference_turn_context_state;
|
||||
}
|
||||
|
||||
pub(crate) fn reset_reference_turn_context_state(&mut self) {
|
||||
self.reference_turn_context_state.reset();
|
||||
}
|
||||
|
||||
pub(crate) fn note_model_visible_turn_context(&mut self, turn_context_item: TurnContextItem) {
|
||||
self.reference_turn_context_state
|
||||
.note_model_visible_turn_context(turn_context_item);
|
||||
}
|
||||
|
||||
pub(crate) fn previous_turn_settings(&self) -> Option<PreviousTurnSettings> {
|
||||
self.reference_turn_context_state.previous_turn_settings()
|
||||
}
|
||||
|
||||
pub(crate) fn set_reference_context_item(&mut self, item: Option<TurnContextItem>) {
|
||||
self.reference_context_item = item;
|
||||
self.reference_turn_context_state
|
||||
.set_reference_context_item(item);
|
||||
}
|
||||
|
||||
pub(crate) fn reference_context_item(&self) -> Option<TurnContextItem> {
|
||||
self.reference_context_item.clone()
|
||||
self.reference_turn_context_state.reference_context_item()
|
||||
}
|
||||
|
||||
pub(crate) fn set_token_usage_full(&mut self, context_window: i64) {
|
||||
@@ -410,8 +509,8 @@ impl ContextManager {
|
||||
/// that boundary.
|
||||
///
|
||||
/// If any trimmed developer message was a mixed `build_initial_context` bundle containing both
|
||||
/// rollback-trimmable contextual fragments and persistent developer text, this also clears the
|
||||
/// stored `reference_context_item` baseline so the next real turn falls back to full
|
||||
/// rollback-trimmable contextual fragments and persistent developer text, this shadows the
|
||||
/// stored turn context as a model-visible baseline so the next real turn falls back to full
|
||||
/// reinjection.
|
||||
fn trim_pre_turn_context_updates(
|
||||
&mut self,
|
||||
@@ -428,7 +527,7 @@ impl ContextManager {
|
||||
// Mixed `build_initial_context` bundles are not reconstructible from
|
||||
// steady-state diffs once trimmed, so the next real turn must fully
|
||||
// reinject context instead of diffing against a stale baseline.
|
||||
self.reference_context_item = None;
|
||||
self.set_reference_context_item(/*item*/ None);
|
||||
}
|
||||
cut_idx -= 1;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ mod normalize;
|
||||
pub(crate) mod updates;
|
||||
|
||||
pub(crate) use history::ContextManager;
|
||||
pub(crate) use history::ReferenceTurnContextState;
|
||||
pub(crate) use history::TotalTokenUsageBreakdown;
|
||||
pub(crate) use history::estimate_response_item_model_visible_bytes;
|
||||
pub(crate) use history::is_codex_generated_item;
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::collections::HashSet;
|
||||
use crate::codex::PreviousTurnSettings;
|
||||
use crate::codex::SessionConfiguration;
|
||||
use crate::context_manager::ContextManager;
|
||||
use crate::context_manager::ReferenceTurnContextState;
|
||||
use crate::protocol::RateLimitSnapshot;
|
||||
use crate::protocol::TokenUsage;
|
||||
use crate::protocol::TokenUsageInfo;
|
||||
@@ -24,10 +25,6 @@ pub(crate) struct SessionState {
|
||||
pub(crate) server_reasoning_included: bool,
|
||||
pub(crate) dependency_env: HashMap<String, String>,
|
||||
pub(crate) mcp_dependency_prompted: HashSet<String>,
|
||||
/// Settings used by the latest regular user turn, used for turn-to-turn
|
||||
/// model/realtime handling on subsequent regular turns (including full-context
|
||||
/// reinjection after resume or `/compact`).
|
||||
previous_turn_settings: Option<PreviousTurnSettings>,
|
||||
/// Startup prewarmed session prepared during session initialization.
|
||||
pub(crate) startup_prewarm: Option<SessionStartupPrewarmHandle>,
|
||||
pub(crate) active_connector_selection: HashSet<String>,
|
||||
@@ -46,7 +43,6 @@ impl SessionState {
|
||||
server_reasoning_included: false,
|
||||
dependency_env: HashMap::new(),
|
||||
mcp_dependency_prompted: HashSet::new(),
|
||||
previous_turn_settings: None,
|
||||
startup_prewarm: None,
|
||||
active_connector_selection: HashSet::new(),
|
||||
pending_session_start_source: None,
|
||||
@@ -64,13 +60,7 @@ impl SessionState {
|
||||
}
|
||||
|
||||
pub(crate) fn previous_turn_settings(&self) -> Option<PreviousTurnSettings> {
|
||||
self.previous_turn_settings.clone()
|
||||
}
|
||||
pub(crate) fn set_previous_turn_settings(
|
||||
&mut self,
|
||||
previous_turn_settings: Option<PreviousTurnSettings>,
|
||||
) {
|
||||
self.previous_turn_settings = previous_turn_settings;
|
||||
self.history.previous_turn_settings()
|
||||
}
|
||||
|
||||
pub(crate) fn clone_history(&self) -> ContextManager {
|
||||
@@ -81,18 +71,37 @@ impl SessionState {
|
||||
&mut self,
|
||||
items: Vec<ResponseItem>,
|
||||
reference_context_item: Option<TurnContextItem>,
|
||||
) {
|
||||
self.history.replace(items);
|
||||
match reference_context_item {
|
||||
Some(turn_context_item) => self
|
||||
.history
|
||||
.note_model_visible_turn_context(turn_context_item),
|
||||
None => self.history.set_reference_context_item(/*item*/ None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn replace_history_with_reference_turn_context_state(
|
||||
&mut self,
|
||||
items: Vec<ResponseItem>,
|
||||
reference_turn_context_state: ReferenceTurnContextState,
|
||||
) {
|
||||
self.history.replace(items);
|
||||
self.history
|
||||
.set_reference_context_item(reference_context_item);
|
||||
.set_reference_turn_context_state(reference_turn_context_state);
|
||||
}
|
||||
|
||||
pub(crate) fn reset_reference_turn_context_state(&mut self) {
|
||||
self.history.reset_reference_turn_context_state();
|
||||
}
|
||||
|
||||
pub(crate) fn set_token_info(&mut self, info: Option<TokenUsageInfo>) {
|
||||
self.history.set_token_info(info);
|
||||
}
|
||||
|
||||
pub(crate) fn set_reference_context_item(&mut self, item: Option<TurnContextItem>) {
|
||||
self.history.set_reference_context_item(item);
|
||||
pub(crate) fn note_model_visible_turn_context(&mut self, turn_context_item: TurnContextItem) {
|
||||
self.history
|
||||
.note_model_visible_turn_context(turn_context_item);
|
||||
}
|
||||
|
||||
pub(crate) fn reference_context_item(&self) -> Option<TurnContextItem> {
|
||||
|
||||
Reference in New Issue
Block a user