use super::*;
pub(crate) fn merge_last_run_selection(
existing: &BTreeMap<String, i64>,
incoming: &BTreeMap<String, i64>,
profiles: &BTreeMap<String, ProfileEntry>,
) -> BTreeMap<String, i64> {
let mut merged = existing.clone();
for (profile_name, timestamp) in incoming {
merged
.entry(profile_name.clone())
.and_modify(|current| *current = (*current).max(*timestamp))
.or_insert(*timestamp);
}
merged.retain(|profile_name, _| profiles.contains_key(profile_name));
merged
}
pub(crate) fn prune_last_run_selection(
selections: &mut BTreeMap<String, i64>,
profiles: &BTreeMap<String, ProfileEntry>,
now: i64,
) {
let oldest_allowed = now.saturating_sub(APP_STATE_LAST_RUN_RETENTION_SECONDS);
selections.retain(|profile_name, timestamp| {
profiles.contains_key(profile_name) && *timestamp >= oldest_allowed
});
}
pub(crate) fn merge_profile_bindings(
existing: &BTreeMap<String, ResponseProfileBinding>,
incoming: &BTreeMap<String, ResponseProfileBinding>,
profiles: &BTreeMap<String, ProfileEntry>,
) -> BTreeMap<String, ResponseProfileBinding> {
let mut merged = existing.clone();
for (response_id, binding) in incoming {
let should_replace = merged
.get(response_id)
.is_none_or(|current| current.bound_at <= binding.bound_at);
if should_replace {
merged.insert(response_id.clone(), binding.clone());
}
}
merged.retain(|_, binding| profiles.contains_key(&binding.profile_name));
merged
}
pub(crate) fn runtime_continuation_binding_lifecycle_rank(
state: RuntimeContinuationBindingLifecycle,
) -> u8 {
match state {
RuntimeContinuationBindingLifecycle::Dead => 0,
RuntimeContinuationBindingLifecycle::Suspect => 1,
RuntimeContinuationBindingLifecycle::Warm => 2,
RuntimeContinuationBindingLifecycle::Verified => 3,
}
}
pub(crate) fn runtime_continuation_status_evidence_sort_key(
status: &RuntimeContinuationBindingStatus,
) -> (u8, u32, u32, u32, u8, i64, i64, i64) {
(
runtime_continuation_binding_lifecycle_rank(status.state),
status.confidence.min(RUNTIME_CONTINUATION_CONFIDENCE_MAX),
status.success_count,
u32::MAX.saturating_sub(status.not_found_streak),
if status.last_verified_route.is_some() {
1
} else {
0
},
status.last_verified_at.unwrap_or(i64::MIN),
status.last_touched_at.unwrap_or(i64::MIN),
status.last_not_found_at.unwrap_or(i64::MIN),
)
}
pub(crate) fn runtime_continuation_status_is_more_evidenced(
candidate: &RuntimeContinuationBindingStatus,
current: &RuntimeContinuationBindingStatus,
) -> bool {
runtime_continuation_status_evidence_sort_key(candidate)
> runtime_continuation_status_evidence_sort_key(current)
}
pub(crate) fn runtime_continuation_status_should_replace(
candidate: &RuntimeContinuationBindingStatus,
current: &RuntimeContinuationBindingStatus,
) -> bool {
match (
runtime_continuation_status_last_event_at(candidate),
runtime_continuation_status_last_event_at(current),
) {
(Some(candidate_at), Some(current_at)) if candidate_at != current_at => {
return candidate_at > current_at;
}
(Some(_), None) => return true,
(None, Some(_)) => return false,
_ => {}
}
match (
runtime_continuation_status_is_terminal(candidate),
runtime_continuation_status_is_terminal(current),
) {
(true, false) => return true,
(false, true) => return false,
_ => {}
}
runtime_continuation_status_is_more_evidenced(candidate, current)
}
pub(crate) fn runtime_continuation_status_last_event_at(
status: &RuntimeContinuationBindingStatus,
) -> Option<i64> {
[
status.last_not_found_at,
status.last_verified_at,
status.last_touched_at,
]
.into_iter()
.flatten()
.max()
}
pub(crate) fn runtime_continuation_status_is_terminal(
status: &RuntimeContinuationBindingStatus,
) -> bool {
status.state == RuntimeContinuationBindingLifecycle::Dead
|| status.not_found_streak >= RUNTIME_CONTINUATION_SUSPECT_NOT_FOUND_STREAK_LIMIT
|| (status.state == RuntimeContinuationBindingLifecycle::Suspect
&& status.confidence == 0
&& status.failure_count > 0)
}
pub(crate) fn runtime_continuation_status_is_stale_verified(
status: &RuntimeContinuationBindingStatus,
now: i64,
) -> bool {
status.state == RuntimeContinuationBindingLifecycle::Verified
&& runtime_continuation_status_last_event_at(status).is_some_and(|last| {
now.saturating_sub(last) >= RUNTIME_CONTINUATION_VERIFIED_STALE_SECONDS
})
}
pub(crate) fn runtime_age_stale_verified_continuation_status(
statuses: &mut RuntimeContinuationStatuses,
kind: RuntimeContinuationBindingKind,
key: &str,
now: i64,
) -> bool {
let Some(status) = runtime_continuation_status_map_mut(statuses, kind).get_mut(key) else {
return false;
};
if !runtime_continuation_status_is_stale_verified(status, now) {
return false;
}
status.state = RuntimeContinuationBindingLifecycle::Warm;
true
}
pub(crate) fn runtime_continuation_status_should_retain_with_binding(
status: &RuntimeContinuationBindingStatus,
now: i64,
) -> bool {
match status.state {
RuntimeContinuationBindingLifecycle::Dead => false,
RuntimeContinuationBindingLifecycle::Verified
| RuntimeContinuationBindingLifecycle::Warm => {
status.confidence > 0
|| status.success_count > 0
|| status.last_verified_at.is_some()
|| status.last_touched_at.is_some()
}
RuntimeContinuationBindingLifecycle::Suspect => {
status.not_found_streak < RUNTIME_CONTINUATION_SUSPECT_NOT_FOUND_STREAK_LIMIT
&& status.confidence > 0
&& status.last_not_found_at.is_some_and(|last| {
now.saturating_sub(last) < RUNTIME_CONTINUATION_SUSPECT_GRACE_SECONDS
})
}
}
}
pub(crate) fn runtime_continuation_status_should_retain_without_binding(
status: &RuntimeContinuationBindingStatus,
now: i64,
) -> bool {
match status.state {
RuntimeContinuationBindingLifecycle::Dead => status
.last_not_found_at
.or(status.last_touched_at)
.is_some_and(|last| now.saturating_sub(last) < RUNTIME_CONTINUATION_DEAD_GRACE_SECONDS),
_ => runtime_continuation_status_should_retain_with_binding(status, now),
}
}
pub(crate) fn runtime_continuation_status_dead_at(
status: &RuntimeContinuationBindingStatus,
) -> Option<i64> {
(status.state == RuntimeContinuationBindingLifecycle::Dead)
.then(|| status.last_not_found_at.or(status.last_touched_at))
.flatten()
}
pub(crate) fn runtime_continuation_dead_status_shadowed_by_binding(
binding: &ResponseProfileBinding,
status: &RuntimeContinuationBindingStatus,
) -> bool {
runtime_continuation_status_dead_at(status).is_some_and(|dead_at| binding.bound_at > dead_at)
}
pub(crate) fn merge_runtime_continuation_status_map(
existing: &BTreeMap<String, RuntimeContinuationBindingStatus>,
incoming: &BTreeMap<String, RuntimeContinuationBindingStatus>,
live_bindings: &BTreeMap<String, ResponseProfileBinding>,
) -> BTreeMap<String, RuntimeContinuationBindingStatus> {
let now = Local::now().timestamp();
let mut merged = existing.clone();
for (key, status) in incoming {
let should_replace = merged
.get(key)
.is_none_or(|current| runtime_continuation_status_should_replace(status, current));
if should_replace {
merged.insert(key.clone(), status.clone());
}
}
merged.retain(|key, status| {
live_bindings.contains_key(key)
|| runtime_continuation_status_should_retain_without_binding(status, now)
});
merged
}
pub(crate) fn merge_runtime_continuation_statuses(
existing: &RuntimeContinuationStatuses,
incoming: &RuntimeContinuationStatuses,
response_bindings: &BTreeMap<String, ResponseProfileBinding>,
turn_state_bindings: &BTreeMap<String, ResponseProfileBinding>,
session_id_bindings: &BTreeMap<String, ResponseProfileBinding>,
) -> RuntimeContinuationStatuses {
RuntimeContinuationStatuses {
response: merge_runtime_continuation_status_map(
&existing.response,
&incoming.response,
response_bindings,
),
turn_state: merge_runtime_continuation_status_map(
&existing.turn_state,
&incoming.turn_state,
turn_state_bindings,
),
session_id: merge_runtime_continuation_status_map(
&existing.session_id,
&incoming.session_id,
session_id_bindings,
),
}
}
pub(crate) fn compact_runtime_continuation_statuses(
statuses: RuntimeContinuationStatuses,
continuations: &RuntimeContinuationStore,
) -> RuntimeContinuationStatuses {
let now = Local::now().timestamp();
let mut merged = merge_runtime_continuation_statuses(
&RuntimeContinuationStatuses::default(),
&statuses,
&continuations.response_profile_bindings,
&continuations.turn_state_bindings,
&continuations.session_id_bindings,
);
merged.response.retain(|key, status| {
if let Some(binding) = continuations.response_profile_bindings.get(key) {
!runtime_continuation_dead_status_shadowed_by_binding(binding, status)
&& runtime_continuation_status_should_retain_with_binding(status, now)
} else {
runtime_continuation_status_should_retain_without_binding(status, now)
}
});
merged.turn_state.retain(|key, status| {
if let Some(binding) = continuations.turn_state_bindings.get(key) {
!runtime_continuation_dead_status_shadowed_by_binding(binding, status)
&& runtime_continuation_status_should_retain_with_binding(status, now)
} else {
runtime_continuation_status_should_retain_without_binding(status, now)
}
});
merged.session_id.retain(|key, status| {
if let Some(binding) = continuations.session_id_bindings.get(key) {
!runtime_continuation_dead_status_shadowed_by_binding(binding, status)
&& runtime_continuation_status_should_retain_with_binding(status, now)
} else {
runtime_continuation_status_should_retain_without_binding(status, now)
}
});
prune_runtime_continuation_status_map(
&mut merged.response,
&continuations.response_profile_bindings,
RUNTIME_CONTINUATION_RESPONSE_STATUS_LIMIT,
);
prune_runtime_continuation_status_map(
&mut merged.turn_state,
&continuations.turn_state_bindings,
RUNTIME_CONTINUATION_TURN_STATE_STATUS_LIMIT,
);
prune_runtime_continuation_status_map(
&mut merged.session_id,
&continuations.session_id_bindings,
RUNTIME_CONTINUATION_SESSION_ID_STATUS_LIMIT,
);
merged
}
pub(crate) fn runtime_continuation_status_retention_sort_key(
key: &str,
status: &RuntimeContinuationBindingStatus,
bindings: &BTreeMap<String, ResponseProfileBinding>,
) -> (u8, u8, u32, u32, u32, u8, i64, i64, i64, i64) {
let evidence = runtime_continuation_status_evidence_sort_key(status);
(
if bindings.contains_key(key) { 1 } else { 0 },
evidence.0,
evidence.1,
evidence.2,
evidence.3,
evidence.4,
evidence.5,
evidence.6,
evidence.7,
bindings
.get(key)
.map(|binding| binding.bound_at)
.unwrap_or(i64::MIN),
)
}
pub(crate) fn prune_runtime_continuation_status_map(
statuses: &mut BTreeMap<String, RuntimeContinuationBindingStatus>,
bindings: &BTreeMap<String, ResponseProfileBinding>,
max_entries: usize,
) {
if statuses.len() <= max_entries {
return;
}
let excess = statuses.len() - max_entries;
let mut coldest = statuses
.iter()
.map(|(key, status)| {
(
key.clone(),
runtime_continuation_status_retention_sort_key(key, status, bindings),
)
})
.collect::<Vec<_>>();
coldest.sort_by_key(|(_, retention)| *retention);
for (key, _) in coldest.into_iter().take(excess) {
statuses.remove(&key);
}
}
pub(crate) fn runtime_continuation_binding_should_retain(
binding: &ResponseProfileBinding,
status: Option<&RuntimeContinuationBindingStatus>,
now: i64,
) -> bool {
match status {
Some(status) if runtime_continuation_dead_status_shadowed_by_binding(binding, status) => {
true
}
Some(status) if runtime_continuation_status_is_terminal(status) => false,
Some(status) => runtime_continuation_status_should_retain_with_binding(status, now),
None => binding.bound_at <= now,
}
}
pub(crate) fn runtime_continuation_binding_retention_sort_key(
binding: &ResponseProfileBinding,
status: Option<&RuntimeContinuationBindingStatus>,
) -> (u8, u32, u32, u32, u8, i64, i64, i64, i64) {
let evidence = status
.map(runtime_continuation_status_evidence_sort_key)
.unwrap_or((0, 0, 0, 0, 0, i64::MIN, i64::MIN, i64::MIN));
(
evidence.0,
evidence.1,
evidence.2,
evidence.3,
evidence.4,
evidence.5,
evidence.6,
evidence.7,
binding.bound_at,
)
}
pub(crate) fn prune_runtime_continuation_response_bindings(
bindings: &mut BTreeMap<String, ResponseProfileBinding>,
statuses: &BTreeMap<String, RuntimeContinuationBindingStatus>,
max_entries: usize,
) {
if bindings.len() <= max_entries {
return;
}
let excess = bindings.len() - max_entries;
let mut coldest = bindings
.iter()
.map(|(response_id, binding)| {
(
response_id.clone(),
runtime_continuation_binding_retention_sort_key(binding, statuses.get(response_id)),
)
})
.collect::<Vec<_>>();
coldest.sort_by_key(|(_, retention)| *retention);
for (response_id, _) in coldest.into_iter().take(excess) {
bindings.remove(&response_id);
}
}
pub(crate) fn prune_profile_bindings_for_housekeeping(
bindings: &mut BTreeMap<String, ResponseProfileBinding>,
profiles: &BTreeMap<String, ProfileEntry>,
now: i64,
retention_seconds: i64,
max_entries: usize,
) {
let oldest_allowed = now.saturating_sub(retention_seconds);
bindings.retain(|_, binding| {
profiles.contains_key(&binding.profile_name) && binding.bound_at >= oldest_allowed
});
prune_profile_bindings(bindings, max_entries);
}
pub(crate) fn prune_profile_bindings_for_housekeeping_without_retention(
bindings: &mut BTreeMap<String, ResponseProfileBinding>,
profiles: &BTreeMap<String, ProfileEntry>,
) {
bindings.retain(|_, binding| profiles.contains_key(&binding.profile_name));
}
pub(crate) fn compact_app_state(mut state: AppState, now: i64) -> AppState {
state.active_profile = state
.active_profile
.filter(|profile_name| state.profiles.contains_key(profile_name));
prune_last_run_selection(&mut state.last_run_selected_at, &state.profiles, now);
prune_profile_bindings_for_housekeeping_without_retention(
&mut state.response_profile_bindings,
&state.profiles,
);
prune_profile_bindings_for_housekeeping(
&mut state.session_profile_bindings,
&state.profiles,
now,
APP_STATE_SESSION_BINDING_RETENTION_SECONDS,
SESSION_ID_PROFILE_BINDING_LIMIT,
);
state
}
pub(crate) fn merge_runtime_state_snapshot(existing: AppState, snapshot: &AppState) -> AppState {
let profiles = if existing.profiles.is_empty() {
snapshot.profiles.clone()
} else {
existing.profiles.clone()
};
let active_profile = snapshot
.active_profile
.clone()
.or(existing.active_profile.clone())
.filter(|profile_name| profiles.contains_key(profile_name));
let merged = AppState {
active_profile,
profiles: profiles.clone(),
last_run_selected_at: merge_last_run_selection(
&existing.last_run_selected_at,
&snapshot.last_run_selected_at,
&profiles,
),
response_profile_bindings: merge_profile_bindings(
&existing.response_profile_bindings,
&snapshot.response_profile_bindings,
&profiles,
),
session_profile_bindings: merge_profile_bindings(
&existing.session_profile_bindings,
&snapshot.session_profile_bindings,
&profiles,
),
};
compact_app_state(merged, Local::now().timestamp())
}