mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
## Summary - track user-like input and tool-output boundaries in current-time reminder state - gate reminder injection when delivery_mode is after_user_or_tool_output - preserve interval debounce and forced reminders after context-window changes ## Why Training can request reminders only after user or tool-output items while keeping the existing canonical pre-inference history-injection path. ## Validation - just test -p codex-core current_time_reminders_can_follow_only_user_or_tool_outputs - just test -p codex-core current_time_reminders_follow_time_interval_and_persist_in_history - just test -p codex-core current_time_reminder_is_refreshed_after_compaction - just fix -p codex-core
107 lines
3.4 KiB
Rust
107 lines
3.4 KiB
Rust
use chrono::DateTime;
|
|
use chrono::Utc;
|
|
use codex_features::CurrentTimeReminderDeliveryMode;
|
|
use codex_protocol::error::CodexErr;
|
|
use codex_protocol::error::Result as CodexResult;
|
|
use codex_protocol::models::ResponseItem;
|
|
|
|
use super::session::Session;
|
|
use super::turn_context::TurnContext;
|
|
use crate::context::ContextualUserFragment;
|
|
use crate::context_manager::is_user_turn_boundary;
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct CurrentTimeReminderState {
|
|
last_delivery_time: Option<DateTime<Utc>>,
|
|
last_window_id: Option<String>,
|
|
pending_user_or_tool_output_boundary: bool,
|
|
}
|
|
|
|
impl CurrentTimeReminderState {
|
|
pub(super) fn note_recorded_items(&mut self, items: &[ResponseItem]) {
|
|
if items.iter().any(|item| {
|
|
is_user_turn_boundary(item)
|
|
|| matches!(
|
|
item,
|
|
ResponseItem::FunctionCallOutput { .. }
|
|
| ResponseItem::CustomToolCallOutput { .. }
|
|
| ResponseItem::ToolSearchOutput { .. }
|
|
)
|
|
}) {
|
|
self.pending_user_or_tool_output_boundary = true;
|
|
}
|
|
}
|
|
|
|
fn take_reminder_due(
|
|
&mut self,
|
|
window_id: &str,
|
|
current_time: DateTime<Utc>,
|
|
interval_seconds: u64,
|
|
delivery_mode: CurrentTimeReminderDeliveryMode,
|
|
) -> bool {
|
|
let is_new_window = self.last_window_id.as_deref() != Some(window_id);
|
|
// Consume the boundary for this inference even if the interval suppresses delivery.
|
|
let follows_user_or_tool_output =
|
|
std::mem::take(&mut self.pending_user_or_tool_output_boundary);
|
|
if delivery_mode == CurrentTimeReminderDeliveryMode::AfterUserOrToolOutput
|
|
&& !is_new_window
|
|
&& !follows_user_or_tool_output
|
|
{
|
|
return false;
|
|
}
|
|
|
|
let reminder_is_due = is_new_window
|
|
|| interval_seconds == 0
|
|
|| self.last_delivery_time.is_none_or(|last_delivery_time| {
|
|
current_time
|
|
.signed_duration_since(last_delivery_time)
|
|
.num_seconds()
|
|
>= i64::try_from(interval_seconds).unwrap_or(i64::MAX)
|
|
});
|
|
|
|
if reminder_is_due {
|
|
self.last_delivery_time = Some(current_time);
|
|
self.last_window_id = Some(window_id.to_string());
|
|
}
|
|
|
|
reminder_is_due
|
|
}
|
|
}
|
|
|
|
pub(super) async fn maybe_record_current_time_reminder(
|
|
sess: &Session,
|
|
turn_context: &TurnContext,
|
|
window_id: &str,
|
|
) -> CodexResult<()> {
|
|
let Some(config) = turn_context.config.current_time_reminder else {
|
|
return Ok(());
|
|
};
|
|
|
|
let current_time = sess
|
|
.services
|
|
.time_provider
|
|
.current_time(sess.thread_id)
|
|
.await
|
|
.map_err(|err| CodexErr::Fatal(format!("failed to read current time: {err:#}")))?;
|
|
|
|
let reminder_is_due = {
|
|
let mut state = sess.state.lock().await;
|
|
state.current_time_reminder.take_reminder_due(
|
|
window_id,
|
|
current_time,
|
|
config.reminder_interval_seconds,
|
|
config.delivery_mode,
|
|
)
|
|
};
|
|
if !reminder_is_due {
|
|
return Ok(());
|
|
}
|
|
|
|
let response_item =
|
|
ContextualUserFragment::into(crate::context::CurrentTimeReminder::new(current_time));
|
|
sess.record_conversation_items(turn_context, std::slice::from_ref(&response_item))
|
|
.await;
|
|
|
|
Ok(())
|
|
}
|