use super::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum RuntimePreviousResponseNotFoundRoute {
Responses,
Websocket,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct RuntimePreviousResponseNotFoundDecision {
pub(crate) retry_delay: Option<Duration>,
pub(crate) retry_reason: Option<&'static str>,
pub(crate) chain_retry_reason: Option<&'static str>,
pub(crate) request_requires_locked_previous_response_affinity: bool,
pub(crate) stale_continuation: bool,
pub(crate) fresh_fallback_allowed: bool,
pub(crate) fresh_fallback_blocked_without_affinity: bool,
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub(crate) struct RuntimePreviousResponseNotFoundDecisionInput<'a> {
pub(crate) route: RuntimePreviousResponseNotFoundRoute,
pub(crate) previous_response_id: Option<&'a str>,
pub(crate) has_turn_state_retry: bool,
pub(crate) request_requires_previous_response_affinity: bool,
pub(crate) trusted_previous_response_affinity: bool,
pub(crate) request_turn_state: Option<&'a str>,
pub(crate) previous_response_fresh_fallback_used: bool,
pub(crate) fresh_fallback_shape: Option<RuntimePreviousResponseFreshFallbackShape>,
pub(crate) retry_index: usize,
}
pub(crate) fn runtime_record_previous_response_not_found_retry_state(
profile_name: &str,
turn_state: Option<String>,
previous_response_retry_candidate: &mut Option<String>,
previous_response_retry_index: &mut usize,
candidate_turn_state_retry_profile: &mut Option<String>,
candidate_turn_state_retry_value: &mut Option<String>,
) -> bool {
if previous_response_retry_candidate.as_deref() != Some(profile_name) {
*previous_response_retry_candidate = Some(profile_name.to_string());
*previous_response_retry_index = 0;
}
let has_turn_state_retry = turn_state.is_some();
if has_turn_state_retry {
*candidate_turn_state_retry_profile = Some(profile_name.to_string());
*candidate_turn_state_retry_value = turn_state;
}
has_turn_state_retry
}
pub(crate) fn runtime_previous_response_not_found_decision(
input: RuntimePreviousResponseNotFoundDecisionInput<'_>,
) -> RuntimePreviousResponseNotFoundDecision {
let request_requires_locked_previous_response_affinity = match input.route {
RuntimePreviousResponseNotFoundRoute::Responses => {
input.request_requires_previous_response_affinity
}
RuntimePreviousResponseNotFoundRoute::Websocket => {
runtime_websocket_request_requires_locked_previous_response_affinity(
input.request_requires_previous_response_affinity,
input.trusted_previous_response_affinity,
input.previous_response_id,
input.request_turn_state,
)
}
};
let locked_previous_response_retry =
matches!(input.route, RuntimePreviousResponseNotFoundRoute::Websocket)
&& input.request_requires_previous_response_affinity
&& !input.has_turn_state_retry;
let retry_delay = (input.has_turn_state_retry || locked_previous_response_retry)
.then(|| runtime_previous_response_retry_delay(input.retry_index))
.flatten();
let retry_reason = if input.has_turn_state_retry {
Some("non_blocking_retry")
} else if locked_previous_response_retry {
Some("locked_affinity_no_turn_state")
} else {
None
};
let chain_retry_reason = match input.route {
RuntimePreviousResponseNotFoundRoute::Responses if input.has_turn_state_retry => {
Some("previous_response_not_found")
}
RuntimePreviousResponseNotFoundRoute::Websocket if locked_previous_response_retry => {
Some("previous_response_not_found_locked_affinity")
}
_ => None,
};
let fallback_policy = runtime_previous_response_not_found_fallback_policy(
RuntimePreviousResponseNotFoundFallbackRequest {
previous_response_id: input.previous_response_id,
has_turn_state_retry: input.has_turn_state_retry,
request_requires_locked_previous_response_affinity,
previous_response_fresh_fallback_used: input.previous_response_fresh_fallback_used,
fresh_fallback_shape: input.fresh_fallback_shape,
},
);
RuntimePreviousResponseNotFoundDecision {
retry_delay,
retry_reason,
chain_retry_reason,
request_requires_locked_previous_response_affinity,
stale_continuation: fallback_policy
.stale_continuation
.requires_stale_continuation(),
fresh_fallback_allowed: fallback_policy.fresh_fallback.allows_fresh_fallback(),
fresh_fallback_blocked_without_affinity: fallback_policy
.fresh_fallback
.blocks_without_affinity(
input.has_turn_state_retry,
request_requires_locked_previous_response_affinity,
),
}
}
pub(crate) fn runtime_previous_response_not_found_observability_outcome(
decision: RuntimePreviousResponseNotFoundDecision,
fresh_fallback_shape: Option<RuntimePreviousResponseFreshFallbackShape>,
) -> Option<&'static str> {
if decision.fresh_fallback_blocked_without_affinity
&& matches!(
fresh_fallback_shape,
Some(RuntimePreviousResponseFreshFallbackShape::ContextDependentContinuation)
)
{
Some("blocked_nonreplayable_without_affinity")
} else if decision.fresh_fallback_blocked_without_affinity {
Some("blocked_without_affinity")
} else {
None
}
}